Fork me on GitHub

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 on


Yes, 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!