15

這裏是我跨運行沒有一些關於DDD這裏提出的觀點和其他地方齧合問題的一個簡單的例子。DDD,實體框架,總結實體行爲(Person.AddEmail等)

說我有一個ASP.NET MVC 3網站創建/操縱的人。控制器訪問應用程序服務層(PersonService),後者又使用域實體(EF 4 POCO)和PersonRepository進行更改並保存它們。爲了簡單起見,我將這裏的所有界面都拋棄了人是這種情況下的根源,爲了簡單起見,只有電子郵件地址(也假定電子郵件不是不可變的,並且可以更新)。

選項1: 嘗試堅持[我的理解] 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: 讓人服務使用的存儲庫來添加/更新的電子郵件地址和不落實的人實體的行爲。在多對多關係的情況下(例如,需要更新兩個表以完成工作的地址),這種情況要簡單得多,但模型變得「貧乏」只是一羣吸氣和吸氣人員。

// 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; } 
} 

無論如何,對此有何看法?

感謝布

+0

好,乾淨,易於理解的問題。:) – 2011-06-16 09:14:38

回答

0

我是NH用戶可能不知道所有的EF侷限性,但總體上說,無論ORM的侷限性,實體應保持儘可能乾淨。服務層已經與數據訪問相結合,因此不會造成任何損害。

我相信EF4應該知道如何跟蹤收藏的變化。如果不是,那麼最好的方法是在Person實體中保留添加/刪除邏輯並保存在PersonService中。

順便說一句,你的EmailAddress的是不是一個實體在這裏,沒有ID(只是一個錯字我猜)。你如何將你的EmailAddress鏈接到Person?

+0

Kostassoid - 人有一個EmailAddress的集合。 EmailAddress確實有一個Id,這是一個錯字。 – user800131 2011-06-16 19:01:44

2

選項1是要走的路。

理由很簡單 - 中改變e-mail地址是域的關注。我敢打賭,你的域名專家表示他們需要更改電子郵件。這會自動將電子郵件不斷變化的邏輯標記爲應該在域模型中生存的業務邏輯。對象主要由它們的行爲來定義,而不是它們所持有的數據。

而且 - 您選擇使用工作模式的單元和周圍的一切都包在服務之前三思而後行。聚合根應該繪製事務邊界,並且如果服務器只是封裝存儲庫和域對象調用,那麼服務通常是無用的。

我想有這樣的事情:

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的 - 這是足夠聰明弄清楚發生了什麼變化,因爲人是堅持最後一次。很難說實體框架究竟如何處理這個問題。

+0

Arnis - 在你的場景中,什麼實體/服務訂閱了該事件,並且是使用EF或存儲庫持續更改的時間?因此,將您的示例和我的示例合併在一起,個人服務將訂閱來自該人員的EmailChanging事件,然後保存,然後在發生更改時進行更改? – user800131 2011-06-16 19:00:35

+0

@ user800131任何對電子郵件發生變化感興趣的東西。事件可用於確定何時保持更改,但這會迫使您在任何地方使用事件。我個人只是依靠NHibernate跟蹤實體的骯髒。 – 2011-06-18 10:52:22

+0

這個Raise(new EmailChanged(this))'看起來很有趣,關於這個的任何信息? :) – Ian 2016-12-15 14:40:22