再有人問(wèn)你DDD,把這篇文章丟給他
DDD(Domain-Driven Design,中文名領(lǐng)域模型設(shè)計(jì))是一種軟件開(kāi)發(fā)方法論,它強(qiáng)調(diào)將業(yè)務(wù)領(lǐng)域中的知識(shí)融入到軟件設(shè)計(jì)中。DDD 強(qiáng)調(diào)將軟件開(kāi)發(fā)過(guò)程分為兩個(gè)主要階段:領(lǐng)域分析和領(lǐng)域建模。領(lǐng)域分析是指深入了解業(yè)務(wù)領(lǐng)域中的問(wèn)題和需求,領(lǐng)域建模是將分析出的領(lǐng)域知識(shí)轉(zhuǎn)化為軟件模型。
在本文中,我不再過(guò)多說(shuō)明DDD的來(lái)龍去脈,我將用多個(gè)例子來(lái)詳細(xì)說(shuō)明使用 DDD 和不使用 DDD 的區(qū)別、優(yōu)勢(shì)和劣勢(shì)。
需求:假設(shè)我們正在開(kāi)發(fā)一個(gè)銀行應(yīng)用程序,需要實(shí)現(xiàn)以下三個(gè)基本功能:
- 存款
- 取款
- 轉(zhuǎn)賬
我們可以使用面向?qū)ο缶幊陶Z(yǔ)言(如 Java)來(lái)實(shí)現(xiàn)這些功能。
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 {}
在這個(gè)示例中,我們定義了一個(gè) Account 類表示銀行賬戶。它包含一個(gè) balance 屬性表示余額,以及 deposit、withdraw、transfer 方法用于存款、取款和轉(zhuǎn)賬操作。
deposit方法用于存款,它會(huì)將傳入的金額加到賬戶余額中。withdraw方法用于取款,它會(huì)從賬戶余額中減去傳入的金額,如果余額不足,則會(huì)拋出 InsufficientFundsException 異常。transfer方法用于轉(zhuǎn)賬,它會(huì)調(diào)用 withdraw 和 deposit 方法實(shí)現(xiàn)轉(zhuǎn)賬操作。
存在的問(wèn)題:
這個(gè)示例中沒(méi)有明確的領(lǐng)域模型,也沒(méi)有領(lǐng)域服務(wù)。所有的功能都由 Account 類實(shí)現(xiàn),這使得代碼變得簡(jiǎn)單和易于理解。但是,這種設(shè)計(jì)方式存在一些問(wèn)題。
- 這種設(shè)計(jì)方式缺乏領(lǐng)域模型。銀行業(yè)務(wù)領(lǐng)域非常復(fù)雜,它包含許多概念和關(guān)系。一個(gè) Account類無(wú)法涵蓋所有的銀行業(yè)務(wù),也無(wú)法提供足夠的靈活性和可擴(kuò)展性。
- 這種設(shè)計(jì)方式缺乏領(lǐng)域服務(wù)。銀行業(yè)務(wù)中還包含許多與賬戶無(wú)關(guān)的操作,如查詢交易記錄、生成報(bào)告等。這些操作無(wú)法由 Account類實(shí)現(xiàn),需要另外定義領(lǐng)域服務(wù)。
- 這種設(shè)計(jì)方式缺乏可測(cè)試性。由于所有的功能都由一個(gè)類實(shí)現(xiàn),測(cè)試變得困難。在測(cè)試轉(zhuǎn)賬功能時(shí),我們需要?jiǎng)?chuàng)建兩個(gè)賬戶對(duì)象并將它們連接起來(lái),這使得測(cè)試變得復(fù)雜和冗長(zhǎng)。
改進(jìn)
接下來(lái),我們將使用 DDD 的方式重新設(shè)計(jì)上面的示例。首先,我們需要進(jìn)行領(lǐng)域分析,深入了解銀行業(yè)務(wù)中的概念和關(guān)系。例如,我們可以定義以下概念:
賬戶(Account):表示銀行賬戶。
交易(transaction):表示銀行交易,包括存款、取款和轉(zhuǎn)賬等。
銀行(Bank):表示銀行機(jī)構(gòu)。
我們可以定義這些概念的領(lǐng)域模型。例如,Account 類可以表示銀行賬戶,Transaction 類可以表示銀行交易,Bank 類可以表示銀行機(jī)構(gòu)。這些類都應(yīng)該是領(lǐng)域模型,它們應(yīng)該包含業(yè)務(wù)領(lǐng)域中的知識(shí)和規(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 {}
在這個(gè)示例中,我們定義了三個(gè)領(lǐng)域模型:Account、Transaction 和 Bank。**Account** 類和之前的示例相同,但它現(xiàn)在是一個(gè)領(lǐng)域模型。**Transaction** 類表示銀行交易,它包含 source、destination、amount 和 timestamp 屬性,分別表示交易的來(lái)源賬戶、目標(biāo)賬戶、金額和時(shí)間戳。execute 方法用于執(zhí)行交易。**Bank** 類表示銀行機(jī)構(gòu),它包含一個(gè) accounts 屬性表示賬戶列表。addAccount 方法用于添加賬戶,getAccounts 方法用于獲取賬戶列表。transfer 方法用于執(zhí)行轉(zhuǎn)賬操作,它創(chuàng)建一個(gè) Transaction 對(duì)象并調(diào)用其 execute 方法實(shí)現(xiàn)轉(zhuǎn)賬操作。
這個(gè)示例中使用了領(lǐng)域模型和領(lǐng)域事件來(lái)支持業(yè)務(wù)邏輯,這使得我們能夠更好地組織和管理業(yè)務(wù)邏輯,同時(shí)也使得代碼更加清晰和易于維護(hù)。
使用 DDD 和不使用 DDD 的比較
代碼結(jié)構(gòu)
使用 DDD 的示例中,代碼結(jié)構(gòu)更加清晰和易于理解。每個(gè)領(lǐng)域模型都有自己的職責(zé)和行為,使得代碼更加模塊化和可組合。而不使用 DDD 的示例中,所有的業(yè)務(wù)邏輯都被放在一個(gè)類中,導(dǎo)致代碼結(jié)構(gòu)混亂且難以維護(hù)。
代碼可讀性
使用 DDD 的示例中,代碼更加易于閱讀和理解。每個(gè)領(lǐng)域模型都有自己的概念和行為,使得代碼更加直觀和易于理解。而不使用 DDD 的示例中,所有的業(yè)務(wù)邏輯都被放在一個(gè)類中,導(dǎo)致代碼可讀性差。
測(cè)試
使用 DDD 的示例中,測(cè)試更加易于編寫和管理。每個(gè)領(lǐng)域模型都有自己的行為,可以單獨(dú)測(cè)試,使得測(cè)試更加模塊化和易于管理。而不使用 DDD 的示例中,所有的業(yè)務(wù)邏輯都被放在一個(gè)類中,導(dǎo)致測(cè)試難以編寫和管理。
擴(kuò)展性
使用 DDD 的示例中,代碼更加易于擴(kuò)展和修改。每個(gè)領(lǐng)域模型都有自己的職責(zé)和行為,使得修改和擴(kuò)展更加局部化和安全。而不使用 DDD 的示例中,所有的業(yè)務(wù)邏輯都被放在一個(gè)類中,導(dǎo)致擴(kuò)展和修改難度大。
再舉一個(gè)例子。
假設(shè)我們正在開(kāi)發(fā)一個(gè)電商平臺(tái),我們需要實(shí)現(xiàn)一個(gè)購(gòu)物車模塊。購(gòu)物車模塊需要完成以下功能:
- 將商品添加到購(gòu)物車
- 從購(gòu)物車中刪除商品
- 更新購(gòu)物車中商品的數(shù)量
- 計(jì)算購(gòu)物車中商品的總價(jià)
首先看一下不使用 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; }}
在這個(gè)示例代碼中,購(gòu)物車的所有邏輯都被放在一個(gè)類中,導(dǎo)致這個(gè)類的職責(zé)非常復(fù)雜。如果在將來(lái)需要修改購(gòu)物車的某個(gè)功能,就需要修改這個(gè)類的某個(gè)方法,這可能會(huì)影響到購(gòu)物車的其他功能,增加代碼的復(fù)雜度。
使用 DDD 改進(jìn):
首先定義一個(gè)購(gòu)物車領(lǐng)域模型:
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; }}
購(gòu)物車領(lǐng)域模型只負(fù)責(zé)購(gòu)物車的業(yè)務(wù)邏輯,包括將商品添加到購(gòu)物車、從購(gòu)物車中刪除商品、更新購(gòu)物車中商品的數(shù)量和計(jì)算購(gòu)物車中商品的總價(jià)。
然后定義一個(gè)購(gòu)物車服務(wù),它負(fù)責(zé)將購(gòu)物車領(lǐng)域模型和其他服務(wù)進(jìn)行組合和協(xié)調(diào):
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(); }}
購(gòu)物車服務(wù)將購(gòu)物車領(lǐng)域模型和產(chǎn)品服務(wù)進(jìn)行組合,負(fù)責(zé)將商品添加到購(gòu)物車、從購(gòu)物車中刪除商品、更新購(gòu)物車中商品的數(shù)量和計(jì)算購(gòu)物車中商品的總價(jià)。
通過(guò)將購(gòu)物車領(lǐng)域模型和購(gòu)物車服務(wù)進(jìn)行分離,我們可以使代碼更加可維護(hù)和可擴(kuò)展。例如,如果我們需要添加一個(gè)新的功能,例如促銷或折扣,我們可以簡(jiǎn)單地修改購(gòu)物車服務(wù)而不必改變購(gòu)物車領(lǐng)域模型。同樣,如果我們需要更改購(gòu)物車領(lǐng)域模型,我們也可以更改它而不必改變購(gòu)物車服務(wù)。
再舉一個(gè)簡(jiǎn)單的例子,例如一個(gè)簡(jiǎn)單的博客系統(tǒng)。在不使用 DDD 的情況下,可能會(huì)編寫以下代碼:
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 類,該類負(fù)責(zé)創(chuàng)建、更新和刪除博客。但是,這個(gè)類并沒(méi)有定義博客的業(yè)務(wù)邏輯,例如如何驗(yàn)證博客的標(biāo)題和內(nèi)容是否有效,如何處理博客的標(biāo)簽或評(píng)論等等。這可能會(huì)導(dǎo)致代碼復(fù)雜度的增加,并且難以擴(kuò)展。
使用 DDD 的話,我們可以將博客作為領(lǐng)域模型進(jìn)行定義,例如:
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) { // 驗(yàn)證標(biāo)題是否有效 this.title = title; } public void setContent(String content) { // 驗(yàn)證內(nèi)容是否有效 this.content = content; } public void addTag(Tag tag) { // 處理標(biāo)簽 this.tags.add(tag); } public void addComment(Comment comment) { // 處理評(píng)論 this.comments.add(comment); } // getter 和 setter 略}
在上面的代碼中,我們可以看到,Blog 類定義了博客的業(yè)務(wù)邏輯,例如如何驗(yàn)證博客的標(biāo)題和內(nèi)容是否有效,如何處理博客的標(biāo)簽和評(píng)論等等。現(xiàn)在,我們可以使用 BlogService 類來(lái)創(chuàng)建、更新和刪除博客,同時(shí)使用 Blog 類來(lái)處理博客的業(yè)務(wù)邏輯,例如添加標(biāo)簽和評(píng)論等等。這樣,我們可以將代碼分離到不同的領(lǐng)域模型中,使代碼更加清晰和易于維護(hù)。
總結(jié)
使用 DDD 的示例比不使用 DDD 的示例更加優(yōu)秀。DDD 提供了一種更好的方式來(lái)組織和管理業(yè)務(wù)邏輯,使得代碼更加模塊化、可組合、易于維護(hù)和擴(kuò)展。雖然使用 DDD 可能會(huì)增加代碼量和開(kāi)發(fā)時(shí)間,但是它可以帶來(lái)更好的代碼質(zhì)量和更好的開(kāi)發(fā)效率。