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?
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.
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.
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.
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.
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.
But it is a major benefit to get if you want to avoid using global vars and state.
@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?
No, just require UserDatabase only. Then you can say (list-all hikaricp-component)
.
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
onYes, the protocol must be implemented for the type(s) of the first parameter of the functions. I mostly use the components' types.
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.
ya, i think component routes in a com.zaxxer.hikariCP
instead of a component, i'm trying to figure out what's going on...
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.
k i think i solved it, i was just destructuring early, and if i destructure less, i get what i wanted 🙂
thanks so much for helping me solve this!
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.
ya, i'm increasingly catching the "vision" of component, thanks so much for passing the torch!