基于SpringAI的在线考试系统-DDD(领域驱动设计)核心概念及落地架构全总结

DDD(领域驱动设计)核心概念及落地架构全总结

本文基于订单管理场景,结合对话中的实操理解,全面梳理DDD(领域驱动设计)的核心概念、层级关系及落地架构方案,涵盖业务抽象、实现组件、架构落地等全维度细节,确保所有讨论内容无遗漏。

一、DDD核心业务概念(纯业务层面,与技术无关)

DDD的核心是围绕业务逻辑构建领域模型,所有概念均先聚焦业务本身,不涉及任何技术实现,具体可按范围从大到小梳理:

1. 领域与子域

领域是对某一具有商业价值的业务范围的抽象,是承载业务逻辑和商业规则的核心范畴。以电商场景为例,订单管理就是一个典型领域,它具备明确的商业价值,涵盖订单从创建到完成的全流程事务性业务,属于领域价值模型。

当系统规模庞大时,领域可进一步细分子域。子域本质是小范围的领域,拥有独立的业务逻辑和边界,专注解决特定业务问题,同时与其他子域协作构成完整领域。例如订单管理领域可拆分出订单创建子域、库存联动子域等。

2. 界定上下文

界定上下文是领域/子域划分的核心依据,它明确了每个子域的职责边界、业务语义及协作规则,相当于给不同业务模块划定"势力范围"。通过界定上下文,可避免业务概念混淆,确保团队对业务边界形成统一认知,同时规范子域间的交互方式。

3. 领域服务

领域服务是业务流程的协调者,专注处理不属于单个实体的跨实体、跨聚合业务逻辑。它不承载业务状态,仅按业务规则编排多个实体、值对象或其他领域服务,完成复杂业务操作。与聚合根聚焦内部管理不同,领域服务站在更高层面,协调多个聚合或实体实现大范围业务流程。例如订单创建后,协调订单实体、库存实体完成库存更新的流程,就由领域服务负责。

4. 聚合与聚合根

聚合是一组相互关联的业务对象(实体、值对象)的集合,作为一个整体处理业务逻辑,确保数据一致性和业务规则的完整性。一个领域下可包含多个聚合,每个聚合围绕一个核心对象展开。

聚合根是聚合的核心控制点,本质上是一个特殊的实体------它首先具备实体的所有特性(有唯一标识、生命周期),同时是聚合内的最高权限对象,管理聚合内其他实体和值对象的操作,对外提供统一接口,屏蔽聚合内部细节。以订单管理为例,订单本身就是聚合根,围绕它的订单商品、收货信息等实体/值对象共同构成订单聚合,订单聚合根协调内部所有对象的状态变化,保障订单业务规则的实现。

5. 实体与值对象

实体是业务载体,拥有唯一标识(如订单号)和完整生命周期(如订单从创建、支付、发货到完成的全流程),其属性会随业务状态变化而改变,且每一次状态变化都对应业务逻辑的流转。实体是聚合根的基础,聚合根本质就是核心实体。

值对象是对实体属性的描述,仅关注自身的值,无独立生命周期,也无唯一标识。它依附于实体存在,用于刻画实体在不同状态下的具体属性特征。例如订单实体的"支付金额""收货地址"就是值对象,它们仅描述订单的属性,不单独存在,当订单状态变化(如修改收货地址)时,本质是值对象的更新。

二、DDD技术实现组件(支撑业务落地,属技术层面)

上述核心概念均为纯业务抽象,需通过技术组件落地实现,这些技术实现统一归属于基础设施层,不侵入领域层的业务逻辑。

1. 仓储(Repository)

仓储是基础设施层的核心技术组件,负责实体数据的持久化存储与读取,是领域层与数据存储层的桥梁。它将领域层的实体数据转化为可存储格式(如数据库记录),同时屏蔽具体的存储技术细节(如MySQL、Redis),让领域层专注于业务逻辑。例如订单实体的创建、更新、查询,均通过仓储组件实现数据落地。

2. 其他基础设施组件

除仓储外,基础设施层还包含其他支撑业务实现的技术组件,如第三方接口调用(支付接口、物流接口)、数据库连接池、缓存服务、消息队列等。所有与技术实现相关的操作,均封装在基础设施层,确保领域层的纯粹性。

三、DDD落地架构方案(将业务概念转化为代码结构)

DDD的核心概念需通过分层架构落地,不同架构适用于不同项目场景,核心原则是职责分离、业务与技术解耦。

1. 经典四层架构(应用最广泛,易理解、好落地)

四层架构按职责自上而下/自内而外划分,各层边界清晰,是DDD落地的经典方案,其核心目录结构(代码层级)如下:

  • 领域层(domain):核心业务层,封装所有纯业务概念和规则。包含实体(entity)、值对象(value object)、聚合根(aggregate root)、领域服务(domain service)、仓储接口(repository interface,仅定义接口,不涉及实现)等。

  • 应用层(application):业务流程协调层,不包含核心业务规则,仅接收用户请求,协调领域层组件完成业务操作,对外提供统一的业务接口。包含应用服务(application service)、业务流程编排逻辑等。

  • 基础设施层(infrastructure):技术支撑层,实现领域层所需的技术能力。包含仓储实现(repository impl)、第三方接口适配、数据库连接、缓存、消息队列等技术组件。

  • 表现层(presentation):用户交互层,负责接收用户输入、展示业务结果。包含接口控制器(如API接口)、页面视图、请求参数校验等。

实操说明:四层架构可根据项目复杂度灵活调整,简单项目可合并部分层级(如表现层与应用层简化整合),核心是保留领域层与基础设施层的分离。

2. 六边形架构(端口与适配器架构)

六边形架构更强调"业务与外部依赖解耦",核心思想是领域模型处于中心,外部依赖通过适配器接入,其经典目录结构如下:

  • 领域层(domain):核心不变,包含实体、值对象、聚合根、领域服务等纯业务逻辑。

  • 端口(ports):定义领域层与外部交互的接口,分为输入端口(如业务服务接口,供外部调用领域逻辑)和输出端口(如仓储接口、第三方服务接口,供领域层调用外部能力)。

  • 适配器(adapters):实现端口定义的接口,适配外部依赖。包含输入适配器(如API控制器、消息消费者,将外部请求转化为领域层可处理的格式)和输出适配器(如仓储实现、第三方接口适配器,将领域层请求转化为外部依赖可处理的格式)。

优势:系统扩展性极强,更换外部依赖(如将MySQL改为PostgreSQL,更换支付厂商)时,仅需修改对应适配器,不影响核心领域逻辑。

3. 整洁架构(依赖倒置架构)

整洁架构以"依赖倒置原则"为核心,所有依赖均指向核心业务,外层框架依赖内层业务,而非反之,其经典目录结构如下:

  • 核心层(core):最内层,包含领域模型(实体、值对象、聚合根)、领域服务、业务规则等,完全独立于任何外部框架。

  • 用例层(use cases):封装应用场景的业务流程,协调核心层组件完成具体业务用例(如"创建订单"用例、"取消订单"用例),依赖核心层。

  • 接口适配层(interface adapters):将核心层、用例层的接口适配为外部框架可调用的格式,包含控制器、仓储适配器、第三方服务适配器等,依赖用例层和核心层。

  • 框架与驱动层(frameworks and drivers):最外层,包含具体的框架实现(如Spring、MyBatis)、数据库、第三方服务等,依赖接口适配层。

优势:核心业务逻辑完全不受外部框架影响,可灵活替换外层框架,适合大型复杂系统长期迭代。

四、核心总结(概念与架构的关联逻辑)

DDD的核心逻辑是"先抽象业务,再落地技术":领域、子域、界定上下文定义业务边界;聚合、聚合根、实体、值对象构建业务模型;领域服务编排业务流程;仓储及基础设施层提供技术支撑;四层架构、六边形架构、整洁架构则是将这些概念转化为可落地代码结构的不同方案。

实操中,架构无需严格拘泥于标准目录,可根据项目规模、团队技术栈灵活调整,核心原则是保持"业务与技术分离""领域层纯粹性",确保系统可维护性、可扩展性与业务一致性。

案例分享

考试系统DDD架构设计与落地实现指南

基于您提供的DDD理论和考试系统表结构,我将设计一个完整的DDD落地架构方案,包含领域建模、限界上下文划分、代码结构和技术实现。

一、考试系统DDD领域建模

1. 限界上下文识别

扩展子域(增值功能)
通用子域(基础能力)
支撑子域(支持核心)
核心子域(核心价值)
考试上下文
阅卷上下文
题库上下文
试卷上下文
用户上下文
基础数据上下文
学情分析上下文
错题管理上下文

二、各限界上下文详细设计

上下文1:考试上下文(Exam Context)

核心业务:考试全生命周期管理
包含
1 * Exam
-id: ExamId
-title: String
-status: ExamStatus
-time: ExamTime
-config: ExamConfig
-paperId: PaperId
-studentIds: List<StudentId>
+publish()
+start()
+end()
+addStudent(studentId)
ExamRecord
-id: ExamRecordId
-examId: ExamId
-studentId: StudentId
-status: ExamRecordStatus
-answers: List<Answer>
+start()
+submit()
+autoSave()
<<enumeration>>
ExamStatus
DRAFT
PUBLISHED
IN_PROGRESS
ENDED
ExamTime
-startTime: LocalDateTime
-endTime: LocalDateTime
-duration: Duration
+isInProgress()
+isExpired()

领域事件

  • ExamPublishedEvent - 考试发布事件
  • ExamStartedEvent - 考试开始事件
  • ExamSubmittedEvent - 考试提交事件

上下文2:阅卷上下文(Marking Context)

核心业务:评卷任务分配与质量控制
分配
质量控制
1 1 * 1 MarkingTask
-id: MarkingTaskId
-examId: ExamId
-status: MarkingTaskStatus
-type: MarkingType
+assign()
+start()
+complete()
MarkingAssignment
-id: MarkingAssignmentId
-taskId: MarkingTaskId
-teacherId: TeacherId
-status: MarkingStatus
+score()
+reject()
QualityControl
-id: QualityControlId
-score1: Score
-score2: Score
-difference: Decimal
+needArbitration()
+arbitrate()

上下文3:题库上下文(QuestionBank Context)

核心业务:知识点与试题管理
关联
1 * Question
-id: QuestionId
-stem: String
-type: QuestionType
-difficulty: Difficulty
-knowledgePoints: List<KnowledgePoint>
+validate()
+calculateScore()
KnowledgePoint
-id: KnowledgePointId
-name: String
-parentId: KnowledgePointId
-path: String
<<enumeration>>
QuestionType
SINGLE_CHOICE
MULTI_CHOICE
JUDGMENT
FILL_BLANK
SHORT_ANSWER

三、DDD四层架构落地实现

项目目录结构

复制代码
src/
├── main/java/com/exam/
│   ├── shared/                          # 共享内核
│   │   ├── kernel/
│   │   │   ├── Identifier.java         # ID基类
│   │   │   ├── ValueObject.java        # 值对象基类
│   │   │   └── DomainException.java    # 领域异常
│   │   └── constants/                  # 常量
│   │
│   ├── exam/                           # 考试上下文
│   │   ├── domain/                     # 领域层
│   │   │   ├── model/                  # 领域模型
│   │   │   │   ├── aggregate/
│   │   │   │   │   ├── exam/          # 考试聚合
│   │   │   │   │   │   ├── Exam.java
│   │   │   │   │   │   ├── ExamTime.java
│   │   │   │   │   │   ├── ExamConfig.java
│   │   │   │   │   │   └── vo/
│   │   │   │   │   │       ├── ExamId.java
│   │   │   │   │   │       └── ExamStatus.java
│   │   │   │   │   └── examrecord/    # 考试记录聚合
│   │   │   │   │       ├── ExamRecord.java
│   │   │   │   │       ├── Answer.java
│   │   │   │   │       └── vo/
│   │   │   │   │           └── ExamRecordId.java
│   │   │   │   │
│   │   │   │   └── valueobject/        # 独立值对象
│   │   │   │       ├── Score.java
│   │   │   │       └── Duration.java
│   │   │   │
│   │   │   ├── service/                # 领域服务
│   │   │   │   ├── ExamService.java
│   │   │   │   └── ExamRecordService.java
│   │   │   │
│   │   │   ├── event/                  # 领域事件
│   │   │   │   ├── ExamPublishedEvent.java
│   │   │   │   └── ExamSubmittedEvent.java
│   │   │   │
│   │   │   └── repository/             # 仓储接口
│   │   │       ├── ExamRepository.java
│   │   │       └── ExamRecordRepository.java
│   │   │
│   │   ├── application/                # 应用层
│   │   │   ├── service/                # 应用服务
│   │   │   │   ├── ExamApplicationService.java
│   │   │   │   └── command/
│   │   │   │       ├── CreateExamCommand.java
│   │   │   │       ├── PublishExamCommand.java
│   │   │   │       └── SubmitExamCommand.java
│   │   │   │
│   │   │   ├── dto/                    # 应用层DTO
│   │   │   │   ├── ExamDTO.java
│   │   │   │   └── ExamRecordDTO.java
│   │   │   │
│   │   │   └── eventhandler/           # 应用层事件处理器
│   │   │       └── ExamEventHandler.java
│   │   │
│   │   ├── interfaces/                 # 接口层
│   │   │   ├── web/                    # Web接口
│   │   │   │   ├── ExamController.java
│   │   │   │   ├── request/
│   │   │   │   │   ├── CreateExamRequest.java
│   │   │   │   │   └── SubmitExamRequest.java
│   │   │   │   └── response/
│   │   │   │       ├── ExamResponse.java
│   │   │   │       └── ExamDetailResponse.java
│   │   │   │
│   │   │   └── rpc/                    # RPC接口
│   │   │       └── ExamRpcService.java
│   │   │
│   │   └── infrastructure/             # 基础设施层
│   │       ├── persistence/            # 持久化实现
│   │       │   ├── mapper/             # MyBatis Mapper
│   │       │   │   ├── ExamMapper.java
│   │       │   │   └── ExamRecordMapper.java
│   │       │   │
│   │       │   ├── converter/          # 转换器
│   │       │   │   ├── ExamConverter.java
│   │       │   │   └── ExamRecordConverter.java
│   │       │   │
│   │       │   └── repository/         # 仓储实现
│   │       │       ├── ExamRepositoryImpl.java
│   │       │       └── ExamRecordRepositoryImpl.java
│   │       │
│   │       ├── client/                 # 外部服务客户端
│   │       │   ├── PaperClient.java
│   │       │   └── UserClient.java
│   │       │
│   │       ├── message/                # 消息队列
│   │       │   └── ExamMessageProducer.java
│   │       │
│   │       └── cache/                  # 缓存
│   │           └── ExamCacheService.java
│   │
│   ├── marking/                       # 阅卷上下文(类似结构)
│   ├── questionbank/                  # 题库上下文
│   └── user/                          # 用户上下文
│
└── test/                             # 测试
    ├── exam/
    │   ├── domain/
    │   ├── application/
    │   └── infrastructure/
    └── integration/

四、核心代码实现示例

1. 值对象实现

java 复制代码
// 考试ID值对象
public class ExamId implements ValueObject {
    private final Long value;
    
    public ExamId(Long value) {
        if (value == null || value <= 0) {
            throw new IllegalArgumentException("考试ID不能为空");
        }
        this.value = value;
    }
    
    public Long getValue() { return value; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ExamId examId = (ExamId) o;
        return Objects.equals(value, examId.value);
    }
    
    @Override
    public int hashCode() { return Objects.hash(value); }
}

// 考试时间值对象
public class ExamTime implements ValueObject {
    private final LocalDateTime startTime;
    private final LocalDateTime endTime;
    private final Duration duration;
    
    public ExamTime(LocalDateTime startTime, LocalDateTime endTime) {
        if (startTime == null || endTime == null) {
            throw new IllegalArgumentException("考试时间不能为空");
        }
        if (startTime.isAfter(endTime)) {
            throw new IllegalArgumentException("开始时间不能晚于结束时间");
        }
        this.startTime = startTime;
        this.endTime = endTime;
        this.duration = Duration.between(startTime, endTime);
    }
    
    public boolean isInProgress() {
        LocalDateTime now = LocalDateTime.now();
        return !now.isBefore(startTime) && !now.isAfter(endTime);
    }
    
    public boolean isExpired() {
        return LocalDateTime.now().isAfter(endTime);
    }
    
    public Duration getRemainingTime() {
        return Duration.between(LocalDateTime.now(), endTime);
    }
}

2. 聚合根实现

java 复制代码
// 考试聚合根
public class Exam implements AggregateRoot<ExamId> {
    private ExamId id;
    private String title;
    private String description;
    private ExamStatus status;
    private ExamTime time;
    private ExamConfig config;
    private PaperId paperId;
    private List<StudentId> studentIds;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    
    // 私有构造函数,防止外部直接创建
    private Exam() {}
    
    // 工厂方法
    public static Exam create(CreateExamCommand command) {
        Exam exam = new Exam();
        exam.id = ExamId.nextId();
        exam.title = command.getTitle();
        exam.description = command.getDescription();
        exam.status = ExamStatus.DRAFT;
        exam.time = new ExamTime(command.getStartTime(), command.getEndTime());
        exam.config = ExamConfig.of(
            command.isAllowPause(),
            command.isAllowRetake(),
            command.isRealTimeJudge(),
            command.getAutoSaveInterval(),
            command.getMaxAttempts()
        );
        exam.paperId = command.getPaperId();
        exam.studentIds = command.getStudentIds();
        exam.createTime = LocalDateTime.now();
        exam.updateTime = LocalDateTime.now();
        
        // 发布领域事件
        exam.registerEvent(new ExamCreatedEvent(exam.id, exam.title));
        
        return exam;
    }
    
    // 发布考试
    public void publish() {
        if (status != ExamStatus.DRAFT) {
            throw new DomainException("只有草稿状态的考试可以发布");
        }
        
        if (time.getStartTime().isBefore(LocalDateTime.now())) {
            throw new DomainException("考试开始时间不能早于当前时间");
        }
        
        this.status = ExamStatus.PUBLISHED;
        this.updateTime = LocalDateTime.now();
        
        // 发布领域事件
        registerEvent(new ExamPublishedEvent(
            this.id, 
            this.title, 
            this.studentIds,
            this.time.getStartTime()
        ));
    }
    
    // 开始考试
    public void start() {
        if (status != ExamStatus.PUBLISHED) {
            throw new DomainException("只有已发布的考试可以开始");
        }
        
        if (!time.isInProgress()) {
            throw new DomainException("未到考试开始时间");
        }
        
        this.status = ExamStatus.IN_PROGRESS;
        this.updateTime = LocalDateTime.now();
        
        registerEvent(new ExamStartedEvent(this.id, this.studentIds));
    }
    
    // 业务规则验证
    public void validateForStudent(StudentId studentId) {
        if (status != ExamStatus.IN_PROGRESS) {
            throw new DomainException("考试未在进行中");
        }
        
        if (!studentIds.contains(studentId)) {
            throw new DomainException("学生没有参加此考试的权限");
        }
    }
    
    // Getters
    public ExamId getId() { return id; }
    public ExamStatus getStatus() { return status; }
    public ExamTime getTime() { return time; }
    public ExamConfig getConfig() { return config; }
    public List<StudentId> getStudentIds() { return Collections.unmodifiableList(studentIds); }
}

3. 领域服务实现

java 复制代码
// 考试安排领域服务
@Service
public class ExamArrangementService {
    private final ExamRepository examRepository;
    private final StudentRepository studentRepository;
    private final PaperRepository paperRepository;
    private final EventPublisher eventPublisher;
    
    @Transactional
    public Exam createExam(CreateExamCommand command) {
        // 1. 验证试卷是否存在
        Paper paper = paperRepository.findById(command.getPaperId());
        if (paper == null) {
            throw new DomainException("试卷不存在: " + command.getPaperId());
        }
        
        // 2. 验证学生是否存在
        List<Student> students = studentRepository.findByIds(command.getStudentIds());
        if (students.size() != command.getStudentIds().size()) {
            throw new DomainException("部分学生不存在");
        }
        
        // 3. 创建考试
        Exam exam = Exam.create(command);
        
        // 4. 保存考试
        examRepository.save(exam);
        
        // 5. 发布事件
        exam.getDomainEvents().forEach(eventPublisher::publish);
        
        return exam;
    }
}

4. 应用服务实现

java 复制代码
// 考试应用服务
@Service
@Transactional
public class ExamApplicationService {
    private final ExamArrangementService examArrangementService;
    private final ExamParticipationService examParticipationService;
    private final ExamRepository examRepository;
    
    public ExamDTO createExam(CreateExamRequest request) {
        // 1. 转换为命令
        CreateExamCommand command = CreateExamCommand.builder()
            .title(request.getTitle())
            .description(request.getDescription())
            .startTime(request.getStartTime())
            .endTime(request.getEndTime())
            .paperId(new PaperId(request.getPaperId()))
            .studentIds(request.getStudentIds().stream()
                .map(StudentId::new)
                .collect(Collectors.toList()))
            .allowPause(request.isAllowPause())
            .allowRetake(request.isAllowRetake())
            .realTimeJudge(request.isRealTimeJudge())
            .autoSaveInterval(request.getAutoSaveInterval())
            .maxAttempts(request.getMaxAttempts())
            .build();
        
        // 2. 调用领域服务
        Exam exam = examArrangementService.createExam(command);
        
        // 3. 转换为DTO
        return ExamDTO.from(exam);
    }
    
    public void publishExam(PublishExamRequest request) {
        Exam exam = examRepository.findById(new ExamId(request.getExamId()));
        if (exam == null) {
            throw new ResourceNotFoundException("考试不存在");
        }
        
        exam.publish();
        examRepository.save(exam);
    }
}

5. 仓储接口与实现

java 复制代码
// 仓储接口
public interface ExamRepository {
    Exam findById(ExamId id);
    List<Exam> findByStatus(ExamStatus status);
    List<Exam> findByTeacher(TeacherId teacherId);
    void save(Exam exam);
    void delete(ExamId id);
}

// 仓储实现
@Repository
@RequiredArgsConstructor
public class ExamRepositoryImpl implements ExamRepository {
    private final ExamMapper examMapper;
    private final ExamConverter examConverter;
    
    @Override
    public Exam findById(ExamId id) {
        ExamDO examDO = examMapper.selectById(id.getValue());
        if (examDO == null) {
            return null;
        }
        return examConverter.toDomain(examDO);
    }
    
    @Override
    public void save(Exam exam) {
        ExamDO examDO = examConverter.toDO(exam);
        
        if (examDO.getId() == null) {
            examMapper.insert(examDO);
        } else {
            examMapper.updateById(examDO);
        }
    }
}

五、数据库表与领域模型映射

1. 考试表映射

java 复制代码
// 数据库对象
@Data
@TableName("mock_exam")
public class ExamDO {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String title;
    private String description;
    private Integer status;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private Long paperId;
    private String studentIds; // JSON格式
    private Boolean allowPause;
    private Boolean allowRetake;
    private Boolean realTimeJudge;
    private Integer autoSaveInterval;
    private Integer maxAttempts;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

// 转换器
@Component
public class ExamConverter {
    public Exam toDomain(ExamDO examDO) {
        return Exam.builder()
            .id(new ExamId(examDO.getId()))
            .title(examDO.getTitle())
            .description(examDO.getDescription())
            .status(ExamStatus.fromCode(examDO.getStatus()))
            .time(new ExamTime(examDO.getStartTime(), examDO.getEndTime()))
            .config(ExamConfig.builder()
                .allowPause(examDO.getAllowPause())
                .allowRetake(examDO.getAllowRetake())
                .realTimeJudge(examDO.getRealTimeJudge())
                .autoSaveInterval(examDO.getAutoSaveInterval())
                .maxAttempts(examDO.getMaxAttempts())
                .build())
            .paperId(new PaperId(examDO.getPaperId()))
            .studentIds(JsonUtil.parseList(examDO.getStudentIds(), StudentId.class))
            .createTime(examDO.getCreateTime())
            .updateTime(examDO.getUpdateTime())
            .build();
    }
    
    public ExamDO toDO(Exam exam) {
        ExamDO examDO = new ExamDO();
        examDO.setId(exam.getId() != null ? exam.getId().getValue() : null);
        examDO.setTitle(exam.getTitle());
        examDO.setDescription(exam.getDescription());
        examDO.setStatus(exam.getStatus().getCode());
        examDO.setStartTime(exam.getTime().getStartTime());
        examDO.setEndTime(exam.getTime().getEndTime());
        examDO.setPaperId(exam.getPaperId().getValue());
        examDO.setStudentIds(JsonUtil.toJson(exam.getStudentIds()));
        examDO.setAllowPause(exam.getConfig().isAllowPause());
        examDO.setAllowRetake(exam.getConfig().isAllowRetake());
        examDO.setRealTimeJudge(exam.getConfig().isRealTimeJudge());
        examDO.setAutoSaveInterval(exam.getConfig().getAutoSaveInterval());
        examDO.setMaxAttempts(exam.getConfig().getMaxAttempts());
        examDO.setCreateTime(exam.getCreateTime());
        examDO.setUpdateTime(exam.getUpdateTime());
        return examDO;
    }
}

六、领域事件处理

1. 领域事件定义

java 复制代码
// 考试发布事件
public class ExamPublishedEvent extends DomainEvent {
    private final ExamId examId;
    private final String title;
    private final List<StudentId> studentIds;
    private final LocalDateTime startTime;
    
    public ExamPublishedEvent(ExamId examId, String title, 
                            List<StudentId> studentIds, LocalDateTime startTime) {
        this.examId = examId;
        this.title = title;
        this.studentIds = studentIds;
        this.startTime = startTime;
    }
}

2. 事件处理器

java 复制代码
// 处理考试发布事件
@Component
@Slf4j
public class ExamPublishedEventHandler {
    private final NotificationService notificationService;
    private final MessageProducer messageProducer;
    
    @EventListener
    public void handleExamPublishedEvent(ExamPublishedEvent event) {
        log.info("处理考试发布事件: examId={}", event.getExamId());
        
        // 1. 发送通知给学生
        notificationService.sendExamNotification(
            event.getExamId(),
            event.getTitle(),
            event.getStudentIds(),
            event.getStartTime()
        );
        
        // 2. 发送消息到消息队列
        messageProducer.sendExamPublishedMessage(
            ExamPublishedMessage.builder()
                .examId(event.getExamId().getValue())
                .title(event.getTitle())
                .startTime(event.getStartTime())
                .build()
        );
    }
}

七、防腐层实现

1. 防腐层接口

java 复制代码
// 用户上下文防腐层
public interface UserContextAdapter {
    Student getStudent(StudentId studentId);
    List<Student> getStudents(List<StudentId> studentIds);
    Teacher getTeacher(TeacherId teacherId);
}

// 试卷上下文防腐层
public interface PaperContextAdapter {
    Paper getPaper(PaperId paperId);
    List<Question> getQuestions(List<QuestionId> questionIds);
}

2. 防腐层实现

java 复制代码
// 通过RPC调用用户上下文
@Component
@RequiredArgsConstructor
public class UserContextAdapterImpl implements UserContextAdapter {
    private final UserFeignClient userFeignClient;
    
    @Override
    public Student getStudent(StudentId studentId) {
        UserDTO userDTO = userFeignClient.getUser(studentId.getValue());
        return convertToStudent(userDTO);
    }
    
    private Student convertToStudent(UserDTO userDTO) {
        return Student.builder()
            .id(new StudentId(userDTO.getId()))
            .name(userDTO.getName())
            .account(userDTO.getAccount())
            .classId(new ClassId(userDTO.getClassId()))
            .gradeId(new GradeId(userDTO.getGradeId()))
            .build();
    }
}

八、测试策略

1. 单元测试

java 复制代码
// 考试聚合单元测试
@ExtendWith(MockitoExtension.class)
class ExamTest {
    
    @Test
    void should_create_exam_successfully() {
        // Given
        CreateExamCommand command = CreateExamCommand.builder()
            .title("期中考试")
            .startTime(LocalDateTime.now().plusDays(1))
            .endTime(LocalDateTime.now().plusDays(1).plusHours(2))
            .paperId(new PaperId(1L))
            .studentIds(Arrays.asList(new StudentId(1001L)))
            .allowPause(true)
            .allowRetake(false)
            .realTimeJudge(true)
            .maxAttempts(1)
            .build();
        
        // When
        Exam exam = Exam.create(command);
        
        // Then
        assertNotNull(exam);
        assertEquals("期中考试", exam.getTitle());
        assertEquals(ExamStatus.DRAFT, exam.getStatus());
        assertTrue(exam.getConfig().isAllowPause());
        assertFalse(exam.getConfig().isAllowRetake());
    }
    
    @Test
    void should_throw_exception_when_publish_draft_exam() {
        // Given
        Exam exam = createDraftExam();
        
        // When & Then
        assertDoesNotThrow(exam::publish);
    }
    
    @Test
    void should_throw_exception_when_publish_started_exam() {
        // Given
        Exam exam = createStartedExam();
        
        // When & Then
        assertThrows(DomainException.class, exam::publish);
    }
}

九、部署与监控

1. 健康检查端点

java 复制代码
@RestController
@RequestMapping("/actuator")
public class HealthController {
    
    @GetMapping("/health")
    public ResponseEntity<HealthResponse> health() {
        return ResponseEntity.ok(HealthResponse.healthy());
    }
    
    @GetMapping("/readiness")
    public ResponseEntity<HealthResponse> readiness() {
        // 检查数据库连接
        // 检查Redis连接
        // 检查消息队列连接
        return ResponseEntity.ok(HealthResponse.ready());
    }
}

十、总结:DDD落地最佳实践

✅ 成功关键

  1. 统一语言:团队使用业务术语沟通
  2. 领域驱动:从业务出发,不是从数据库出发
  3. 渐进式演进:不要一开始就设计完美模型
  4. 持续重构:随着业务理解加深,不断改进模型

🚨 常见陷阱

  1. 贫血模型:把领域对象当成数据容器
  2. 过大聚合:一个聚合包含太多实体
  3. 基础设施侵入领域层:在领域层直接操作数据库
  4. 忽略限界上下文边界:不同上下文过度耦合

📈 演进建议

复制代码
第1阶段:识别核心子域,建立统一语言
第2阶段:设计核心聚合,实现基本功能
第3阶段:引入领域事件,解耦上下文
第4阶段:优化性能,引入缓存、异步
第5阶段:扩展功能,增加监控、分析

这个完整的DDD落地架构为考试系统提供了清晰的业务边界、可维护的代码结构和可扩展的技术架构,确保系统能够随着业务发展持续演进。

相关推荐
2501_942191772 小时前
【深度学习应用】香蕉镰刀菌症状识别与分类:基于YOLO13-C3k2-MBRConv5模型的实现与分析
人工智能·深度学习·分类
AI小怪兽2 小时前
YOLO26:面向实时目标检测的关键架构增强与性能基准测试
人工智能·yolo·目标检测·计算机视觉·目标跟踪·架构
为自己_带盐2 小时前
架构演进:从数据库“裸奔”到多级防护
数据库·架构
知乎的哥廷根数学学派2 小时前
基于卷积特征提取和液态神经网络的航空发动机剩余使用寿命预测算法(python)
人工智能·pytorch·python·深度学习·神经网络·算法
高洁012 小时前
AIGC技术与进展(2)
人工智能·python·深度学习·机器学习·数据挖掘
岑梓铭2 小时前
YOLO深度学习(计算机视觉)—毕设笔记(yolo训练效率加快)
人工智能·笔记·深度学习·神经网络·yolo·计算机视觉
黎雁·泠崖2 小时前
Java初识面向对象+类与对象+封装核心
java·开发语言
悟能不能悟2 小时前
java controller的DTO如果有内部类,应该注意什么
java·开发语言
Nautiluss2 小时前
一起调试XVF3800麦克风阵列(十六)
人工智能·单片机·音频·语音识别·dsp开发·智能硬件