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

2

14

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

Romaic answered 15/6, 2011 at 18:48 Comment(1)
Nice, clean and easy to understand question. :)Ansilma
A
2

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.

Ansilma answered 16/6, 2011 at 9:50 Comment(6)
Arnis - in your scenario, what entity/service subscribes to the event and is that the time that the changes would be persisted using EF or a repository? So merging your example and mine, the person service would subscribe to the EmailChanging event from the Person and then save then changes when that occurs?Romaic
@Romaic anything that is interested in fact that email has changed. events can be used for figuring out when to persist changes, but that would force You to use events everywhere. I personally just rely on NHibernate which tracks dirtiness of entities.Ansilma
This Raise(new EmailChanged(this)) looks interesting, any info on that? :)Diastase
@Kleky from what I remember - what I meant was simple event pub/sub mechanism. unsure what more you want to know about thatAnsilma
@ArnisL. Of course, I was in the web environment so curious as to how that would work. Thanks anywayDiastase
@Kleky you wrote that as if web environment somehow interferes with itAnsilma
H
0

I'm an NH user and may not know all EF limitations but generally speaking, whatever the limitations of ORM, entities should be left as clean as possible. Service Layer is already coupled with Data Access so no harm's done.

And I believe EF4 should know how to track collection changes. If not, then the best way is to leave the adding/removing logic in your Person entity and persist in PersonService.

BTW, your EmailAddress isn't an entity here, no Id (just a typo I guess). And how do you link your EmailAddress to Person?

Hispaniola answered 16/6, 2011 at 8:6 Comment(1)
Kostassoid - Person has a collection of EmailAddress's. EmailAddress does have an Id, it was a typo.Romaic

© 2022 - 2024 — McMap. All rights reserved.