Night of the living Repositories
This is a response to Greg’s post in reply to mine. I can’t recall the last time I had a blog debate, they are usually fun to have. I am going to comment on Greg’s post as I read it. So this part is written before I did anything but skim the first paragraph or so.
A few things that I want to clarify:
- His post was originally intended to be an “alternative to the repository pattern” which he believes “is dead”.
- Of course this is far from a new idea
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.
The notion of query objects is also far from my invention, this is just to clear that out, it is a well established pattern that I am particularly fond of.
Now, let us move to the part that I really object to:
What is particularly annoying is the sensationalism associated with this post. It is extremely odd to argue against a pattern by suggesting to use the pattern eh? The suggested way to avoid the Repository pattern is to use the Repository pattern which shortening the definition he provided
Provides the domain collection semantics to a set of aggregate root objects.
So now that we have determined that he has not actually come up with anything new and is actually still using repositories let’s reframe the original argument into what it really is.
I fail to see how I am suggesting the use of the repository pattern in my post. This seems to be a fairly important point in Greg’s argument. And no, I don’t follow how I did that. Using the approach that I outlined in the post, there is no such thing as repository. Persistence concerns are handled by the persistence framework directly (using ISession in NHibernate, for example), queries are handled using Query Objects.
The notion of providing in memory collection semantics is not needed anymore, because that responsibility is no longer in the user code, it is the responsibility of the underlying persistence framework. Also note that I explicitly targeted the general use of the repository, not just the DDD use of it.
The problem here is that the Repository interface is not necessarily Object Oriented. The Repository represents an architectural boundary, it is intended to be a LAYER/TIER boundary. Generally speaking when we define such interfaces we define them in a procedural manner (and with good cause).
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…”
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.
The problem is, again, we aren’t attempting to take advantage on the capabilities of the persistence frameworks that we have. We can have OO semantics on top of persistence store, because the responsibility to handle that is in the hands of the persistence framework.
Analyzing the situation given of a CustomerRepository what would happen if we were to want to put the data access behind a remote facade?
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. And there is a reason why I don’t try to give architectural advice without having a lot more context. In this case, we have a future requirement that doesn’t really make sense at all.
What would happen though if we used the “other” Repository interface that is being suggested? Well our remote facade would need to support the passing of any criteria dynamically across its contract, this is generally considered bad contract design as we then will have great trouble figuring out and optimizing what our service actually does.
If I need to make the data access remotely, then I am still within my own service (remember, services can span several machines, and multiple applications can take part of the same service). As such, I know exactly what the requirements and the queries that my remote data access is going to require. More than that, within a service, I want as much flexibility as I can get.
It is on a service boundary, that I start pouring out concrete and post the armed guards.
I also think that there is some sort of miscommunication, or perhaps, as usual, I split a thought across several posts, because a few posts after the post Greg is talking about, I suggested just what he is talking about.
If you don’t want a LAYER/TIER boundary don’t have one just use nhibernate directly …
That is what I am advocating. Or Linq to Sql, or whatever ( as long as it has enough smarts to support what you need without doing nasty things to your code).
My whole argument is that the persistence framework is smart enough today that we don’t need to roll this stuff by hand anymore!
At this point you probably shouldn’t have a domain either though …
And I call bull on that. Repositories != domain, and how you do data access has nothing to do with how you structure your application.
Something that me & Greg & Udi have discussed quite often in the past is the notion of Command Query Separation. I’ll let Greg talk about that, and then add my comments:
I have had this smell in the past as well but instead of destroying the layering I am building into my domain (with good reason, see DDD by Evans for why) I went a completely different route. I noticed very quickly that it was by some random chance that my fetch plans were being different. I had a very distinct place where things were different, I needed very different fetching plans between when I was getting domain objects to perform a writing behaviour on them as opposed to when I was reading objects to say build a DTO.
Well, yes & no. There are quite a few scenarios in which I want to have a different fetch plan for writing behavior even when using CQS. But before I show the example, I want to point out that it is just an example. Don’t try to nitpick this example, talk about the generic principle.
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.
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.
And yes, they are not big applications, with a lot of users and a lot of data, but they are often important applications, with a lot of complexity and behavior.
Comments
This discussion smells like politics, hope it doesn't turn into a religious war. There are so many ways of writing good software, even with repositories inside, that it's practically impossible to definitely tell that some pattern is good or bad. And conversely, best practices can be used to produce crappy soft. So, for some applications repository makes a perfect sense (like when you have very well defined set of data operations) and for other apps its just an obstacle.
Oren,
instead of debating on the meaning of words and approaches (I know, sometimes I like to do it as well, it's sooo fun :)) can you provide us with a sample of this in practice?
Or a link to some code sample that use this?
Simo
I think the main point is that NHibernate is mature enough and feature rich leaving Repositories little to cover which is leading to questioning the need for repositories. The argument is valid and but it's only applicable to a specific kinds of project; those that are DDDish and do data access NHibernate style. if you have a little different scenario then you need repositories.
The whole discussion has remembered me of one very important point: don't stop questioning critically what you're doing.
I mean, I've been a repository guy for quite some time. But when this discussion came up I started thinking about the way I'm using repos and what benefits and problems I have with them - I realized that maybe I've often been using repositories out of habit.
Therefore I'd like to thank Oren, Greg and all the others for having this discussion in the first place, may it even be political or religious or whatever. For me it's not about which approach is the best - it's about intentionally considering and choosing options. That's what S/W engineering is about, isn't it? Thanks people for reminding me of that once more!
BTW, I'll give Oren's approach a shot in a current greenfield project, I'll see where it takes me.
Rafal is correct.
As long as one can reliably deliver working software that is fit for purpose, promotes open-closed principle throughout its design and is easy to understand/test/maintain then surely this is good enough many times.
Daniel
Oren, just a quick question now that you mention IFetchingStrategy. Using "your" approach, do you define the fetching strategy in the query object?
BTW: in my first post "remembered" in the first sentence should have been "reminded".
"The whole discussion has remembered me of one very important point: don't stop questioning critically what you're doing.
I mean, I've been a repository guy for quite some time. But when this discussion came up I started thinking about the way I'm using repos and what benefits and problems I have with them - I realized that maybe I've often been using repositories out of habit."
Andre - Those are wise words. When I read that I couldn't help but think about one of the points Scott Bellware made on alt.net podcast #17. People need to think critically about the patterns / technologies they are using as opposed to just using them because out of habit or because it's the "alt.net way".
The fact is there is no silver bullet. Each approach has it's pros and cons. I started a few a years ago with query objects to a generic repository (basically what Oren's suggesting without the abstraction of the repository), switched to named methods on concrete repos, and am now back to query objects on my current project. Both approaches have pros and both approaches have cons. It's really up to what kind of project you have which should determine your approach.
Andre,
I don't, I let the context decide that, this is pretty easy to do, since we don't have fixed queries
I use the repository pattern for a couple of reasons:
It is technology agnostic - web-service calls, nhibernate, etc.
Much easier to mock in unit tests and much faster then actual database call(even with in-mem db)
I have not done DDD approach to limit the repository to accessing aggregate roots only, so not sure how that would play out, I am going to head over to Greg's blog and see what he has to say about the paging, sorting and all that ;)
Best regards,
Petar
Comment preview