Domain Objects and IDataErrorInfo by Mark Seemann
Occasionally I get a question about whether it is reasonable or advisable to let domain objects implement IDataErrorInfo. In summary, my answer is that it's not so much a question about whether it's a leaky abstraction or not, but rather whether it makes sense at all. To me, it doesn't.
Let us first consider the essence of the concept underlying IDataErrorInfo: It provides information about the validity of an object. More specifically, it provides error information when an object is in an invalid state.
This is really the crux of the matter. Domain Objects should be designed so that they cannot be put into invalid states. They should guarantee their invariants.
Let us return to the good old DanishPhoneNumber example. Instead of accepting or representing a Danish phone number as a string or integer, we model it as a Value Object that encapsulates the appropriate domain logic.
More specifically, the class' constructor guarantees that you can't create an invalid instance:
private readonly int number; public DanishPhoneNumber(int number) { if ((number < 112) || (number > 99999999)) { throw new ArgumentOutOfRangeException("number"); } this.number = number; }
Notice that the Guard Clause guarantees that you can't create an instance with an invalid number, and the readonly keyword guarantees that you can't change the value afterwards. Immutable types make it easier to protect a type's invariants, but it is also possible with mutable types - you just need to place proper Guards in public setters and other mutators, as well as in the constructor.
In any case, whenever a Domain Object guarantees its invariants according to the correct domain logic it makes no sense for it to implement IDataErrorInfo; if it did, the implementation would be trivial, because there would never be an error to report.
Does this mean that IDataErrorInfo is a redundant interface? Not at all, but it is important to realize that it's an Application Boundary concern instead of a Domain concern. At Application Boundaries, data entry errors will happen, and we must be able to cope with them appropriately; we don't want the application to crash by passing unvalidated data to DanishPhoneNumber's constructor.
Does this mean that we should duplicate domain logic at the Application Boundary? That should not be necessary. At first, we can apply a simple refactoring to the DanishPhoneNumber constructor:
public DanishPhoneNumber(int number) { if (!DanishPhoneNumber.IsValid(number)) { throw new ArgumentOutOfRangeException("number"); } this.number = number; } public static bool IsValid(int number) { return (112 <= number) && (number <= 99999999); }
We now have a public IsValid method we can use to implement an IDataErrorInfo at the Application Boundary. Next steps might be to add a TryParse method.
IDataErrorInfo implementations are often related to input forms in user interfaces. Instead of crashing the application or closing the form, we want to provide appropriate error messages to the user. We can use the Domain Object to provide validation logic, but the concern is completely different: we want the form to stay open until valid data has been entered. Not until all data is valid do we allow the creation of a Domain Object from that data.
In short, if you feel tempted to add IDataErrorInfo to a Domain Class, consider whether you aren't about to violate the Single Responsibility Principle. In my opinion, this is the case, and you would be better off reconsidering the design.
Comments
Too often i see domain objects implementing a lot of validation code. I think that most of validation logic must be out of domain objects.
DanishPhoneNumber value can't be less than 112. In reality we are modeling - such a phone number just does not exist. So it makes sense to disallow existence of such an object and throw an error immediately.
But there might be cases when domain contains temporary domain object invalidity from specific viewpoint/s.
Consider good old cargo shipment domain from Blue book. Shipment object is invalid and shouldn't be shipped if there's no cargo to ship and yet such a shipment can exist because it's planned out gradually. In these kind of situations - it might make sense to use IDataErrorInfo interface.
We must keep in mind that we are not modeling the real world, but rather the business logic that addresses the real world. In your example, that would be represented by a proper domain object that models that a shipment is still in the planning stage. Let's call this object PlannedShipment.
According to the domain model, PlannedShipment has its own invariants that it must protect, and the point still remains: PlannedShipment itself cannot be in an invalid state. However, PlannedShipment can't be shipped because it has not yet been promoted to a 'proper' Shipment. Such an API is safer because it makes it impossible to introduce errors of the kind where the code attempts to ship an invalid Shipment.
Thanks
A Value Object benefits very much from being immutable, so I always design them that way, but that doesn't mean that I make Entities immutable as well. I usually don't, although I'm sometimes toying with that idea.
In any case, if you have more than 4 or 5 fields in a class (no matter if you fill them through a constructor or via property setters), you most likely have a new class somewhere in there waiting to be set free. Clean Code makes a pretty good case of this. Once again, too many primitives in an Entity is a smell that the Single Responsibility Principle is violated.
With your phone number example in mind, the validation should imho never be encapsulated in the domain object, but belong to a separate validation. When you put validation inside the constructor you will eventually break the Open/closed principle. Of course we have to validate for null input if they will break the functionality, but I would never integrate range check etc. into the class it self.
However, consumers of those Value Objects need not be. While I didn't show it, DanishPhoneNumber could implement IPhoneNumber and all clients consume the interface. That would make DanishPhoneNumber a leaf of a composition while still keeping the architecture open for extensibility.
The point is to define each type so that their states are always consistent. Note that for input gatherers, invalid data is considered consistent in the scope of input gathering. That's where IDataErrorInfo makes sense :)
Second thing I wanted to emphasize is about Your sentence of making sense - we should focus on technology we are using not only to be able to express ourselves, but to be aware (!) of how we are doing it and be able to adjust that.
Patterns, OOP, .NET, POCO and whatnot are tools only. IDataErrorInfo is a tool too. Therefore - if it feels natural to use it to express our domain model (while it's suboptimal cause of arguments You mentioned), there is nothing wrong with using it per se. An agreement that our domain model objects (in contrast to reality) can be invalid if it simplifies things greatly (think ActiveRecord) is a tool too.
To me, internal consistency and the SRP is so important that I would feel more comfortable having IDataErrorInfo outside of domain objects, but there are no absolutes :)