基于SpringAI的在线考试系统-企业级软件研发工程应用规范实现细节

从"考生参加考试"看DDD四层架构:把业务需求拆成可落地的代码逻辑

在企业级在线考试系统中,"考生参加考试并提交答卷"是核心场景之一。我们用DDD四层架构,把用户的自然语言需求,转化为"业务规则→流程→接口→技术实现"的完整链路。

一、先明确:用户的"自然语言业务需求"是什么?

这是领域层的输入源,必须先把用户的口语化需求,提炼成清晰的业务规则:

  1. 考生只能参加"已发布且未结束"的考试;
  2. 考试开始后,系统自动加载对应试卷,且试卷内容不可修改;
  3. 考生答题过程中,系统每5分钟自动保存一次答案;
  4. 考生主动交卷/考试时间到,系统自动计算客观题分数,主观题进入阅卷队列;
  5. 考试结束后,考生能立即看到客观题得分和错题提示。

二、领域层:把"自然语言规则"转化为业务代码

领域层是业务规则的载体,我们要把上面的需求,封装成实体、聚合根、领域服务:

1. 核心领域实体/聚合根设计

Exam(考试)为聚合根(因为考试是该场景的核心载体),包含关联的实体/值对象:

java 复制代码
// 聚合根:Exam(考试)
@AggregateRoot
@Data
public class Exam {
    @Identifier
    private Long id;
    private String title; // 考试名称
    private Long paperId; // 关联试卷ID
    private LocalDateTime startTime; // 开始时间
    private LocalDateTime endTime; // 结束时间
    private ExamStatus status; // 状态:DRAFT(草稿)/PUBLISHED(已发布)/FINISHED(已结束)
    
    // 值对象:封装"考试配置"的业务规则
    @Embedded
    private ExamConfig config;

    // 业务规则1:判断考生是否能参加考试
    public boolean isJoinable() {
        LocalDateTime now = LocalDateTime.now();
        return this.status == ExamStatus.PUBLISHED 
                && now.isAfter(this.startTime) 
                && now.isBefore(this.endTime);
    }

    // 业务规则2:考试开始后不能修改试卷
    public boolean canModifyPaper() {
        return LocalDateTime.now().isBefore(this.startTime);
    }
}

// 值对象:ExamConfig(考试配置)
@ValueObject
@Data
public class ExamConfig {
    private Integer autoSaveInterval; // 自动保存间隔(分钟)

    // 业务规则3:获取自动保存的时间间隔
    public Duration getAutoSaveDuration() {
        return Duration.ofMinutes(this.autoSaveInterval);
    }
}

// 实体:AnswerSheet(考生答卷)
@Entity
@Data
public class AnswerSheet {
    @Id
    private Long id;
    private Long examId; // 关联考试ID
    private Long userId; // 关联考生ID
    private Map<Long, String> answers; // 题目ID→考生答案
    private LocalDateTime lastSaveTime; // 最后自动保存时间
    private BigDecimal objectiveScore; // 客观题得分

    // 业务规则4:判断是否需要自动保存
    public boolean needAutoSave(ExamConfig examConfig) {
        LocalDateTime nextSaveTime = this.lastSaveTime.plus(examConfig.getAutoSaveDuration());
        return LocalDateTime.now().isAfter(nextSaveTime);
    }
}

2. 领域服务:跨聚合的业务规则封装

如果业务需要多个聚合协同(比如"计算客观题分数"需要AnswerSheet+Question聚合),用领域服务封装:

java 复制代码
// 领域服务:AnswerSheetDomainService
@Service
public class AnswerSheetDomainService {
    // 依赖Question聚合的仓储(接口,由基础设施层实现)
    private final QuestionRepository questionRepository;

    // 业务规则4:计算客观题分数
    public BigDecimal calculateObjectiveScore(AnswerSheet answerSheet) {
        List<Question> questions = questionRepository.findByExamId(answerSheet.getExamId());
        BigDecimal score = BigDecimal.ZERO;
        for (Question question : questions) {
            if (question.getType() == QuestionType.OBJECTIVE) { // 只算客观题
                String correctAnswer = question.getCorrectAnswer();
                String userAnswer = answerSheet.getAnswers().get(question.getId());
                if (correctAnswer.equals(userAnswer)) {
                    score = score.add(question.getScore());
                }
            }
        }
        answerSheet.setObjectiveScore(score);
        return score;
    }
}

三、应用层:把"业务规则"编排成"用户流程"

应用层不写业务规则,只负责把领域层的能力串成用户需要的操作流程(对应"考生参加考试"的完整步骤):

java 复制代码
// 应用服务:ExamApplicationService
@Service
public class ExamApplicationService {
    // 依赖领域层的仓储和服务
    private final ExamRepository examRepository;
    private final AnswerSheetRepository answerSheetRepository;
    private final AnswerSheetDomainService answerSheetDomainService;
    private final MessageProducer messageProducer; // 消息发送(基础设施层提供)

    // 编排"考生参加考试并提交答卷"的流程
    public ExamResultDTO participateAndSubmit(Long examId, Long userId, Map<Long, String> answers) {
        // 步骤1:校验考试是否可参加(调用领域层规则)
        Exam exam = examRepository.findById(examId)
                .orElseThrow(() -> new BusinessException("考试不存在"));
        if (!exam.isJoinable()) {
            throw new BusinessException("当前考试不可参加");
        }

        // 步骤2:创建/更新答卷(调用领域层实体)
        AnswerSheet answerSheet = answerSheetRepository.findByExamIdAndUserId(examId, userId)
                .orElse(new AnswerSheet(examId, userId));
        answerSheet.setAnswers(answers);
        answerSheet.setLastSaveTime(LocalDateTime.now());

        // 步骤3:计算客观题分数(调用领域服务)
        BigDecimal objectiveScore = answerSheetDomainService.calculateObjectiveScore(answerSheet);
        answerSheetRepository.save(answerSheet);

        // 步骤4:发送"主观题阅卷"消息(调用基础设施层能力)
        messageProducer.sendSubjectiveMarkingMessage(answerSheet.getId());

        // 步骤5:组装结果返回
        return ExamResultDTO.builder()
                .examId(examId)
                .userId(userId)
                .objectiveScore(objectiveScore)
                .build();
    }
}

四、接口层:把"流程"包装成"对外服务"

接口层是系统的对外入口,负责把应用层的流程,暴露为前端/其他系统可调用的形式(比如RESTful API):

java 复制代码
// 接口层:ExamController
@RestController
@RequestMapping("/api/exam")
public class ExamController {
    private final ExamApplicationService examApplicationService;

    // 对外提供"参加考试并提交答卷"的API
    @PostMapping("/{examId}/submit")
    public Result<ExamResultDTO> submitExam(
            @PathVariable Long examId,
            @RequestParam Long userId,
            @RequestBody Map<Long, String> answers) {
        ExamResultDTO result = examApplicationService.participateAndSubmit(examId, userId, answers);
        return Result.success(result);
    }
}

五、基础设施层:把"业务逻辑"落地为"技术实现"

基础设施层是纯技术细节,负责实现领域层/应用层需要的技术能力(数据库、消息队列等):

1. 仓储的技术实现(比如MySQL)

java 复制代码
// 基础设施层:ExamRepository的MySQL实现
@Repository
public class ExamRepositoryImpl implements ExamRepository {
    private final JdbcTemplate jdbcTemplate;

    @Override
    public Optional<Exam> findById(Long id) {
        String sql = "SELECT * FROM exam WHERE id = ?";
        return jdbcTemplate.query(sql, new Object[]{id}, rs -> {
            if (rs.next()) {
                Exam exam = new Exam();
                exam.setId(rs.getLong("id"));
                exam.setTitle(rs.getString("title"));
                // 其他字段赋值...
                return Optional.of(exam);
            }
            return Optional.empty();
        });
    }

    @Override
    public void save(Exam exam) {
        // 实现MySQL的插入/更新逻辑
    }
}

2. 消息队列的技术实现(比如RabbitMQ)

java 复制代码
// 基础设施层:MessageProducer的RabbitMQ实现
@Service
public class RabbitMQMessageProducer implements MessageProducer {
    private final RabbitTemplate rabbitTemplate;

    @Override
    public void sendSubjectiveMarkingMessage(Long answerSheetId) {
        rabbitTemplate.convertAndSend(
                "subjective-marking-exchange",
                "subjective.marking",
                answerSheetId
        );
    }
}

六、总结:四层架构的"职责边界"

通过"考生参加考试"场景,我们能清晰看到DDD四层的分工:

层级 角色 核心产出物
领域层 业务规则的"翻译器" 实体、聚合根、领域服务
应用层 业务流程的"串线工" 应用服务(流程编排)
接口层 对外服务的"窗口" RESTful API/消息处理器
基础设施层 技术实现的"工具箱" 仓储实现、消息队列集成、外部接口适配

这样的分层设计,既能保证业务规则不被技术细节污染 (领域层独立),又能让流程和技术实现灵活替换(比如把MySQL换成MongoDB,只改基础设施层),是企业级系统"易维护、易扩展"的关键。

相关推荐
阿猿收手吧!3 分钟前
【C++】C++模板特化:精准定制泛型逻辑
开发语言·c++·算法
qq_12498707534 分钟前
基于springboot的尿毒症健康管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·spring·毕业设计·计算机毕业设计
编程彩机6 分钟前
互联网大厂Java面试:从Spring Boot到微服务优化场景解析
spring boot·分布式事务·微服务架构·java面试·技术解析
青瓷程序设计10 分钟前
【交通标志识别系统】python+深度学习+算法模型+Resnet算法+人工智能+2026计算机毕设项目
人工智能·python·深度学习
Mr.huang12 分钟前
RNN系列模型演进及其解决的问题
人工智能·rnn·lstm
智驱力人工智能16 分钟前
货车走快车道检测 高速公路安全治理的工程实践与价值闭环 高速公路货车占用小客车道抓拍系统 城市快速路货车违规占道AI识别
人工智能·opencv·算法·安全·yolo·目标检测·边缘计算
老百姓懂点AI20 分钟前
[RAG架构] 拒绝向量检索幻觉:智能体来了(西南总部)AI agent指挥官的GraphRAG实战与AI调度官的混合索引策略
人工智能·架构
ws20190721 分钟前
技术迭代与湾区赋能:AUTO TECH China 2026广州汽车零部件展的四大核心价值
人工智能·科技·汽车
ghie909022 分钟前
MATLAB中编写不平衡磁拉力方程
开发语言·matlab
源于花海28 分钟前
迁移学习简明手册——迁移学习相关资源汇总
人工智能·机器学习·迁移学习