Fork me on GitHub
#component
<
2016-10-26
>
josh.freckleton00:10:45

Quick question on "Boundaries" as used in duct, how exactly do you wire them up? I have a users table in a db, and a db component (the duct hikaricp component), and so I should make a UsersDatabase boundary, with a protocol implementing HikariCP, right? But, if I understand, the Boundary is not a Component, correct? Now, say I want to list-all users, that db access code lives in the boundary, but then how do I use that list-all fn in, say, a login endpoint? Is UsersDatabase a dependency injected by component? Or just something I req in the namespace?

sandqvist17:10:25

I don't use Duct, but I can try to guess what the documentation means based on the "normal" use of component. Your components are records that hold state. The hikaricp component holds the db connection pool. A function that lists all users should take the db component as its first parameter. That's all you really need to do.

sandqvist17:10:41

Then if/when you want to mock the database functions for testing higher-layer functionality, you can create a UsersDatabase protocol (and call it a Boundary if you like). Then all higher-level code should include and use the protocol functions only. This allows you to create a mock db component and implement the UsersDatabase protocol for it. After that, just swapping the record changes the functionality in your system.

sandqvist17:10:19

On the "injection": the db component is a dependency of the handler component, so the db component can be taken from the handler component by any function which takes the handler as a parameter. The reason for having the dependencies in a tree structure is that you don't have to (and should not) pass the full system around all namespaces and will have a good understanding of your dependencies.

sandqvist17:10:08

It may help you not to think of the component library as a dependency injection framework, but as a way to conveniently organize your parameters in the correct way for functional programming.

sandqvist18:10:09

Look at the parameter for print-users. It is not convenient to write something like that when you have more dependencies. component creates and maintains that structure for you so you can give it to the top-level function. That's all it does.

sandqvist18:10:02

But it is a major benefit to get if you want to avoid using global vars and state.

josh.freckleton18:10:24

@sandqvist That clears up a bit. But not all the way for me. So, if you want to create a new boundary, eg UserDatabase, duct generates a file with a (defprotocol UserDatabase ...) where I would stick the API description, such as list-all. Then, it generates code to (extend-protocol UserDatabase duct.component.hikaricp.HikariCP ...) where I could implement list-all, eg (jdbc/query ...) Do I need to inject UserDatabase as a component? Or how do I use that in, for ex, and endpoint that wants to access the UserDb?

sandqvist18:10:53

No, just require UserDatabase only. Then you can say (list-all hikaricp-component).

josh.freckleton18:10:46

ah ok, I'd tried that with the error:

No implementation of method: :list-all of protocol: #'trackit.boundary.user-database/UserDatabase found for class: com.zaxxer.hikari.HikariDataSource
but I think that's because com.zaxxer.hikari... isn't the same as the duct.component... I'm extending, but I missed that when I first saw the error, and I need to watch what I call list-all on

sandqvist18:10:10

Yes, the protocol must be implemented for the type(s) of the first parameter of the functions. I mostly use the components' types.

sandqvist18:10:07

The other option is that the handler ns functions extract the com.zaxxer.hikariCP instance from the connection pool component, but it breaks the abstraction.

josh.freckleton18:10:58

ya, i think component routes in a com.zaxxer.hikariCP instead of a component, i'm trying to figure out what's going on...

sandqvist18:10:12

If you are using the duct components, it may be that a function in your handler ns is looking inside the cp component to get the pool itself.

josh.freckleton18:10:32

k i think i solved it, i was just destructuring early, and if i destructure less, i get what i wanted 🙂

josh.freckleton18:10:39

thanks so much for helping me solve this!

sandqvist18:10:47

No problem, I like to help people get started with component. It would have saved me a lot of work if someone had explained this stuff to me when I started learning Clojure.

josh.freckleton18:10:08

ya, i'm increasingly catching the "vision" of component, thanks so much for passing the torch!