再有人问你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 可能会增加代码量和开发时间,但是它可以带来更好的代码质量和更好的开发效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值