📜 ⬆️ ⬇️

Routing in the clojure web application

There are libraries in various languages ​​that have common features. This compojure, sinatra, grape, express, koa and the like.


They have a similar approach to routing. They do not impose any restrictions and do not offer a structure for organizing a url. Developers in such conditions tend not to care about the structure and subsequently receive poorly supported code.


Another common feature is one-pointedness. Those. A specific request corresponds to a specific request. Developers are forced to prescribe url strings in templates. It is not possible to specify in the form of a language construct which url to generate. This leads to the fact that in the views are dead links, and there is no way to find them, except how to pierce all the pages.


I'll tell you how to improve code maintainability in the Clojure ecosystem, and show you how:


  1. organize urls
  2. structure handler code
  3. use language constructs to generate url

It is important to understand that the libraries listed above have the means to organize handlers into modules; you can manually wrap the url strings in functions and use only these functions. But this, as a rule, is thought too late.


I’m a ruby ​​developer, and in other ecosystems (clojure, js, erlang, go) I don’t have enough routing organization like rails. I do not have enough REST and the concept of "resource". I miss the controllers. I do not have enough helpers to generate a url, like admin_page_path(@page) .


If you are not familiar with rails, then here is a link to the routing description "Rails Routing from the Outside In" . If you have forgotten what HTTP or REST is, I advise you to read the short and humorous article "15 Trivial Facts about Correctly Working with the HTTP Protocol" or more detailed "Why do you need this REST, as well as some of the intricacies of implementing RESTful applications" .


So use REST to organize the url.


In order to deal with the remaining two points, I will show examples of using my darkleaf / router library. Since this is a clojure ecosystem, then of course it is ring-compatible routing.


 (ns hello-world.core (:require [darkleaf.router :refer :all])) (def pages-controller {:middleware (fn [handler] (fn [req] req)) :member-middleware some-other-middleware :index (fn [req] some-ring-response) :show (fn [req] some-ring-response)}) (def routes (build-routes (resources :pages 'page-id pages-controller))) (def handler (build-handler routes)) (def request-for (build-request-for routes)) (handler {:uri "/pages", :request-method :get}) ;; call index action from pages-controller (request-for :index [:pages]) ;; returns {:uri "/pages", :request-method :get} (handler {:uri "/pages/1", :request-method :get}) ;; call show action from pages-controller (request-for :show [:pages] {:page-id "1"}) ;; returns {:uri "/pages/1", :request-method :get} 

Here, like rails, the controller is declared. In this case, with two actions: index and show.
A controller is just a map, and you can, for example, create slightly different controllers with your function.
Routes is a flat vector of routes generated by functions like resources.
Handler is a ring-compatible request handler, and request-for is used to receive a request by the name of the route and its area. They are generated using macros, since used inside core.match.


The controller may contain only the following keys:



Here is a complete example for a resource:


 (resources :pages 'page-id {:index identity :new identity :create identity :show identity :edit identity :update identity :destroy identity} :collection [(action :archived identity)] :member [(resources :comments 'comment-id {:index identity})]) 

The first argument specifies the name of the resource, it also sets the segment in the url. The second parameter is the name of the resource identifier.


As I mentioned above, resources can include nested routes of two types: collection and member.


 ;; pages collection routes (request-for :archived [:pages] {}) ;; #=> {:uri "/pages/archived", :request-method :get} ;; pages member routes (request-for :index [:pages :comments] {:page-id "some-id"}) ;; #=> {:uri "/pages/some-id/comments", :request-method :get} 

In addition to the function-generator of resources resources are also: root, action, wildcard, not-found, scope, guard, resource. I will not dwell on them, you will find detailed examples of their use in the tests .


There is no way to add new actions to the controller. This is a conscious restriction pushing for REST and nested resources. If you still need additional actions, and you do not want to use the nested resources, you can use the nested route, as shown above for the route: archived. But it will be a separate function outside the controller.


As you have already noticed, request-for returns the entire structure of the request, unlike rail helpers that return only a url. This is useful when headers or other request parameters are used to define a handler, for example, host. In future releases, we plan to support clojurescript, and you can use the same request-for to build requests to the backend.


Since request-for returns the request entirely, then there is a check in it that the received request falls into the necessary handler. This is useful when 2 similar urls are processed in different ways or a restriction is present.


 (guard :locale #{"ru" "en"} (action :localized-page identity)) (not-found itentity) 

(request-for :localized-page [:locale] {:locale "it"}) will generate an error because The handler for /it/localized-page is not-found , not :localized-page [:locale] .


The library is divided into 2 neymspeysa:


darkleaf.router and darkleaf.router.low-level . If you have any specific requirements for routing or you need to support the old url scheme, then you can write your functions on top of darkleaf.router.low-level, just like it is done in darkleaf.router.


Full examples of use can be found in the darkleaf.router-test and darkleaf.router.low-level-test tests.


The library uses core.match and quite amusing macros. But this is a topic for a separate article. Write in the comments if you are interested to find out how it works inside.


UPDATE 10-02-2017 released version 0.2, without macros and third-party libraries, there is support for clojurescript https://github.com/darkleaf/router


')

Source: https://habr.com/ru/post/311500/


All Articles