再有人问你DDD,把这篇文章丢给他
DDD(Domain-Driven Design,中文名领域模型设计)是一种软件开发方法论,它强调将业务领域中的知识融入到软件设计中。DDD 强调将软件开发过程分为两个主要阶段:领域分析和领域建模。领域分析是指深入了解业务领域中的问题和需求,领域建模是将分析出的领域知识转化为软件模型。
在本文中,我不再过多说明DDD的来龙去脉,我将用多个例子来详细说明使用 DDD 和不使用 DDD 的区别、优势和劣势。
需求:假设我们正在开发一个银行应用程序,需要实现以下三个基本功能:
- 存款
- 取款
- 转账
我们可以使用面向对象编程语言(如 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 类实现,这使得代码变得简单和易于理解。但是,这种设计方式存在一些问题。
- 这种设计方式缺乏领域模型。银行业务领域非常复杂,它包含许多概念和关系。一个 Account类无法涵盖所有的银行业务,也无法提供足够的灵活性和可扩展性。
- 这种设计方式缺乏领域服务。银行业务中还包含许多与账户无关的操作,如查询交易记录、生成报告等。这些操作无法由 Account类实现,需要另外定义领域服务。
- 这种设计方式缺乏可测试性。由于所有的功能都由一个类实现,测试变得困难。在测试转账功能时,我们需要创建两个账户对象并将它们连接起来,这使得测试变得复杂和冗长。
改进
接下来,我们将使用 DDD 的方式重新设计上面的示例。首先,我们需要进行领域分析,深入了解银行业务中的概念和关系。例如,我们可以定义以下概念:
账户(Account):表示银行账户。
交易(transaction):表示银行交易,包括存款、取款和转账等。
银行(Bank):表示银行机构。
我们可以定义这些概念的领域模型。例如,Account 类可以表示银行账户,Transaction 类可以表示银行交易,Bank 类可以表示银行机构。这些类都应该是领域模型,它们应该包含业务领域中的知识和规则。
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 方法用于执行交易。**Bank** 类表示银行机构,它包含一个 accounts 属性表示账户列表。addAccount 方法用于添加账户,getAccounts 方法用于获取账户列表。transfer 方法用于执行转账操作,它创建一个 Transaction 对象并调用其 execute 方法实现转账操作。
这个示例中使用了领域模型和领域事件来支持业务逻辑,这使得我们能够更好地组织和管理业务逻辑,同时也使得代码更加清晰和易于维护。
使用 DDD 和不使用 DDD 的比较
代码结构
使用 DDD 的示例中,代码结构更加清晰和易于理解。每个领域模型都有自己的职责和行为,使得代码更加模块化和可组合。而不使用 DDD 的示例中,所有的业务逻辑都被放在一个类中,导致代码结构混乱且难以维护。
代码可读性
使用 DDD 的示例中,代码更加易于阅读和理解。每个领域模型都有自己的概念和行为,使得代码更加直观和易于理解。而不使用 DDD 的示例中,所有的业务逻辑都被放在一个类中,导致代码可读性差。
测试
使用 DDD 的示例中,测试更加易于编写和管理。每个领域模型都有自己的行为,可以单独测试,使得测试更加模块化和易于管理。而不使用 DDD 的示例中,所有的业务逻辑都被放在一个类中,导致测试难以编写和管理。
扩展性
使用 DDD 的示例中,代码更加易于扩展和修改。每个领域模型都有自己的职责和行为,使得修改和扩展更加局部化和安全。而不使用 DDD 的示例中,所有的业务逻辑都被放在一个类中,导致扩展和修改难度大。
再举一个例子。
假设我们正在开发一个电商平台,我们需要实现一个购物车模块。购物车模块需要完成以下功能:
- 将商品添加到购物车
- 从购物车中删除商品
- 更新购物车中商品的数量
- 计算购物车中商品的总价
首先看一下不使用 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; }}
购物车领域模型只负责购物车的业务逻辑,包括将商品添加到购物车、从购物车中删除商品、更新购物车中商品的数量和计算购物车中商品的总价。
然后定义一个购物车服务,它负责将购物车领域模型和其他服务进行组合和协调:
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(); }}
购物车服务将购物车领域模型和产品服务进行组合,负责将商品添加到购物车、从购物车中删除商品、更新购物车中商品的数量和计算购物车中商品的总价。
通过将购物车领域模型和购物车服务进行分离,我们可以使代码更加可维护和可扩展。例如,如果我们需要添加一个新的功能,例如促销或折扣,我们可以简单地修改购物车服务而不必改变购物车领域模型。同样,如果我们需要更改购物车领域模型,我们也可以更改它而不必改变购物车服务。
再举一个简单的例子,例如一个简单的博客系统。在不使用 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 类,该类负责创建、更新和删除博客。但是,这个类并没有定义博客的业务逻辑,例如如何验证博客的标题和内容是否有效,如何处理博客的标签或评论等等。这可能会导致代码复杂度的增加,并且难以扩展。
使用 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 类定义了博客的业务逻辑,例如如何验证博客的标题和内容是否有效,如何处理博客的标签和评论等等。现在,我们可以使用 BlogService 类来创建、更新和删除博客,同时使用 Blog 类来处理博客的业务逻辑,例如添加标签和评论等等。这样,我们可以将代码分离到不同的领域模型中,使代码更加清晰和易于维护。
总结
使用 DDD 的示例比不使用 DDD 的示例更加优秀。DDD 提供了一种更好的方式来组织和管理业务逻辑,使得代码更加模块化、可组合、易于维护和扩展。虽然使用 DDD 可能会增加代码量和开发时间,但是它可以带来更好的代码质量和更好的开发效率。