Most server-side applications and many desktop applications contains data that is tied to a particular task that’s being executed. A common solution is to keep that kind of data in thread-local storage; to keep the data in variables bound to the executing thread. Convenient, but a practice based on a faulty assumption.
Bob Martin wrote an article about the problem of assuming that a unit-of-work has a one-to-one relationship with a thread.
ThreadLocal variables are a wonderfully convenient way to associate data with a given thread. Indeed, frameworks like Hibernate take advantage of this to hold session information. However, the practice depends upon assumption that a thread is equivalent to a unit-of-work. This is a faulty assumption.
ThreadLocal is the Java terminology, but the construct is common in multi-threaded environments. Bob remembers:
Thirteen years ago, while working on my first book, Jim Coplien and I were having a debate on the nature of threads and objects. He made a clarifying statement that has stuck with me since. He said: “An object is an abstraction of function. A thread is an abstraction of schedule.”
Mapping the data of a unit-of-work to one thread is a standard pattern today; a pattern that is found in many popular frameworks, and even though that approach works most of the time, there are situations where the faulty abstraction leaks.
It is not uncommon for a task to have different priorities for different parts of the task, and there are no rules that a task must be single-threaded. Bob exemplifies by saying that a unit-of-work may very well need responsive communication with an external service while performing a relatively long-time computation based upon the incoming data; a problem commonly solved by using two threads. He asks:
Where are the unit-of-work related variables? They can’t be kept in a ThreadLocal since each part of the task runs in a separate thread. They can’t be kept in static variables since there is more than one thread. The answer is that they have to be passed around between the threads as function arguments on the stack, and recorded in the data structured placed on the queue.
TapsaKoo encountered a similar situation a while ago. When trying to work in a domain-driven way in WinForms, he described his problems finding a place to keep session-specific data.
If the application has only one form open at a time, I could save the session-object into the CallContext. What if the application has multiple forms open at a time and each of them wants to have a separate instance of my session-class? CallContext is out of the question. So are all thread-specific alternatives. What is left? Nothing? I’m not the first person pondering this issue. A solution probably exists but I can’t find it. Do I really have to inject the session-object into every object instance that might need it? Or should I refactor a lot behavior from domain-classes into services and inject the session-object into them. I don’t like this approach because I want my classes to be more than data containers.
When reading Uncle Bobs post, TapsaKoo agreed that there are no easy solutions to the problem:
It doesn’t matter if you have 1 or 10 threads. The problem is always the same. UnitOfWork or SessionState should have a place that does not depend on threads. It’s a dangerous assumption that UnitOfWork is directly related to one single thread. That assumption seriously limits your other architectural choices.
Bob concludes that something seems to be missing:
So, though convenient, ThreadLocal variables confuse the issue of separating function from schedule. They tempt us to couple function and schedule together. This is unfortunate since the correspondence of function and schedule is weak and accidental.
What we’d really like is to be able to create UnitOfWorkLocal variables.