写点什么

解密领域驱动设计(DDD):搭建强大、灵活的软件架构神器

作者:xfgg
  • 2023-05-19
    福建
  • 本文字数:6046 字

    阅读完需:约 20 分钟

解密领域驱动设计(DDD):搭建强大、灵活的软件架构神器

在软件开发领域,每天都会出现新的开发模式和架构。这些方法旨在改进我们的业务逻辑,提高代码质量和可维护性。今天,我们将探讨领域驱动设计 (Domain-Driven Design,简称 DDD) — 一种诞生于 2003 年的敏捷软件开发模式。对于我们常见的复杂系统,使用 DDD 构建的清晰 Java 架构可以为您带来便利。

这里将简单介绍 DDD 的概念,主要通过例子实际感受 DDD 架构


领域驱动设计的基本概念

领域驱动设计是一种针对复杂软件项目的方法论和设计模式,重点关注开发团队与领域专家之间的有效协作、领域模型的建立,以及面向真实业务场景的稳定、可扩展的代码结构。

DDD 的核心观念在于将现实世界的领域概念映射到软件设计中,从而使应用能更好地适应业务需求的变化。要实现这一目标,首先需要深入探讨业务的本质,充分理解相关的领域概念。


领域驱动设计(DDD)的四大模块

  1. 实体(Entity): 实体指具有唯一标识和持久化属性的业务概念。它负责封装与自身相关的状态和行为,如用户、订单等。需要注意的是,实体的标识和生命周期在整个系统中都具有一致性。

  2. 值对象(Value Object): 值对象没有自己的生命周期和唯一标识,而是通过属性值来区分。与实体相比,值对象更注重属性的完整性和一致性。一旦创建,值对象就不能被修改。例如:日期、金额等。

  3. 聚合(Aggregate): 聚合由一组实体和值对象组成,对外表现为一个整体。聚合为访问对象提供一个统一的入口点,确保应用与领域模型之间的交互过程是符合业务规则的。例如:订单与订单项关系就是一个聚合关系。

  4. 领域事件(Domain Event): 领域事件是一种表示业务领域能上发生的重要变化的消息。当某些操作改变了领域状态时,领域事件负责在各个组件之间传递信息。例如:用户注册成功、订单支付完成等。


实现 DDD 架构的关键原则

  1. 明确领域边界: 适当划分领域边界可以让团队更加关注核心业务逻辑。创立一个领域边界上下文(Bounded Context)可以隔离应用中的模型和概念,减少系统各部分之间的耦合。同时,领域边界也有助于划分子域,使得系统在功能上更容易扩展。

  2. 团队协作: 领域驱动设计强调开发团队与领域专家的紧密协作,以便更好地理解业务需求并将之转化为领域模型。团队成员应以领域专家的语言进行沟通,消除歧义,通过“通用语言”(Ubiquitous Language)提升协作效率。

  3. 设计富领域模型: 富领域模型(Rich Domain Model)是指具有强烈业务实体和行为的模型。在构建富领域模型时,要使业务状态和行为尽量封装在实体和值对象中。这样做有助于保持模型的简洁性,提高代码的可读性和可维护性。

  4. 模块化和松耦合: 模块化和松耦合的软件设计更易于扩展和管理。在实际开发中,我们应遵循单一职责原则(SRP)和依赖反转原则(DIP),通过接口及抽象类降低依赖性,提高组件之间的灵活性。


DDD 实践

图书管理系统

目录结构

  1. application: 该目录包含应用服务,应用服务充当应用程序的边界,用于协调领域层和基础设施层。应用服务处理与领域无关的逻辑和任务,例如:注册领域事件处理程序,启动和提交事务等。

  2. domain: 该目录包含 DDD 中所有核心部件,包含模型(实体,值对象,聚合根),领域服务,仓库接口,工厂和领域事件等。

    AggregateRoot: 包含聚合根类。聚合根是一个特殊的实体,充当聚合中实体和值对象之间的访问入口。在图书管理系统示例中,Book 类是一个聚合根。

    Entity: 包含实体类。实体具有唯一标识,并携带领域模型的状态和行为。在图书管理系统示例中,Member 类是一个实体。

    ValueObject: 包含值对象类。值对象表示某个具体概念,但不具有唯一标识。值对象通常是不可变的。在图书管理系统示例中,Author 和 ISBN 类均为值对象。

    DomainEvent: 领域事件表示在业务领域发生的重要变化。领域事件可以用于跟踪业务逻辑的变化,提高系统的扩展性和灵活性。在图书管理系统示例中,BookBorrowedEvent 和 BookReturnedEvent 类均为领域事件。

    DomainService: 领域服务处理多个领域对象之间的业务逻辑协作。领域服务负责处理这些对象之间的互动。在图书管理系统示例中,我们有一个 BookService 领域服务。

    Repository: 仓储接口负责对领域模型的持久化,将数据访问逻辑与领域模型分离,使领域模型专注于业务逻辑。在图书管理系统示例中,我们有 BookRepository 和 MemberRepository 仓储接口。

    Factory: 工厂负责创建领域对象实例。工厂方法确保对象的创建和初始化始终遵循特定的业务规则。在图书管理系统示例中,我们可以创建一个 BookFactory

  3. 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 的概念和原则有助于提高开发效率,并支持项目不断变化的需求。

发布于: 刚刚阅读数: 11
用户头像

xfgg

关注

还未添加个人签名 2022-11-03 加入

还未添加个人简介

评论

发布
暂无评论
解密领域驱动设计(DDD):搭建强大、灵活的软件架构神器_Java_xfgg_InfoQ写作社区