news 2026/4/29 22:08:25

从‘大泥球’到清晰边界:用DDD六边形架构改造你的在线图书馆管理系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从‘大泥球’到清晰边界:用DDD六边形架构改造你的在线图书馆管理系统

从‘大泥球’到清晰边界:用DDD六边形架构改造你的在线图书馆管理系统

当你面对一个已经运行多年的在线图书馆系统时,是否经常遇到这样的困境:每次新增一个功能都要修改十几处代码,业务逻辑散落在各个角落,测试覆盖率低得可怜,团队成员都不敢轻易改动那些"祖传代码"?这正是典型的"大泥球"架构症状。本文将带你用领域驱动设计(DDD)和六边形架构,像外科手术般精准解耦这个系统。

1. 诊断系统现状:识别架构痛点

在我们开始重构之前,先来看看这个典型的"大泥球"系统有哪些症状:

  • 业务逻辑四散:借阅规则可能出现在Controller、Service甚至DAO层
  • 高度耦合:修改用户模块可能意外破坏罚款计算功能
  • 测试困难:没有清晰的领域边界,单元测试需要启动整个Spring容器
  • 技术入侵业务:数据库表结构直接决定了领域模型设计

以一个常见的借阅场景为例,原始代码可能是这样的:

@RestController public class LibraryController { @Autowired private BookRepository bookRepo; @Autowired private UserRepository userRepo; @PostMapping("/borrow") public String borrowBook(@RequestParam Long bookId, @RequestParam Long userId) { Book book = bookRepo.findById(bookId).orElseThrow(); User user = userRepo.findById(userId).orElseThrow(); if(!book.isAvailable()) { return "Book not available"; } if(user.getBorrowedBooks().size() >= 5) { return "Exceed max borrowing limit"; } book.setAvailable(false); bookRepo.save(book); BorrowRecord record = new BorrowRecord(book, user, LocalDate.now()); borrowRecordRepo.save(record); return "Success"; } }

这段代码暴露了典型的问题:业务规则(最大借阅量)与持久化逻辑混杂在一起,没有清晰的领域边界。

2. 战略设计:划分限界上下文

DDD的战略设计帮助我们识别系统的核心子域,并划分清晰的限界上下文。对于图书馆系统,我们可以识别出以下几个核心上下文:

限界上下文核心职责关键领域概念
借阅管理处理图书借还业务借阅记录、借阅规则
馆藏管理管理图书元数据和库存图书、副本、分类
用户管理管理读者账户和权限读者、账户、权限组
罚款管理计算和处理逾期罚款罚款规则、支付记录

每个上下文都应该有明确的边界,并通过定义良好的接口进行通信。例如,借阅上下文需要知道图书是否可借,但不需要了解图书的分类细节。

3. 战术设计:构建纯净领域模型

在确定了限界上下文后,我们需要在每个上下文中应用DDD的战术模式。以借阅上下文为例:

实体BorrowRecord需要唯一标识来跟踪每笔借阅

public class BorrowRecord { private BorrowRecordId id; private BookId bookId; private UserId userId; private LocalDate borrowDate; private LocalDate dueDate; // 领域行为 public boolean isOverdue() { return LocalDate.now().isAfter(dueDate); } }

值对象BorrowPolicy封装借阅规则

public class BorrowPolicy { private final int maxBorrowDays; private final int maxBorrowCount; public boolean canBorrow(UserBorrowStatus status) { return status.currentBorrowCount() < maxBorrowCount; } public LocalDate calculateDueDate(LocalDate from) { return from.plusDays(maxBorrowDays); } }

聚合根Borrowing作为入口点保护不变条件

public class Borrowing { private List<BorrowRecord> activeRecords; private BorrowPolicy policy; public BorrowResult borrowBook(BookId bookId, UserId userId) { if(!policy.canBorrow(getUserStatus(userId))) { return BorrowResult.rejected("Exceed max borrow limit"); } BorrowRecord record = new BorrowRecord( BorrowRecordId.generate(), bookId, userId, LocalDate.now(), policy.calculateDueDate(LocalDate.now()) ); activeRecords.add(record); return BorrowResult.success(record); } }

4. 六边形架构实现

六边形架构(端口与适配器)帮助我们隔离领域核心与外部依赖。以下是关键实现步骤:

4.1 定义领域端口

首先定义领域需要与外界交互的端口(接口):

// 左侧端口:领域服务需要的外部功能 public interface BookCatalog { BookDetail getDetail(BookId id); boolean isAvailable(BookId id); } public interface UserRepository { UserInfo getUserInfo(UserId id); } // 右侧端口:持久化接口 public interface BorrowRecordRepository { void save(BorrowRecord record); List<BorrowRecord> findActiveByUser(UserId userId); }

4.2 实现适配器

然后为这些端口提供具体实现适配器:

// 适配外部图书查询服务 @Adapter public class RestBookCatalog implements BookCatalog { private final RestTemplate restTemplate; @Override public BookDetail getDetail(BookId id) { return restTemplate.getForObject( "/books/{id}", BookDetail.class, id.getValue() ); } } // 数据库持久化适配器 @Repository public class JpaBorrowRecordRepository implements BorrowRecordRepository { private final BorrowRecordJpaRepository jpaRepo; @Override public void save(BorrowRecord record) { BorrowRecordEntity entity = toEntity(record); jpaRepo.save(entity); } }

4.3 组装应用

使用依赖注入将适配器注入领域核心:

@Configuration public class LibraryConfig { @Bean public BorrowingService borrowingService( BookCatalog bookCatalog, UserRepository userRepository, BorrowRecordRepository recordRepo ) { return new BorrowingService( new Borrowing(new BorrowPolicy(30, 5)), bookCatalog, userRepository, recordRepo ); } }

5. 测试策略

六边形架构的一个巨大优势是便于测试。我们可以轻松地为领域核心编写单元测试:

class BorrowingTest { private Borrowing borrowing; private BorrowPolicy policy; @BeforeEach void setUp() { policy = new BorrowPolicy(30, 5); borrowing = new Borrowing(policy); } @Test void should_reject_when_exceed_max_limit() { // 准备测试替身 UserRepository userRepo = mock(UserRepository.class); when(userRepo.getUserInfo(any())) .thenReturn(new UserInfo(5)); // 已借5本 BorrowResult result = borrowing.borrowBook( new BookId("1"), new UserId("1"), userRepo ); assertTrue(result.isRejected()); } }

对于适配器,我们可以编写集成测试:

@SpringBootTest class JpaBorrowRecordRepositoryTest { @Autowired private BorrowRecordRepository repository; @Test void should_save_and_retrieve_record() { BorrowRecord record = new BorrowRecord(...); repository.save(record); List<BorrowRecord> found = repository .findActiveByUser(record.getUserId()); assertFalse(found.isEmpty()); } }

6. 演进式重构策略

对于已有系统,我们推荐采用渐进式重构:

  1. 识别热点:从变更最频繁的模块开始
  2. 提取子域:将相关功能提取到独立模块
  3. 定义防腐层:在新旧系统间建立转换层
  4. 逐步替换:功能点逐个迁移到新架构
  5. 最终切换:当新架构覆盖所有场景后移除旧代码

例如,可以先从借阅功能开始重构:

原始结构: library/ ├── controller/ ├── service/ ├── repository/ └── model/ 重构后结构: library/ ├── borrowing/ │ ├── domain/ │ ├── application/ │ └── infrastructure/ ├── legacy/ └── shared/

7. 领域事件解耦

使用领域事件进一步解耦系统组件。当借阅发生时,发布领域事件:

public class Borrowing { private final List<DomainEvent> events = new ArrayList<>(); public BorrowResult borrowBook(...) { // ...借阅逻辑 events.add(new BookBorrowed( record.getId(), record.getBookId(), record.getUserId(), record.getDueDate() )); return result; } public List<DomainEvent> getEvents() { return Collections.unmodifiableList(events); } }

其他上下文可以订阅这些事件:

// 罚款上下文处理逾期事件 @Service @RequiredArgsConstructor public class FineEventHandler { private final FineCalculator calculator; @EventListener public void handle(BookBorrowed event) { // 设置定时器在到期日检查 scheduleDueDateCheck(event.dueDate(), event.recordId()); } }

这种设计使得系统各部分的耦合降到最低,每个上下文只需要关心自己感兴趣的事件。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/29 22:06:46

18 年 GitHub 忠实用户因频繁故障,携 Ghostty 项目“出走”另寻平台

18 年 GitHub 老用户携 Ghostty 项目“出走”Mitchell Hashimoto 日前宣布 Ghostty 项目将正式离开 GitHub&#xff0c;这位 GitHub 第 1299 号用户、18 年的忠实用户与该平台的关系迎来重大转折。Hashimoto 于 2008 年 2 月注册成为 GitHub 早期用户&#xff0c;过去 18 年间几…

作者头像 李华
网站建设 2026/4/29 22:05:39

解锁喜马拉雅音频宝藏:跨平台下载器的完全探索指南

解锁喜马拉雅音频宝藏&#xff1a;跨平台下载器的完全探索指南 【免费下载链接】xmly-downloader-qt5 喜马拉雅FM专辑下载器. 支持VIP与付费专辑. 使用GoQt5编写(Not Qt Binding). 项目地址: https://gitcode.com/gh_mirrors/xm/xmly-downloader-qt5 还在为喜马拉雅音频…

作者头像 李华