这里有一个我遇到的问题的简单例子,它与这里和其他地方提出的关于DDD的一些想法不一致。
假设我有一个ASP.NET MVC 3网站,它可以创建/操纵一个人。控制器访问一个应用程序服务层(PersonService),该层又使用域实体(EF 4 POCO)和PersonRepository进行更改并将其持久化。为了简单起见,我省略了所有接口。在这种情况下,Person是根,为了简单起见,它只有电子邮件地址(也假设电子邮件不是不可变的,并且可以更新)。
选项1:试着坚持[我的理解]DDD的基本知识,在DDD中,与实体直接相关的行为是作为实体的一部分实现的(Person实现AddEmail、ChangeEmail等)。除了Add*方法之外,唯一的问题是,Person需要了解上下文或实体框架部分(这将消除任何持久性的无知),或者需要使用"服务"或存储库将电子邮件标记为已修改。
// 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; }
}
选项2:让个人服务使用存储库来添加/更新电子邮件地址,而不在个人实体上实现该行为。在多对多关系的情况下(例如,address,其中需要更新两个表来完成工作),这要简单得多,但模型随后变得"贫血",只是一群getter和setter。
// 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; }
}
不管怎样,你对此有什么想法吗?
谢谢,Brian
选项1就是我们要走的路。
推理很简单——更改电子邮件地址是领域问题。我敢打赌,你的领域专家已经说过,他们将需要更改电子邮件。这会自动将电子邮件更改逻辑标记为业务逻辑,该逻辑应该存在于域模型中。对象主要由它们的行为定义,而不是由它们所持有的数据定义。
此外,在您选择使用工作单元模式并包装服务中的所有内容之前,请三思而后行。聚合根应该绘制事务边界,如果服务只是包装存储库和域对象调用,那么它们通常是无用的。
我想要这样的东西:
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});
}
}
我正在使用NHibernate——它足够聪明,可以弄清楚自从上次Person被持久化以来发生了什么变化。很难说实体框架是如何处理这一问题的。
我是NH用户,可能不知道所有EF限制,但一般来说,无论ORM的限制是什么,实体都应该尽可能干净。服务层已经与数据访问相耦合,因此不会造成任何危害。
我相信EF4应该知道如何跟踪收集的变化。如果没有,那么最好的方法是将添加/删除逻辑留在Person实体中,并在PersonService中持久存在。
顺便说一句,你的电子邮件地址在这里不是一个实体,没有Id(我想只是打字错误)。如何将您的电子邮件地址链接到个人?