More on Repository

Oren has since responded so let me in turn respond. Its something that we will never agree upon because we have been in such different contexts.

He starts by saying

First, careful re-reading of the actual post doesn’t show me where I said that the repository pattern is dead. What I said was that the pattern doesn’t take into account advances in the persistence frameworks, and that in many cases, applying it on top of existing persistence framework don’t give us much.

I am sorry the exact statements were … “Repositories are a FAD" and “Repositories are the new Singleton”. I incorrectly used the term “dead” but the other statements are just as sensational to me.

Of course I have already rebutted the argument that is being presented that it doesn’t take into account the “advances in persistence frameworks” by bringing up the fact that it represents a LAYER/TIER boundary in an architecture. To this Oren replies:

Hm, I can see Greg’s point here, but I am not sure that I agree with him here. I would specify it differently .Service boundaries are procedural (be it RPC or message based, doesn’t matter). But a service spans both layers and tiers, and I am not going to try to create artificial boundaries inside my service. And yes, they are artificial. Remember: “A Repository mediates between the domain and data mapping layers…"

The boundary is not arbitrary or artificial. The boundary comes back to the reasons we were actually creating a domain model in the first place. it seems what Oren is actually arguing against is not whether “advances in ORMs” have changed things but that he questions the isolation at all. The whole point of the separation is to remove such details from our thinking when we deal with the domain and to make explicit the boundaries around the domain and the contracts of those boundaries. If you want further reasons read up on Hexagonal (Port/Adapter) architectures they are very well explained (note these are sort of like the “onion architecture” which was just a restatement of these ideas …. still waiting on that post to show the differences Jeff).

If we take Oren’s advice, we can store our data anywhere … so long as it looks and acts like a database. If that is not the case then oops we have to either

a) Make it look and act like a full database
b) Scrap our code that treated it as such and go back to the explicit route.

Just to be clear on this point … He has baked in fetch paths, full criteria, and ordering into his Query objects so any new implementation would also have to support all of those things. Tell me how do you do this when you are getting your data now from an explicit service?

Do all systems need to have ports/adapters around data access? No! but these same systems are likely good candidates to not be using a domain model but to instead be using one of the many simpler mechanisms out there. When you pop the bubble that has been built around your domain why not just do it again? who needs application services, we can just bind directly to domain objects …

Oren backs up his beliefs with YAGNI

I am going to call YAGNI on that. Until and unless I have that requirement, I am not going to think about that. There is a reason we have YAGNI.

Its not just about YAGNI its about risk management. We make the decision early (Port/Adapter) that will allow us to delay other decisions. It also costs us next to nothing up front to allow for our change later. YAGNI should never be used without thought of risk management, we should always be able to quantify our initial cost, the probability of change, and the cost of the change in the future.

In this case we have an initial cost which is the creation of thin repository classes that encapsulate our query objects (see Generic Repository post for example). These cost us nothing to create up front as compared to what Oren is showing… Literally minutes, especially when using command/query separation! What is the probability that we will have to work with a data store other than a database in the future? Say a tier boundary to a data service? This can change depending on your scenario … What is the cost if it happens, this is the key. the cost is large.

I think it becomes more obvious what the problem is when Oren defines a Repository as

A repository is a gateway to the actual persistence store. The persistence store itself may be another service, it is usually a remote machine, and the interface to that is by necessity pretty procedural. Trying to model a repository on top of that would by necessity lead us to procedural code. But that is a bad thing.

This is not a Repository this is generally a DAO. Repositories only give collection semantics to the domain, they SHOULD NOT contain the implementation I have gone through this in the past in a post I referenced in my last The Generic Repository. When you view a repository in this way things start to make more sense.

 

Continuing along…

A simple example would be a shopping cart, and the following commands:

  • AddProduct { ProductId = 12, Quantity = 2}
    • This require us to check if the product already exists in the cart, so we need to load the Items collections
  • Purchase
    • We can execute this with properties local to the shopping cart, so no need to load the items collection, this just charge the customer and change the cart status to Ordered

As I said, this is a simple example, and you could probably poke holes in it, that is not the point. The point is that this is a real example of real world issues. There is a reason why IFetchingStrategy is such an important concept.

Let me point out a large failure in logic here. You assume an impedance mismatch with a relational database that results in a much higher cost of getting the children with the parents. If I am using other mechanisms like say storing the ShoppingCart as a document in CouchDb that the cost will be nearly identical whether I bring back only the Cart or the Items.

Evans was very explicit in talking about aggregate root boundaries in that we need to consider them to be wholely loaded or not loaded at all. The whole concept of fetching strategies is nothing but a micro-optimization for a specific scenario … Relational Databases … If you are getting to the point where you absolutely need fetching paths you probably have your aggregate roots defined incorrectly and should use it as a smell to revisit them. I may have to revisit this in another post.

That is leaving aside that like all architectural patterns, CQS is something that you shouldn’t just apply blindly. You should make a decision base on additional complexity vs. required scope before using it. And in many applications, CQS, or a separate OLTP vs. Reporting models, are often not necessary.

I disagree with this and it shows a lack of understanding of CQS. I would recommend every system to apply CQS. At its root that is just a statement that you should have a separate layer for queries that builds DTOs off of projections as opposed to being built off of domain objects. There is no extra work to do or added complexity it is only different work in that you are putting the code somewhere else.

The idea of using a separate reporting model is allowed by CQS but it does not have to be used.  In fact I would imagine most systems would not use but would instead be projecting their OLTP model, they will still end up with more control and a better overall system that they had projecting off their domain objects. If at some point later non-functional requirements dictate a need for a separate reporting model it can be introduced

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

6 Responses to More on Repository

  1. Wxevxywz says:

    2hXLm1 comment6 ,

  2. Greg says:

    @Nick

    Because its separated off in another that is tightly coupled to a reading database?

    When using CQS you don’t try to hide the fact that there is a database being used in anyway … you query directly against the db as opposed to querying off an abstracted model.

  3. Peter Morris says:

    I’d like to see an example app which uses data access for DTOs and CQS. Do you know of any existing ones?

  4. Michael Hart says:

    Greg, what sort of patterns are you using to query in your domain for commands that require it?

    A similar question was asked on the DDD list a while back, but ended up deviating down a separate path (just for a change).

    For example, let’s say my application layer receives a command to update all orders containing a particular product and assume that order is itself an aggregate root (ie, the product is not an aggregate of orders).

    Does the application layer query the “query layer” and receive, for example, a DTO containing the IDs of the order aggregates to update?

    If so, then what sort of persistence-ignorant patterns would you use to perform this query from the application layer? Would it be a similar mechanism to the queries required by, for example, the presentation layer?

  5. Nick Gieschen says:

    In the previous repository post you said:

    “I should point out that for the read layer, what is being shown by Oren is a great way of implementing it. ”

    And in this one you say:

    “He has baked in fetch paths, full criteria, and ordering into his Query objects so any new implementation would also have to support all of those things. Tell me how do you do this when you are getting your data now from an explicit service?”

    I’m not try to catch you out or anything, but how does your objection then not apply to your read layer if it’s implemented as Oren advises.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>