Thursday, January 14, 2010

From Java constructors to domain-driven design concepts

Interestingly, a constructor in Java can be thought of as a callback method which is to be invoked upon instance allocation. Consider this simple example:
public final class Constructors
{
   
public static void main(String[] args) {
       
final A a1 = new A(1, "a1");
   
}
  
   
static class A {
       
final int _id;
       
final String _tag;
      
        A
(int id, String tag) {
           
_id = id;
            _tag = tag;
       
}
    }
}

What I am asking the application (in this case rather the virtual machine itself) to do by invoking new A(...) is:
"Please, allocate the memory for the object of class samples.A, then pass the control to one of the methods (constructors) in my class with the arguments I supplied; let the method do its work and return me a reference to the newly allocated and initialized instance".
In other words I have requested the VM to perform a memory allocation and to call a specific method back when the allocation operation is over. For the program both things are done in one go which can be seen as a kind of syntatic sugar provided by the compiler.
Why did I start thinking of that well-known fact at all? Yesterday I pondered upon what should be the proper place for validation logic. Indeed, how can we make sure that the entity state is valid from business logic perspective and is safe to be used in various calculations? Placing validation in a constructor does not seem to be a right choice as constructor works as a user-defined extension (in a form of callback method) to the VM activities. Logic in constructor is hardly related to business logic of application, though, to be fair, it can do some basic checks on the parameters, decide to keep those values in the respective fields, or even reject a value by throwing an exception. It is possible that, while the parameter values are just enough to create an instance, their combination does not constitute the ultimate state we want to keep. The instance may further go through a series of set operations to reach the complete state. Any property mutations can easily render the state invalid at any moment.
It is important to note that unless the type is immutable we must also re-validate the state after updates. What was valid before an update, does not neccessarily remains valid after. Thus we have two generic cases when validation comes into play - 1) instance is created and 2) instance is updated. Somewhere in the application something must be responsible for triggering validation in both cases. The first idea is to make the corresponding Java class take care of that but there is a problem - a class is not aware of the instances as well as it is incognizant of when an instance is created or updated. Well, that means we need a facility which would look after all our instances, the entity domain, in other words. Hey, that is what Repository in DDD is for, right? OK, lets use this word from now on.
Now we want our repository to perform validation when entity is created or updated. There should be a distinctive moment within the application life when the corresponding logic is triggered. A perfect candidate is to validate the object just before it is saved to the repository. We can make repository responsible for doing validation when the application requests to save a state.
What if our validation rules are sophisticated enough? What if they react not on a single property value but on their combinations? Lets furnish our entity class A with getters:
final class A
{
   
final int _id;
   
final String _tag;
   
    A
(int id, String tag) {
       
_id = id;
        _tag = tag;
   
}

   
int getId() { return _id; }

   
String getTag() { return _tag; }
}

Also, lets create another entity class B:
class B
{
   
int _x;
   
int _y;

   
int getX() { return _x; }
   
void setX(int x) { _x = x; }
   
   
   
int getY() { return _y; }
   
void setY(int y) { _y = y;}
}

We will define some simple validation rules for both A and B:
interface IValidator<T>
{
   
void validate(T bean, Collection<Object> errors);
   
    IValidator<A> A_VALIDATOR =
new IValidator<A>() {
       
@Override
       
public void validate(A bean, Collection<Object> errors) {
           
if (bean.getId() <= 0) {
               
errors.add("Id value must be greater than 0");
           
}
           
if (bean.getTag() == null || bean.getTag().isEmpty()) {
               
errors.add("Tag value cannot be null or empty");
           
}
        }
    }
;
   
    IValidator<B> B_VALIDATOR =
new IValidator<B>() {
       
@Override
       
public void validate(B bean, Collection<Object> errors) {
           
if (bean.getX() + bean.getY() > 100) {
               
errors.add("X+Y cannot exceed 100");
           
}
        }
    }
;
}

In case of class A validation rules are simple and independent. Setting property A.Id to -1 would cause a validation error. Same would happen if A.Tag is set to null. In case of B, however, situation is different. We can set B.X to 108 and it would result in a validation error, but if we simultaneously set B.Y to -28 yielding the total X+Y of 80 the validation rule would pass. It will pass for as long as we first set both X and Y and only then perform the validation. To be able to do so we need to postpone validation until both values are in place. What if we first create a copy of the state, apply all the changes to it, and then attempt to save it into the repository? That sounds reasonable and that is where UnitOfWork helps us much.

To be continued...

No comments:

Post a Comment