Unit Of Work is the new Singleton
Let me start with the Singleton: it’s a valid pattern for specific cases. For the other 99% cases there are better ways. Simply put, the Singleton became an anti pattern because people misused it.
Enter the Unit Of Work (UoW), a quite trendy pattern these days. Go read again M. Fowler’s description . Notice an interesting word there: database. The UoW is a persistence related pattern. It doesn’t care about your Domain, it cares only about the database. And yet, you have this extremely harmful tutorial lying around.A lot of people learn from it, because it’s from Microsoft. They learn the wrong way to use both the Repository and UoW. Nevermind that the business logic (as thin as it is) is shoved directly in the controllers. The point is that people learn that they should have a UoW class and they can use that in the BL. Yes, they can use an abstraction, that’s not the issue, the problem is that the BL is tightly coupled to an infrastructural concern like UoW. Does the UoW has a Business/Domain meaning? I bet that in 99.99% of cases it doesn’t. It’s a technical thing, worse, it’s a persistence detail that has no place in the BL, yet a lot of people are coupling their BL services to the UoW. At most, the BL knows about the Repository because it’s defined in that layer. But UoW makes no sense, it’s a persistence pattern. It’s great when you’re developing an ORM, but that’s it. The Domain/BL should never know about a UoW.
A little secret you might not be aware of is that when modelling a Domain or designing the Business Layer you should not think as a programmer. A programmer thinks algorithms and that’s bad for your design. You need to think like an architect i.e to organize things. You need to think in domain concepts, using domain language. You don’t write code, you’re expressing the domain using code. And a business transaction is not really a UoW nor a db transaction.
Think about this real life scenario: you order something from Amazon. They misspelled your address, the product goes to another person. What happens then? If you think like a programmer, you’ll say “Well, it’s part of transaction so let’s roll it back”. How does that translate in the real world? Well, the package goes back to Amazon, they refund your money and ask you nicely to place that order again. Do you see that happening? No? Why not? Because it’s stupid!
Delivering the product at a wrong address is a mistake, an error, but the business transaction is not rolledback. The mistake is corrected and the product goes to your address. And be sure that some heads will roll, because that mistake costs Amazon money. Business always deal with failure, it’s not the end of the world, it’s a common business scenario. It’s annoying and you don’t want it to happen often, but that’s it. There’s no rollback in the business world, that’s a technical term and in fairness it should remain at the db level.
While DDD doesn’t say much abut how you should implement your persistence, it does say an important thing: your repositories should work only with Aggregate Roots. That’s because the AR ensures the aggregate consistency. The repo always persists the whole AR not just parts of it. Persisting may mean partial updates of the db model, but that’s a technical persistence detail. A repository might unknowingly use a UoW implementation, but it’s not a part of a UoW. The repo responsibility is to save the specified AR, it isn’t its concern to care about other ARs that might be or not involved.
UoW can’t exist in a properly designed Domain. Take the domain changes A,B,C . They happen in a certain order and that’s it. If C has a problem, everything goes forward and the past is not erased. There’s no commit or rollback. There’s only handling a business case. A business transaction might be a long process. There can be many ARs involved until a transaction is completed. If one AR doesn’t accept changes the process doesn’t stop there or it’s rolled back. Process continues and the business transaction finishes successfully or with a (business) failure. But all changes are persisted. Looking at our Amazon example, delivery failed, that event remains in the past, but now we do a corrective action which will allow the delivery to complete successfully.
In the Domain, everything flows in one direction: forward. When something bad happens, a correction is applied. The Domain doesn’t care about the database and UoW is very coupled to the db. In my opinion, it’s a pattern which is usable only with data access objects, and in probably 99% of the cases you won’t be needing it. As with the Singleton, there are better ways but everything depends on proper domain design.
I’m certain someone will ask me “What if I have at least 2 business objects/AR that need to be persisted together?”. I can point you to the obvious solution to pass a UoW to your repositories where UoW = Db transaction. This works in the majority of cases without problems. And it’s an easy to understand and implement solution, even easier if you’re using an ORM. The downside is that it’s a one trick pony which works great as long as you’re using one ACID compliant db and everything takes place in one process. Once you have a distributed app or you need to work with different incompatible storages, things become very complicated. If you get there, modelling the business transaction as it’s really implemented in the business is the cleanest method (and that implies a Saga and domain events) because it’s really scalable and doesn’t care about specific databases or if your app is distributed.
My point is that, usually, there are 2 ways to approach the implementation of a business transaction: the programmer way or the domain way. The programmer way is the most straight forward approach, it uses the familiar db transactions and procedural thinking. This is where UoW pattern makes sense regardless of how hard or easy is to implement it. The domain way is more verbose, requires to understand the business process, to come up with the correct semantics (domain events), to use a service bus (or an event delivery mechanism), sagas, eventual consistency. It’s very scalable and clean but it requires more expertise and effort to implement compared to the first solution. But you don’t need a UoW (well persisting an AR might use a db transaction but that’s it).
So you have to make a decision: you want the straight forward way with the risk of being rigid and not very scalable or the seemingly complicated way that is very flexible and matches the domain process. Basically, you have to decide when to pay the cost: at the beginning or in real time. However, a lot of apps will never need to scale and they’ll always use one db etc. In this case, it’s obvious the first approach is the best one. And UoW will always be the ORM implementation or a db transaction. But in any case, no matter how big or small the app is, the UoW has no place in BL and it’s always a persistence detail. Keep the UoW out of BL.