duct

viesti 2019-08-07T06:27:48.051400Z

Hmm, can I have two :duct.server.http/jetty servers on different ports with different handlers?

viesti 2019-08-07T06:29:24.052500Z

Tried out like this:

[:duct.server.http/jetty :dev/mock-server] {:port 2001
                                             :handler #ig/ref :pot/mock-handler}
[:duct.server.http/jetty :http/main-server]   {:port      2000
                                                :handler   #ig/ref :http/handler}

viesti 2019-08-07T06:29:45.052900Z

But getting an error about ambiquous keys: > Execution error (ExceptionInfo) at integrant.core/ambiguous-key-exception (core.cljc:71). > Ambiguous key: :duct.server.http/jetty. Found multiple candidates: [:duct.server.http/jetty :http/main-server], [:duct.server.http/jetty :dev/mock-server]

viesti 2019-08-07T08:40:15.054100Z

Ahaa! Turns out that I had a reference elsewhere to :duct.server.http/jetty, which I forgot to turn into [:duct.server.http/jetty :http/main-server]. Now it works ๐Ÿ™‚

๐Ÿ‘ 2
danielytics 2019-08-07T11:33:23.060200Z

I have some conceptual confusion I'd like some input on. Basically, I'm trying to figure out where domain logic should live. Boundaries should hide details like transactions and should be generally high level operations (like the transfer example in the docs), but it also sounds like they should be only logic that's specific to that external service/db, so its not really the place for broader domain logic but rather just the aspects that relate to that external thing. But sometimes domain logic is tightly intermingled (for example, what if there's some logic that has to happen within the transfer operation/transaction but is really unrelated to the actual database logic of the transfer. How should this be handled? Higher order functions? Am I thinking about it wrong? Any thoughts, tips or links on how to best architect things in duct greatly appreciated.

danielytics 2019-08-08T09:51:03.060500Z

Thanks, although that's counter to what is said in the documentation, which is to prefer something like this:

(defprotocol AccountsDatabase
  (transfer [db from to amount]))
over something like this:
(defprotocol AccountsDatabase
  (with-tx [db callback])
  (credit [db to amount])
  (debit [db from amount]))
https://github.com/duct-framework/duct/wiki/Boundaries

danielytics 2019-08-08T09:51:20.060700Z

I actually started off with something like you describe, until I reread that

danielytics 2019-08-08T09:55:54.060900Z

from that page: > Any functionality specific to the service should ideally be placed behind the boundary. For example, many databases support transactions, but it's bad practice to expose this through the boundary.

teaforthecat 2019-08-08T14:07:39.061100Z

I guess we both need more supporting argumentation from the bad practice claim.

dangercoder 2019-08-09T09:15:27.061500Z

https://softwarecampament.wordpress.com/portsadapters/#tc2 I think its inspired from this

dangercoder 2019-08-09T09:16:51.061900Z

but not sure ๐Ÿ™‚

danielytics 2019-08-09T13:18:17.062100Z

thanks! I'll give that a read (I'm aware of the hexagon architecture, but perhaps its time for a refresher)

danielytics 2019-08-09T13:20:04.062300Z

one argument for hiding details like transactions is that different boundaries can be for different databases which require different transnational primitives (and can't be used together transnationally), so by not exposing transactions, you remove a potential error where you try to use these together in a single transaction when its not valid to do so

danielytics 2019-08-09T13:20:25.062500Z

(and it hides the implementation details of talking to the external service/database)

danielytics 2019-08-09T13:20:46.062700Z

but... that doesn't solve the domain logic issue, hence this question

dangercoder 2019-08-09T14:26:48.062900Z

I actually had a similar issue with boundaries and with-tx/with-conn. Had to send a bunch of redis commands within the same connection in a pipeline , im using a with-conn macro-wrapper , what I wanted was: update-user-status create-match send-notification but i ended up having to wrap this all in one protocol having a function called: match-found.

teaforthecat 2019-08-09T16:02:19.063200Z

When I read โ€œYou can have layersโ€ฆโ€ in that article it hit home for me all of a sudden. The realization I had, which may still not be totally in agreement, is that if you write a boundary like I had mentioned above, you should also move it out of the application into a library. For us that could be a namespace/directory outside of the app namespace, or another project. And you would have another boundary (in the app) that would use that boundary (outside of the app). Layers of boundaries. Iโ€™m not sure if that would be good practice and hexagon style? Seems like maybe?

danielytics 2019-08-09T16:13:22.063400Z

so, boundaries become a part of the domain (the part that happens to be implemented by a database or external service) and the boundary can call lower level libraries so transfer is the domain and with-tx, credit and debit are a lower level library that the boundary uses.

danielytics 2019-08-09T16:13:30.063600Z

that does make sense

danielytics 2019-08-09T16:18:38.063800Z

but i'm still unclear on what happens if domain logic needs to be intermingled. I can't think of a good example (perhaps that's a sign that its a non-issue in reality??), but imagine if during transfer you need to do something else. Currency conversion perhaps? Where does that logic go. ok ok currency conversion can probably happen before transfer, but it seems rather low level if you have to get the currency from account A, currency from account B (meaning a boundary with low level getters is needed), calculate the values for both and finally pass those to transfer but maybe that is the correct way to do it and the entire operation gets wrapped in a domain service: service "transfer" gets data and does calculations boundary "transfer" wraps the low level operations to call the library library implements transaction, credit and debit functions. But for fun, to throw another spanner in the works, what if currencies can change at any time and therefore conversion should be in the transaction? I'm guessing if this is necessary, maybe the boundary functions should take a higher order function, right?

danielytics 2019-08-09T16:24:49.064100Z

@teaforthecat I suppose if you can have low level boundaries and high level boundaries (high level are like the "good" boundaries in the documentation and low level like the "bad"), then it would work well enough. Then inject any additional domain logic by passing functions in, if necessary.

danielytics 2019-08-09T16:25:22.064300Z

I know I'm rambling a bit, sorry ๐Ÿ˜› thinking in text, I guess.

danielytics 2019-08-09T16:25:32.064500Z

:duckie:

dangercoder 2019-08-09T17:40:36.064700Z

Nothing wrong with writing your thoughts down ๐Ÿ™‚

teaforthecat 2019-08-12T14:44:49.064900Z

ok, it took me another read of that article, but I think Iโ€™m getting it. Thanks so much for posting that @jarvinenemil!

dangercoder 2019-08-12T15:45:59.065100Z

You are welcome @teaforthecat โœŒ๏ธ

teaforthecat 2019-08-07T14:03:44.060300Z

That is an interesting question. Iโ€™ll share my thoughts, which are that the boundary protocol should shift away from the domain as much as possible. If the boundary is a db I think a good interface/protocol would be insert,`update`,`delete`, or any more general methods that are not related to the domain, like execute or something. In your scenario, you might need within-transaction, which is very general and not domain specific. That is how I think about boundaries. Does that help at all?