In Martin Fowler's book I read about UnitOfWork
and IdentityMap
patterns.
Author mentioned that it is a good idea to put identityMap inside the UnitOfWork. But how to do it ?
As far I understood IdentityMap
is bounded by session but author doesn't mentioned it about UnitOfWork
Is UnitOfWork
instance bounded by session?
Let's say we have client and order entities.
public clas Client{
List<Order> orders;
...
}
and we got request to update client information(phone numeber) and add new order:
How many unitOfWork instances do wee need here? for each session? should we have separated instances for client and for order ?
How many IdentityMap instances do we need here ? for each instance ? should we have separated instances for client and for order ?
How many IdentityMap instances do we need for each unitOfWork instance ?
What if we got 2 concurrent requests ?
Q: Is the
UnitOfWork
instance bounded by session?
In Chapter 11 of Martin's book in question you read:
“A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you’re done, it figures out everything that needs to be done to alter the database as a result of your work. [...]
“As soon as you start doing something that may affect a database, you create a Unit of Work to keep track of the changes. Every time you create, change, or delete an object you tell the Unit of Work. You can also let it know about objects you’ve read so that it can check for inconsistent reads by verifying that none of the objects changed on the database during the business transaction.”
So, the UnitOfWork
does not need to be bound to the session. The extent of the existence of the UnitOfWork
instance depends on your design. In Martin's example in that same book, we can see that the UnitOfOWork instance is created per HTTP request (which I believe is the most classical use).
class UnitOfWorkServlet...
final protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
UnitOfWork.newCurrent();
handleGet(request, response);
UnitOfWork.getCurrent().commit();
} finally {
UnitOfWork.setCurrent(null);
}
}
abstract void handleGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
In the JPA specification, the unit of work can be of two different natures: transaction-scoped persistence context, and extended-persistence context, but one can also handle it manually and get an application-managed unit of work (see this other answer). Which one you use depends on the use case.
Q: How many IdentityMap instances do we need?
Martin Fowler also answers that question in the book. In his section on IdentityMap
there's an entire section about it.
The book reads:
“Here the decision varies between one map per class and one map for the whole session."
Where you could understand the session as the UnitOfWork
in this case. Later in the book he explains:
“If you have multiple maps, the obvious route is one map per class or per table, which works well if your database schema and object models are the same. ”
A few paragraphs later Martin explains where to put the IdentityMap
:
“Identity Maps need to be somewhere where they’re easy to find. They’re also tied to the process context you’re working in. You need to ensure that each session gets its own instance that’s isolated from any other session’s instance. Thus, you need to put the Identity Map on a session-specific object. If you’re using Unit of Work that’s by far the best place for the Identity Maps since the Unit of Work is the main place for keeping track of data coming in or out of the database. If you don’t have a Unit of Work, the best bet is a Registry that’s tied to the session.”
So, there you have it, if your UnitOfWork
is bound to a request, then you will have one or more IdentityMaps
in that instance of your UnitOfWork
.
So, is the Unit of Work bounded by business transaction?
Yes, it is.
Now the extent of the "business transaction" could be short-lived, e.g. bound to the life of the HTTP request as in Martin Fowler's example above where the UnitOfWork seems to be bound to a thread-local variable per request.
Or the "business transaction" could encompass multiple requests, e.g. in JPA there's a concept of an extended persistence context, where the persistent context in JPA corresponds to a UnitOfWork
pattern. In an extended persistence context, the business transaction extends for a longer period of time. A classical example of this is a shopping cart: when the user starts putting items in her shopping cart a context/unit of work is created, and the context won't get cleared until the user checks out her shopping cart (committing the changes) or her session expires (discarding everything in the unit of work).
Now, there is also the idea of application-managed context, which basically means, start your unit of work when you think it's appropriate and close it when you no longer need it. For example, suppose you have a desktop application and a small database with isolated concurrency (i.e. every user only manages his own data and never conflict with other user's data). Suppose that the user's data could perfectly fit in the computer's memory. In a case like that, you could start your UnitOfWork
at the start of the application and use it as a sort of cache for your database. When appropriate, you can flush the UnitOfWork to disk, e.g. when the user clicks a save button, but still keep it alive. When the user closes the application, the UnitOfWork is discarded.
So, you can see there is a lot of nuances on what a "business transaction" actually means, or for how long the UnitOfWork
should exist.
Multiple Units of Work
Based on the explanation so far, you can have multiple units of work for, among others, the following reasons:
UnitWork
per request, and if your application handles concurrent requests, then you would have multiple units of work instances at the same time.UnitOfWork
per extended transaction, and so, tied to the user's session. If you have multiple users, you can have multiple units of work.However, besides these, I have found other reasons why you may want to spawn a new unit of work during the same "business transaction".
It is not unusual that while you're executing a business transaction you may want to execute another totally independent one. For example, suppose that in order to place an order for the customer, the customer record must exist in the database, but if the customer record fails to be created (e.g. perhaps because another customer has same conflicting email), you still want to place the order in pending review status.
So, the problem you face here is that if you attempt to create the customer during the business transaction of placing an order and creating the customer fails, that contaminates your order transaction and your unit of work is forced to roll back everything. In a situation like this you may want to spawn a new unit of work and therefore a new, separate database transaction, to create the customer and if that separate unit of work fails to create the customer, this does not contaminate your order creation unit of work, allowing you to take measures to still persist your order without a customer in a pending review status.
I believe JPA's concept of a "context" is a very good example of how to define a unit of work. I would suggest that you study their specification particularly their section about the EntityManager
.