Ask AyendeLife without repositories, are they worth living?
With regards to my quests against repositories, Matt asks:
For example, you dismiss the repository pattern, but what are the alternatives? For example, in an ASP.NET web application you have controllers. I do NOT want to see this code in my controllers:
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession()) { using (var transaction = session.BeginTransaction()) { // do a large amount of work
// save entities session.SaveOrUpdate(myEntity); transaction.Commit();
} }
That is ugly, repetitive code. I want in my service methods to Get, update, save, and not have to worry about the above.
This is a straw dummy. Set up the alternative as nasty and unattractive as possible, then call out the thing you have just set up as nasty and unattractive. It is a good tactic, except that this isn’t the alternative at all.
If you go with the route that Matt suggested, you are going to get yourself into problems. Serious ones. But that isn’t what I recommend. I talked about this scenario specifically in this post. This is how you are supposed to set things up. In a way that doesn’t get in the way of the application. Everything is wired in the infrastructure, and we can just rely on that to be there. And in your controller, you have a Session property that get the current property, and that is it.
For bonus points, you can move your transaction handling there as well, so you don’t need to handle that either. It makes the code so much easier to work with, because you don’t care about all those external concerns, they are handled elsewhere.
More posts in "Ask Ayende" series:
- (28 Feb 2012) Aggregates and repositories
- (31 Jan 2012) What about the QA env?
- (25 Jan 2012) Handling filtering
- (19 Jan 2012) Life without repositories, are they worth living?
- (17 Jan 2012) Repository for abstracting multiple data sources?
Comments
Doing my application development just like you proposed in the article. Build your infrastructure and everything is much easier from that point on. Thumbs Up!
In my controllers, these examples typically look like this:
// do a large amount of work session.Save(myEntity);
everything else is hidden from the user (inside filters, modules, etc), and still no repository. if myEntity is already a persistent entity (not a new one), even the .Save line is not needed
ooops, formatting... it should have been:
// do a large amount of work session.Save(myEntity);
ok, I give up on trying to break this into 2 lines, but you get the point...
zdeslav, use pastebin and link it here.
bus.Send(command); // \o/
two things i wonder about. how do you test the application with an unit testing framework when so little is abstracted away? how would you mock the database connection?
and the second thing that has become more common, you make a web application and later on some of the functionality has to be shared with some other application, mobile/desktop/whatever.
now you have a lot of business logic in the specific controllers? how would you deal with this scenario?
personally i like to keep the web app ignorant of all things database and BL. it increases the complexity, but i feel it is worth it.
@eatfrog - A lot of the time that sort of stuff is CRUD, it's not real business logic. There's no real need to abstract CRUD into 15 layers.
@Phillip, sure i agree, maybe 70-80% is just plain crud. but then you have the 20-30% that is not (in my use cases). But isn't it better to keep it all in one place? Just because you could do some crud in the web app and put business logic in some other place, it just feels wrong.
and consider something that initially was just crud, and later on you need somekind of validation on inserts. now you might have to look up every place where that insert is done?
The benefit of using a repository (in a correct manner, not like in Northwind) is that you can swap NHibernate for EF or even RavenDB.
If you introduce ISession to your application you can not. Let's say you already have a big e-commerce website running on Nhibernate, but you want to migrate certain parts to RavenDB or a ServiceBus. All you have to do is to bind a new implementation of your repository to the interface. At least that is the whole intention of IoC.
Or am I getting a dinosaur by still following the SOLID principles? Not using repositories (or actually hiding data implementation behind a interface implementation) makes it very hard not to break the 'L' (Liskov Substitution) principle..
If you follow the SOLID principles you should be able to just change the data provider without touching any other code and the (web) application should still be working. If it's not, you've broken the principles..
Dave, when did you last swap your data provider? ;-) Did it go well?
I wish people would stop bringing up this old chestnut of swapping NHibernate for Entity Framework or RavenDB.
Besides the complete YAGNI factor, it doesn't save you any work. You'll either have to go for the lowest common denominator, or else you'll have to build an abstraction layer so complex that it's more work to maintain than rewriting your code in the first place should the need arise. Ayende covered this in some detail some time ago here: http://ayende.com/blog/4567/the-false-myth-of-encapsulating-data-access-in-the-dal
Seriously, that kind of thing takes you deep into Architecture Astronaut territory. Just pick one or the other and stick with it.
Dave, how would your repository differ from that in the application that Ayende is reviewing?
Would you ever really consider swapping ORM's? How many legacy projects have you worked on and re-implemented the DAL? Good luck getting a business case approved for that one!
It would be nice to see an example how to handle queries like:
session.QueryOver<Order>.Where(x => x.Customer == session.Load<Customer>(customerId).OrderBy(x => x.Timestamp).
If I have that in my code then I need a way to mock it in unit tests. I can use sqlite but I don't want to use it for every test because I might be testing completely different thing. So for the sake of example let's say I have to perform three complex queries on different entities, then do something else and finally send an email. If my test only cares about the fact that email is sent then how do I handle the queries? It would seem like a lot of work to mock all those Where, OrderBy etc.
One way is to capsulate each query into own class and mock those but if I do that am I just reinventing the repository pattern?
Hm...is this repository thing really worth the hole discussion? Everyone can do it simply the way he prefers. For me, i like it to abstract away the ORM in my app and provide a clear and simple interface to the developer. I call it simply IEntityAccess and for the most cases it does what we need from it. That means not that i try to completely hide the ORM. In the cases where i need features from NH i simply use it. This is no problem, since all data access happens in services or models. I also think it is difference if you build an ASP app or if you build an WPF/WinForm app. In the latter case i can't imagine how to build a working session management without any abstraction.So in the end i think the truth lies somewhere in the middle.
Honestly, I've dropped the entire Repository pattern out the window a long time ago.
I just have a QueryService, SaveOrUpdateCommand and a DeleteCommand. I can extend the query service in any way needed, because it just returns an IQueryable.
If I need something more complex, I can always wrap, decorate or implement it whenever the need presents itself.
I can go back to the Repository pattern whenever, but it would just group the above services. It doesn't need to be any more complex than this.
Oh, and you always swap out the implementation at will, of course. The operations are always the same, either query or execute a command that does something to your entity.
As Dino Esposito kept stressing in his book “Microsoft.NET: Architecting Applications for the Enterprise”; it Depends.
I believe the choice depends on your specific scenario. If all the controller does is coordinate a relatively simple process (e.g. CRUD) then yes, it does not do any harm to put the logic in the controller.
On the other hand, if the controller coordinating some business process, (for example receiving order information, validating the order, checking stock, check postage and tracking options, some additional business rules then finally processing the order) then the whole ordering process could be wrapped up in a service layer or business façade. The same argument holds for repositories.
If a particular database persistence scenario involves coordinating a number of SQL update queries (through ORM or parameterized SQL) and stored procedures, then yes I will gladly hide the implementation details behind a repository.
If for consistency you want all data access to go through some kind of service, then that is up to you.
If you want to use mix and match depending on your scenario that is up to you as well.
Also if the same logic is going to be reused in say a WPF or C++ application, then hiding the data access code behind a repository (or service / business façade) could be a cleaner approach.
@Ayende, Do you consider your ORM to be your data access and abstraction layer (repository)? Some folks do. Others don’t see it that way.
They see their ORM hiding behind a repository that coordinates calls to the ORM.
Neither is wrong. It depends.
@Toni
Why not just mock ISession?
I'm the guy who asked this question, and it was to illustrate a point more than for an answer. However, I like this post - you're describing the problem and telling us the right way to do it - thanks for that.
I actually do the same thing in my web apps, except I use structure map - it amounts to the same thing except I find unit testing a little easier (yes, some places do mandate unit tests on everything!).
@Ayende - I'm interested in your comment about transactions. I generally don't wrap my transactions up in the HTTP request scope - is there no penalty to having a BeginTransaction() at the beginning of every request in the context of NHibernate?
@Mihai - I don't want to make any assumptions about your API, but I strongly recommend not passing around IQueryables in your application. This can lead to a whole world of pain as complexity grows - especially when method chaining starts happening with shared code, and you start seeing these massive expressions being built because some developer didn't quite understand the implication of using a method that returns an IQueryable within a method that returns an IQueryable, and so on. I prefer to defining what can be retrieved and how - which admittedly is in direct contravention with what Ayende is arguing in this thread.
Dave, being able to swap out NHibernate for EF or other by using Repository is a pipe dream no one has ever managed to pull off.
Mihai, I have pretty much the same architecture as you using command classes and query objects. No repositories needed.
I think a lot of it depends on both the general architecture of the system, and certainly the team implementing it.
Where I see this pattern breaking down is with systems that have many moving parts. For instance, there might be 1 (or even more) front-end web applications. There might be a couple of back-end services that handle work from a message queue. There might be a web service API exposed for 3rd parties. And all of these "share" the same data access layer, and perhaps do many of the same things. At this point it certainly might be a good idea to abstract data access behind some internal API.
I agree with this 100%. At first I was going to write how it felt "sloppy" but when I wrote out an example to show it... it wasn't sloppy at all. There isn't much difference between
var customer = session.Get<Customer>(id)
andvar customer = customerRepository.Find(id)
in fact they are the same thing. About the only thing that feels "out of place" is the using block for the session, and that's trivial.Thank you, Ayende. I used to be a huge fan of Repositories for everything but now, not so much, not when using an ORM that does the same thing.
@Wayne M - I'm not convinced. It's easy to justify with trivial examples, but what do you do when you have different ways of querying the Customers table?
For example, you have an application that also needs to fetch customers via email, or mobile number.
Do you put, in your controller
I seem to recall Ayende saying that he would use some kind of extension method to handle an example like that, but I don't 100% remember.
Ugh, apologies for the terrible formatting there.
That was with entity framework. NHibernate is different. You could put a static method on Customer, of course, but that has unit test implications.
IMO the core of the problem stems from using one model for both writes and reads. for my write side, each use case sits behind an role interface who is responsible for fetching (including eager) and whatever else needs to be performed and simply handing back a ref to the interface. so now my application layer (client) only knows about a particular behavior abstraction and nothing else.
Matt,
That's easily done using extension methods. Clean, simple and reusable.
public static IEnumerable<T> GetAll<T>(this ISession instance, Expression<Func<T, bool>> where) where T : class { return instance.QueryOver<T>().Where(where).List(); }
var customers = Current.Session.GetAll<Customer>(x => x.Email == "foo@bar");
@Matt You can create an action filter for doing the transactions and place them where ever you need them. You can also specify the isolation level that way.
Matt, your query could be written like this:
var customer = Session.Query<Customer>() .Where(x => x.Email == email) .FirstOrDefault();
And in a view you might need to display some customer settings as well. var customer = Session.Query<Customer>() .Where(x => x.Email == email) .Fetch(x => x.Settings) .FirstOrDefault();
In unittests, whats different between
var filteredProducts = Testsession.Query.WhereProductsOnSale().ToList(); Asser.Equal(2, filteredProducts.Count);
var filteredProducts = ProductsRepository.GetProductsOnSale().ToList(); Asser.Equal(2, filteredProducts.Count);
@Josh - nice idea - never thought of doing that but it makes perfect sense. Thanks for the tip.
@Stefan and @Alexander - I take your point and yes it does look like it covers most scenarios, the query is just an example. What about queries that require snapshot data i.e. a combination of a number of tables for a report?
What about settling in the middle a bit -- I totally agree that the amount of ceremony in the "best practices examples" is absolutely overblown. But I think having direct data access inside your UI layer is a pretty bad choice as well -- even if the modern UI layer is a bit more testable and fungable than the UI layers of years past. I also think having an implied transaction around every HTTP request is perhaps overkill, especially with everything hitting the managed pipeline. Which is a really long-winded way of saying "what about our good buddy the service layer? He can handle the complex cases rather transparently, access other services and should be allowed to access data directly. We can have explicit save methods for those of us that like those things. And I don't feel like a dirty little ****** by talking to the session in MVC action methods."
Talking of handling sessions what do you think of this?
http://nhforge.org/blogs/nhibernate/archive/2011/03/03/effective-nhibernate-session-management-for-web-apps.aspx
@Matt, personally I abstract away complex scenarios when needed. Those are usually quite rare compared to the number of simple queries (in my own experience). Depends on the application/context.
I also like adding helpers that only extract away the resource management and not further abstracting the nhibernate API like so:
public void ExecuteAndCommit(Action<Session> actionOnSession){ using(var session = _sessionFactory.OpenSession()) { using(var transaction = session.BeginTransaction()) { actionOnSession(session); } } }
and using it like:
var task = new Task(); _dbExecutor.ExecuteAndCommit((s) => s.Save(task));
It all really just depends on how much complexity the system actually needs.
If you're just building a single web-app that talks to a single data-source, then you certainly don't need multiple levels of abstraction. You can get away with writing your data access logic against your ORM in an MVC controller because each unit of code/logic is likely to only ever exist in that one place, and thus be tested.
If you're building a larger-scale, maybe an "enterprise" level system, with multiple application-layer components, then abstracting these gritty details into a "service" layer with some well-designed API might be the pragmatic way to go.
@John That is a lot of code... Also, I don't see a way to set the transactions isolation level when doing it in an HttpModule like that.
so how are you recommending caching when making calls directly from the controllers? i would not think extension methods as those are "nice to have" and do not force the client using the ISession/IDbContext.
i'm also talking about caching data, not the results the view displays.
@John I've found using Ninject.Web.Mvc the easiest way to handle sessions. http://pastebin.com/uiwdNKY8
Ayende had a series of posts titled "Refactoring toward frictionless & odorless code" that covered how you could wire up NHibernate transactions into your infrastructure, meaning your MVC controllers didn't have to worry about this - see http://ayende.com/blog/4803/refactoring-toward-frictionless-odorless-code-the-baseline for the first one.
Ayende what are your thoughts about using S#arp Architecture and its built in repositories? I've found this framework to be really nice for larger more complex applications coded with MVC.
Ayende is definitely wrong on this topic
@Wyatt Barnett, what data access code is in the controlers? The ORM is the actual data acess layer. Your controller is telling it what to fetch.
In my experience, when people abstract the ORM behind some construct, you'll see stuff like a Search or Filter method where you pass in some abstractions to define the filter to use. So why not just use the ORM constructs (especially since most ORMs support LINQ)?
JamesG, A controller's action is subject to SRP as well, most of the time, when I am putting a lot in the action, that is because it is doing a read, and that is the best place to put it. All of those thins that you mentioned are logic, and that happen on write actions. I have very little problem with putting them behind a service boundary.
Matt, There is a cost for doing BeginTransaction (you have to go to the DB for it). But you can do that lazily (on first session access, frex). That is the power of abstracting away the infrastructure.
Joseph, Why would they share the same data layer? They have drastically different needs. They might (or might not) share the same database, but that is beside the point.
Marco, Caching is not a cross cutting concern. I am afraid that I don't understand your question
Mark, I have done a review about S#arp (look at the reviews tag) And I have a review about S#arp Life that is supposed to be published in a few weeks
wrongwrong, You are so articulate, you convinced me.
Thanks, I had always assumed that was the case, and have wrapped transactions where I've needed them. I really like Josh's idea in the MVC world of using an attribute for that purpose.
I have to admit, with NHibernate I am seeing your point and I think a half-way approach is probably best. For complex queries that are useful in a shared context I'm still for moving it into a service layer of some sort. Update commands - service layer.
However, for simple queries, I have to concede there is no point in adding it to the repository - it serves little to no purpose. Pragmatism and applying the principle of DRY.
I myself am slowly moving away from the more traditional repository patterns and adapting more and more aspects of CQRS. Two important articles that have been the driving force behind that are these:
"Meanwhile… on the command side of my architecture" http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91
"Meanwhile… on the query side of my architecture" http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92
We've applied both in a large enterprise-class system and they've proven to make our life a lot easier. We've applied TDD all the way and usually use SqlLIte to make sure our optimized queries still work when using NH's new QueryOver API.
Comment preview