Hi all,
I'm trying to use Bidi with liberator and Enlive and have a small problem with circular dependencies...
Basically I want to use bidi.bidi/path-for
to generate a route definition in one of my resources - but doing so will introduce a circular dependency as path-for
needs access to my routes
but I can't do this because my routes depend on my resources and my resources depend on my pages (enlive templates).
These are currently split into three namespaces, so i can't really use a declare
here... How do people normally factor their routes/resources/pages to avoid this?
It seems this circular dependency is implied for routes to be bidirectional, was wondering if bidi could possibly augment the request with the routes map
One way is to bind your routes
to a future and use that from within your handlers.
yeah I did think about using an atom or promise or something
I guess a promise would be best
Oh, yes, I meant promise.
I, personally, use component and co-dependencies from the modular lilbs: https://github.com/juxt/modular.co-dependency/
It allows you to specify deps and co-deps in your system (I have a routes-provider component). The co-deps are resolved via deref and allow circular deps.
> The penalty for not understanding this point is days spent figuring out why you can't see a co-dependency's data. It's because you're still trying to access this data within the start phase of your component. Don't do this. Defer the lookup until after the entire start phase of your system has completed.
lol
interesting I guess components would help
regarding the promise pattern: https://github.com/juxt/bidi/blob/master/doc/patterns.md
ahh now thats exactly what I was looking for
With bidi you can use tagged routes, so you only need to pass around a promis that contains the routes
So instead of promising the handlers, promise the routes
yeah I was thinking I'd promise routes
Or you can bind your routes to the request, at :routes
and should be able to avoid promises at all. *thinking*
That was what I was refering to earlier -- not sure how to do that though
I guess I could put a middleware in that would bind them after the routes were loaded
Yes like that.
(defn wrap-routes [h routes] (fn [req] (h (assoc req :routes routes))))
I like that.
yeah its pretty simple - but means every page will need to have the parameter passed to it
not a big deal though
You can apply wrap-routes very „top level"
yeah I know -- just each resource will need to destructure the routes from the ctx and either do the path-for
or pass the routes to the enlive templates to do it
I see. You can always use a dynamic var if that bugs you. And a function that accesses it.
(def :^private :^dynamic current-routes nil)
(defn path-for [& args]
(apply bidi/path-for current-routes args))
;; and a middleware
(defn wrap-bind-routes [h routes]
(fn [req] (binding [current-routes routes] (h req))))
like that.
path-for is a little suggar enabling (path-for ::user :id „some-user“)
and you can omit the routes altogether :simple_smile:
Not sure, however, how this would compose for nested route definitions when you have multiple subsystem that offer routes which you would mount together.
yeah I tend to avoid dynamic vars unless I really need them
In this case, when you consider the routes a global thing (like you would with the promises) they come in handy. But testing can be a pain.
true
might do that after all - as in this case they're not going to change post initialisation... Thanks for talking this through with me... it was useful
@rickmoynihan: this circular dependency problem comes up occasionally, that's what co-dependency tries to help with - in this case the circular dependency is hard to get round, it's at the very core of the web's design - URIs identify resources, resources produces representations, representations should contain URIs, which identify resources ....
@malcolmsparks: yeah I realise its basically because bidi is bidirectional :simple_smile:
if you're into hypermedia APIs that is, and that's what bidi is really built to support
that implies circular dependency
co-dependency looks interesting -- but seems like overkill for just this -- though I have been looking at introducing component at some point
fwiw, I use co-dependency so that components that contain routable resources can also capture, via lexical scope, the overall 'routes' that each resource might need to produce representations that contain URIs
@rickmoynihan: ah, I'd only recommend co-dependency if you're already using component, if you aren't then use another approach like a promise
co-dependency is designed to workaround the fact that component enforces an ADT
yeah - I think I'm just going to throw it in a middleware in the request map
yep, that'll work (if it's a promise and you deref it of course)
I actually use a notation for derefables (promises, futures, refs), a star prefix
like *routes
so I remember to deref: @*routes
nice idea
it's an idea from @jarohen , but he uses a !
! is too overloaded
yeah, I don't think deref'ing is related to side-effects
ditto
@malcolmsparks: was meaning to ask you -- I recently looked at yada which looks really quite nice.... I especially like the liberator style + swagger... however I eventually chose liberator because it seemed like yada was still in flux and is less mature... Is that a fair assessment? Should I have chosen yada yet? :simple_smile:
oh yes, that's right. yada is very much a work-in-progress - it isn't ready for production use
liberator is much more mature
I suggest use a function path-for
which uses and derefs the global promise of the routes.
@ordnungswidrig: that's a nice idea
one thing to bear in mind is that bidi's path-for can be a little slow (compared to dispatch, which has been heavily tuned)
I've been talking with a co-contributor about creating an index, but until then I suggest you memoize if performance is critical
it's probably not a big deal, just bear in mind if you generate loads of uris
bidi must walk its tree each time - but we could easily create an index to speed it up, just haven't got round to it yet
@malcolmsparks: well I'll keep an eye on yada
I'd really like swagger support in liberator - has anyone done that yet?
(declare routes)
(defn path-for [tag & args] (apply bidi/path-for @routes tag args))
(defresource foo [] :handle-ok (fn [ctx] (path-for ::bar)))
(defresource bar [] :handle-ok (fn [ctx] (path-for ::foo)))
(def routes (promise [/ [[„this-is-foo“ (tag foo ::foo)]
[„this-is-bar“ (tag bar ::bar)]]]))
…like that@rickmoynihan: I’ve seen people doing it but I don’t know of a general solution
@malcolmsparks: yes, I think memoize would be the way to go. the promise contains a static route definition in the end.
the problem with doing swagger in liberator, generally speaking, is that liberator is based around boolean answers to questions - true, true, false, true, false, etc..
it's hard to build a swagger data model from this
you can use a swagger data model, though, and use that to declare your liberator resources.
you mean go from swagger to liberator resource models? yes, that's true
but I think swagger is a poor choice for your source model
or use your own data model and derive swagger and the liberator resource declaration from it.
yes, that would be better
yada is trying to help those who really can't be bothered to do all that just to get swagger output
(they shouldn't be using swagger at all of course, it's very un RESTful 😉
lol - well I don't care for swagger as such... more just that I want the automatic doc generation and test ui
I've not looked into swagger too much to know the details
@ordnungswidrig: FYI you can't use promise
like that (its a arity 0) - I think we actually need a delay
@rickmoynihan: oh, sorry, yes. that should’ve been (def routes (promise))
and (deliver routes […])
@ordnungswidrig: Or just (def routes (delay [ ... ]))
@rickmoynihan: yes, if you can make sure it’s not derefed early :simple_smile:
well, the promise would hang in that case.
we totally need some documentation on the options.
@malcolmsparks: shall we dump some of the ideas to bidi’s docs?
@ordnungswidrig: yeah it would be useful - bidi's main selling point is moot without having a solution to this
Does bidi have anything for assembling query string parameters? Or do I just need to str them on myself?
no, use str
str is not enough, you need to properly urlencode the keys and values
yes, and interpose with &, I've got a function somewhere that does that