在软件开发领域,每天都会出现新的开发模式和架构。这些方法旨在改进我们的业务逻辑,提高代码质量和可维护性。今天,我们将探讨领域驱动设计 (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 的概念和原则有助于提高开发效率,并支持项目不断变化的需求。
评论