咱們長時間爭論什麼方案是實現域業務領域層架構的最佳方法。最後,咱們用一個在線商店案例來講明,其中忽略了許多以前遇到的一些場景。在線商店對不少人來講更容易理解。 前端
1、在線商店項目簡介 web
1. 用例選擇 數據庫
Use-case後端 |
Description架構 |
Registers to the sitemvc |
The user fills in the application form and becomes an official customer of the I-Buy-Stuff site.app |
Log in to start using the siteasp.net |
The user enters credentials and logs in. Credentials can be entered directly to the site or via a couple of social networks.ide |
Subscribe to the newsletter函數 |
The user adds an email address to receive the newsletter. |
Search an order |
The user indicates the number of one of her orders and reviews details such as estimated delivery date, items, and costs. |
Place an order |
The user fills a shopping cart with a list of products and then proceeds with making payment and entering the details related to delivery |
2. 方法選擇
在線商城的需求,幾乎全部的開發人員都或多或少的瞭解。在須要分析後和一般語言的定義下,將在線商城清晰的定義爲一些實體。能夠將項目中的類分開建立爲類庫。域模型將定義爲如下域實體:Admin, Customer, Order, OrderItem, Product, Subscriber, 和FidelityCard。
3. 設計在線商城項目結構
前面的章節中專一於探討分層結構在DDD中的做用,如今看下實際項目中是如何使用的。下圖是VS2013中的項目結構
圖中能夠的地到Demain Layer,Infrastructure Layer, 和site。IBuyStuff解決方案中,site目錄中實際上就是一個asp.net mvc應用程序。從邏輯上劃分,能夠將系統分爲前端和後端兩部分。Demain Layer 和Infrastructure Layer是後端的部分,後端儘量多的在同一個上下文中處理前端的動做。
4. 技術選擇
IBuyStuff解決方案使用了一系列.net技術,大部分經過NuGet包引入。前面已經提到,前端是使用了ASP.NET Identity 的asp.net mvc5的應用程序。Twitter Bootstrap技術也用戶界中使用。針對於移動視圖,使用界面使用了WURFL來感知終端設備,Asp.net經過路由來展示View。
後端的持久化使用了Entity Framework Code First實現。在示例代碼中本地的SqlServer實例在web站點的App_Data目錄下。最後,項目中全部的依賴注入都是使用Microsoft Patterns and Practices Unity。
5. 邊界上下文設計
I-Buy-Stuff在線商城是一個刻意簡單小型、上下文邊界中業務不須要面對額外的需求。演示一個廣義的DDD架構它可能不是最佳的例子,但別一方面,一個太複雜的例子又太難講解。
在域上下文中分解業務,一個合理簡單的方案,將業務分爲如下幾個邊界上下文:
MembershipBC用於身份認證和用戶帳戶管理。OrderingBC用於管理購物車和訂單處理,CatalogBC用於後臺管理,如產品更新、添加、刪除,更新庫存等。
OrderingBC最多是一個Asp.net Mvc應用程序,如在線商城。Membership在同一個應用中做爲不一樣的功能模塊獨立存在。CatalogBC是管理系統,更可能是數據庫的CRUD操做,沒在必要使用DDD設計。
6. 邊界上下文間的通訊
Product的增刪改查什麼時候通知OrderingBC上下文邊界?OrderingBC要對數據儘量頻繁讀取來獲取CatalogBC對數據庫的更新是不太可能的,別外一種方法,當CatalogBC更新時,同時調用一個OrderingBC的RPC。在大型複雜的系統中,你甚至會用Service bus將架構中許多小模塊鏈接起來。
7. 添加其它的上下文邊界
隨着商城的複雜度成長,你要添加其它的上下文,好比:ShippingBC,PricingBC,PurchaseBC等。你能夠將他們放到同下big上下文,能夠將他們分割成多個小的上下文。這兒沒有什麼硬性指標來決策怎麼作。
8. I-Buy-Stuff解決方案中的上下文圖表
以下圖所示,I-Buy-Stuff上下文是一個ASP.NET MVC應用程序,它要在域模型架構上運行,是一個至關大的上下文,國爲它包含了會員、訂購邏輯、甚至是包含了購買上下文。I-Buy-Stuff上下文承載着大部分業務邏輯,Shipping和Purchase被實現爲服務形式。
2、高大尚的域模型建模
現代軟件的根本問題是,開發都經常寧願關注代碼而不肯意麪對模型設計,這種方法自己沒有錯,可是你將面對的是難以管理的複雜度。若是你沒有進行域模型設計的狀況下寫出了成功的代碼,這樣作有錯嗎?固然沒錯,但你有可能在複雜度增加時暴露出問題。它可能出如今下一個項目或同一個項目中的需求增加和變動。
1. anemic model 弱模型
DDD權威指出沒有模型沒有必要是弱模型。軟件設計中的面向對象指出,一個類是對數據和行爲的封裝,而一個模型只是用於映射數據庫,基本上沒有行爲方法,這樣的類被稱之爲弱類 anemic model。
業務邏輯的位置
實際上,面向行爲本質上是找出業務邏輯模塊的位置。
class Invoice
{
public string Number {get; private set;}
public DateTime Date {get; private set;}
public Customer Customer {get; private set;}
public string PayableOrder {get; private set;}
public ICollection<InvoiceLine> Lines {get; private set;}
...
}
上面的Invoice類中沒有方法,是錯誤的寫法?仍是弱模型的一部分?若是對象僅沒有方法,它也不必定是一個弱模型。弱模型是指將業務邏輯放到了模型外的地方實現。
2. Entities scaffolding
一個DDD實體應該是一個同時包含數據和行爲的類。一個實體可能有公有屬性,但他不會被用做爲一個數據容器。下面是DDD實體的特性
3.Value objects scaffolding
與entity相似,value object是由一系列數據組成的類。行爲對值對象來講是不須要的。值對象也能夠有方法,可是這些方法都是一些輔助類方法。於entity不一樣,value object不須要惟一性,它們沒有可變狀態,它們只用於存儲數據。如DateTime類型的構造函數。
4. 明確合集
將話題轉到I-Buy-Stuff這個示例應用程序上,I-Buy-Stuff是一個簡單的在線商城,下圖反應出應用中實體的關係。在站點中,咱們認爲只有少許的用例,如:搜索,下單。Customer、Order、Product是合集,Customer和Produc是單件實體合集。
Customer的特色是有一些我的數據,如name,address,username,或payment details。咱們明確假設I-Buy-Stuff系統中只有一種類型用戶,不用作任何區分。如:法人客戶可能和我的客戶,法人客戶下可能包含多個我的客戶。I-Buy-Stuff選擇第3種表示方法,不區分。
Customer定義
public class Customer : IAggregateRoot
{
public static Customer CreateNew(Gender gender, string id,string firstname, string lastname, string email)
{
var customer = new Customer {
CustomerId = id,
Address = Address.Create(),
Payment = NullCreditCard.Instance,
Email = email,
FirstName = firstname,
LastName = lastname,
Gender = gender
};
return customer;
}
public string CustomerId { get; private set; }
public string PasswordHash { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string Email { get; private set; }
public Gender Gender { get; private set; }
public string Avatar { get; private set; }
public Address Address { get; private set; }
public CreditCard Payment { get; private set; }
public ICollection<Order> Orders { get; private set; }
// More methods here such as SetDefaultPaymentMode.
// A customer may have a default payment mode (e.g., credit card), but
// different orders can be paid in different ways.
...
}
其它幾個合集定義……
5. 實體持久化 Persisting the model
域模型要支持持久化,常見的是用O/RM來實現,如Entity Framework Code-First 方法實現。本質上Entity Framework有兩種實現風格:Database First 與Code First。另外還有第三種風格,Model First,它是一種混合風格。Database-First方法中Entity Framework讀取數據庫結構,生成和一系列partial類型的弱類(anemic class),可使用partial來擴展類。Code-First風格是生成一系列類,如I-Buy-Stuff定義的合集,而後添加一個額外的層來映射到數據庫表。這個映射層告訴O/RM關於數據庫信息,及如何CRUD數據。I-Buy-Stuff示例中使用的是Code-First風格。
Code-First經過繼承DbContext類爲中心實現持久化。
public class DomainModelFacade : DbContext
{
static DomainModelFacade()
{
Database.SetInitializer(new SampleAppInitializer());
}
public DomainModelFacade() : base("naa4e - 09")
{
Products = base.Set<Product>();
Customers = base.Set<Customer>();
Orders = base.Set<Order>();
FidelityCards = base.Set<FidelityCard>();
}
public DbSet<Order> Orders { get; private set; }
public DbSet<Customer> Customers { get; private set; }
public DbSet<Product> Products { get; private set; }
public DbSet<FidelityCard> FidelityCards { get; private set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
}
}
Mapping properties to columns
Code First中有兩種方法將properties映射到columns。能夠在demain class中使用data annotation attributes,也可使用Code-First API自帶的configuration classes來重寫OnModelCreating方法輕鬆實現。如:
modelBuilder.ComplexType<Money>();
modelBuilder.ComplexType<Address>();
modelBuilder.ComplexType<CreditCard>();
modelBuilder.Configurations.Add(new FidelityCardMap());
modelBuilder.Configurations.Add(new OrderMap());
modelBuilder.Configurations.Add(new CustomerMap());
modelBuilder.Configurations.Add(new OrderItemMap());
modelBuilder.Configurations.Add(new CurrencyMap());
3、實現業務邏輯
不少狀況下,並不全部的業務邏輯都須要融入到域模型的類裏。而非業務邏輯的功能,如持久化功能也要添加到域模型中。在I-Buy-Stuff項目中有兩個主要的業務邏輯:查詢訂單和訂單下單。
1.查詢訂單
下面的代碼中Controller接受一個查找訂單的用戶請求,controller方法中使用一個Service來獲取訂單數據。
public ActionResult SearchResults(int id)
{
var model = _service.FindOrder(id, User.Identity.Name);
return View(model);
}
public SearchOrderViewModel FindOrder(int orderId, string customerId)
{
var order = _orderRepository.FindByCustomerAndId(orderId, customerId);
if (order is NullOrder)
return new SearchOrderViewModel();
return SearchOrderViewModel.CreateFromOrder(order);
}
application service中FindOrder方法調用域服務處倉儲服務來獲取訂單。
2. 訂單下單
當用戶在站點上點擊購買時,商城系統反回一個用戶界面,用於建立訂單,另外,還有添加到購物車等方法
public ActionResult New()
{
var customerId = User.Identity.Name;
var shoppingCartModel = _service.CreateShoppingCartForCustomer(customerId);
shoppingCartModel.EnableEditOnShoppingCart = true;
SaveCurrentShoppingCart(shoppingCartModel);
return View("shoppingcart", shoppingCartModel);
}
//添加到購物車
public ActionResult AddToShoppingCartCommand(int productId, int quantity = 1)
{
var cartModel = RetrieveCurrentShoppingCart();
cartModel = _service.AddProductToShoppingCart(cart, productId, quantity);
SaveCurrentShoppingCart(cartModel);
return RedirectToAction("AddTo");
}
public ShoppingCartViewModel AddProductToShoppingCart(ShoppingCartViewModel cart, int productId, int quantity)
{
var product = (from p in cart.Products where p.Id == productId select p).Single();
cart.OrderRequest.AddItem(quantity, product);
return cart;
}