duct

Yehonathan Sharvit 2019-09-04T10:48:42.000900Z

Hello there, I discovered integrant and duct a couple of weeks ago and I felt in love with the data approach they take.

Yehonathan Sharvit 2019-09-04T10:49:18.001700Z

I have built a new app name Mr Hankey at work and I would be very happy if someone could review my config file

Yehonathan Sharvit 2019-09-04T10:49:32.001800Z

Yehonathan Sharvit 2019-09-04T10:50:36.002900Z

Please share as much feedback as you can. I really want to learn the duct way! @weavejester

2019-09-04T15:11:40.005500Z

looks fine to my eyes @viebel… Only thing that stands out is having an intermediate aggregating component… presumably to avoid the boilerplate. So long as you’re happy with the trade offs that brings in terms of convenience vs potentially inflated responsibilities, e.g. is it the case that ::list-dbs needs the :rabbitmq-producers?

2019-09-04T15:12:36.006300Z

I tend to favour only passing things the components actually require; at the cost of verbosity

Yehonathan Sharvit 2019-09-04T15:12:56.006700Z

That’s an interesting tradeoff

Yehonathan Sharvit 2019-09-04T15:13:21.007300Z

What do you think is the cost of inlfated responsibilites?

Yehonathan Sharvit 2019-09-04T15:14:46.009100Z

I mean in that specific case of handlers

2019-09-04T15:17:42.012400Z

- Potentially security (though unlikely in practice) - Understandability, i.e. if I’m debugging a component and I see it has something given to it, I expect it to use it… - Minimising dependencies is usually a good practice; as it can introduce artificial problems… e.g. you might end up starting an otherwise unused component and never know… that component may be unused but prevent your app from starting without it… e.g. your app may require a connection to a service during init, and expect that service to be there… but in practice never use it.

2019-09-04T15:18:06.012700Z

would probably cover the main issues

2019-09-04T15:19:09.014300Z

but you might be able to split handlers into for example readers and writers… and mitigate that way whilst retaining more of the DRYness you have.

Yehonathan Sharvit 2019-09-04T15:19:47.014900Z

Good idea

Yehonathan Sharvit 2019-09-04T15:20:28.015800Z

Another thing that bothers me is that all the keys for the handlers appear at the root of the config. I would prefer to nest them into a :handlers key

Yehonathan Sharvit 2019-09-04T15:20:53.016500Z

But I don’t if it is doable in duct and also if it follows the spirit of duct

Yehonathan Sharvit 2019-09-04T15:21:09.016800Z

What do you think @rickmoynihan?

2019-09-04T15:22:20.017200Z

I personally wouldn’t do that. What you might do, is abuse the profiles feature to group things together… derive :mr-hanky/handlers from :duct/profile, and group the handlers there. Then include the profile in the set that get merged. But I doubt it’s the duct way — if there is such a thing beyond the basics of integrant etc.

2019-09-04T15:23:11.018100Z

But I also have some objections with the default duct template layout

Yehonathan Sharvit 2019-09-04T15:23:47.018300Z

What objections?

Yehonathan Sharvit 2019-09-04T15:24:33.018600Z

And why won’t you do stuff like that?

2019-09-04T15:29:24.019Z

Firstly duct is great. I don’t like to criticise. But I strongly dislike frameworks and templates that group by the incidental complexity of web apps; rather than by the concepts of your domain. So I really, really dislike having app.handlers app.models app.views and vastly prefer app.feature-1 app.feature-2 … I think grouping handlers is following that trend. Grouping by feature, and I’d be with you.

Yehonathan Sharvit 2019-09-04T15:31:24.020400Z

You mean renaming the keywords. Instead of :mr-hankey.handler.realm/list-realms have :mr-hankey.realm/list-realms?

2019-09-04T15:31:56.020600Z

and the accompanying namespaces

2019-09-04T15:32:01.020800Z

yes

2019-09-04T15:32:53.021600Z

Well you’re creating a dummy intermediate component; that only serves to group things. It doesn’t need the things it groups. To me it feels artificial.

Yehonathan Sharvit 2019-09-04T15:33:00.021700Z

Would you put the code that connects to the db also in this namespace? Or would you keep the tradidional split bewteen handlers namespaces and models namespaces?

Yehonathan Sharvit 2019-09-04T15:33:52.022400Z

But the cost of repeeating the components 10 times or more is very high!

2019-09-04T15:34:47.023Z

Yes, simple not easy 🙂

Yehonathan Sharvit 2019-09-04T15:35:13.023600Z

It hurts but I tend to agree with you

2019-09-04T15:35:31.023900Z

I don’t really understand why you want to group them together anyway.

Yehonathan Sharvit 2019-09-04T15:35:57.024400Z

to avoid the need to declare the components again and again in the config file

2019-09-04T15:36:47.024700Z

I definitely would yes. If features need to views and database models etc… and theres enough code to warrant splitting those concerns, then yes I’d have app.feature-1.db app.feature-1.view whatever you want.

Yehonathan Sharvit 2019-09-04T15:36:53.025Z

and also, it makes it easier in the REPL to access a componet

2019-09-04T15:37:41.025500Z

i.e. pattern is app.[vertical|feature].[horizontal]

Yehonathan Sharvit 2019-09-04T15:37:56.026Z

I can write (-> system :components :mongo-connection) instead of (-> system :duct.database.mongodb/monger)

2019-09-04T15:38:08.026100Z

with a few things like app.lib or whatever for supporting concerns, likewise probably an app.middleware

2019-09-04T15:39:33.026500Z

everything in the system map is a component though.

2019-09-04T15:40:26.027100Z

also not sure what you’re saving, you still need to have the config for each component.

2019-09-04T15:42:18.029Z

You can dedupe that in various ways with duct… - Modules is one (it’s very heavy handed and increases complexity a lot in my mind) — it’s kinda like macros for duct config data, but less terse, and less constrained. - ig/prep-key - derived components - composite keys - and refsets.

2019-09-04T15:49:58.034200Z

If for example all of your handlers follow the same shape/pattern you can have one defmethod for say a ::html-rendering-handler. It would then take all the standard stuff, :db a :view component etc… and wire them together in the a standard way. Then all you would have is config, along with view components, and :db-model components… You’ll end up with smaller components and more config. It’s the duct way. If the quantity of config then bothers you and you have sufficient commonality you can generate the config with modules… though I personally think modules tend to be a step too far.

2019-09-04T15:51:06.035Z

At the end of the day, duct config is pretty easy to write and maintain… even when you have thousands of lines of it.

2019-09-04T15:52:07.036Z

But we run a multi-tennant duct app… so each customer has a profile of config, that is merged over a common core layer. It’s not a standard duct app by any means.

Yehonathan Sharvit 2019-09-04T16:56:56.036300Z

I need to think about it

Yehonathan Sharvit 2019-09-04T16:57:18.036400Z

I need to think about how to apply this pattern in my case

Yehonathan Sharvit 2019-09-04T16:58:32.037Z

> It would then take all the standard stuff, :db a :view component etc… and wire them together in the standard way. What do you mean by “wire them together in the standard way”?

2019-09-04T16:59:18.037400Z

Sorry, I meant “a standard way for your app”.

Yehonathan Sharvit 2019-09-04T17:00:01.037800Z

Still, I don’t understand what you mean

Yehonathan Sharvit 2019-09-04T17:00:07.038Z

:thinking_face:

2019-09-04T17:01:11.039200Z

I just mean that if you can create a common abstraction across all your handlers or a subset of them; then you can use that to reuse code, effectively trading it for more config.

Yehonathan Sharvit 2019-09-04T17:01:56.039600Z

Can you show me a edn snippet that illustrates your point?

2019-09-04T17:20:21.043500Z

ok this isn’t necessarily suitable for everyone… it depends on what you’re doing — also there are other decisions/choices one can make. For example the view and handler separation may be overkill depending on what you’re doing… Also this is not tested just written into slack — so just illustrative of the ideas:

2019-09-04T17:20:25.043700Z

Firstly the config:

2019-09-04T17:20:27.044Z

{
 :app/layout {:db #ig/ref :app.feature-1/db}

 :app/db {,,,}
 
 :app.feature-1/view _
 :app.feature-2/view _

 :app.feature-1/model {:db #ig/ref :app/db}
 :app.feature-2/model {:db #ig/ref :app/db}
 
 [:app.handler :feature/1] {:view #ig/ref :app.feature-1/view
                            :layout #ig/ref :app/layout
                            :model #ig/ref :app.feature-1/model}

 [:app.handler :feature/2] {:view #ig/ref :app.feature-2/view
                            :layout #ig/ref :app/layout
                            :model #ig/ref :app.feature-2/model}

 :duct.router/ataraxy {:routes
                       {[:get "/feature-1"] :feature-1
                        [:get "/feature-2"] :feature-2}

                       :handlers {:feature-1 #ig/ref [:app.handler :feature/1]
                                  :feature-2 #ig/ref [:app.handler :feature/2]}}

 }

2019-09-04T17:21:11.044700Z

Then the handler defmethod would be shared and might look something like this:

2019-09-04T17:21:15.044900Z

(defmethod ig/init-key :app.handler [_ {:keys [layout db view]}]
  (fn [req]
    (if-let [view-model (model req)]
      [::response/ok (layout (view view-model req)
                             req)]
      [::response/not-found "Not found"])))

2019-09-04T17:22:05.045700Z

Our apps nav is driven by the data, so we made the layout/navbar etc a component in its own right, that gets given to every handler.

2019-09-04T17:25:23.047300Z

Also perhaps a page in your app is made of several panels… they can also be components… so perhaps you have:

[:app.page :page/dashboard] {:panel [#ig/ref :app.foo/panel
                                      #ig/ref :app.bar/panel
                                      #ig/ref :app.baz/panel]}

2019-09-04T17:26:07.048100Z

and the page doesn’t see the insides of those panels at all. Really depends on the granularity of HTTP responses to panels etc; and also what level of reuse you need.