I’m going to be talking at this evening’s Open Source .NET Exchange at Skills Matter. It’s just around the corner from Farringdon station and is ably organised by Gojko Adzic. I think there are still some places left. It’s free, but you need to register in advance. It should be great evening with some excellent speakers. I’ll be there too :)
You can download the slides for my talk here:
http://static.mikehadlow.com/Implementing the Repository Pattern.pptx
See you there!
Hi, Mike,
ReplyDeleteGreat slides. I really appreciate the simple, comparative approach you've taken here. I hope you don't mind if I take advantage of that straigh ahead nature an pose a question or two for you (or your kind commentors) to weigh in on. I'm all for accessing children through their aggregate root and this makes complete sense for retrieval. Where I get lost is on updates to the children. If I follow the aggregate root = repository logic I'd have something like:
class Stock {
...
IList<StockQuote> StockQuotes {get;}
...
}
StockRepository : IRepository<Stock> {
Stock GetStock(string symbol);
IList<Stocks> GetStocks();
void AddStock(Stock stock);
void SaveStock(Stock stock);
void RemoveStock(Stock stock);
}
From that I can easily get to any Stock and its StockQuotes. But what if I need to add or modify a StockQuote? Should I have a StockQuoteRepository? If so, how do I work it into the code? For example:
void SaveStockQuote(Stock stock, StockQuote quote) {
StockQuoteRepository rep = new StockQuoteRepository();
rep.SaveStockQuote(quote);
stock.StockQuotes.Add(quote);
}
That seems to violoate the access via the aggreate root rule. Thoughts?
-Brian
Hi Brian,
ReplyDeleteA good ORM (such as NHibernate) will track the changes to any entity that it retrieves. So you get your stock and modify one of the stock quotes as you describe. When the unit of work commits, the stock quote will get saved to the database.
This means that you don't call SaveStock except when saving a new stock. Updates just happen. This fits in with the notion that a repository should mimic an in-memory collection. The unit of work should be controlled from infrastructure. People often use a unit-of-work per action or request pattern for this.
Mike,
ReplyDeleteCan you elaborate on what you mean when you say that the Repository should not control transactions?
--Jeff
Hi Jeff,
ReplyDeleteAs I explained in the talk, the repository should look like a collection of aggregate roots to its client. Obviously you wouldn't expect a collection to have transactional responsibilities, so you shouldn't have methods on your repository like StartTransaction, CommitTransaction or RollbackTransaction. It's a far better plan to separate the transaction into some other infrastructure. For example, we use the Windsor transaction attribute on our controllers.
Unit of Work and Transaction are different concepts but typically have similar scope.
This would be a great subject for a blog post :)
I understand why you don't want to litter up the repository interface with transaction specific methods. But what about encapsulating transaction logic? Example: imagine an OrderRepository with an Order aggregate root. I want to transactionally save the Order and its OrderDetails. Would you see a problem in simply performing the transaction in the repository without exposing the transaction mechanism to the client code which calls the repository?
ReplyDeleteHi Jeff,
ReplyDeleteI guess it depends on the implementation details of your data access. I'm using NHibernate for my current project. The NHibernate session is handled by the Windsor NHibernate Facility. My repository implementation is a very thin wrapper around the NHibernate session. Transactions are managed by the Windsor Transaction Facility which is orthogonal to the repository. Typically I would decorate my action methods with TransactonAttribute. So transactions are handled entirely separately from from the repository itself.
However if you are rolling your own data access using a lower level API and wrapping that in a repository you might want to implement some transactional code internally. But even then I'd be wary. Starting and ending a transaction is not the same as calling Save(). You might have several 'saves' or 'updates' in a single transaction. I can't see how you could let your repository control the transaction scope without having some public methods such as BeginTransaction and CommitTransaction. And of course that's contrary to the idea of a repository that looks like a collection.
As I said before, I should blog about this, because it's an important point.