Repositories and the Save Method

Sunday, June 13, 2010

One of the questions I've been getting lately goes like this:

Should a Repository class have a Save method?

And the standard answer:

It depends.

It's hard to keep track of all the different approaches to implementing the repository pattern these days, but I assume when someone asks me this type of question they are thinking of using code like this:

var employee = new Employee();
employeeRepository.Save(employee);

var account = new Account();
accountRepository.Save(account);

This style always strikes me as odd. It looks like code that wants to move away from an active record approach, but the code hasn't quite reached escape velocity. That's not to say the approach doesn't work. I can think of a couple scenarios where this approach can work well:

  • Applications using a single screen to edit each entity type (straight up CRUD).
  • Applications using the concept of aggregate roots from domain-driven design.

In other scenarios I think this approach only creates additional complications in sharing connections, transaction management, and maintaining entity identities. That's why I expect most repositories to work in conjunction with a unit of work object.

var employee = new Employee();
employeeRepository.Add(employee);

var account = new Account();
accountRepository.Add(account);

uow.Commit();

It's a small difference in code, but a considerable difference in philosophy. No entities are persisted until the unit of work commits. Behind the scenes, each repository working inside the same logical transaction is associated with the same unit of work. This approach simplifies the issues revolving around connections, transactions, and identity. 


Comments
gravatar scott Monday, June 14, 2010
Testing comments ....
gravatar scott Monday, June 14, 2010
Apologies to everyone who emailed and told me they couldn't comment. Needed some database tweaks after a long period of neglect.
gravatar Nikola Malovic Monday, June 14, 2010
I find the second approach favorable in general but it gets a bit more complicated in cases when we have multiple UoW active at the same time (mapping the different bounded contexts) but that goes outside of boundaries of a post comment to a separate post I have to write of my own :)
gravatar Gabriel Perez Monday, June 14, 2010
I think the second approach is really better since you get all the benefits of the UoW. The first approach would make it harder to implement things like batch updates which the UoW can do really well. Also the fist one looks more like a DAO then a Repository in my opinion.
gravatar thinkbeforecoding Monday, June 14, 2010
I validate the first approach for Aggregate Roots since the definition of aggregate Roots states that it's a consistency boundary : you should not change two aggregate roots in a single unit of work... or it is an indication that you have a problem with your aggregate root choice.
gravatar Andrew Wolfe Monday, June 14, 2010
Scott,

You're uncovering why POJOs and CRUDs are, well, cruddy.

Suggest you explore the Oracle ADF BC framework which has a highly disciplined approach to transactions and commits.

Disclaimers: I am a proud Oracle employee but cannot speak for Oracle at all. I'm also interested in comments about my blog post blog.schemaczar.com/2008-12/crud-in-crud-out/
corey coogan Monday, June 14, 2010
I prefer the first method as it makes Persistence Ignorance a tad easier. I don't want to implement a UoW wrapper around NHibernate, L2S, EF, etc.

Using the Session/Request pattern, I can inject my UoW into my repositories and call Commit at the end of my request.
gravatar Scott Monday, June 14, 2010
@corey - good point, it could be done either way. So then the question becomes more a matter of style and "explicitness".
gravatar James Kovacs Monday, June 14, 2010
Good question, Scott. For a long while, I've been implementing IFooRepository with Query/Save/Delete where Foo is an aggregate root. Repository isn't really where Save/Delete should happen. They should be the responsibility of the unit of work.

var employee = new Employee();
uow.Add(employee);

var account = new Account();
uow.Add(account);

uow.Commit();

Then the repositories are only responsible for querying... Also when dealing with ORMs that support persistence by reachability, it becomes much clearer when you need to call Save and when you don't. You could have UnitOfWork.Add(T obj) and UnitOfWork.Delete(T obj) constrained to be an aggregate root (marker interface or abstract base class). Hmmm... This is becoming a blog post... :)
gravatar James Kovacs Monday, June 14, 2010
BTW - Comment submission is failing from FireFox, but working from Chrome...
gravatar Ido Ran Thursday, June 24, 2010
Hi, I also think that this question is not as easy to answer as it might look.
Try to think about more complex object model - for example think on model of social network where you have people and relations.
For sure you have PeopleRepository so you can retrieve all the people and it is also possible you have RelationRepository so you can retrieve all the relations but what about RelationRepository of a specific people?
When you have specific people (p) you might want to ask for all the relations she has (p.Relations) which return a repository with only the relations this person has - now changing this repository should change the main repository, and other repositories because if I delete relation from one person it should be deleted from the other side.
Another reason to have this design is that now you can load only the relations of the people you really need, not having to load the whole relations just to work with 2 out or 2M.

Thanks for the post,
Ido.
gravatar Bunter Wednesday, June 30, 2010
Composite PK-s send this beautiful UoW.Save() spiraling to oblivion. You _have_ to specify ISession.Save-s in correct order in that case.
gravatar Steve Michelotti Tuesday, September 21, 2010
Good post Scott - and on a related note, I really liked your MSDN whitepaper "Testability and Entity Framework 4.0".

I agree there are a ton of different implementations of the repository pattern. I'm curious what API you typically create when saving an existing object (i.e., update), perhaps one that was passed in from another tier (e.g., it came in from a web service call and the original object context is long gone). Your examples above have an Add() method for inserting a new object. But do they also have some Update() method or updating an existing object? From an EF-centric perspective, where do you typically handle the "Attach" of an existing entity? Curious to see how others are doing it. Thanks.
gravatar scott Wednesday, September 22, 2010
Hey Steve - I like to treat the updates as implicit. For example, you use IRepository<T> to query for objects, you set properties on the objects, then call Commit on a unit of work type object (and that saves all the updated property values). So - no Update or Save method on a repository.

I've manage to avoid Attach for the most part, but if I had to use it I think I'd make it part of the IRepository interface.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!