Fork me on GitHub
#liberator
<
2015-08-19
>
rickmoynihan13:08:07

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?

rickmoynihan13:08:50

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

ordnungswidrig13:08:23

One way is to bind your routes to a future and use that from within your handlers.

rickmoynihan13:08:50

yeah I did think about using an atom or promise or something

rickmoynihan13:08:56

I guess a promise would be best

ordnungswidrig13:08:33

Oh, yes, I meant promise.

ordnungswidrig13:08:43

I, personally, use component and co-dependencies from the modular lilbs: https://github.com/juxt/modular.co-dependency/

ordnungswidrig13:08:29

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.

ordnungswidrig13:08:09

> 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.

rickmoynihan13:08:19

interesting I guess components would help

rickmoynihan13:08:06

ahh now thats exactly what I was looking for

ordnungswidrig13:08:14

With bidi you can use tagged routes, so you only need to pass around a promis that contains the routes

ordnungswidrig13:08:53

So instead of promising the handlers, promise the routes

rickmoynihan13:08:34

yeah I was thinking I'd promise routes

ordnungswidrig13:08:10

Or you can bind your routes to the request, at :routes and should be able to avoid promises at all. *thinking*

rickmoynihan13:08:43

That was what I was refering to earlier -- not sure how to do that though

rickmoynihan13:08:15

I guess I could put a middleware in that would bind them after the routes were loaded

ordnungswidrig13:08:24

(defn wrap-routes [h routes] (fn [req] (h (assoc req :routes routes))))

rickmoynihan13:08:02

yeah its pretty simple - but means every page will need to have the parameter passed to it

rickmoynihan13:08:10

not a big deal though

ordnungswidrig13:08:28

You can apply wrap-routes very „top level"

rickmoynihan13:08:33

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

ordnungswidrig14:08:40

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))))

ordnungswidrig14:08:26

path-for is a little suggar enabling (path-for ::user :id „some-user“) and you can omit the routes altogether simple_smile

ordnungswidrig14:08:17

Not sure, however, how this would compose for nested route definitions when you have multiple subsystem that offer routes which you would mount together.

rickmoynihan14:08:23

yeah I tend to avoid dynamic vars unless I really need them

ordnungswidrig14:08:06

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.

rickmoynihan14:08:00

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

malcolmsparks14:08:39

@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 ....

rickmoynihan14:08:23

@malcolmsparks: yeah I realise its basically because bidi is bidirectional simple_smile

malcolmsparks14:08:31

if you're into hypermedia APIs that is, and that's what bidi is really built to support

rickmoynihan14:08:32

that implies circular dependency

rickmoynihan14:08:39

co-dependency looks interesting -- but seems like overkill for just this -- though I have been looking at introducing component at some point

malcolmsparks14:08:44

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

malcolmsparks14:08:39

@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

malcolmsparks14:08:02

co-dependency is designed to workaround the fact that component enforces an ADT

rickmoynihan14:08:05

yeah - I think I'm just going to throw it in a middleware in the request map

malcolmsparks14:08:23

yep, that'll work (if it's a promise and you deref it of course)

malcolmsparks14:08:58

I actually use a notation for derefables (promises, futures, refs), a star prefix

malcolmsparks14:08:17

so I remember to deref: @*routes

malcolmsparks14:08:35

it's an idea from @jarohen , but he uses a !

rickmoynihan14:08:46

! is too overloaded

malcolmsparks14:08:03

yeah, I don't think deref'ing is related to side-effects

rickmoynihan14:08:03

@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

malcolmsparks14:08:51

oh yes, that's right. yada is very much a work-in-progress - it isn't ready for production use

malcolmsparks14:08:01

liberator is much more mature

ordnungswidrig14:08:02

I suggest use a function path-for which uses and derefs the global promise of the routes.

malcolmsparks14:08:55

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)

malcolmsparks14:08:46

I've been talking with a co-contributor about creating an index, but until then I suggest you memoize if performance is critical

malcolmsparks14:08:03

it's probably not a big deal, just bear in mind if you generate loads of uris

malcolmsparks14:08:23

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

rickmoynihan14:08:57

@malcolmsparks: well I'll keep an eye on yada

rickmoynihan14:08:20

I'd really like swagger support in liberator - has anyone done that yet?

ordnungswidrig14:08:23

(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

ordnungswidrig14:08:55

@rickmoynihan: I’ve seen people doing it but I don’t know of a general solution

ordnungswidrig14:08:39

@malcolmsparks: yes, I think memoize would be the way to go. the promise contains a static route definition in the end.

malcolmsparks14:08:44

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..

malcolmsparks14:08:54

it's hard to build a swagger data model from this

ordnungswidrig14:08:15

you can use a swagger data model, though, and use that to declare your liberator resources.

malcolmsparks14:08:36

you mean go from swagger to liberator resource models? yes, that's true

malcolmsparks14:08:51

but I think swagger is a poor choice for your source model

ordnungswidrig14:08:02

or use your own data model and derive swagger and the liberator resource declaration from it.

malcolmsparks14:08:49

yes, that would be better

malcolmsparks14:08:12

yada is trying to help those who really can't be bothered to do all that just to get swagger output

malcolmsparks14:08:23

(they shouldn't be using swagger at all of course, it's very un RESTful 😉

rickmoynihan14:08:18

lol - well I don't care for swagger as such... more just that I want the automatic doc generation and test ui

rickmoynihan14:08:24

I've not looked into swagger too much to know the details

rickmoynihan15:08:03

@ordnungswidrig: FYI you can't use promise like that (its a arity 0) - I think we actually need a delay

ordnungswidrig15:08:08

@rickmoynihan: oh, sorry, yes. that should’ve been (def routes (promise)) and (deliver routes […])

rickmoynihan15:08:03

@ordnungswidrig: Or just (def routes (delay [ ... ]))

ordnungswidrig15:08:27

@rickmoynihan: yes, if you can make sure it’s not derefed early simple_smile

ordnungswidrig15:08:38

well, the promise would hang in that case.

ordnungswidrig15:08:54

we totally need some documentation on the options.

ordnungswidrig15:08:25

@malcolmsparks: shall we dump some of the ideas to bidi’s docs?

rickmoynihan15:08:04

@ordnungswidrig: yeah it would be useful - bidi's main selling point is moot without having a solution to this

rickmoynihan15:08:01

Does bidi have anything for assembling query string parameters? Or do I just need to str them on myself?

ordnungswidrig16:08:42

str is not enough, you need to properly urlencode the keys and values

malcolmsparks19:08:03

yes, and interpose with &, I've got a function somewhere that does that