[ANN] Silk, an isomorphic routing library for Clojure and ClojureScript

2,145 views
Skip to first unread message

Dom Kiva-Meyer

unread,
Aug 4, 2014, 11:04:22 PM8/4/14
to clo...@googlegroups.com, clojur...@googlegroups.com
I have recently been experimenting with isomorphic Clojure and ClojureScript architecture. The goal is to make Clojure[Script] web applications that can share logic for routing, rendering, and other functionality in order to be able run in both server-side and browser-side environments.

I couldn't find a suitable routing library that worked in both Clojure and ClojureScript nor could I find a routing library with a design that was suitable for porting to the other platform, so I wrote one.

https://github.com/DomKM/silk

The core functionality of Silk is entirely compatible with both Clojure and ClojureScript.

Silk is easily extensible, which is especially important for an isomorphic library. For example, you may have routes that, on the server, should only match GET requests, but which should also work in the browser where you are not routing from an HTTP request. This functionality is trivial to add to Silk (and, as a convenience, is built in).

In Silk, route matching and handling are decoupled. While this is an important architecture concern anywhere, it is an essential feature for isomorphism. Matching URLs on the server and browser will be very similar, but what you do after will likely be entirely different. You can think of routing in Silk as bidirectional (yes, routes are named) pure functions that match URLs to extract parameters or take parameters to form URLs.

If this sounds like it could be useful for you, please give it a try and let me know what you think.

Thanks!

Craig

unread,
Aug 5, 2014, 1:10:28 AM8/5/14
to clo...@googlegroups.com, clojur...@googlegroups.com
How would you position Silk in relation to Bidi?

Thanks for any insights.

Joel Holdbrooks

unread,
Aug 5, 2014, 10:35:12 PM8/5/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Awesome work. It's fantastic to see a library that's interested in targeting both the front-end and the back-end. This is the type of attitude I would love to see more often in the Clojure community.

OTOH, it would have been awesome to have heard your thoughts WRT the concept of isomorphic routing on the Secretary issue tracker.

You said you couldn't find something suitable, why didn't you complain or suggest a patch? We would have been happy to have supported your endeavor in making that possible and your ideas. In fact, we would have been willing to make breaking changes for them!

Many of us want this!

Originally, several people - myself included - were interested in seeing Clout be the library that everyone used both on the client and on the server. Unfortunately, James never merged the pull requests (two of them) for ClojureScript support. AFAICT it was because he wasn't clear on the role it would serve. James is also a busy man. :-)

I'm not trying to call you out; the work you have done is fantastic (there's already a few things I'd like to steal!). Rather, I am trying to bring up a more important issue within our community WRT this topic: routing. We now have at least 4 libraries designed for this task (most of them targeting the server).

We should not burden the community with dozens of choices. Instead we should enrich the community by working together to improve it. Fewer, well designed choices surely outweigh many "okay" solutions.

So enough talk. Personally, I would love to work together on the issue of an isomorphic router. Secretary is a well known choice for the client and I think together we could make it better by supporting the server. We have three core team members on Secretary, and speaking for the team, I think we would love to bring an end to this saturation of routers in the ClojureScript world. Would you be interested in joining our team and help us work toward this goal?

I don't mean to put you on the spot publicly but this is, partly, a public NIH issue and a tweet won't hold this.

Email me privately if you desire. :-)

Joel Holdbrooks

unread,
Aug 5, 2014, 10:38:26 PM8/5/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Edit: s/\(routers in the Clojure\)Script/\1

Dom Kiva-Meyer

unread,
Aug 6, 2014, 4:27:44 AM8/6/14
to clo...@googlegroups.com, clojur...@googlegroups.com
Hi Craig,

Great question! Bidi is a fantastic library and was my favorite Clojure routing library prior to Silk. The design of Silk was heavily influenced by that of Bidi.

In terms of commonalities, both Silk and Bidi are bidirectional, pure (no side effects), based on data composition (not function/macro composition), extensible via protocols, and decouple matching from handling.

In terms of differences, Bidi is (obviously) not compatible with ClojureScript. Bidi's design is the most amenable to porting to ClojureScript of any Clojure library that I've reviewed, but it still makes many assumptions about using Ring and running in a server environment.
Bidi routes are defined in a tree structure that branches at URL path segments. While Silk can match any part of a URL, Bidi can only match the URL path due to its routes structure. I also think that Silk's route syntax is a lot easier to read and programmatically manipulate than Bidi's, but this is subjective.

Hope that helps. :)

Cheers,
Dom



On Mon, Aug 4, 2014 at 10:10 PM, Craig <craig....@gmail.com> wrote:
How would you position Silk in relation to Bidi?

Thanks for any insights.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Dom Kiva-Meyer

unread,
Aug 6, 2014, 4:58:59 AM8/6/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Hi Joel,

Thanks for your feedback. Off topic, but Garden is awesome and Ankha has been indispensable when developing Om applications! Thanks for those.

I didn't complain or suggest a patch because, aside from Bidi, I didn't find a Clojure or ClojureScript routing library that I could conceive of fitting my criteria without fundamental, completely breaking alterations -- alterations that warrant a new library, not a new version number. I care about compatibility, extensibility, bidirectionally, purity, transparency, and decoupling -- which is why I wrote Silk.

Since you brought up Secretary as a routing library that could potentially be made isomorphic, I'll compare it to Silk as I did with Bidi. I am not as familiar with Secretary as I am with Bidi, and it looks to have changed since I last used it, so please do correct me if I say anything incorrect or misleading.

In terms of commonalities with Silk, Secretary obviously works in ClojureScript and has named routes. It is also somewhat extensible via protocols.

In terms of differences, Secretary is obviously incompatible with Clojure, couples route definition with route handling, uses a complex macro to define routes instead of using data structures, can only match URL paths, and is impure. While I value all of these features that Secretary lacks, I think that last one, impurity, is the most significant.

I love Clojure's pragmatic philosophy; it is mostly pure but very practical. Mutation is always possible, but uncontrolled mutation is frowned upon and intentionally difficult. Where we draw the line between functional purity and impurity is unclear and very open to interpretation, but it is my strong opinion that route creation and matching should sit firmly on the pure side.

The public interface for defining routes in Secretary is `defroute`, a macro which only sometimes defines something but always mutates a global atom. When using Secretary, how does one know the order in which routes are matched? I assume it is in dependency order as determined by the Google Closure compiler, but it is unclear to me. What about if Secretary is ported to Clojure and you hot reload namespaces as you develop with it? You could potentially change the routing order from what it would be at runtime. Component is extremely useful for development and its popularity is well warranted, but Secretary's design fundamentally precludes compatibility with Component or anything that requires that mutation be limited and controllable.

Silk is also, in many ways, lower level than Secretary. Secretary could be built on top of Silk without much additional effort since extending Silk to match paths specified with Clout/Secretary syntax ("/path/to/:param") would be trivial. On the other hand, building Silk on top of Secretary would be extremely difficult.

It is not my intention to lambast Secretary and I hope it doesn't come across that way. Solving front-end routing problems is difficult and is a different beast altogether from back-end routing because it lacks the established Ring model (adapters, requests, etc.) and even lacks the HTTP request/response model. Secretary did a good job at solving these difficult problems.

We share a desire to bring an end to the saturation of routing libraries. I just don't think that retrofitting a library designed for one paradigm will work without causing significant pain to users.  At some point, it just makes sense to start fresh. Silk is designed from the ground up to work on both platforms, be extremely extensible, and include all of the best parts of the various routing libraries that I have used and examined. These strict flexibility requirements led to a design which, I think, is very simple and fairly low-level while remaining reasonably convenient.

I appreciate your invitation to help you work toward the goal of making Secretary isomorphic and would be happy to talk to you about it. However, I don't think that Secretary or any other routing library can easily replace Silk. Therefore, I also invite you to help improve any deficiencies you see in Silk. I would greatly appreciate constructive criticism. I'll take you up on your offer to email you privately. :)

Cheers,
Dom



--

Note that posts from new members are moderated - please be patient with your first post.
---
You received this message because you are subscribed to the Google Groups "ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojurescrip...@googlegroups.com.
To post to this group, send email to clojur...@googlegroups.com.
Visit this group at http://groups.google.com/group/clojurescript.

Joel Holdbrooks

unread,
Aug 6, 2014, 12:56:53 PM8/6/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Dom,

We’re actually well aware of many of the flaws you have pointed out with Secretary. In fact, we even have open issues for some of them.

> While I value all of these features that Secretary lacks, I think that last one, impurity, is the most significant... Mutation is always possible, but uncontrolled mutation is frowned upon and intentionally difficult

It is not as if we don’t understand the benefits of immutability or know Clojure idioms. There’s no need to school anyone. We're not n00bs here. :-) When I became a committer to the library I did what I could to improve what was already there.

Personally, I do not like the overly complex macro that we have nor do I like the global state. These are just implementation details and it wouldn’t require too much effort to switch to something that’s immutable with a minimum amount of breaking change. We already have several vanilla functions for adding/removing routes that could easily be made stateless. The three of us (myself, Gianni, and Joel) have been busy and just haven’t had the chance to fix it.

Anyway, you’ve done a great job enumerating your reasons for writing Silk and flaws about Secretary; some of it is accurate, some of it is misguided. I’m not going to return fire by addressing them because I don’t think it will make much of a difference. Really, what we need to do is work together.

We can continue this discussion elsewhere.

Joel

Craig

unread,
Aug 6, 2014, 4:26:04 PM8/6/14
to clo...@googlegroups.com, clojur...@googlegroups.com
Hi Dom,

Thanks! The comparison is much appreciated, as is the contribution to the community.

regards,
Craig

whodidthis

unread,
Aug 6, 2014, 5:21:12 PM8/6/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Very nice one, Dom.

Bidirectional routes are indeed especially important to render and dispatch routes in Om etc. In secretary its a bit awkward since you have to write stuff like (defroute front-page "/" [] :front-page) and then a separate thingie for matching the keywords back to the routes.

On the other hand secretary will probably serve non-React apps well with its dispatch actions when you dont have React lifecycle methods.

Joel Holdbrooks

unread,
Aug 6, 2014, 8:22:20 PM8/6/14
to clojur...@googlegroups.com, clo...@googlegroups.com
> Bidirectional routes are indeed especially important to render and dispatch routes in Om etc. In secretary its a bit awkward since you have to write stuff like (defroute front-page "/" [] :front-page) and then a separate thingie for matching the keywords back to the routes.

We have written several Om applications at work and this style of routing has never been a source of problems. The `defroute` macros does , perhaps unfortunately, provide the dual concern of adding a route to the global routes (which we plan to remove) and, optionally, giving you a route generator function if you name it (which we'll probably keep). We added this additional option because we felt that these two concerns came up frequently enough in the same context that it should just be convenient to do them at the same time. That is to say, every time we created a route we would have a function for generating a url to go with it.

> On the other hand secretary will probably serve non-React apps well with its dispatch actions when you dont have React lifecycle methods.

I think this observation is a bit misguided. We have actually found Secretary to be solid in practice and that it works *very* well with Om and React. If you interpret route changes as a top level state transition this becomes easy to recognize.

Each of our routing functions returns all of the data necessary to transition the app to the next state such as route parameters, view name, etc. Since secretary/dispatch! returns the result of the routing function, we can pass that data to a transition function which handles the actual mutation of the global application state. Each view name is mapped to a component which then receives the application state, so and so forth. This allows us to treat each of our main views as if they were pages (except much better, of course).

This actually fits in with the lifecycle perfectly because the mounting/unmounting for a "view" component can be thought of as visiting and leaving a page. It works out nicely for situations like route and query parameter changes.

tl;dr an Om and Secretary combination does work. In fact, our routes.cljs (where we defroute) and history.cljs (where we dispatch!) are files we rarely edit because this design works without much fuss. To recap the pattern for this looks like:

(Google History) hash change token → dispatch! → data → transition! (Om)

In conclusion, I would argue that the choices you make about how you manipulate your application state will have more consequences than the routing library you choose.

Dylan Butman

unread,
Aug 7, 2014, 1:35:18 PM8/7/14
to clojur...@googlegroups.com, clo...@googlegroups.com
I agree with Joel that I've found that secretary works very well with Om, especially with a few abstractions built over it to built the corresponding state. The opposite direction is tricky though, and the biggest problem I've run into is that matching order is based on runtime route declaration order, and as Dom points out, if you try to do this across namespaces, you're in for trouble. In practive, I've never wanted or needed to define routes in any way but top to bottom in a single file.

That said, I think the Silk approach is very elegant. First, isomorphism is great, good one. Second, being able to easily define subsets of routes and match/unmatch on arbitrary combinations of them is very powerful and highly composable. You could use the same (Google History) hash change token → dispatch! → data → transition! (Om), where dispatch! is a match on a group of routes.

The state -> route direction is much cleaner with silk. It simplifies the problem a lot to be able to filter your routes to only include those dealing with your current app state, then selecting from your app state and matching on the filtered routes. I was trying to do something similar with Tao (although it was an alpha level mess), but ended up being hampered by the somewhat hidden nature of secretary matching/unmatching.

I agree it's a shame to keep reinventing the wheel, but while I like secretary, I've never felt that it's approach was the be all end all. I appreciate some of the sentiment of "if it ain't broke, don't fix it," but more good ideas in the mix only push us all to write better code.

Dom, some questions and thoughts for improvement.

If you define routes with :path and :query, will the route match/unmatch with undefined query keys? If so, how are they handled? If not, I'd suggest making query matching optional, where nils are substituted.

It's a little unclear how your matching functions relate to route. It looks like Silk always breaks at / in path and matches, is that correct?

There are some really good things in secretary. What do you think about them?

Splat, regex, format matchers.

protocol based render function for multiple arity "unmatching." this is really great.

and more I'm sure I'm missing

Joel Holdbrooks

unread,
Aug 7, 2014, 5:54:04 PM8/7/14
to clojur...@googlegroups.com, clo...@googlegroups.com
I'm in agreement that Silk is a step in the right direction. I've reached out to Dom and I think we can learn a lot from each other and work together to improve the routing story in Clojure overall.

> There are some really good things in secretary. What do you think about them?
> Splat, regex, format matchers.
> protocol based render function for multiple arity "unmatching." this is really great.

These are definitely nice things and I'm willing to bet Silk would be capable of supporting some of them.

It's obvious to me to that if we can iron out the details with Silk, Secretary could built on top of it as a higher level interface while at the same time taking advantage of what Silk has to offer. It might mean some breaking changes in Secretary but those were already slated anyway.

Dom Kiva-Meyer

unread,
Aug 7, 2014, 10:31:56 PM8/7/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Thanks for your feedback, Dylan!

If you define routes with :path and :query, will the route match/unmatch with undefined query keys? If so, how are they handled? If not, I'd suggest making query matching optional, where nils are substituted.

I'm not entirely sure what you mean, but I'll give it a shot. Please provide some example code if I answered the wrong question.

`nil` is a pattern that matches anything. If your URL pattern query is `nil` then the URL query will not be checked.
A map is a pattern that matches its values against the values of another map. Therefore, `nil` and `{}` are equivalent when used as a query pattern.
You can make a query value pattern optional by wrapped it with `silk/option`. 


It's a little unclear how your matching functions relate to route. It looks like Silk always breaks at / in path and matches, is that correct?

Yes. There is a URL type in Silk and matching is done against instances of it. The path is represented as a vector of segments.

The readme is currently very deficient and I apologize for that.


There are some really good things in secretary. What do you think about them?
Splat, regex, format matchers.

In terms of regex matching, Silk used to have a built-in regex pattern but I removed it when I made a big architectural change. I forget why I removed it, but I'll re-add it since it does seem like a very common requirement.

Currently, part of Secretary's splat exists as a built-in Silk pattern. For example, `(silk/composite ["foo" :* "bar"])` would match "fooanythingbar" and return `{:* "anything"}`. The `:*` isn't special; it's just a keyword. Format is just a specific case of composite: `(silk/composite [:* "." :format])`. Unlike Secretary, Silk does not have a built-in special syntax for string patterns. This is because special syntax strings are not composable and, since Silk matches against unencoded strings, who am I to say you can't have ":" or "*" in your URL paths? ;)

Looking at the Secretary readme, there appear to be two ways to use splat that Silk currently does not have built-in support for. In Secretary, "/foo/*" would match "/foo/bar/baz" and return `{:* "bar/baz"}`. Also, "/*/*" would match "/a/b" and return `{:* ["a" "b"]}`. I keep saying "built-in" because, while multi-segment path patterns and binding the same parameter key to multiple path segments does not currently exist in Silk, it is very easy to extend Silk with that functionality. You could easily create a pattern that did exactly what Secretary and Clout do by default and use it to match a path instead of a vector. However, I do question the utility of these two features. Are either of these common requirements and, if so, could I see some examples of why they are necessary or helpful? This isn't rhetorical; please let me know if Silk is missing something that is within its scope and is useful to most consumers.

protocol based render function for multiple arity "unmatching." this is really great.

I don't think I fully understand the use cases for this protocol. Do you want to be able to look routes up by types? If so, since route names in Silk can be anything, you could use a type as a name. Anyway, I put together this little gist of ways in which Silk could be used similarly to how I *think* someone might use Secretary's IRenderRoute. Do these cover the use cases for that protocol?


I also agree with everything in Joel's response and look forward to working with him on improving the routing story. :)



--

Note that posts from new members are moderated - please be patient with your first post.
---

Allen Rohner

unread,
Aug 9, 2014, 1:06:17 PM8/9/14
to clo...@googlegroups.com, clojur...@googlegroups.com
I'd like to thank everyone in the community for both Silk, and Secretary. 

I'll throw out some (uninvited) feature requests I'd love to see in a future route-matching library. 

1) Make trie-based route dispatching possible. A feature pedestal has/will soon have, is to compile the routing table into trie, rather than the compojure-style wrapped functions. This can have a nice speedup on busy applications. I'm not asking anyone to write this code, just consider the design such that it's possible to add this behavior in the future. 

2) I'll claim that making route definition order is a misfeature. Routes should always be fully qualified, such that re-arranging them doesn't affect routing behavior (and therefore, the route table should be an unordered collection, like a map or set, not a vector). One nice readability reason for this is that if your route order does matter, than at least one route definition is "lying" about which routes it actually dispatches on. 

Just things to consider :-)

Thanks,
Allen

Dom Kiva-Meyer

unread,
Aug 9, 2014, 3:00:50 PM8/9/14
to clo...@googlegroups.com, clojur...@googlegroups.com
Hi Allen,

Thanks for the feedback!

1) This, and precompiling regexes where possible, is my intention with Silk.

2) I'm not convinced that requiring fully-qualified routes would be a feature. Let's say we have route A which should match "/foo/bar" and route B which should match "/foo/*". If these routes are unordered, route B would have to additionally be constrained with `(not= * "bar")`. It seems like this could make route definition very painful when working in a large application with many routes that match on multiple parts of the URL. 


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com

Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---

marc

unread,
Aug 9, 2014, 11:05:16 PM8/9/14
to clo...@googlegroups.com, clojur...@googlegroups.com
I've been playing and like Silk a lot!

However the following I find curious as I'm wondering what the intended behaviour should be:
user=> (silk/match (silk/composite ["user-" (silk/integer :id) "-fred" (silk/option :this "that") "s"]) "user-42-fredjs")
{:id 42, :this "j"}

user
=> (silk/match (silk/composite ["user-" (silk/integer :id) "-fred" (silk/option :this "that") "s"]) "user-42-freds")
nil


I would have thought the last one would have produced:

user=> (silk/match (silk/composite ["user-" (silk/integer :id) "-fred" (silk/option :this "that") "s"]) "user-42-freds")
{:id 42, :this "that"}



My only other suggestion, not worthy of a pull request, is the following:

(defn domkm.silk/encode-query
 "Takes a query map.
 Returns a string of query pairs encoded and joined."

 
[query]
 
(->> query
   
(apply into sorted-map) ; ensure consistent ordering to improve cache-ability of URLs...
   
(map (fn [[k v]] (str (encode k) "=" (encode v))))
   
(str/join "&")))


Regards,

Marc

Dom Kiva-Meyer

unread,
Aug 10, 2014, 12:12:51 PM8/10/14
to clo...@googlegroups.com, clojur...@googlegroups.com
Good stuff, Marc! Thanks for the feedback.

That behavior in `composite` is a bug. Thanks for reporting.

Sorting the query map seems reasonable. Good suggestion!

Thanks again, Marc.

Olli Piepponen

unread,
Oct 7, 2014, 7:29:41 AM10/7/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Has anyone here had a chance to use Silk for a project they'd be willing to share? I'm a front-end web-dev newbie who has been researching server-side Om/React rendering, and it seems like Silk is ideal for this use case. However I'd be much more comfortable starting out if there were some examples to go by.

Dylan Butman

unread,
Oct 7, 2014, 3:05:49 PM10/7/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Sorry i don't have time to really explain any of this...

but here's some code I pulled out of a recent project. maybe it'll be helpful to you. unfortunately I can't share the whole project.

https://gist.github.com/pleasetrythisathome/7adbdc9c8b7ab689df45

Dom Kiva-Meyer

unread,
Oct 8, 2014, 8:02:05 PM10/8/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Thanks for putting that together, Dylan. Looking forward to seeing what you're building. :)

Olli, please feel free to reach out to me (email, Twitter, GitHub issue, whatever) if you have any questions. Thanks for trying it out!

Colin Yates

unread,
Oct 10, 2014, 4:05:10 AM10/10/14
to clo...@googlegroups.com, clojur...@googlegroups.com
For clarity, can you confirm the relationship between this and ring and compojure? Am I right in saying the defined routes are ring compatible (using domkm.silk.serve) and therefore silk is a replacement for compojure (albeit compojure has some more middleware utilities)?

I understand I can just use silk in the UI and ring/compojure on the backend, but if I am using silk on the front end then simplicity would suggest using ring/silk on the backend.

This looks great, and I am just about to write my first “Clojure-all-the-way” web app using Om instead of coffeescript and ExtJS, and I want to make sure I don’t go down a rabbit hole (of which there are many!). To make life interesting, it is also a non-trivial SPA so client routing will help significantly.

Dylan Butman

unread,
Oct 10, 2014, 12:25:50 PM10/10/14
to clo...@googlegroups.com, clojur...@googlegroups.com

I’ve been using silk in conduction with compojure. Most middleware aren’t compojure specific, but I’ve just found it easier to stick with base level compojure routes and then pass uris to silk for pattern matching. This is mostly because there is such a wealth of documentation and examples to draw from with compojure. For example, session management, login flows with friends, etc have already been solved and there is plenty of code to strip. Silk wouldn’t complicated them (in some cases it might simplify), but when the wheel rolls, push it.


I’ve been approaching silk solely as a library for pattern matching within the specific domain of urls, which it does very cleanly. More importantly, it’s currently the only one that does it in both clojure and clojurescript, which makes it possible to have the same routing code used on client and server, which is necessary if you want to do server rendering, or more complicated websocket updates. 

Dom Kiva-Meyer

unread,
Oct 12, 2014, 11:28:25 PM10/12/14
to clojur...@googlegroups.com, clo...@googlegroups.com
Thanks for the experience reports, Dylan!

Colin, Silk is Ring-compatible and meant to be used as a single replacement for both Compojure and Secretary (or any other server/browser routing combination with incompatible syntax and semantics). But, as Dylan said, it's totally fine to use it in conjunction with Compojure instead of replacement. Silk should also work fine with all Ring middleware.
In the next version, Silk will have a more familiar and mostly Compojure-compatible syntax for defining routes. My hope is that this will make it easier to translate Compojure examples/code to Silk.


On Fri, Oct 10, 2014 at 9:25 AM, Dylan Butman <dbu...@gmail.com> wrote:

I’ve been using silk in conduction with compojure. Most middleware aren’t compojure specific, but I’ve just found it easier to stick with base level compojure routes and then pass uris to silk for pattern matching. This is mostly because there is such a wealth of documentation and examples to draw from with compojure. For example, session management, login flows with friends, etc have already been solved and there is plenty of code to strip. Silk wouldn’t complicated them (in some cases it might simplify), but when the wheel rolls, push it.


I’ve been approaching silk solely as a library for pattern matching within the specific domain of urls, which it does very cleanly. More importantly, it’s currently the only one that does it in both clojure and clojurescript, which makes it possible to have the same routing code used on client and server, which is necessary if you want to do server rendering, or more complicated websocket updates. 

--

Colin Yates

unread,
Oct 14, 2014, 1:37:41 PM10/14/14
to clo...@googlegroups.com
Thanks both.

Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com

Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/D95anPmhNhU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Malcolm Sparks

unread,
Dec 29, 2014, 9:25:04 AM12/29/14
to clo...@googlegroups.com, clojur...@googlegroups.com
Thanks for the nice words about Bidi (https://github.com/juxt/bidi). For the record, Bidi now supports ClojureScript too.

btw. The reason for matching on the path (rather than allowing matches on the query-string) is intentional, since I think that encourages a more RESTful architecture. However, it's possible to extend the Pattern protocol to match on anything in the request, should that be needed.

Daniel Jomphe

unread,
Apr 10, 2015, 8:08:30 PM4/10/15
to clojur...@googlegroups.com, clo...@googlegroups.com
This thread over Silk, Bidi and Secretary has been very interesting. I looked at the three projects to see how they evolved after this thread started. Any cross-pollination or progress worth sharing now that I've missed?

From what I could gather:

* Secretary is evolving towards a conservative 2.0 and won't, after all, be re-implemented on top of Silk. This may be a good thing: not bringing more transitive dependencies in all projects using Secretary. Thus, Secretary would remain the first stop for Compojure-like easy routing in ClojureScript apps.
* Bidi was already quite mature and comparable to Silk and remains as attractive as it was, especially with regard to its established production usage and corporate backing.
* Silk's development slowed for some time and this may make for a good occasion to reflect on its state and the value of its ambitions (which seemed quite interesting to me).
* I would add, from distant memory, that Pedestal's isomorphic routing is planned to be extracted into a self-contained library. Judging from the advent of Clojure 1.7's reader conditionals and Om's model 2 for which I'm expecting an announcement in a few weeks, I suppose Cognitect may start providing again their leadership on the front-end. Either they start recommending something like Bidi or Silk, or they (more probably) reader-cond-port their routing lib to ClojureScript.

I'm watching this space and thanks for any comments.

Malcolm Sparks

unread,
Apr 11, 2015, 10:46:42 AM4/11/15
to clo...@googlegroups.com, clojur...@googlegroups.com
Author of bidi here. 

In his blog article, REST Litmus Test for Web Frameworks - https://www.innoq.com/blog/st/2010/07/rest-litmus-test-for-web-frameworks/ - Stefan Tilkov asks of REST libraries: 

    "Is there an easy way to produce links that "point back" resources identified by whatever means the framework exposes (such as some form of routing)?"

The idea of bidi came out of a discussion I had with Philipp Meier about Liberator's support for hyperlinks, to meet Stefan's litmus test. Philipp argued that while he felt Liberator should remain independent of the URI routing layer, any routing layer that was part of a proper REST API should make it easy, and reliable, to produce hyperlinks. Now that REST APIs are a mission-critical part of many websites, I believe that producing reliable working hyperlinks is too important to be left to ad-hoc string concatenation, however disciplined. If the community are to standardise on a routing library, I feel it should be one that support isomorphism (as Pedestal, bidi and Silk do)

The design of bidi was heavily influenced by my experiences with Pedestal routing, which demonstrated the value of a data structure over macros. Unfortunately, I felt that Pedestal's integration of routing with its interceptor definitions meant that you couldn't extract an independent routing library from it. I also didn't really like the terse versus expanded format, and wanted bidi to have a single format, one that would strike the balance between being easy to write by hand and easy to generate. I'm still a fan of Pedestal but wish it had been offered from the beginning as a set of independent libraries which worked well together. 'All or nothing' is never a great choice.

Silk was heavily influenced by bidi, as Dom has described earlier in this thread, and if you compare their source code they are very similar. Now that bidi supports ClojureScript, the key difference is in syntax and the fact that bidi routing is hierarchical, whereas Silk allows you to dispatch at a number of levels, as far as I can tell. The syntax differences are such that you could probably create some records and record constructors, satisfying the bidi protocols, that allowed you to layer Silk's syntax over bidi, a sort of 'silk-flavored-bidi' if you will. 

The hierarchical design of bidi was driven by the desire to support modularity- when you have multiple people or teams working on different parts of a website, it helps to be able to 'remount' one of the parts without anything breaking. Also, bidi excludes support for dispatching on differences in query parameters, as I believe that query parameters shouldn't, by definition, determine which resource is identified by the URI. Opinionated? Maybe!

Until recently, the way that you did isomorphism in bidi was to use a handler as an argument to path-for, which would return the route. That required URI-forming code (like views) to have access to all the handlers in the system - not great. I played around with using keywords, with some funky record injection in the route structure to convert them to handlers during dispatch, but then I looked at Secretary's 'named routes' and slapped myself very hard on the forehead (doh!). Since then, bidi has a 'tag' function that you use to tag a handler with a keyword, from then on you can use that keyword when forming routes instead of the handler itself.

bidi will undergo some rework when the new reader conditionals of Clojure 1.7 arrive out of beta, which I expect will replace its use of cljx. Otherwise, bidi itself is going to remain fairly stable. It is relied on by many production websites, such as https://www.onthemarket.com/, so can't change radically. However, building on protocols opens up numerous ways of extending bidi. For example, we're working on a new REST library (http://yada.juxt.pro) which combines bidi routing structures with resource maps to generate published Swagger definitions. This is achieved by adding a couple of new small records which satisfy bidi's protocols, demonstrating the endless flexibility of Clojure's protocols feature. So I'd hope that a 'standard' routing library would be built on protocols too.

So, in summary, I think it would be useful to have a single 'default' routing library in Clojure that supported isomorphism and was built on protocols, as a minimum. Now that Clojure is attracting so many new users, it would be great to discuss the outstanding differences between all the routing libraries and try to drive some consensus as to what a combined library would include.

kovas boguta

unread,
Apr 11, 2015, 11:40:12 AM4/11/15
to clojur...@googlegroups.com, clo...@googlegroups.com
On Sat, Apr 11, 2015 at 10:46 AM, Malcolm Sparks <mal...@juxt.pro> 
So, in summary, I think it would be useful to have a single 'default' routing library in Clojure that supported isomorphism and was built on protocols, as a minimum. Now that Clojure is attracting so many new users, it would be great to discuss the outstanding differences between all the routing libraries and try to drive some consensus as to what a combined library would include.

I'm on board with most of this post (and the Bidi approach in particular), I'm not sure consensus is necessary but I'll throw in my 2 cents. 

- Please, no more defroute etc macros 
- Routing should be composable. I want to take some routes and just plug them in at some level of my existing hierarchy. 
- Middleware should be decoupled from the routes as much as possible. The process for associating middleware to the request should be parallel/complementary to resolving the resource. Maybe "middleware" is not the best concept to begin with. 
 


Joel Holdbrooks

unread,
Apr 24, 2015, 7:49:43 PM4/24/15
to clojur...@googlegroups.com, clo...@googlegroups.com
I'm definitely in agreement that the approach Bidi and Silk have taken is much better than what exists on master in Secretary. Most of the undesirable aspects pointed out about Secretary have largely been resolved on the 2.0.0 branch for Secretary. The other desirable qualities found in Silk (unsure about Bidi) have been in existence in Secretary for sometime but the library was not oriented properly to make those features "idiomatic".

I do not agree that "defroute etc macros" should be eliminated from the picture. There is nothing inherently wrong with authoring and/or encouraging their use. It is only an issue when those macros are the only way to use a library effectively. People enjoy using DSL's (until they hit a wall with them) and for small to medium scale projects they are completely appropriate and just as effective as the "it's just data" style of routing for "getting shit done."

I will also say the same about middleware. Although I do not like the middleware pattern I do not believe it is worth throwing out the window just because it is "not the best concept." It is a familiar pattern and sometimes that familiarity is worth avoiding the friction of learning a something else for some individuals and teams.

tl;dr retaining features that people enjoy using and are familiar with is fine so long as there is a composable API underneath it that can be used alternatively.
Reply all
Reply to author
Forward
0 new messages