Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD, Entity Framework, Aggregate Entity Behavior ( Person.AddEmail, etc)

Here's a simple example of a problem I'm running across that is not meshing with some of the ideas presented here and other places regarding DDD.

Say I have an ASP.NET MVC 3 site that creates/manipulates a person. The controllers access an application service layer (PersonService) which in turn uses the domain entities (EF 4 POCO) and the PersonRepository to make changes and persist them. I'm leaving out all interfaces here for simplicity. Person is the root in this case and for simplicity only has email addresses (also assume email is not immutable and can be updated).

Option 1: Try to stick with [my understanding] of the basics of DDD where behavior directly related to the entity is implemented as part of the entity (Person implements AddEmail, ChangeEmail, etc). The only problem with this, with the exception of the Add* methods, is that the Person would need to know about the context or entity framework pieces (which would remove any persistence ignorance) or need to use a "service" or repository to mark the email as modified.

// Person Service
public class PersonService {
    // constructor injection to get unit of work and person repository...
    // ...methods to add/update a person
    public EmailAddress AddEmailAddress(int personId, EmailAddress email)
    {   
        Person p = personRepository.Find(p => p.Id == personId).First();
        p.AddEmail(email);   
        uow.SaveChanges();
        return email; 
    }

    public EmailAddress ChangeEmailAddress(EmailAddress email)
    {
        Person p = personRepository.Find(p => p.Id == personId).First();
        p.ChangeEmail(email);   
        // change state of email object here so it's updated in the next line???
        // if not here, wouldn't the Person entity have to know about the context
        // or use a service?
        uow.SaveChanges();
        return email;    
    }
}

// Person Repository
public class PersonRepository
{
   // generic repository implementation
}

// Person Entity
public class Person
{
    public string Name { get;set; }
    public IEnumerable<EmailAddress> EmailAddresses { get;set; }

    public void AddEmail(EmailAddress email)
    {
        this.EmailAddresses.Add(email);
    }

    public void ChangeEmail(EmailAddress email)
    {
        EmailAddress orig = this.EmailAddresses.First(e => e.Id == email.id);

        // update properties on orig

        // NOW WHAT? [this] knows nothing about the context in order to change state,
        etc, or do anything to mark the email add updated
    }
}

// Email 
public class EmailAddress
{
    public string Email { get;set; }
    public bool IsPrimary { get;set; }
}

Option 2: Let the person service use the repository to add/update the email address and don't implement the behavior on the person entity. This is much simpler in the case of many to many relationships (for example, address, where two tables need to be updated to complete the work) but the model then becomes 'anemic' being just a bunch of getters and setters.

// Person Service
public class PersonService {
    // constructor injection to get unit of work and person repository...
    // ...methods to add/update a person
    public EmailAddress AddEmailAddress(int personId, EmailAddress email)
    {   
        Person p = personRepository.Find(p => p.Id == personId).First();
        personRepository.AddEmail(personId, email);   
        uow.SaveChanges();
        return email; 
    }

    public EmailAddress ChangeEmailAddress(EmailAddress email)
    {
        personRepository.ChangeEmail(email);   
        uow.SaveChanges();
        return email;    
    }
}

// Person Repository
public class PersonRepository
{
   // generic repository implementation
}

// Person Entity
public class Person
{
    public string Name { get;set; }
    public IEnumerable<EmailAddress> EmailAddresses { get;set; }
}

// Email 
public class EmailAddress
{
    public string Email { get;set; }
    public bool IsPrimary { get;set; }
}

Anyway, any thoughts on this?

Thanks, Brian

like image 659
user800131 Avatar asked Jun 15 '11 18:06

user800131


1 Answers

Option 1 is the way to go.

Reasoning is simple - changing e-mail addresses is domain concern. I bet Your domain experts have said that they will need to change emails. That automatically marks email changing piece of logic as business logic which is supposed to live in domain model. Objects primarily are defined by their behavior and not data that they hold.

Also - think twice before You choose to use unit of work pattern and wrap around everything in services. Aggregate roots are supposed to draw transaction boundaries and services usually are useless if they just wrap repository and domain object calls.

I would have something like this:

public class Person{
  public Email Email{get;private set;}
  public void SpecifyEmail(Email email){
    //some validation, if necessary
    EnsureEmailCanBeChanged();
    //applying state changes
    Email=email;
    //raising event, if necessary
    Raise(new EmailChanged(this));
  }
  public class EmailChanged:Event<Person>{
    public EmailChanged(Person p):base(p){}
  }
}
public class Email{
  public Email(string email){
    //validations (e.g. email format)
    Value=email;
  }
  //implicit to string, explicit from string conversions
}

public class PersonController{
  public ActionResult SpecifyEmail(int person, string email){
    _persons.Get(person).SpecifyEmail((Email)email);
    return RedirectToAction("Person",new{person});
  }
}

I'm using NHibernate - it's smart enough to figure out what has changed since Person was persisted last time. Hard to say how exactly entity framework handles this.

like image 108
Arnis Lapsa Avatar answered Sep 29 '22 07:09

Arnis Lapsa