maandag 26 maart 2012

Domain Driven Development - Business Logic in Domain Objects (Part 2)

In Part 1 we saw how we can move Business Logic into Domain Objects. It was an easy example with a parent and a list of child objects. Let's look at a bit more complicated example.

Let's take for example this Business Rule:
When a Proposition is closed, no more Tasks can be added to the Phases of the Proposition.

If we build a simple system these three objects can look like this in code:

public class Proposition
{
   public int Id { getset; }
   public bool IsClosed {  get  set ; }
   public List<Phase> Phases {  get  set ; }
}

public class Phase
{
   public int Id { getset; }
   public List<Task> Tasks {  get  set ; }
   public  Proposition Proposition {  get  set ; }

   public void AddTask(Task task)
   {
      if(Proposition.IsClosed)
      {
         throw new Exception("Proposition is closed.");
      }
      Tasks.Add(task);
   }
}

public class Task
{
    public  string Name {  get   set ; }
}

And we can interact with Phase and Task like this:

var phase = PhaseRepository.GetById(1);
phase.AddTask(new Task());


What is the problem with this code?
The problem with the code above is that a child object (Phase) is depending on a parent object (Proposition). Why is that a problem? Because if we add another property to Proposition that has to be added to the Business Rule, we now have to edit two objects:

public class Proposition
{
   ...
    public int Value { getset; } 
} 



public class Phase
{
   public void AddTask(Task task)
   {
      if(Proposition.IsClosed)
      {
         throw new Exception("Proposition is closed.");
      }


      if(Proposition.Value > 100)
      {
         throw new Exception("Propositionvalue is more than 100.");
      }


      Tasks.Add(task);
   }
}


What is the solution?
One solution is to move this Business Rule to the Proposition object, like this:


public class Proposition
{
   public int Id { getset; }
   public bool IsClosed {  get  set ; }
   public List<Phase> Phases {  get  set ; }


   public void AddTask(Task task,  int phaseId)
   {
      if(IsClosed)
      {
         throw new Exception("Proposition is closed.");
      }


      if(Value > 100)
      {
         throw new Exception("Propositionvalue is more than 100.");
      }

       Phases.Single(it => it.Id ==  phaseId ).AddTask(task);
   }
}



public class Phase
{
   public int Id { getset; }
   private List<Task> _tasks;
   public  ReadOnlyCollection<Task> ReadOnlytasks
   {
      get
      {
         return new ReadOnlyCollection<Task>(_tasks);
      }
   }

   internal void AddTask(Task task)
   {
      Tasks.Add(task);
   } 

}

Note that AddTask in the Phase class is made internal, so it's only accessible to Proposition, and not in for example an MVC controller, or a business logic layer.

Geen opmerkingen:

Een reactie plaatsen