Dave Gladfelter has written a great question which I think a lot of people may be asking an therefore I am sharing the letter with the answers here…
Misko,Thanks for all your interesting posts on testability. I’ve been using DI and other techniques for some time for both testability and for maintainability and readability, but you’ve helped me understand some subtleties that I think will really help me write better code.
I have a question about your distinction between “newable”, value classes and “injectable” or “service” classes.
Domain-Driven_Design by Eric Evans divides classes/objects into basically 3 categories, SERVICE objects, ENTITY objects and VALUE OBJECTS. VALUE OBJECTS are leaf classes like string classes and are obviously “newable”. SERVICES represent external infrastructure, they don’t model domain state, and they tend to be singletons. This makes them very similar to injectable objects. My main concern is with ENTITY objects. ENTITY objects in Eric’s methodology represent domain concepts that have specific lifetimes and identity. Eric is primarily focused on enterprise/database style applications where the primary concern is data integrity, so the concept of the ENTITY is key to his modeling technique.
The domains I’m working in at present are more functional / operational and aren’t concerned with identity, so I haven’t had an opportunity to put Eric’s methodology through the wringer yet to see what leaks out. After reading your articles I am now confused about how to fit ENTITY’s into the compelling design-for-test methodology you’re advocating.
In your post “Writing Testable Code”, you say that a CreditCard class would tend to be a value object. In Eric Evan’s methodology, I would think it would be an ENTITY. I get the impression you advocate using value objects to hold domain data. This would make maintaining domain-dictated identity and lifetimes hard, since value objects are newable, often copyable, and generally not too concerned with lifetime or identity.
I get the impression you are okay with treating domain data as dumb data which service objects manipulate based on the application domain logic. In my mind, this creates an impedance mismatch between the object hierarchy and the domain under modeling. I’ve always thought it a good rule of thumb to place an operation on the class whose state is most queried/modified by the operation. In the case of processing a credit card transaction, it would be the credit card that is most affected by the transaction, not the credit card transaction processor service, and it would therefore be natural to place the operation on the credit card object. The card would still need to collaborate with such a service for aspect-oriented binding for services such as fraud prevention, but it would be the credit card’s responsibility to form the messages and operations that comprise a credit card transaction and to pass them to the appropriate players.
So, if I understand Eric’s approach properly and if he were trying to design for testability as well, I think he would advocate creating the credit card with a factory that would tie it to whatever services its various methods would need, and internal fields would be set during construction-time in the factory. The credit card would then use internal logic to determine what messages to send to the services to respond to application requests (Charge transaction, generate monthly late notice batch script, etc.)
If I understand your methodology, there would be several global credit card servicing objects with the business logic for the various operations, and the application would pass dumb value objects to those services as needed to fulfill requests.
Now, imagine one logical credit card operation required several orthogonal services to complete. Where would the business logic for that operation live? Would there be yet another service object responsible for coordinating subordinate service objects? What if one of the operations failed and the transaction needs to be rolled back. Who is responsible for coordinating this? Following Eric Evan’s methodology, the credit card would have all the information and logic needed to keep track of these higher-level concerns and would use the services as needed to create a valid application state even in the face of failures (the strong exception guarantee.) The credit card would not allow multiple simultaneous operations to create a race condition in multithreaded environments because it would be simple to add locking to the operations on the credit card class. In the test-driven model you describe, who would be responsible for detecting multiple simultaneous operations on the same card? If a service had to do it, then it would have to maintain internal state corresponding to all the active credit cards in the system to detect such conditions, which would be duplicative and would decrease data coherence in the system. You couldn’t put locking on the value-object-CreditCard class because it could have been copied (or deserialized multiple times) and therefore there would be no connection between the domain concept of serialization of transactions and the behavior of multiple credit card value objects with their own mutexes.
Are you familiar with this book? Do you see its design methodology as being compatible with design for testability? If so, how do you map the concepts in the two methodologies?
Sorry in advance for another long message. There aren’t any questions in it, and it is purely for your benefit, so feel free to read it whenever or never. I’ve been thinking about your comments and I reviewed some key sections of Evan’s book. I think there is not as much of a conflict as I thought. It is remarkable to me that the testable design methodology and the domain-driven design methodology are so compatible since they serve somewhat differing goals.
Anyway, I think I got hung up on “newable” as meaning “value object”. Entities need to be aware of their external, long-lived identities, but I think it is okay for the purposes of testability to make them newable. I think it would be bad for anyone other than a “repository” (Eric’s term for a persistent-storage-backed factory) to create a credit card object. Except during testing!
Also, Eric does anticipate that some services will peform purely domain operations. He agrees with you that keeping the set of operations on entities relatively uncluttered is crucial. Some domain operations are simply defined by a set of procedural business rules, and they belong in a stateless service.
I was initially confused at how newable entities would get re-serialized. At first, it seemed to me, since one doesn’t want (usually domain-state-free) services holding references to domain objects, and one doesn’t want domain objects holding references to services, that the only way to commit a transaction would be to have a method call that spans every transaction.
If an entity becomes unreachable from all threads in the process, then it will be unable to commit because it will not have access to its repository and there is no other object that can give it access to the repository, since no object has a reference to it other than the GC. This means that for a testable, domain-driven system, either:
1. All “write” operations need to occur within the context of at least one, continuous thread of execution.
2. There must be some injectable service that needs to hold references to domain objects that are in-use.
The first choice is not scalable and the second choice violates the literal definition of Eric Evan’s definition of a SERVICE (“A SERVICE is an operation offered as an interface that stands alone in the model, without encapsulating state, as ENTITIES and VALUE OBJECTS do.”). However, since moving transaction state from the stack to the heap is arguably just a change in execution strategy for performance reasons, I guess I could argue that the spirit of Evan’s methodology is not violated because a service that holds suspended / waiting transactions could arguably be said to simply be executing those transactions temporarily off-thread. Modern OO libraries even have such services pre-defined for just this purpose.
Anyway, thanks for your help, After hearing from you and thinking a bit more about this, I think the two techniques actually match up incredibly well. I do recommend the book if you get a chance, and I would look forward to reading about your thoughts on it in your blog posts.