The Repository’s Daughter
Keeping up with the undead theme, this post is a response to Greg’s. I’ll just jump into the parts that I disagree with:
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.
As I understand Greg’s interpretation of my points, I agree. For quite a few needs, there is no need to create an explicit boundary between the persistence medium and the code. Transparent lazy loading and persistence by reachability allow us to hand the entire problem to the infrastructure. The two things that we have to worry about it controlling the fetch paths and making sure that we aren’t doing stupid things like calling the database in a loop.
Those things are the responsibilities of the controllers layer (not necessarily an MVC controller, by the way, I am talking about the highest level in the app that isn’t actually about presentation concerns).
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
- Make it look and act like a full database
- 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?
Well, duh! Of course they would need that. We need to be able to do that to be able to execute business logic. Going back to the example that I gave in the previous post, “Add Product” and “Charge Order” have drastically different data requirements, how are you going to handle that without having support for fetch paths?
The last statement there is quite telling, I think. I thought that my previous post made it clear that I am advocating doing this inside a service boundary. The problem that Greg is trying to state doesn’t exist since you don’t do that across a service boundary.
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.
I call bull on that. Saying that it using an adapter cost “next to nothing” is wrong and misleading. Leaving aside the problems of trying to expose the advance functionality that you need aside, it also doesn’t work. A good example would be using a repository using NHibernate, which take part in a Unit of Work and uses persistence by reachability and auto flush on dirty.
Chances are, the developers aren’t even aware that they are taking advantage of all of that. Trying to switch that with a web service based repository introduce quite a lot of friction to the mix. I know, I was there, and I saw what it did.
That is leaving aside things like how do you expose concurrency violations or transaction deadlocks using different persistence options. You need to control that, and an adapter is generally either a very leaky abstract or a huge effort to write, and it is still leaky. Worse, using an adapter, you are forced to go with the lowest common denominator for features. Of course you would want to isolate that, you are working at a level so low you might as well be writing things to the disk without the benefit of even a file system. That doesn’t mean that this is the smart choice.
Trying to abstract things away unless you have a firm requirement is just about the definition of YAGNI. And handwaving effort required to build this sort of infrastructure doesn’t really make it go away.
Yes, the approach that I am advocating makes a lot of assumptions. If you remove any of them, the approach that I am advocating is invalid. But when the assumptions are valid (inside a service boundary, using a database, using a smart persistence framework), not making use of that is… stealing from your client.
Arguments against my approach should be made in the context that I am establishing it.
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.
Again, I am talking about something in context. Taking it out of context make the argument invalid. I am going to stop here, because I don’t think that there is any value in parallel monologues. Arguments against the approach that I suggesting should be made in the context that I am outlying my suggestion, not outside it.
Comments
Very interesting discussion - could you show an example how you would create a typical application (with UI Layer, Business Layer & Data Layer)?
Thank you very much
Robert,
The whole point of the discussion?
Traditional architecture with DL, BL, UI are not appropriate in many cases
Personally reading all this at a conceptual level is just hurting my brain. I'd really like to see real world examples on both sides, even something as simple as 'customer with orders'.. and I want to get customers starting with 'S', ordered by how much they've spent, and I want the top five orders they've made in the last 6 months.. I sure as hell don't want to get 8 billion orders back and have that filtered in memory- and I also don't want a stupid method like:
GetCustomersStartingWithAndOrderByTotalSpendageIncludingTopOrdersOverPeriod("s", 5, 6);
Because I honestly get the feeling thats what greg is expecting me to do in the real world..
To be perfectly honest I've lost faith in the whole conceptual model at the client.. I'd much rather prefer to have single entities and a way to describe their relationships when querying.. because in my reality, every query I do tends to have drastically different requirements about how related data is included and filtered.. of which using a conceptual model becomes really ugly.
Actually, I've been enjoying this conversation quite a bit lately. As the weakest part of my application api (I feel for my applications) is my repository code.
I keep switching between having one giant repository (that does everything the application needs) and multiple smaller repositories (that are mostly domain object specific).
But the actual methods in each repository (outside of GetById, List, Delete, and Save) is what truely varies. They range from the standard four to hundreds. So far I have not been able to resolve this completely (outside of doing linq magic in my presenters and services).
Bonus points: I only get to use NHibernate about half of the time. Sometimes SubSonic, Entity Framework, Linq To Sql, or my own Fluent Ado.net (raw Ado.net calls). My current standard works between all of those.
You definition of "Servie" & "Service Boundary" isn't clear from the post. Have I missed it? Understanding that will help a lot with grokking your whole point.
What you seem to imply is service spans multiple layers/classes/code files. Is that different from "vertical feature", i.e. a single concern that spans the app from UI to App controllers/servoces to Domain (if any) to DAL.
@Stephen that is exactly why one would want to use command and query separation... By using a separate layer we stop querying our conceptual domain model to build DTOs and instead just issue 1 off queries.
@Oren
It should be pointed out that 90% of what you discuss is web applications. I also hold the position that DDD should not be used as discussed in DDD on web applications (because they tend to be data centric and the boundaries are certainly quite different, a data tier for instance does not make much sense in most web architectures).
Stephen,
What you are asking for is reporting. That is something that I would do with straight up SQL or OLAP.
No logic required.
Victor,
No, think a service as in SOA.
A service is one part of the business, and it encapsulate everything required to perform its operation.
Order Service
Shipping Service
Helpdesk Service
Greg,
I am not sure that I agree with the 90% figure, but I will go with it.
Also note that I am not even touching DDD, except the comment of not trying it unless you really mean it.
My comments are about data access as used in a vast number of applications.
Web apps are a part of that, but I am using the same approach for backend processing systems where the logic is the system
Ayende, If I had to write SQL every time I did a query like that I might as well not bother having any abstractions over my data and work straight with SqlConnection and whatever data object takes my fancy..
These kind of 'complex' queries aren't at all unusual in any website I build, surely I don't get to a point with a data abstraction like datamapping and repos where its 'nah too advanced' forget all of that and 'go raw'.
But greg I tried to find the video on cqs you suggested previously on codebetter, didn't manage but I'll do some research.
Stephen,
What is the purpose of using these types of queries. What you are talking about sounds like a report.
For a report, assuming there is not business logic, the easiest way to work with it is to use SQL and just bind a dataset to a grid.
If you are doing this for something that require business logic, then sure, that is different. But from the description, it sounded a lot more like report / BI than BL
Well it was just an example, a recent real query I've done in the last few days was that I wanted to display comments related to a certain element, I only wanted to display the top x comments, and only the comments of which were not suspended (or the owning user suspended).
Regardless if the feature sounds bizzare, thats the requirement- tools like linq to sql and the entity framework (even nhibernate) will handle these scenarios perfectly fine, I just need to hint to it how I'm going to use the data for it to be realistic (such as fetch strategy).
My point was that if I harshly abstract either of those technologies, I lose the ability to do any hinting on the calling side.. and may will end up with monolithic methods as described (which I just won't do).
Also the conceptual models rarely make sense when reading data, they make perfect sense when making changes to the data.. which was my point about having flat models that had no implicit properties for relations, but instead a simple syntax for explaining conceptual relations at query time.
Stephen,
I am sorry, but while different requirement may have similar potential implementation, I generally classify them differently based on their purpose.
Your first example looked very much like something that I would want to use reporting / BI tools for. It looked like exploratory style of report to try to figure out to whom & how we can sell more.
Reading your original comment with the new example, it make a lot more sense to me. But, an entity is rarely useful on its own, it is only meaningful in the context it exists in (the object graph that represent some business scenario).
Focusing on entities & relations leads to a model that is very anemic. The question then becomes, where is the actual system behavior?
Yes I see your point, I think behavior is generally more important when you want to add / modify / remove data, I just tend to find that when reading data, behavior is not so important- given that I assume correctness that the 'add / modify / remove' enforces.
But then if I have the conceptual model to write, modify and remove data (which will need to read most likely as well) I guess it becomes additional work to create a flatter model just to slightly better suit more 'read and compose' scenarios.
But it does make me wonder if there are any designs based on flatter conceptual models, and system behavior is simply defined in a different (more detached) way.
Stephen,
Take a look at designs based on document databased or key value stores.
As for the rest:
ayende.com/.../Querying-is-a-business-concern.aspx
Hmm, thanks Ayende- I've got some reading to do.
Interesting stuff, I've found your ongoing debate with Greg very illuminating. Particularly timely too, I'm prepping for a TechEd talk, a big part of which is a discussion of Repositories.
Thank you for this very interesting disussion, I enjoyed it. Basically the point which is made here is something i had to learn the hard way.
I started working on a small company two years ago (i was 15) doing some kind of financial services. They had a LOB application based on Access DB's, 7 to be exact with identical schemas for 7 different clients. The application was .Net 2.0 with the famous "SQL in UI" pattern. The dev originally architecting this solultion left the company shortly before i entered. After fixing some serious bugs i soon realized the application wouldnt scale wiht future business requirements. So i started architecting a new solution based on a SQL Server.
After doing 2-3 Months of research my head was flooded with things like n-tier, SOA, DAL/Adapter etc. When I learned about Linq2Sql and the whole ORM thing i opted to go down this road. After basic prototyping and finishing my first "pseudo agile" iteration I learned about the repository pattern and started refactoring and using it instead of Linq2Sql directly in my controllers. The point of why I'm telling you all this is that this whole story is about the same point you made.
I did (quite) randomly apply patterns and violated YAGNI, because i had absolutely no experience.
I was so damn scared of delving into a anti-DRY because of the experiences i made while maintaining the previous LOB application. Today i would not use repository pattern for this application but some kind of CQS, because the app is 1/3 about modifying data and 2/3 about querying.
@Ayende
Whats intriguing me is how you would approach testing without a repository which you can mock out? Do you mock ISession or use a SQLite Db?
See the post about testing NHibernate
Comment preview