再有人問你DDD,把這篇文章丟給他
DDD(Domain-Driven Design,中文名領域模型設計)是一種軟件開發(fā)方法論,它強調將業(yè)務領域中的知識融入到軟件設計中。DDD 強調將軟件開發(fā)過程分為兩個主要階段:領域分析和領域建模。領域分析是指深入了解業(yè)務領域中的問題和需求,領域建模是將分析出的領域知識轉化為軟件模型。
在本文中,我不再過多說明DDD的來龍去脈,我將用多個例子來詳細說明使用 DDD 和不使用 DDD 的區(qū)別、優(yōu)勢和劣勢。
需求:假設我們正在開發(fā)一個銀行應用程序,需要實現以下三個基本功能:
- 存款
- 取款
- 轉賬
我們可以使用面向對象編程語言(如 Java)來實現這些功能。
public class Account { private int balance; public Account(int balance) { this.balance = balance; } public void deposit(int amount) { balance = amount; } public void withdraw(int amount) { if (balance < amount) { throw new InsufficientFundsException(); } balance -= amount; } public void transfer(Account destination, int amount) { withdraw(amount); destination.deposit(amount); } public int getBalance() { return balance; }}public class InsufficientFundsException extends RuntimeException {}
在這個示例中,我們定義了一個 Account 類表示銀行賬戶。它包含一個 balance 屬性表示余額,以及 deposit、withdraw、transfer 方法用于存款、取款和轉賬操作。
deposit方法用于存款,它會將傳入的金額加到賬戶余額中。withdraw方法用于取款,它會從賬戶余額中減去傳入的金額,如果余額不足,則會拋出 InsufficientFundsException 異常。transfer方法用于轉賬,它會調用 withdraw 和 deposit 方法實現轉賬操作。
存在的問題:
這個示例中沒有明確的領域模型,也沒有領域服務。所有的功能都由 Account 類實現,這使得代碼變得簡單和易于理解。但是,這種設計方式存在一些問題。
- 這種設計方式缺乏領域模型。銀行業(yè)務領域非常復雜,它包含許多概念和關系。一個 Account類無法涵蓋所有的銀行業(yè)務,也無法提供足夠的靈活性和可擴展性。
- 這種設計方式缺乏領域服務。銀行業(yè)務中還包含許多與賬戶無關的操作,如查詢交易記錄、生成報告等。這些操作無法由 Account類實現,需要另外定義領域服務。
- 這種設計方式缺乏可測試性。由于所有的功能都由一個類實現,測試變得困難。在測試轉賬功能時,我們需要創(chuàng)建兩個賬戶對象并將它們連接起來,這使得測試變得復雜和冗長。
改進
接下來,我們將使用 DDD 的方式重新設計上面的示例。首先,我們需要進行領域分析,深入了解銀行業(yè)務中的概念和關系。例如,我們可以定義以下概念:
賬戶(Account):表示銀行賬戶。
交易(transaction):表示銀行交易,包括存款、取款和轉賬等。
銀行(Bank):表示銀行機構。
我們可以定義這些概念的領域模型。例如,Account 類可以表示銀行賬戶,Transaction 類可以表示銀行交易,Bank 類可以表示銀行機構。這些類都應該是領域模型,它們應該包含業(yè)務領域中的知識和規(guī)則。
public class Account { private int balance; public Account(int balance) { this.balance = balance; } public void deposit(int amount) { balance = amount; } public void withdraw(int amount) { if (balance < amount) { throw new InsufficientFundsException(); } balance -= amount; } public int getBalance() { return balance; }}public class Transaction { private Account source; private Account destination; private int amount; private LocalDateTime timestamp; public Transaction(Account source, Account destination, int amount) { this.source = source; this.destination = destination; this.amount = amount; this.timestamp = LocalDateTime.now(); } public void execute() { source.withdraw(amount); destination.deposit(amount); } public LocalDateTime getTimestamp() { return timestamp; }}public class Bank { private List<Account> accounts; public Bank() { this.accounts = new ArrayList<>(); } public void addAccount(Account account) { accounts.add(account); } public List<Account> getAccounts() { return accounts; } public void transfer(Account source, Account destination, int amount) { Transaction transaction = new Transaction(source, destination, amount); transaction.execute(); }}public class InsufficientFundsException extends RuntimeException {}
在這個示例中,我們定義了三個領域模型:Account、Transaction 和 Bank。**Account** 類和之前的示例相同,但它現在是一個領域模型。**Transaction** 類表示銀行交易,它包含 source、destination、amount 和 timestamp 屬性,分別表示交易的來源賬戶、目標賬戶、金額和時間戳。execute 方法用于執(zhí)行交易。**Bank** 類表示銀行機構,它包含一個 accounts 屬性表示賬戶列表。addAccount 方法用于添加賬戶,getAccounts 方法用于獲取賬戶列表。transfer 方法用于執(zhí)行轉賬操作,它創(chuàng)建一個 Transaction 對象并調用其 execute 方法實現轉賬操作。
這個示例中使用了領域模型和領域事件來支持業(yè)務邏輯,這使得我們能夠更好地組織和管理業(yè)務邏輯,同時也使得代碼更加清晰和易于維護。
使用 DDD 和不使用 DDD 的比較
代碼結構
使用 DDD 的示例中,代碼結構更加清晰和易于理解。每個領域模型都有自己的職責和行為,使得代碼更加模塊化和可組合。而不使用 DDD 的示例中,所有的業(yè)務邏輯都被放在一個類中,導致代碼結構混亂且難以維護。
代碼可讀性
使用 DDD 的示例中,代碼更加易于閱讀和理解。每個領域模型都有自己的概念和行為,使得代碼更加直觀和易于理解。而不使用 DDD 的示例中,所有的業(yè)務邏輯都被放在一個類中,導致代碼可讀性差。
測試
使用 DDD 的示例中,測試更加易于編寫和管理。每個領域模型都有自己的行為,可以單獨測試,使得測試更加模塊化和易于管理。而不使用 DDD 的示例中,所有的業(yè)務邏輯都被放在一個類中,導致測試難以編寫和管理。
擴展性
使用 DDD 的示例中,代碼更加易于擴展和修改。每個領域模型都有自己的職責和行為,使得修改和擴展更加局部化和安全。而不使用 DDD 的示例中,所有的業(yè)務邏輯都被放在一個類中,導致擴展和修改難度大。
再舉一個例子。
假設我們正在開發(fā)一個電商平臺,我們需要實現一個購物車模塊。購物車模塊需要完成以下功能:
- 將商品添加到購物車
- 從購物車中刪除商品
- 更新購物車中商品的數量
- 計算購物車中商品的總價
首先看一下不使用 DDD 的示例代碼:
public class ShoppingCart { private List<CartItem> cartItems = new ArrayList<>(); public void addItem(CartItem item) { cartItems.add(item); } public void removeItem(CartItem item) { cartItems.remove(item); } public void updateItemQuantity(CartItem item, int quantity) { for (CartItem cartItem : cartItems) { if (cartItem.equals(item)) { cartItem.setQuantity(quantity); break; } } } public BigDecimal calculateTotalPrice() { BigDecimal totalPrice = BigDecimal.ZERO; for (CartItem cartItem : cartItems) { totalPrice = totalPrice.add(cartItem.getPrice().multiply(BigDecimal.valueOf(cartItem.getQuantity()))); } return totalPrice; }}
在這個示例代碼中,購物車的所有邏輯都被放在一個類中,導致這個類的職責非常復雜。如果在將來需要修改購物車的某個功能,就需要修改這個類的某個方法,這可能會影響到購物車的其他功能,增加代碼的復雜度。
使用 DDD 改進:
首先定義一個購物車領域模型:
public class ShoppingCart { private List<CartItem> cartItems = new ArrayList<>(); public void addItem(CartItem item) { cartItems.add(item); } public void removeItem(CartItem item) { cartItems.remove(item); } public void updateItemQuantity(CartItem item, int quantity) { for (CartItem cartItem : cartItems) { if (cartItem.equals(item)) { cartItem.setQuantity(quantity); break; } } } public BigDecimal calculateTotalPrice() { BigDecimal totalPrice = BigDecimal.ZERO; for (CartItem cartItem : cartItems) { totalPrice = totalPrice.add(cartItem.getPrice().multiply(BigDecimal.valueOf(cartItem.getQuantity()))); } return totalPrice; }}
購物車領域模型只負責購物車的業(yè)務邏輯,包括將商品添加到購物車、從購物車中刪除商品、更新購物車中商品的數量和計算購物車中商品的總價。
然后定義一個購物車服務,它負責將購物車領域模型和其他服務進行組合和協(xié)調:
public class ShoppingCartService { private ShoppingCartRepository shoppingCartRepository; private ProductService productService; public void addToCart(Long productId, int quantity, ShoppingCart shoppingCart) { Product product = productService.getProductById(productId); CartItem cartItem = new CartItem(product, quantity); shoppingCart.addItem(cartItem); shoppingCartRepository.save(shoppingCart); } public void removeFromCart(Long productId, ShoppingCart shoppingCart) { Product product = productService.getProductById(productId); CartItem cartItem = shoppingCart.findItemByProduct(product); if (cartItem != null) { shoppingCart.removeItem(cartItem); shoppingCartRepository.save(shoppingCart); } } public void updateCartItemQuantity(Long productId, int quantity, ShoppingCart shoppingCart) { Product product = productService.getProductById(productId); CartItem cartItem = shoppingCart.findItemByProduct(product); if (cartItem != null) { cartItem.setQuantity(quantity); shoppingCart.updateItemQuantity(cartItem); shoppingCartRepository.save(shoppingCart); } } public BigDecimal calculateCartTotalPrice(ShoppingCart shoppingCart) { return shoppingCart.calculateTotalPrice(); }}
購物車服務將購物車領域模型和產品服務進行組合,負責將商品添加到購物車、從購物車中刪除商品、更新購物車中商品的數量和計算購物車中商品的總價。
通過將購物車領域模型和購物車服務進行分離,我們可以使代碼更加可維護和可擴展。例如,如果我們需要添加一個新的功能,例如促銷或折扣,我們可以簡單地修改購物車服務而不必改變購物車領域模型。同樣,如果我們需要更改購物車領域模型,我們也可以更改它而不必改變購物車服務。
再舉一個簡單的例子,例如一個簡單的博客系統(tǒng)。在不使用 DDD 的情況下,可能會編寫以下代碼:
public class BlogService { private BlogRepository blogRepository; public BlogService(BlogRepository blogRepository) { this.blogRepository = blogRepository; } public void createBlog(String title, String content) { Blog blog = new Blog(title, content); blogRepository.save(blog); } public void updateBlog(Long id, String title, String content) { Blog blog = blogRepository.findById(id); blog.setTitle(title); blog.setContent(content); blogRepository.save(blog); } public void deleteBlog(Long id) { Blog blog = blogRepository.findById(id); blogRepository.delete(blog); }}
在上面的代碼中,我們可以看到 BlogService 類,該類負責創(chuàng)建、更新和刪除博客。但是,這個類并沒有定義博客的業(yè)務邏輯,例如如何驗證博客的標題和內容是否有效,如何處理博客的標簽或評論等等。這可能會導致代碼復雜度的增加,并且難以擴展。
使用 DDD 的話,我們可以將博客作為領域模型進行定義,例如:
public class Blog { private Long id; private String title; private String content; private List<Tag> tags; private List<Comment> comments; public Blog(String title, String content) { this.title = title; this.content = content; this.tags = new ArrayList<>(); this.comments = new ArrayList<>(); } public void setTitle(String title) { // 驗證標題是否有效 this.title = title; } public void setContent(String content) { // 驗證內容是否有效 this.content = content; } public void addTag(Tag tag) { // 處理標簽 this.tags.add(tag); } public void addComment(Comment comment) { // 處理評論 this.comments.add(comment); } // getter 和 setter 略}
在上面的代碼中,我們可以看到,Blog 類定義了博客的業(yè)務邏輯,例如如何驗證博客的標題和內容是否有效,如何處理博客的標簽和評論等等?,F在,我們可以使用 BlogService 類來創(chuàng)建、更新和刪除博客,同時使用 Blog 類來處理博客的業(yè)務邏輯,例如添加標簽和評論等等。這樣,我們可以將代碼分離到不同的領域模型中,使代碼更加清晰和易于維護。
總結
使用 DDD 的示例比不使用 DDD 的示例更加優(yōu)秀。DDD 提供了一種更好的方式來組織和管理業(yè)務邏輯,使得代碼更加模塊化、可組合、易于維護和擴展。雖然使用 DDD 可能會增加代碼量和開發(fā)時間,但是它可以帶來更好的代碼質量和更好的開發(fā)效率。