技術最前線

網域模型的設計

Dino Esposito

 

實體架構 4.1 和新的第一個程式碼的開發模式的最新的版本會中斷伺服器開發的基本規則: 不會以一個步驟,如果資料庫不在的位置。程式碼首先會告知開發人員專注於商務領域,並且建立其模型的類別。建議的方式,第一個程式碼踴躍網域導向的設計 (DDD) 原則中的應用程式。NET 的空間。商務領域會填入相關的互連的實體,每一個都有它自己公開為屬性的資料,可能會公開 (公開) 的透過方法和事件的行為。[驗證規則的可能是動態清單。 更重要的是,每個實體可能會有一個狀態,並繫結至

寫入物件模型供實際案例中,會引發不在目前的示範與教學課程中會有一些問題。在本文中我將採用所面臨的挑戰,並討論客戶類別、 建置碰觸於一些設計模式和實務一路合作對象模式、 彙總的根目錄、 工廠,和技術,例如程式碼的合約與企業程式庫驗證應用程式區塊 (VAB)。

對於參考,我建議您看一下這裡所討論的程式碼是一小部分的開放原始碼專案。Andrea Saltarello,Northwind 入門套件專案所建立的 (nsk.codeplex.com) 旨在說明有效的作法,為架構的多層次的解決方案。

物件模型 vs。網域模型

爭論是否使用物件模型或網域模型似乎意義,而且大多數的情況下只要術語。但精確的詞彙是確保所有小組成員都有相同的概念考慮到使用特定合約時的關鍵因素。

全民幾乎用於軟體工業,物件模型會是泛型,但可能相關物件的集合。計算方式是一個網域模型不同?總而言之,網域模型仍然是物件模型,所以交替使用兩個術語可能無法可怕的錯誤。儘管如此,某些重點則與使用 「 網域模型 」 一詞時,它可能會執行與其組成物件圖形的相關部份期望。

這種網域模型與馬丁 Fowler 所提供的定義是相關聯: 物件模型,其中包含資料和行為的網域。行為,表示特定的邏輯和規則 (請參閱 bit.ly/6Ol6uQ)。

DDD 會加入一個網域模型中的一大堆務實的規則。以這個觀點來看,根據網域模型和不同大量使用它的基本型別的建議值物件中的物件模型。整數,比方說,可以是許多動作-溫度、 金額、 大小、 數量。網域模型可使用每個不同案例的特定值的物件型別。

此外,網域模型應能識別彙總的根目錄。彙總的根是藉由一起編排其他實體的實體。彙總的根目錄中的物件都無關外,這表示沒有使用案例存在若不傳遞從根物件正在使用。彙總項目的標準的範例是根的訂單實體。包含彙總的委刊單項目,但不是產品的順序。很難想像 (即使,會決定僅由您的規格) 時,可能需要以委刊單項目處理,而它來自訂單。[訂貨主檔的使用案例。 相反地,您可能也在其中您使用的產品項目但不包含彙總的根 ca 是負責維護它們的子物件處於有效狀態,並保存它們。

最後,某些網域模型類別可能會提供公用工廠方法,來建立新的執行個體,而不是建構函式。當類別很獨立而不全然正確部分的階層,或建立類別的步驟會在用戶端部分感興趣的則可以接受使用一般的建構函式。具有複雜物件,例如彙總的根目錄,不過,您需要額外的抽象層次上具現化。DDD 介紹工廠物件 (或多個直接稱為工廠方法上某些類別) 用來將用戶端需求的分離,內部物件和其關聯性和規則。DDD 非常清楚且簡要介紹裁決 bit.ly/oxoJD9

合作對象模式

我們只討論客戶類別。之項目前面討論過下面是可能的簽章:

public class Customer : Organization, IAggregateRoot
{
  ...
}

您的客戶是誰? 個人、 公司或兩者皆是嗎? 合作對象模式建議您分辨兩者,明確定義的屬性很常見,而且其中只屬於個人或是組織。 在 [程式碼圖 1 限於個人和組織。您可以讓它更詳細的分割成非營利商業企業組織,如果您的業務領域有此需要。

[圖 1 類別根據廠商模式

public abstract class Party
{
  public virtual String Name { get; set; }
  public virtual PostalAddress MainPostalAddress { get; set; }
}
public abstract class Person : Party
{
  public virtual String Surname { get; set; }
  public virtual DateTime BirthDate { get; set; }
  public virtual String Ssn { get; set; }
}
public abstract class Organization : Party
{
  public virtual String VatId { get; set; }
}

永遠不會是個好主意回想一下您的目標應該以產生密切模型化您的實際商務網域,不抽象表示企業模型。 [合作對象模式並無嚴格要求即使它引入了未來的擴充性點。 如果您的需求只會講的以個人的客戶,然後套用

客戶與彙總的根類別

彙總的根是一種類別,表示為獨立實體模型中的,另一個則相對於其他實體不存在。 大多數情況下,您必須是個別的類別,不會管理任何子物件,或是也許只要指向其他彙總的根目錄的彙總根目錄。 [圖 2會顯示更多的 「 客戶 」 類別。

[圖 2 客戶類別,為彙總的根目錄

public class Customer : Organization, IAggregateRoot
{
  public static Customer CreateNewCustomer(
    String id, String companyName, String contactName)
  {
    ...
}
 
  protected Customer()
  {
  }
 
  public virtual String Id { get; set; }
    ...
public virtual IEnumerable<Order> Orders
  {
    get { return _Orders; }
  }
   
  Boolean IAggregateRoot.CanBeSaved
  {
    get { return IsValidForRegistration; }
  }
 
  Boolean IAggregateRoot.CanBeDeleted
  {
    get { return true; }
  }
}

如您所見,「 客戶 」 類別會實作 (自訂) 的 IAggregateRoot 介面。 以下是介面:

public interface IAggregateRoot
{
  Boolean CanBeSaved { get; }
  Boolean CanBeDeleted { get; }
}

將彙總的根是什麼意思? 彙總的根處理彙總其子物件的持續性,並負責強制牽涉到群組的恆成立條件。 開啟彙總的根才能檢查整個堆疊可以儲存或刪除。 獨立的彙總根只會傳回,則為 true 的而不需任何進一步的檢查。

工廠和建構函式

建構函式是特定型別的。 如果物件是只是一種類型-沒有彙總,並不複雜的初始化邏輯-使用一般的建構函式沒有多個問題。 一般情況下,不過,原廠是很有用的額外的抽象概念。 工廠可以是簡單的靜態方法,在實體類別或其本身的個別元件。 具有原廠方法也能協助增加可讀性,因為它清楚您正在建立給定執行個體的原因。 與建構函式,您的電源,來處理不同的執行個體化的情況是更受限制的],如建構函式不具名方法,因此只能辨別透過簽章。 尤其是長的簽章,就難以找出之後,取得特定的執行個體的原因。 [圖 3 「 客戶 」 類別會顯示工廠方法。

在 「 客戶 」 類別上的 [圖 3 Factory 方法

public static Customer CreateNewCustomer(
  String id, String companyName, String contactName)
{
  Contract.Requires<ArgumentNullException>(
           id != null, "id");
  Contract.Requires<ArgumentException>(
           !String.IsNullOrWhiteSpace(id), "id");
  Contract.Requires<ArgumentNullException>(
           companyName != null, "companyName");
  Contract.Requires<ArgumentException>(
           !String.IsNullOrWhiteSpace(companyName), "companyName");
  Contract.Requires<ArgumentNullException>(
           contactName != null, "contactName");               
  Contract.Requires<ArgumentException>(
           !String.IsNullOrWhiteSpace(contactName), "contactName");
 
  var c = new Customer
              {
                Id = id,
                Name = companyName,
                  Orders = new List<Order>(),
                ContactInfo = new ContactInfo
                              {
                                 ContactName = contactName
                              }
              };
  return c;
}

工廠方法是不可部分完成、 取得輸入的參數、 不會工作,並傳回給定型別的新執行個體。 傳回的執行個體應該保證處於有效狀態。 工廠負責履行所有定義的內部的驗證規則。

工廠也需要驗證輸入的資料。 因此,使用程式碼合約先決條件保留程式碼簡潔且非常容易閱讀。 您也可以使用達成條件來形容為確保傳回的執行個體處於有效狀態,就像這樣:

Contract.Ensures(Contract.Result<Customer>().IsValid());

至於使用整個類別的恆定性,經驗顯示您永遠無法承擔它們。 不變項目的可能過於侵犯,尤其是大型而複雜的模型。 程式碼合約恆定性有時幾乎太對角色尊敬的規則集,而且有時候程式碼中您想要更大的彈性。 然後,限制必須強制執行不變項目的區域是比較慣用的方式。

驗證

可能的網域類別的屬性,就必須加以驗證,確定沒有必要的欄位留白,不太長文字則放在有限的容器中,值落在適當範圍等等。 您也必須考慮跨屬性驗證和複雜的商務規則。 您如何編寫驗證?

驗證條件的程式碼是否有效,因此,總而言之,其實組合幾個如果陳述式,並傳回布林值。 撰寫驗證層與一般的程式碼,並沒有任何的架構或技術或許可行,但是它並不是個好主意。 產生的程式碼,那不是很容易閱讀,而不會輕易地發展,仍有某些專有的程式庫會讓事情容易。 受限於真實的商業規則,驗證可以經常變動,而且必須考慮您的實作。 總而言之,您無法只需要撰寫程式碼會驗證 ; 您必須撰寫程式碼,即會開啟與不同的規則相同的資料驗證。

使用驗證,有時候您想要吼叫,如果傳遞無效的資料,且有時您只想蒐集錯誤,並且會報告至其他圖層上的程式碼。 請記住,不要驗證程式碼的合約。 它們會檢查條件,並接著擲回例外狀況,如果條件不會套用。 使用集中式的錯誤處理常式,您可以從例外狀況復原,並能適當降級。 一般情況下,我會建議使用網域實體中的程式碼的合約,只是要捕捉潛在的嚴重錯誤會導致不一致的狀態。 若要在工廠中使用的程式碼合約是合理-在本例中,如果傳遞的資料不正確,,必須擲回程式碼。 是否要使用於屬性的設定存取子方法的程式碼合約是您的呼叫。 我偏好取用柔和的路由,並透過屬性驗證。 但是,此屬性呢?

資料的註解 vs。 VAB

資料註釋的命名空間和企業程式庫 VAB 都很相似。 這兩種架構是以屬性為基礎,而且可以擴充以表示自訂規則的自訂類別。 在這兩種情況下,您可以定義跨屬性驗證。 最後,這兩種架構都有的驗證程式 API 來評估執行個體,並傳回錯誤的清單。 其中有不同?

資料的註解是 Microsoft 的一部分。Net 應用程式,而且不需要個別的下載。 企業程式庫是個別的下載。不是什麼大問題在中大型的專案,但仍有問題,因為它可能需要在公司的案例中的核准。 企業程式庫可以輕易地透過 NuGet 安裝 (請參閱本期的文章中,「 管理專案文件庫的 NuGet,")。

企業程式庫 VAB 是在其上層層資料附註在一個地方不一樣: 它可以設定透過 XML 的規則集。 XML 的規則集是一個項目在您想在您用來描述,驗證組態檔中。 不用說,您可以不用甚至涉及程式碼以宣告方式變更項目。 [圖 4示範範例規則集。

[圖 4 企業程式庫的規則集

<validation>
   <type assemblyName="..." name="ValidModel1.Domain.Customer">
     <ruleset name="IsValidForRegistration">
       <properties>
         <property name="CompanyName">
           <validator negated="false"
                      messageTemplate="The company name cannot be null" 
                      type="NotNullValidator" />
           <validator lowerBound="6" lowerBoundType="Ignore"
                      upperBound="40" upperBoundType="Inclusive" 
                      negated="false"
                      messageTemplate="Company name cannot be longer ..."
                      type="StringLengthValidator" />
         </property>
         <property name="Id">
           <validator negated="false"
                      messageTemplate="The customer ID cannot be null"
                      type="NotNullValidator" />
         </property>
         <property name="PhoneNumber">
           <validator negated="false"
                      type="NotNullValidator" />
           <validator lowerBound="0" lowerBoundType="Ignore"
                      upperBound="24" upperBoundType="Inclusive"
                      negated="false"
                      type="StringLengthValidator" />
         </property>
         <property name="FaxNumber">
           <validator negated="false"
                      type="NotNullValidator" />
           <validator lowerBound="0" lowerBoundType="Ignore"
                      upperBound="24" upperBoundType="Inclusive"
                      negated="false"
                      type="StringLengthValidator" />
         </property>
       </properties>
     </ruleset>
   </type>
 </validation>

規則集列出您想要套用於指定的型別上指定屬性的屬性。 在 [程式碼中,您驗證規則集執行下列,如下所示:

public virtual ValidationResults ValidateForRegistration()
{
  var validator = ValidationFactory
          .CreateValidator<Customer>("IsValidForRegistration");
  var results = validator.Validate(this);
  return results;
}

此方法適用於 IsValidForRegistration 規則集,以指定的執行個體中所列的驗證程式。

在 [驗證和文件庫的最後一個注意事項。 並未涵蓋在每個受歡迎的驗證程式庫,但不會讓最明顯的差異。 很重要的一點是考慮是否您的商務規則變更及頻率。 根據如此,您可以決定資料的註解、 VAB、 程式碼的合約或其他媒體櫃是較為適合。 在 [我的經驗,如果您知道完全要如何達成目標,然後 「 對 」 的驗證程式庫很容易選擇。

完成

物件模型供實際商務網域根本很難可以是一般的屬性和類別集合。 此外,設計考量優先於技術。 Well-done 的物件模型來表示需要哪一方面的網域。 大多數情況下,這代表類別,可以輕易地初始化,並驗證,以及豐富的內容和邏輯。 DDD 作法最好不要 dogmatically,但改為顯示路来走 guideposts。

Dino Esposito 方面的 < 程式設計 Microsoft ASP。NET 4"(在 [微軟出版品,2011年),並"以程式設計 Microsoft ASP。NET MVC"(微軟出版品,2011年) 和 coauthor 的安裝。NET: 高階的企業應用程式 」 (在 [微軟出版品,2008年)。 居住在義大利,Esposito 是在世界各地的產業活動演說。 在 Twitter 上都追隨他 twitter.com/despos

因為有到下列的技術專家來檢閱這份文件: 奧德 FahndrichAndrea Saltarello