architecture

Joe 2020-03-29T19:45:17.005500Z

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?

seancorfield 2020-03-29T19:53:24.006Z

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

seancorfield 2020-03-29T19:53:52.006500Z

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

Joe 2020-03-29T19:58:59.008500Z

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!

seancorfield 2020-03-29T20:00:53.009200Z

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

👍 2
Joe 2020-03-29T23:02:12.016800Z

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.

kszabo 2020-03-29T23:07:41.017200Z

we use this approach for domain modelling: https://vvvvalvalval.github.io/posts/2018-07-23-datascript-as-a-lingua-franca-for-domain-modeling.html

👍 1
seancorfield 2020-03-29T23:08:12.018500Z

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.

kszabo 2020-03-29T23:08:19.018600Z

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

kszabo 2020-03-29T23:08:56.019300Z

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

seancorfield 2020-03-29T23:09:00.019400Z

https://www.infoq.com/presentations/Clojure-Large-scale-patterns-techniques/ I think this is it.

seancorfield 2020-03-29T23:10:10.020100Z

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

seancorfield 2020-03-29T23:11:11.021300Z

(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)

seancorfield 2020-03-29T23:11:29.021700Z

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

Joe 2020-03-29T23:26:26.026700Z

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!

seancorfield 2020-03-29T23:28:16.028300Z

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.

seancorfield 2020-03-29T23:32:13.031700Z

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

seancorfield 2020-03-29T23:32:58.032700Z

(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)

seancorfield 2020-03-29T23:38:13.037700Z

(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)

seancorfield 2020-03-29T23:39:19.039500Z

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.

Joe 2020-03-29T23:45:25.042Z

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.

seancorfield 2020-03-29T23:46:56.043Z

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.

seancorfield 2020-03-29T23:47:23.043500Z

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

seancorfield 2020-03-29T23:47:48.044100Z

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

seancorfield 2020-03-29T23:48:22.044900Z

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

👍 1