CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
软件设计与架构入门/课时/架构风格
RawBlame
课时目录12 节

软件设计与架构入门

  1. 01整洁代码
  2. 02设计原则
  3. 03面向对象
  4. 04编程范式
  5. 05设计模式
  6. 06架构原则
  7. 07架构风格
  8. 08架构模式
  9. 09领域驱动设计
  10. 10企业模式
  11. 11综合架构练习
  12. 12路线图对照复习

架构风格

单体、微服务与 Serverless。

30 分钟

架构风格:分层、六边形、CQRS 与事件驱动

这一课你将能做到

  1. 说出分层、六边形、CQRS、事件驱动四种架构风格的核心结构和适用场景
  2. 在判题平台里画出四层分层架构图,明确每层能调谁、不能调谁
  3. 判断"读写分离"和"事件总线"什么时候适合用,什么时候不适合

为什么要学

一个判题平台刚开始可能只是个单体应用,但随着用户量增长,读多写少、判题异步、多终端接入,架构风格的选择决定了扩展路径。分层架构是入门必备,六边形让核心业务不依赖框架,CQRS 让查询性能飞起,事件驱动让异步流程解耦。本课给你一张架构地图,知道什么时候走哪条路。

先建立图像

分层架构              六边形架构(端口适配器)
──────────           ──────────────────────
Controller            外部 HTTP/API 适配器
  ↓                   核心业务(领域)
Service               外部 DB/MQ 适配器
  ↓
Repository
  ↓
Database

CQRS                   事件驱动
──────                 ────────
写模型: Submission      SubmissionSubmitted → EventBus
        ↓保存             ↓
读模型: SubmissionView  JudgeEngine 消费 → 判题
        ↓查询             ↓
        Dashboard         JudgeResultEvent → 通知/更新

跟着做

步骤 1:四层分层架构——判题平台版

┌──────────────┐
│ 用户界面层   │   Controller、Filter、WebSocketHandler
├──────────────┤
│ 应用层       │   Service、CommandHandler、事件发布
├──────────────┤
│ 领域层       │   Entity、ValueObject、DomainService
├──────────────┤
│ 基础设施层   │   RepositoryImpl、MessageQueue、外部API
└──────────────┘

规则:上层可调用下层,同层可调用,下层不能调上层。领域层是核心,不依赖 Spring、不依赖 MyBatis。

// 领域层:纯 POJO,不依赖框架
public class Submission {
    private SubmissionId id;
    private UserId userId;
    private LessonId lessonId;
    private CodeSnippet code;
    private SubmissionStatus status;

    public void judge(JudgeResult result) {
        this.status = result.isAccepted() ? ACCEPTED : REJECTED;
    }
}

// 基础设施层:依赖领域层,实现 Repository 接口
@Repository
public class MybatisSubmissionRepository implements SubmissionRepository {
    private final SubmissionMapper mapper;
    @Override
    public void save(Submission submission) { /* 转换并写入 DB */ }
}

步骤 2:六边形架构——端口与适配器

六边形也叫"端口适配器",核心思想:业务逻辑不依赖外部世界,外部世界通过"端口"接入。

// 输入端口(驱动端口)
public interface SubmitUseCase {
    SubmissionId handle(SubmitCommand command);
}

// 输出端口(被驱动端口)
public interface SubmissionRepository {
    void save(Submission submission);
    Optional<Submission> findById(SubmissionId id);
}

// 核心业务在六边形内部
@Service
public class SubmitService implements SubmitUseCase {
    private final SubmissionRepository repository;
    public SubmitService(SubmissionRepository repository) {
        this.repository = repository;
    }
    public SubmissionId handle(SubmitCommand command) {
        Submission submission = Submission.create(command);
        repository.save(submission);
        return submission.getId();
    }
}

// HTTP 适配器(六边形外部)
@RestController
public class SubmissionController {
    private final SubmitUseCase submitUseCase;   // 依赖端口,不依赖实现
    @PostMapping("/submissions")
    public ResponseEntity<SubmissionId> submit(@RequestBody SubmitRequest req) {
        return ResponseEntity.ok(submitUseCase.handle(req.toCommand()));
    }
}

换 WebSocket 入口?新建一个 WebSocketSubmitAdapter 实现 SubmitUseCase 端口,核心业务不动。换存储?新建一个 RedisSubmissionRepository 实现 SubmissionRepository 端口。

步骤 3:CQRS——读写分离

判题平台里,写(提交代码)和读(查提交列表、排行榜)的模型完全不同。

// 写模型:命令
@Service
public class SubmitService {
    public SubmissionId submit(SubmitCommand cmd) {
        Submission s = Submission.create(cmd);
        repository.save(s);
        eventBus.publish(new SubmissionSubmitted(s.getId(), s.getUserId(), s.getLessonId()));
        return s.getId();
    }
}

// 读模型:专门为查询优化
@Service
public class SubmissionQueryService {
    public List<SubmissionView> listByLesson(long lessonId, int page, int size) {
        return submissionViewMapper.selectByLesson(lessonId, page, size);   // 宽表/ES
    }
    public LeaderboardView getLeaderboard(long lessonId) {
        return redisLeaderboard.get(lessonId);   // Redis 排序集合
    }
}

写走 MySQL(事务、一致),读走 Redis/ES/宽表(性能、聚合)。两者通过领域事件同步。

步骤 4:事件驱动——异步判题流

// 提交完成事件
public record SubmissionSubmitted(SubmissionId submissionId, UserId userId, LessonId lessonId) {}

// 判题完成事件
public record JudgeFinished(SubmissionId submissionId, JudgeResult result) {}

@Service
public class JudgeEventListener {
    @EventListener
    public void onSubmissionSubmitted(SubmissionSubmitted event) {
        judgeEngine.judge(event.submissionId());
    }

    @EventListener
    public void onJudgeFinished(JudgeFinished event) {
        submissionRepository.findById(event.submissionId())
            .ifPresent(s -> {
                s.judge(event.result());
                submissionRepository.save(s);
            });
    }
}

提交→事件→判题→事件→更新,全程异步,主线程不阻塞。

讲清楚要点

  1. 分层架构:经典四层(UI/应用/领域/基础设施),适合 CRUD 项目,入门友好。
  2. 六边形架构:核心不依赖外部,通过端口适配器接入,适合测试驱动、框架迁移。
  3. CQRS:命令和查询模型分离,适合读多写少、查询模型复杂(排行榜、报表)。
  4. 事件驱动:通过事件解耦服务,适合异步流程、削峰填谷、跨服务协作。
  5. 风格可以混合:判题平台单体阶段用分层 + 事件驱动;微服务阶段用六边形 + CQRS。

你可能会懵的地方

  • 分层和六边形什么区别? 分层是"上下关系",六边形是"内外关系"。分层关心 Controller→Service→Repository 的调用链;六边形关心"核心业务不依赖框架"。两者不冲突,可以六边形内部分层。
  • CQRS 是不是必须两个数据库? 不是。小项目可以同库不同表;大项目才分 MySQL + Redis/ES。关键是"模型分离",不是"物理分离"。
  • 事件驱动怎么保证最终一致? 用本地事务表 + 可靠投递(Outbox),或直接用 MQ 事务消息。下一课会详细讲 Saga 和 Outbox。
  • 事件驱动会不会导致代码难追踪? 会。需要给事件加 traceId,链路追踪工具(如 Sleuth + Zipkin)要跟上。事件流图要文档化。
  • 初学者该用哪种风格? 先用分层架构把关注点分离做好;业务复杂了再上六边形;读多写少再上 CQRS;异步多了再上事件驱动。不要一步到位。

错法对照

错法问题正解
所有代码都在 Controller没有分层至少拆出 Service 和 Repository
Service 直接 new RedisTemplate依赖具体框架六边形:抽象 CacheRepository,实现放基础设施层
写模型里塞查询逻辑模型职责混乱CQRS:写用 Command,查用 Query
所有调用都改成异步事件调试困难,一致难保证只在需要解耦/削峰的地方用事件
六边形里直接依赖 Spring违背核心独立原则领域层用纯 POJO,Spring 注解只在应用层

卡点急救

  1. 不知道分层该分几层:先四层(UI/应用/领域/基础设施),业务简单可以 UI 和应用合并,但领域层必须独立。
  2. 六边形里的端口不知道怎么抽:先写一个纯 POJO 的 Service,问自己"它需要什么外部能力?"每个能力就是一个端口。
  3. CQRS 的事件同步怎么落地:先写本地事务表(Outbox),定时任务扫描并投递 MQ,保证最终一致。

面试常问

  • 分层架构和六边形架构的区别? 答:分层是上下调用关系,六边形是内外依赖反转。分层里底层可能被框架绑定;六边形要求核心业务不依赖任何框架,通过端口接入。两者可结合:六边形内部分层。
  • CQRS 什么时候用? 答:读多写少、查询模型复杂且与写入模型差异大(如排行榜、报表、搜索)。判题平台里提交代码是写,查排行榜是读,两者模型不同,适合 CQRS。
  • 事件驱动的优缺点? 答:优点是解耦、异步、削峰;缺点是调试难、一致性难保证。适合跨服务协作、非关键路径。判题异步执行适合事件驱动,但用户登录不适合。

自测三问

  1. 判题平台为什么适合用事件驱动?如果不异步,会有什么问题?
  2. 六边形架构里,核心业务依赖什么?外部框架怎么接入?
  3. CQRS 写模型和读模型如果不同步,应该怎么保证最终一致性?

小结一句话

分层是基础,六边形让核心独立,CQRS 让查询飞起,事件驱动让流程解耦;风格是地图,不是终点。

延伸

  • 《实现领域驱动设计》Vaughn Vernon,第 2/3 章
  • 《架构整洁之道》Robert C. Martin,第 15-17 章
  • CQRS 模式 - Microsoft Azure
  • 下一课:架构模式——Saga、Outbox、Strangler Fig。
加载本节练习…
← 上一课架构原则下一课 →架构模式