掘金 后端 ( ) • 2024-06-29 14:42

领域驱动设计(Domain-Driven Design,简称DDD)是一种软件设计方法,它强调以业务领域为中心进行软件开发,通过结合业务专家的知识和软件开发者的专业技术,创建出能够准确反映业务需求的软件系统。

业务范围描述:

DDD通常应用于复杂的业务领域,这些领域具有以下特点:

  • 复杂的业务逻辑:业务规则复杂,难以用简单的方法描述或实现。
  • 专业性强:业务领域需要专业知识和深入理解。
  • 持续变化:业务需求经常变化,需要软件系统能够灵活适应。
  • 高度的业务价值:软件系统对业务流程和决策具有重要影响。

DDD特性:

  1. 领域模型:创建一个丰富的领域模型来表达业务概念和业务逻辑。
  2. 统一语言(Ubiquitous Language) :开发团队和业务专家使用统一的语言来沟通,减少误解。
  3. 限界上下文(Bounded Context) :明确定义系统的边界,每个限界上下文内部有一致的模型和语言。
  4. 实体(Entity)和值对象(Value Object) :区分具有唯一标识的实体和描述性的值对象。
  5. 聚合(Aggregate) :一组相关对象的集合,它们一起作为数据修改的单元。
  6. 领域服务(Domain Service) :不属于任何实体或值对象的领域逻辑。
  7. 领域事件(Domain Event) :表示领域中发生的有意义的业务事件。
  8. 仓储(Repository) :领域模型和数据存储之间的抽象层。
  9. 应用层(Application Layer) :协调领域层对象以完成用户请求。
  10. 持续重构:随着对业务领域的深入理解,不断重构领域模型。

DDD优点:

  1. 提高业务理解:通过统一语言和领域模型,开发团队可以更深入地理解业务。
  2. 增强沟通效率:统一语言减少了开发团队和业务专家之间的沟通障碍。
  3. 更好的软件设计:领域模型帮助设计出更符合业务需求的软件系统。
  4. 提高系统的可维护性:清晰的模型和限界上下文使得系统更易于维护和扩展。
  5. 适应快速变化:DDD的设计原则使得系统能够快速适应业务需求的变化。
  6. 促进领域专家的参与:领域专家在设计过程中扮演重要角色,提高了设计的准确性。
  7. 提高代码质量:通过关注领域逻辑和业务规则,提高了代码的质量和可读性。
  8. 支持复杂业务场景:DDD提供了一套方法论来处理复杂的业务场景。

书店系统业务需求:

  • 用户可以浏览书籍。
  • 用户可以搜索书籍。
  • 用户可以购买书籍。
  • 书籍有库存限制。
  • 用户可以查看订单状态。
  • 订单可以有不同的支付状态。

书店领域模型设计范围:

  1. 实体(Entities)

    • Book:具有唯一ISBN,包含标题、作者、价格和类型等属性。
    • User:用户实体,包含用户信息和订单列表。
    • Order:订单实体,包含订单详情和支付状态。
  2. 值对象(Value Objects)

    • Address:地址信息,不可变。
    • PaymentDetails:支付详情,包括支付方式和支付信息。
  3. 聚合(Aggregates)

    • Book Aggregate:包含Book和相关的值对象。
    • Order Aggregate:包含Order和关联的值对象,如Address和PaymentDetails。
  4. 领域服务(Domain Services)

    • InventoryService:管理库存。
    • OrderService:处理订单逻辑,如创建订单、支付订单。
  5. 领域事件(Domain Events)

    • BookAddedEvent:新书添加到库存时触发。
    • OrderPlacedEvent:新订单创建时触发。
  6. 仓储(Repositories)

    • BookRepository:提供对书籍数据的访问。
    • OrderRepository:提供对订单数据的访问。
  7. 应用层(Application Layer)

    • 协调领域层对象以完成用户请求。
  8. 控制器(Controllers)

    • BookController:处理书籍浏览和搜索请求。
    • OrderController:处理订单创建和支付请求。

UML设计:

image.png

UML设计说明:

  • 这个UML类图展示了在线书店系统中的主要领域模型组件和它们之间的关系。
  • Book 类表示书籍实体,它与 BookRepository 通过聚合关系相连。
  • User 类表示用户实体,持有订单列表,并且可以下单。
  • Order 类表示订单实体,包含订单详情和支付状态,与 OrderRepository 相连。
  • Address 和 PaymentDetails 是值对象,用于描述订单的地址和支付信息。
  • InventoryService 和 OrderService 是领域服务,处理特定的业务逻辑。
  • 控制器(BookController 和 OrderController)没有在UML图中显示,但它们是应用层的一部分,负责处理用户的HTTP请求。

DDD案例代码

实体和值对象定义:

Book.java

public class Book {
    private String isbn;
    private String title;
    private String author;
    private BigDecimal price;
    private String genre;

    // Constructors, getters and setters
}

Address.java

public class Address {
    private final String street;
    private final String city;
    private final String zipCode;

    public Address(String street, String city, String zipCode) {
        this.street = street;
        this.city = city;
        this.zipCode = zipCode;
    }

    // Getters
}

PaymentDetails.java

public class PaymentDetails {
    private final String paymentMethod;
    private final String cardNumber;

    public PaymentDetails(String paymentMethod, String cardNumber) {
        this.paymentMethod = paymentMethod;
        this.cardNumber = cardNumber;
    }

    // Getters
}

聚合根和领域服务:

Order.java

public class Order {
    private String orderId;
    private User user;
    private List<Book> books = new ArrayList<>();
    private BigDecimal total;
    private String status;
    private Address address;
    private PaymentDetails paymentDetails;

    // Constructors, getters and setters

    public void addBook(Book book) {
        books.add(book);
        recalculateTotal();
    }

    private void recalculateTotal() {
        total = books.stream()
            .map(Book::getPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

InventoryService.java

@Service
public class InventoryService {
    public boolean checkAvailability(Book book, int quantity) {
        // Logic to check if the book is available in the required quantity
        return true; // Simplified for example
    }
}

OrderService.java

@Service
public class OrderService {
    private final InventoryService inventoryService;
    private final OrderRepository orderRepository;

    public OrderService(InventoryService inventoryService, OrderRepository orderRepository) {
        this.inventoryService = inventoryService;
        this.orderRepository = orderRepository;
    }

    public Order createOrder(User user, List<Book> books, Address address, PaymentDetails paymentDetails) {
        if (!inventoryService.checkAvailability(books)) {
            throw new InventoryException("Books not available");
        }
        Order order = new Order(/* parameters */);
        order.setBooks(books);
        order.setAddress(address);
        order.setPaymentDetails(paymentDetails);
        // Process payment and set order status
        return orderRepository.save(order);
    }
}

仓储接口:

BookRepository.java

public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findAll();
    Optional<Book> findById(String isbn);
}

OrderRepository.java

public interface OrderRepository extends JpaRepository<Order, String> {
    List<Order> findAll();
    void save(Order order);
}

控制器:

BookController.java

@RestController
@RequestMapping("/api/books")
public class BookController {
    private final BookRepository bookRepository;

    @Autowired
    public BookController(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @GetMapping
    public ResponseEntity<List<Book>> getAllBooks() {
        return ResponseEntity.ok(bookRepository.findAll());
    }
}

OrderController.java

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final OrderService orderService;

    @Autowired
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody OrderRequest orderRequest) {
        try {
            Order order = orderService.createOrder(
                orderRequest.getUser(),
                orderRequest.getBooks(),
                orderRequest.getAddress(),
                orderRequest.getPaymentDetails()
            );
            return ResponseEntity.ok(order);
        } catch (InventoryException e) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
    }
}