ORDINA BLOGT

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

How can we more Business Logic into Domain Objects.

  • 26 maart 2012

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 { get; set; }
    public bool IsClosed {  get ;  set ; }
    public List<Phase> Phases {  get ;  set ; }
 }
 
public class Phase
 {
    public int Id { get; set; }
   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 { get; set; }
}

 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 1000.");
      }
 

      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 { get; set; }
    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 1000.");
       }
 
       Phases.Single(it => it.Id ==  phaseId ).AddTask(task);
    }
 }
 
public class Phase
 {
   public int Id { get; set; }
   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.