Fork me on GitHub
#architecture
<
2020-03-29
>
Joe19:03:17

Hi, I'm trying to do an onion-y architecture with Clojure, looking for a way to use the injected repository in the domain layer. In OO you would simply pass in the repo object and then call the interface methods from the more central layer. From scattered google results it seems possible to do something similar with protocols in Clojure, but is there a best practice way of doing this, or even a wholly different pattern to use?

seancorfield19:03:24

@allaboutthatmace1789 Have you looked at Stuart Sierra's component library?

seancorfield19:03:52

It's a common way to manage the subsystem dependencies and startup lifecycle in Clojure.

Joe19:03:59

I saw it used in Clojure Applied as a way to set up channels for messaging between components, but don't think it touched on persistence. Thanks, I'll check it out!

seancorfield20:03:53

Here's an example of it used to wrap the startup of database, web server, and application subsystems https://github.com/seancorfield/usermanager-example

👍 8
Joe23:03:12

Looking at the documentation for Component, I can see how you would use it to inject the repository connection information into the component (call it A) that uses it. But it looks to me like the examples have functions in A that rely on a specific implementation of the repository, like the following (which I'm guessing is in component A's namespace) assumes a SQL database, and even a specific schema.

(defn get-user [database username]
  (execute-query (:connection database)
    "SELECT * FROM users WHERE username = ?"
    username)) 
What I am (maybe wrongly) looking for is a way to decouple the domain layer from the repo, so the domain layer doesn't care about how the repo is implemented, following this definition from DDD. > A Repository represents all objects of a certain type as a conceptual set (usually emulated). It acts like a collection, except with more elaborate querying capability. Objects of the appropriate type are added and removed, and the machinery behind the Repository inserts them or deletes them from the database.

seancorfield23:03:12

Stuart Sierra has a talk somewhere about using abstractions over services, along with Component, which would give you what you seem to be looking for... let me see if I can find that.

kszabo23:03:19

and attach the implementation via queries through the domain-db in separate repos

kszabo23:03:56

of course your domain has to be complicated enough for this to matter (as always 🙂 )

seancorfield23:03:10

Basically you write a protocol for the service and then you can have multiple implementations, wired up via Component.

seancorfield23:03:11

(personally I feel that is overkill because, aside from testing, I don't believe there are many situations where you really need the same API implemented over SQL and implemented over various other persistence layers -- at least, not at that sort of level)

seancorfield23:03:29

I mean, how many times do you even change from one SQL DB to a different one?

Joe23:03:26

Totally onboard with the argument that you rarely will have to radically shift implementations, and my applications are definitely not large scale. My intent is more to maintain the isolation between layers to prevent the ickiness that tends to build up if you don't, with lesser benefits being comprehensibility, testing, portability. Thanks for the articles!

seancorfield23:03:16

If your Repository is a Component, nothing that uses the Repository needs to know how it is implemented -- the Database Component is just a dependency of the Repository so it remains internal -- and the business layer just asks the Repository for entities.

seancorfield23:03:13

Going back to your method above:

(defn get-user [repo username]
  (jdbc/execute! (:datasource repo) ["select * from users where username = ?" username]))
only the repo-based functions need to know how to get the actual SQL stuff -- repo is opaque elsewhere

seancorfield23:03:58

(but that's really just a convention -- since Clojure doesn't have encapsulation: any code that has repo could just pull the :datasource out of it... but shouldn't)

seancorfield23:03:13

(a common thing we do at work just to provide a level of indirection across components is to implement IFn on a Component so what we'd actually do above is more like this: (jdbc/execute! ((:database repo)) [...]) so (:database repo) just accesses the Database Component and the extra ( ) invoke it -- via IFn -- which in turn lifts the actual (connection pooled) datasource out of the Database Component)

seancorfield23:03:19

That way the Repository only depends on Database and all it knows is that you invoke the Database Component to be able to use it in a SQL operation. And nothing outside of the Repository would be touching the Database at all.

Joe23:03:25

I think I see. So a way to think about it is that your functions for fetching stuff from the repo in the business layer aren't totally abstracted, since you're still settling on an SQL implementation, and your business layer needs to know your database schema. But ultimately you're just tying yourself to SQL as the query language, which, you would have to write some sort of query language for the repo interface anyway. So I guess as long as your database schema is properly reflective of your domain model you're not going to confuse things too much.

seancorfield23:03:56

You can keep them as separate as you want or need. The business layer knows about a repository against which it can a bunch of load/save/query style functions.

seancorfield23:03:23

The repository wraps how the database is accessed (if you're using a database).

seancorfield23:03:48

It is also where any translation between your business domain data structures and your persistence schema is performed.

seancorfield23:03:22

If your persistence schema happens to match your business schema, well then you have a simpler set of transforms in the repo functions.

👍 4