在软件开发领域,每天都会出现新的开发模式和架构。这些方法旨在改进我们的业务逻辑,提高代码质量和可维护性。今天,我们将探讨领域驱动设计 (Domain-Driven Design,简称 DDD) — 一种诞生于 2003 年的敏捷软件开发模式。对于我们常见的复杂系统,使用 DDD 构建的清晰 Java 架构可以为您带来便利。
这里将简单介绍 DDD 的概念,主要通过例子实际感受 DDD 架构
领域驱动设计的基本概念
领域驱动设计是一种针对复杂软件项目的方法论和设计模式,重点关注开发团队与领域专家之间的有效协作、领域模型的建立,以及面向真实业务场景的稳定、可扩展的代码结构。
DDD 的核心观念在于将现实世界的领域概念映射到软件设计中,从而使应用能更好地适应业务需求的变化。要实现这一目标,首先需要深入探讨业务的本质,充分理解相关的领域概念。
领域驱动设计(DDD)的四大模块
- 实体(Entity): 实体指具有唯一标识和持久化属性的业务概念。它负责封装与自身相关的状态和行为,如用户、订单等。需要注意的是,实体的标识和生命周期在整个系统中都具有一致性。 
- 值对象(Value Object): 值对象没有自己的生命周期和唯一标识,而是通过属性值来区分。与实体相比,值对象更注重属性的完整性和一致性。一旦创建,值对象就不能被修改。例如:日期、金额等。 
- 聚合(Aggregate): 聚合由一组实体和值对象组成,对外表现为一个整体。聚合为访问对象提供一个统一的入口点,确保应用与领域模型之间的交互过程是符合业务规则的。例如:订单与订单项关系就是一个聚合关系。 
- 领域事件(Domain Event): 领域事件是一种表示业务领域能上发生的重要变化的消息。当某些操作改变了领域状态时,领域事件负责在各个组件之间传递信息。例如:用户注册成功、订单支付完成等。 
实现 DDD 架构的关键原则
- 明确领域边界: 适当划分领域边界可以让团队更加关注核心业务逻辑。创立一个领域边界上下文(Bounded Context)可以隔离应用中的模型和概念,减少系统各部分之间的耦合。同时,领域边界也有助于划分子域,使得系统在功能上更容易扩展。 
- 团队协作: 领域驱动设计强调开发团队与领域专家的紧密协作,以便更好地理解业务需求并将之转化为领域模型。团队成员应以领域专家的语言进行沟通,消除歧义,通过“通用语言”(Ubiquitous Language)提升协作效率。 
- 设计富领域模型: 富领域模型(Rich Domain Model)是指具有强烈业务实体和行为的模型。在构建富领域模型时,要使业务状态和行为尽量封装在实体和值对象中。这样做有助于保持模型的简洁性,提高代码的可读性和可维护性。 
- 模块化和松耦合: 模块化和松耦合的软件设计更易于扩展和管理。在实际开发中,我们应遵循单一职责原则(SRP)和依赖反转原则(DIP),通过接口及抽象类降低依赖性,提高组件之间的灵活性。 
DDD 实践
图书管理系统
目录结构
- application: 该目录包含应用服务,应用服务充当应用程序的边界,用于协调领域层和基础设施层。应用服务处理与领域无关的逻辑和任务,例如:注册领域事件处理程序,启动和提交事务等。
 
- domain: 该目录包含 DDD 中所有核心部件,包含模型(实体,值对象,聚合根),领域服务,仓库接口,工厂和领域事件等。
 - AggregateRoot: 包含聚合根类。聚合根是一个特殊的实体,充当聚合中实体和值对象之间的访问入口。在图书管理系统示例中,- Book类是一个聚合根。
 - Entity: 包含实体类。实体具有唯一标识,并携带领域模型的状态和行为。在图书管理系统示例中,- Member类是一个实体。
 - ValueObject: 包含值对象类。值对象表示某个具体概念,但不具有唯一标识。值对象通常是不可变的。在图书管理系统示例中,- Author和- ISBN类均为值对象。
 - DomainEvent: 领域事件表示在业务领域发生的重要变化。领域事件可以用于跟踪业务逻辑的变化,提高系统的扩展性和灵活性。在图书管理系统示例中,- BookBorrowedEvent和- BookReturnedEvent类均为领域事件。
 - DomainService: 领域服务处理多个领域对象之间的业务逻辑协作。领域服务负责处理这些对象之间的互动。在图书管理系统示例中,我们有一个- BookService领域服务。
 - Repository: 仓储接口负责对领域模型的持久化,将数据访问逻辑与领域模型分离,使领域模型专注于业务逻辑。在图书管理系统示例中,我们有- BookRepository和- MemberRepository仓储接口。
 - Factory: 工厂负责创建领域对象实例。工厂方法确保对象的创建和初始化始终遵循特定的业务规则。在图书管理系统示例中,我们可以创建一个- BookFactory。
 
- infrastructure: 包含实现领域层接口的基础设施代码,如:数据访问层实现、事件发布等。
 - dataSource: 该目录包含数据访问层实现类。实现领域层定义的仓库接口,用于持久化聚合根实例。在图书管理系统示例中,我们有- BookRepositoryImpl和- MemberRepositoryImpl实现类
 
 - src  |- main    |- java      |- com        |- example          |- bookstore            |- application              |- BookApplicationService.java            |- domain              |- AggregateRoot                |- Book.java              |- Entity                |- Member.java              |- ValueObject                |- Author.java                |- ISBN.java              |- DomainEvent                |- BookBorrowedEvent.java                |- BookReturnedEvent.java              |- DomainService                |- BookService.java              |- Repository                |- BookRepository.java                |- MemberRepository.java              |- Factory                |- BookFactory.java        |- infrastructure          |- dataSource            |- BookRepositoryImpl.java            |- MemberRepositoryImpl.java
   复制代码
 聚合根
聚合根(Aggregate Root):图书(Book)
 package com.example.bookstore.domain.AggregateRoot;
public class Book {    private Long id;    private String title;    private Author author;    private ISBN isbn;    private boolean isBorrowed;    private Member currentBorrower;
    // Getters, setters and helper methods are omitted for brevity}
   复制代码
 实体
1、实体:会员(Member)
 package com.example.bookstore.domain.Entity;
public class Member {    private Long id;    private String name;
    // Getters, setters and helper methods are omitted for brevity}
   复制代码
 值对象
1、值对象:作者(Author)
 package com.example.bookstore.domain.ValueObject;
public class Author {    private String name;
    // Getters, setters and helper methods are omitted for brevity}
   复制代码
 2、值对象:国际标准书号(ISBN)
 
package com.example.bookstore.domain.ValueObject;
public class ISBN {    private String number;
    // Getters, setters and helper methods are omitted for brevity}
   复制代码
 领域服务
负责执行借阅和归还书籍的操作
 package com.example.bookstore.domain.DomainService;
import com.example.bookstore.domain.Entity.Member;import com.example.bookstore.domain.AggregateRoot.Book;
public interface BookService {    void borrowBook(Member member, Book book);    void returnBook(Member member, Book book);}
   复制代码
 仓库接口
负责持久化聚合根实例
 package com.example.bookstore.domain.Repository;
import com.example.bookstore.domain.AggregateRoot.Book;import com.example.bookstore.domain.Entity.Member;
import java.util.Optional;
public interface BookRepository {    void save(Book book);    Optional<Book> findById(Long id);    Optional<Book> findByISBN(String isbn);    List<Book> findAll();}
public interface MemberRepository {    void save(Member member);    Optional<Member> findById(Long id);    List<Member> findAll();}
   复制代码
 应用服务
协调领域层和基础设施层
 package com.example.bookstore.application;
import com.example.bookstore.domain.AggregateRoot.Book;import com.example.bookstore.domain.Entity.Member;import com.example.bookstore.domain.DomainService.BookService;import com.example.bookstore.domain.Repository.BookRepository;import com.example.bookstore.domain.Repository.MemberRepository;
public class BookApplicationService {    private BookRepository bookRepository;    private MemberRepository memberRepository;    private BookService bookService;
    public BookApplicationService(BookRepository bookRepository,                                  MemberRepository memberRepository,                                  BookService bookService) {        this.bookRepository = bookRepository;        this.memberRepository = memberRepository;        this.bookService = bookService;    }
    // Implement methods to orchestrate domain services and repositories}
   复制代码
 领域事件
1、创建一个领域事件接口
 package com.example.bookstore.domain.DomainEvent;
public interface DomainEvent {    Long getEntityId();}
   复制代码
 2、创建 BookBorrowedEvent 和 BookReturnedEvent 事件类:
 package com.example.bookstore.domain.DomainEvent;
public class BookBorrowedEvent implements DomainEvent {    private final Long bookId;    private final Long memberId;
    public BookBorrowedEvent(Long bookId, Long memberId) {        this.bookId = bookId;        this.memberId = memberId;    }
    @Override    public Long getEntityId() {        return bookId;    }
    public Long getMemberId() {        return memberId;    }}
public class BookReturnedEvent implements DomainEvent {    private final Long bookId;    private final Long memberId;
    public BookReturnedEvent(Long bookId, Long memberId) {        this.bookId = bookId;        this.memberId = memberId;    }
    @Override    public Long getEntityId() {        return bookId;    }
    public Long getMemberId() {        return memberId;    }}
   复制代码
 3、修改领域服务(BookService)触发事件
 package com.example.bookstore.domain.DomainService;
import com.example.bookstore.domain.Entity.Member;import com.example.bookstore.domain.AggregateRoot.Book;import com.example.bookstore.domain.DomainEvent.BookBorrowedEvent;import com.example.bookstore.domain.DomainEvent.BookReturnedEvent;
import java.util.function.Consumer;
public class BookServiceImpl implements BookService {    private final Consumer<DomainEvent> eventPublisher;
    public BookServiceImpl(Consumer<DomainEvent> eventPublisher) {        this.eventPublisher = eventPublisher;    }
    @Override    public void borrowBook(Member member, Book book) {        // Implement borrowing logic        book.setBorrowedBy(member);        book.setBorrowed(true);
        // Publish event        eventPublisher.accept(new BookBorrowedEvent(book.getId(), member.getId()));    }
    @Override    public void returnBook(Member member, Book book) {        // Implement returning logic        book.setBorrowedBy(null);        book.setBorrowed(false);
        // Publish event        eventPublisher.accept(new BookReturnedEvent(book.getId(), member.getId()));    }}
   复制代码
 请注意,我们在 BookService 的构造函数中注入了一个事件发布器(eventPublisher),这是一个函数式接口。它允许我们将事件发布的具体实现延迟到基础设施层。
4、应用服务(BookApplicationService)处理事件
 package com.example.bookstore.application;
import com.example.bookstore.domain.AggregateRoot.Book;import com.example.bookstore.domain.Entity.Member;import com.example.bookstore.domain.DomainService.BookService;import com.example.bookstore.domain.Repository.BookRepository;import com.example.bookstore.domain.Repository.MemberRepository;import com.example.bookstore.domain.DomainEvent.DomainEvent;
// Add necessary imports
public class BookApplicationService {    private BookRepository bookRepository;    private MemberRepository memberRepository;    private BookService bookService;
    public BookApplicationService(BookRepository bookRepository,                                  MemberRepository memberRepository,                                  BookService bookService) {        this.bookRepository = bookRepository;        this.memberRepository = memberRepository;        this.bookService = bookService;    }
    // Implement methods to orchestrate domain services and repositories
    private void handleDomainEvent(DomainEvent event) {        // Consume the event and perform necessary actions        // e.g., logging, notifying other services, etc.
        if (event instanceof BookBorrowedEvent) {            // Handle BookBorrowedEvent        } else if (event instanceof BookReturnedEvent) {            // Handle BookReturnedEvent        }    }}
   复制代码
 总结
通过上面的图书管理系统示例,我们展示了如何在 Java 项目中应用领域驱动设计(DDD)架构。首先,我们设计了一个合理的目录结构以帮助组织代码。然后,我们根据 DDD 的核心概念创建了领域模型(实体、值对象、聚合根),领域服务,仓库接口,以及引入了领域事件和应用服务。
在这个示例中,我们的聚合根是 Book 类,它负责处理与图书相关的业务逻辑,确保整个聚合的状态和行为的一致性。我们创建了领域服务(BookService)来处理跨多个领域对象(如 Book 和 Member)的业务逻辑,并通过应用服务(BookApplicationService)来协调领域层和基础设施层。我们还引入了领域事件(BookBorrowedEvent 和 BookReturnedEvent)来提高系统的灵活性和可扩展性。
领域驱动设计(DDD)架构鼓励我们深入业务领域,关注领域模型和业务逻辑,而不仅仅是技术实现。通过此种方法,我们可以创建出具有良好代码组织,易于理解和维护的系统。此外,DDD 的概念和原则有助于提高开发效率,并支持项目不断变化的需求。
评论