Fork me on GitHub
#duct
<
2018-10-17
>
curlyfry07:10:46

Hi! I'm trying to understand a few aspects around boundaries (https://github.com/duct-framework/duct/wiki/Boundaries). It looks like a neat concept, and I love functional core/imperative shell architecture and making it as explicit as possible. I have a few things I'm wondering about, though: 1. If I use a dedicated boundary namespace with regular functions rather than protocol methods, does that have any downsides except not being as easy to mock? 2. Isn't the fact that instrument doesn't work on protocol methods (https://dev.clojure.org/jira/browse/CLJ-2109) a big downside? In my experience the boundaries are the places were spec give you the most leverage, and this seems to diminish that. 3. Should I use boundaries even when I don't have a "service" to wrap, though I interact with the outside world? An example of this would be making external api calls.

lukas.rychtecky07:10:46

1. I don’t know about other drawbacks. How would you “mock” a function without protocols? with-redefs? 3. IMO it’s still good, because “out-side-world” is in separated namespace and not mixed with internal code.

curlyfry07:10:38

Thanks for the reply! 🙂 1. Yes, I guess I'd use with-redefs or similar if I were to mock (though I'm aware it's dangerous in quite a few occasions). I would however prefer to avoid mocking altogether and instead have unit tests for my functional core, and just a few "sanity checking" integration tests that use a real db, etc. 3. Makes a lot of sense. I could perhaps keep the URL:s in my integrant config and make the "api call" boundary depend on it in order to make it very explicit that no other code should care about the external urls.

lukas.rychtecky07:10:35

1. Personally I feel “nasty” when using with-redefs. Thus I prefer protocols. 3. IMO this is a good idea also for other prog. languages (having a structure be domain logic, explicit boundaries, …).

curlyfry07:10:13

I agree with both. I do however feel that the instrument issue is a bigger downside than not being able to mock stuff (which I'd generally like to avoid anyway). Also, if I were to mock something I'd very much like to be able to rely on a spec to be reasonably sure I'm actually mocking the right thing.

dadair13:10:41

To get around instrument, you can delegate the protocol method to a standard “impl” function that itself is spec’d

dadair13:10:19

(extend-type SomeProto (some-func [this x] (-some-func-impl this x))), where the impl function has the fdef

dadair13:10:43

Boundaries do not have to represent services; they represent “ports” on the boundary of your system, that port could lead to a service, an api, a database, a shell script, etc

👍 12
curlyfry14:10:17

Good to know there's a way around it, thanks!

curlyfry15:10:35

I'll keep reading up on the subject, I remain to be entiiirely convinced that the benefits of using protocols as boundaries are worth the effort and ceremony (partly because I'm mot a big fan of mocks). Side note: I'm currently not using duct, just Integrant on its own.

dadair15:10:34

Mocking (for testing) is just one benefit of protocols. For example, I’ve developed an internal library at work for handling pubsub-based event handling. By coding to a protocol, I can easily expose two different implementations: (1) a core-async, in-memory, low-latency version for either development convenience or for services not requiring horizontal scalability, or (2) a zeroMQ/redis implementation for production or services that demand horizontal scalability.

dadair15:10:00

Mocking for testing is a side-benefit IMO

dadair15:10:07

Could you simply expose X different namespaces, where they all define functions with the same names/etc, to easily switch out? Sure! But there is less safety around ensuring names/signatures are the same, etc

curlyfry15:10:05

That's indeed really cool, but it also seems like I can add it when I need it rather than up front? From what I've seen, the code tends to look pretty much the same at the call site (when using protocols vs namespaces)

dadair15:10:44

Protocols will constrain your signatures to be generic; you do not get the same (by enforcement) through namespaces. Almost guaranteed you will have impl-specific changes to namespace function signatures as the system grows, and it becomes less easy to simply flip the implementation simply by passing an alternate Boundary record/type.

dadair15:10:20

But play around and see! If it works for your use-case, it works :)

curlyfry15:10:36

Great pointers, sorry for being stubborn :) Just want to make sure I really understand things before I start implementing something. I'll try the different approaches out myself!

pithyless09:10:29

I’m working with a similar idea: a domain namespace with specced functions as the public api, which internally just redirect to calling protocol methods, and custom record/types that implement said protocol. This way you can replace implementations, while the rest of the system doesn’t know or care. (I’m not using duct or integrant at the moment, but I think a lot of people are moving in similar directions -> see also https://polylith.gitbook.io/polylith/ which does something similar, only with symlinks).