业务代码编程陷阱案例(领域驱动设计)


xinwang_m@163.com
2020-07-20 02:53:39 (4年前)
业务代码编程

当我们开始编写软件时,我们总是希望有一个好的设计。我们阅读书籍,运用最佳实践,最后,我们常常一团糟。根据我在一家定制软件开发公司的经验,我每天必须处理此类代码,尤其是在某些旧系统上工作时。

当我们开始编写软件时,我们总是希望有一个好的设计。我们阅读书籍,运用最佳实践,最后,我们常常一团糟。根据我在一家定制软件开发公司的经验,我每天必须处理此类代码,尤其是在某些旧系统上工作时。

造成这种情况的原因多种多样,我将尝试在一系列文章中以一些实际的方式来探讨其中的一些原因。在我的第一个示例中,我将说明为什么简单的软件会演变成一场噩梦,并建议进行一些改进。我将只专注于处理业务逻辑的服务层。

让我们从一个简单的存储应用程序开始。我们拥有带有服务,存储库的产品资源,并且我们可以执行我们认为需要的CRUD操作。我们的产品服务如下所示:

public class ProductService {
public String create(Product product) {
return productRepository.create(product);
}
public String update(Product product) {
return productRepository.update(product);
}
public Product get(String productId) {
return productRepository.get(productId);
}
public void delete(Product product) {
productRepository.delete(product);
}
}
还会有其他一些东西,例如DTO到实体的映射,控制器等。但是正如我所说的,我们将考虑将它们编写为简化起见。我们的产品实体是简单的Java Bean,我们的存储库保存在正确的数据库表中。然后,我们得到另一个要求,即我们还将创建一个在线商店,并且需要一种下订单的方法。因此,我们添加了快速订购服务来满足我们仍然很简单的要求:

public class OrderService {
public String saveOrder(Order order) {
return orderRepository.save(order);
}
}
它简单,易读且有效!然后,下订单时就需要更新库存中的产品的新要求。我们这样做:

public class OrderService {
public String saveOrder(Order order) {
Product product=productService.get(order.getProductId());
product.setAvailableQuantity(product.getAvailableQuantity()-order.getQuantity());
productService.update(product);

  1. return orderRepository.save(order);
  2. }

}
我们又碰到三个新需求:

我们需要致电运输服务将该产品运送到一个地址
如果没有足够的库存来履行订单,则抛出一个错误
如果产品的可用数量低于最低数量以进行重新库存。
结果如下:

public class OrderService {
public String saveOrder(Order order) {
Product product=productService.get(order.getProductId());

  1. //The order service works more like a product service in the following liness
  2. if(product.getAvailableQuantity()<order.getQuantity()){
  3. throw new ProductNotAvailableException();
  4. }
  5. product.setAvailableQuantity(product.getAvailableQuantity()-order.getQuantity());
  6. productService.update(product);
  7. if(product.getAvailableQuantity()<Product.MINIMUM_STOCK_QUANTITY){
  8. productService.restock(product);
  9. }
  10. //It also needs to know how shipments are created
  11. Shipment shipment=new Shipment(product, order.getQuantity(), order.getAddressTo());
  12. shipmentService.save(shipment);
  13. return orderRepository.save(order);
  14. }

}
我知道这可能是一个极端的例子,但是我确信我们在项目中已经看到了类似的代码。这样做有多个问题–责任z职责共担,与其他领域的逻辑和基础架构混淆在以前等等。如果这是一个真实的商店,那么接单的人就像总经理–照顾一切,从实际订购库存维护和交付。

更好的版本

让我们尝试以不同的方式处理相同的情况。我将从订购服务开始。为什么我们调用方法saveOrder?因为我们将其视为开发人员,而不是从业务角度来看。我们开发人员的想法通常是数据库驱动的(或REST驱动的),我们将我们的软件视为一系列CRUD操作。通常,当我们阅读有关领域驱动设计的书籍时,会提到通用语言-开发人员和用户之间的通用语言。如果我们尝试在我们的代码中为业务建模,那么为什么不使用正确的术语。我们可以将初始代码更改为:

public class OrderService {
public String placeOrder(Order order) {
return orderRepository.save(order);
}
}
使用placeOrder替代了原理的saveOrder方法名。

进行很小的更改,但即使那样也会使其更具可读性。这是业务层,而不是数据库层–我们去商店时下订单place order,但不保存订单save order。然后,当其他需求出现时,而不是开始使用带有CRUD操作的现有服务来编码它们,我们可以尝试重新创建业务模型。我们询问业务人员,他们告诉我们,下订单时,接单的人员会致电库存部门,询问他们产品是否可用,然后进行储备并致电带有预定编号和地址的交货人员,以便他们装运它。是什么阻止我们在代码中执行相同的操作?

public class OrderService {
public String placeOrder(Order order) {
String productReservationId=productService.requestProductReservation(order.getProductId, order.getQuantity());
String shippingId=shipmentService.requestDelivery(productReservationId, order.getAddressTo());
order.addShippingId(shippingId);
return orderRepository.save(order);
}
}
在我看来,它看起来更加干净,代表了实际商店中发生的事件的顺序。订单服务不需要知道产品如何工作或运输如何工作。它只是使用完成工作所需的方法。我们也需要修改其他服务:

public class ProductService {
//Method used in Orders Service
public String requestProductReservation(String productId, int quantity){
Product product=productRepository.get(productId);
product.reserve(quantity);
productRepository.update(product);
return createProductReservation(product, quantity);
}

  1. private String createProductReservation(Product product, int quantity){
  2. ProductReservation reservation=new ProductReservation(product,quantity);
  3. reservation.setStatus(ReservationStatus.CREATED);
  4. return reservationRepository.save(reservation);
  5. }
  6. //Method used in Shipment Service
  7. public ProductReservation getProductsForDelivery(String reservationId){
  8. ProductReservation reservation=reservationRepository.getProductReservation(reservationId);
  9. reservation.getProduct.releaseReserved(reservation.getQuantity());
  10. if(reservation.getProduct().needRestock()){
  11. this.restock(product);
  12. }
  13. reservation.setStatus(ReservationStatus.PROCESSED);
  14. reservationRepository.update(reservation);
  15. }

}
产品服务提供了其他服务要使用的两种方法,但对它们的结构一无所知。它不关心订单,发货等。当产品需要补货以及产品数量是否足够时,逻辑就在实际产品内部。

public class Product() {
//Fields, getters, setters etc…

  1. public void reserve(int quantity){
  2. if(this.availableQuantity - this.reservedQuantity > quantity){
  3. this.reservedQuantity+=quantity;
  4. } else
  5. throw new ProductReservationException();
  6. }
  7. public releaseReserved(int requested){
  8. if(this.reservedQuantity>=requested){
  9. this.reservedQuantity-=requested;
  10. this.availableQuantity-=requested;
  11. } else
  12. throw new ProductReservationException();
  13. }
  14. public boolean needsRestock(){
  15. return this.availableQuantity<MINIMUM_STOCK_QUANTITY;
  16. }

}
装货服务:

public class ShipmentService {
public String requestDelivery(String reservationId, Address address){
ProductReservation reservation=productService.getProductForDelivery(reservationId);
Shipment shipment=new Shipment(reservation, address);
return shipmentRepository.save(shipment);
}
}
我并不是说这是最好的设计,但我认为它要干净得多。每个服务都照顾自己的领域,并且对其他服务了解得最少。实际的实体不仅是数据持有者,而且还携带与之相关的逻辑,因此服务不需要直接修改其内部状态。在我看来,最有价值的是代码真正代表了业务运作方式。,当我们开始编写软件时,我们总是希望有一个好的设计。我们阅读书籍,运用最佳实践,最后,我们常常一团糟。根据我在一家定制软件开发公司的经验,我每天必须处理此类代码,尤其是在某些旧系统上工作时。

造成这种情况的原因多种多样,我将尝试在一系列文章中以一些实际的方式来探讨其中的一些原因。在我的第一个示例中,我将说明为什么简单的软件会演变成一场噩梦,并建议进行一些改进。我将只专注于处理业务逻辑的服务层。

让我们从一个简单的存储应用程序开始。我们拥有带有服务,存储库的产品资源,并且我们可以执行我们认为需要的CRUD操作。我们的产品服务如下所示:

public class ProductService {
public String create(Product product) {
return productRepository.create(product);
}
public String update(Product product) {
return productRepository.update(product);
}
public Product get(String productId) {
return productRepository.get(productId);
}
public void delete(Product product) {
productRepository.delete(product);
}
}
还会有其他一些东西,例如DTO到实体的映射,控制器等。但是正如我所说的,我们将考虑将它们编写为简化起见。我们的产品实体是简单的Java Bean,我们的存储库保存在正确的数据库表中。然后,我们得到另一个要求,即我们还将创建一个在线商店,并且需要一种下订单的方法。因此,我们添加了快速订购服务来满足我们仍然很简单的要求:

public class OrderService {
public String saveOrder(Order order) {
return orderRepository.save(order);
}
}
它简单,易读且有效!然后,下订单时就需要更新库存中的产品的新要求。我们这样做:

public class OrderService {
public String saveOrder(Order order) {
Product product=productService.get(order.getProductId());
product.setAvailableQuantity(product.getAvailableQuantity()-order.getQuantity());
productService.update(product);

  1. return orderRepository.save(order);
  2. }

}
我们又碰到三个新需求:

我们需要致电运输服务将该产品运送到一个地址
如果没有足够的库存来履行订单,则抛出一个错误
如果产品的可用数量低于最低数量以进行重新库存。
结果如下:

public class OrderService {
public String saveOrder(Order order) {
Product product=productService.get(order.getProductId());

  1. //The order service works more like a product service in the following liness
  2. if(product.getAvailableQuantity()<order.getQuantity()){
  3. throw new ProductNotAvailableException();
  4. }
  5. product.setAvailableQuantity(product.getAvailableQuantity()-order.getQuantity());
  6. productService.update(product);
  7. if(product.getAvailableQuantity()<Product.MINIMUM_STOCK_QUANTITY){
  8. productService.restock(product);
  9. }
  10. //It also needs to know how shipments are created
  11. Shipment shipment=new Shipment(product, order.getQuantity(), order.getAddressTo());
  12. shipmentService.save(shipment);
  13. return orderRepository.save(order);
  14. }

}
我知道这可能是一个极端的例子,但是我确信我们在项目中已经看到了类似的代码。这样做有多个问题–责任z职责共担,与其他领域的逻辑和基础架构混淆在以前等等。如果这是一个真实的商店,那么接单的人就像总经理–照顾一切,从实际订购库存维护和交付。

更好的版本

让我们尝试以不同的方式处理相同的情况。我将从订购服务开始。为什么我们调用方法saveOrder?因为我们将其视为开发人员,而不是从业务角度来看。我们开发人员的想法通常是数据库驱动的(或REST驱动的),我们将我们的软件视为一系列CRUD操作。通常,当我们阅读有关领域驱动设计的书籍时,会提到通用语言-开发人员和用户之间的通用语言。如果我们尝试在我们的代码中为业务建模,那么为什么不使用正确的术语。我们可以将初始代码更改为:

public class OrderService {
public String placeOrder(Order order) {
return orderRepository.save(order);
}
}
使用placeOrder替代了原理的saveOrder方法名。

进行很小的更改,但即使那样也会使其更具可读性。这是业务层,而不是数据库层–我们去商店时下订单place order,但不保存订单save order。然后,当其他需求出现时,而不是开始使用带有CRUD操作的现有服务来编码它们,我们可以尝试重新创建业务模型。我们询问业务人员,他们告诉我们,下订单时,接单的人员会致电库存部门,询问他们产品是否可用,然后进行储备并致电带有预定编号和地址的交货人员,以便他们装运它。是什么阻止我们在代码中执行相同的操作?

public class OrderService {
public String placeOrder(Order order) {
String productReservationId=productService.requestProductReservation(order.getProductId, order.getQuantity());
String shippingId=shipmentService.requestDelivery(productReservationId, order.getAddressTo());
order.addShippingId(shippingId);
return orderRepository.save(order);
}
}
在我看来,它看起来更加干净,代表了实际商店中发生的事件的顺序。订单服务不需要知道产品如何工作或运输如何工作。它只是使用完成工作所需的方法。我们也需要修改其他服务:

public class ProductService {
//Method used in Orders Service
public String requestProductReservation(String productId, int quantity){
Product product=productRepository.get(productId);
product.reserve(quantity);
productRepository.update(product);
return createProductReservation(product, quantity);
}

  1. private String createProductReservation(Product product, int quantity){
  2. ProductReservation reservation=new ProductReservation(product,quantity);
  3. reservation.setStatus(ReservationStatus.CREATED);
  4. return reservationRepository.save(reservation);
  5. }
  6. //Method used in Shipment Service
  7. public ProductReservation getProductsForDelivery(String reservationId){
  8. ProductReservation reservation=reservationRepository.getProductReservation(reservationId);
  9. reservation.getProduct.releaseReserved(reservation.getQuantity());
  10. if(reservation.getProduct().needRestock()){
  11. this.restock(product);
  12. }
  13. reservation.setStatus(ReservationStatus.PROCESSED);
  14. reservationRepository.update(reservation);
  15. }

}
产品服务提供了其他服务要使用的两种方法,但对它们的结构一无所知。它不关心订单,发货等。当产品需要补货以及产品数量是否足够时,逻辑就在实际产品内部。

public class Product() {
//Fields, getters, setters etc…

  1. public void reserve(int quantity){
  2. if(this.availableQuantity - this.reservedQuantity > quantity){
  3. this.reservedQuantity+=quantity;
  4. } else
  5. throw new ProductReservationException();
  6. }
  7. public releaseReserved(int requested){
  8. if(this.reservedQuantity>=requested){
  9. this.reservedQuantity-=requested;
  10. this.availableQuantity-=requested;
  11. } else
  12. throw new ProductReservationException();
  13. }
  14. public boolean needsRestock(){
  15. return this.availableQuantity<MINIMUM_STOCK_QUANTITY;
  16. }

}
装货服务:

public class ShipmentService {
public String requestDelivery(String reservationId, Address address){
ProductReservation reservation=productService.getProductForDelivery(reservationId);
Shipment shipment=new Shipment(reservation, address);
return shipmentRepository.save(shipment);
}
}
我并不是说这是最好的设计,但我认为它要干净得多。每个服务都照顾自己的领域,并且对其他服务了解得最少。实际的实体不仅是数据持有者,而且还携带与之相关的逻辑,因此服务不需要直接修改其内部状态。在我看来,最有价值的是代码真正代表了业务运作方式。

0 条回复
  1. 动动手指,沙发就是你的了!
登录 后才能参与评论