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路线图对照复习

企业模式

PoEAA 与事务脚本。

30 分钟

企业模式:PoEAA 在判题平台的落地

这一课你将能做到

  1. 说出事务脚本、领域模型、表模块、活动记录四种企业模式,并判断判题平台该用哪种
  2. 用 Identity Map 解决"重复查询同一用户"的性能问题
  3. 用 Unit of Work 管理一次提交涉及的多个数据库变更

为什么要学

PoEAA(Patterns of Enterprise Application Architecture)是企业级应用的"组织代码"方式。判题平台不是 demo,是真实业务:用户提交代码、判题引擎跑用例、更新练习记录、发通知、算排行榜。这些操作怎么组织、怎么保证数据一致、怎么避免 N+1 查询,就是 PoEAA 要解决的问题。

先建立图像

事务脚本              领域模型              表模块
─────────            ─────────            ─────────
一个方法一个事务       对象有行为           一个类管一张表
过程式                面向对象             数据 + 简单操作
适合简单 CRUD         适合复杂业务          适合报表类

活动记录              Identity Map         Unit of Work
─────────            ─────────            ─────────
对象自己管 CRUD        缓存已加载对象        收集变更批量提交
Rails/Django 风格      避免重复查询         保证事务完整性

跟着做

步骤 1:事务脚本 vs 领域模型

事务脚本(适合简单业务):

@Service
public class SubmissionTransactionScript {
    @Autowired private SubmissionMapper submissionMapper;
    @Autowired private PracticeRecordMapper practiceMapper;

    @Transactional
    public void submitAndJudge(SubmitRequest req) {
        // 一个方法一个事务,过程式
        Submission submission = new Submission();
        submission.setUserId(req.userId());
        submission.setLessonId(req.lessonId());
        submission.setCode(req.code());
        submission.setStatus(0); // PENDING
        submissionMapper.insert(submission);

        // 判题逻辑也在同一个事务里
        JudgeResult result = judgeClient.call(req.code(), req.lessonId());
        submission.setStatus(result.isAccepted() ? 1 : 2);
        submissionMapper.updateById(submission);

        practiceMapper.upsert(req.userId(), req.lessonId(), result.isAccepted());
    }
}

优点:简单直接;缺点:业务规则散落,复用差,测试难。

领域模型(适合复杂业务):

public class Submission {
    private SubmissionId id;
    private CodeSnippet code;
    private SubmissionStatus status;

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

@Service
public class SubmissionService {
    @Autowired private SubmissionRepository repository;
    @Autowired private JudgeEngineFactory engineFactory;

    @Transactional
    public void submitAndJudge(SubmitCommand cmd) {
        Submission submission = Submission.create(cmd);
        JudgeEngine engine = engineFactory.create(cmd.language());
        submission.judge(engine);   // 业务规则在领域对象里
        repository.save(submission);
    }
}

业务规则在 Submission.judge() 里,Service 只协调,不实现规则。

步骤 2:Identity Map——避免重复查询

@Component
public class UserIdentityMap {
    private final Map<UserId, CfUser> cache = new ConcurrentHashMap<>();

    public CfUser get(UserId id, Function<UserId, CfUser> loader) {
        return cache.computeIfAbsent(id, loader);
    }

    public void clear() { cache.clear(); }
}

@Service
public class SubmissionQueryService {
    @Autowired private UserIdentityMap userMap;
    @Autowired private UserMapper userMapper;

    public List<SubmissionDto> listByLesson(long lessonId) {
        List<Submission> submissions = submissionMapper.selectByLesson(lessonId);
        return submissions.stream().map(s -> {
            // 同一用户多次出现也只查一次
            CfUser user = userMap.get(s.getUserId(), id -> userMapper.selectById(id.value()));
            return SubmissionDto.of(s, user.getNickname());
        }).toList();
    }
}

一个请求内,同一用户只查一次数据库,后续从 Identity Map 拿。

步骤 3:Unit of Work——批量提交变更

@Component
public class UnitOfWork {
    private final List<Object> newObjects = new ArrayList<>();
    private final List<Object> dirtyObjects = new ArrayList<>();
    private final List<Object> removedObjects = new ArrayList<>();

    public void registerNew(Object obj) { newObjects.add(obj); }
    public void registerDirty(Object obj) { dirtyObjects.add(obj); }
    public void registerRemoved(Object obj) { removedObjects.add(obj); }

    @Transactional
    public void commit() {
        // 统一在事务内批量执行
        newObjects.forEach(this::insert);
        dirtyObjects.forEach(this::update);
        removedObjects.forEach(this::delete);
        clear();
    }
}

判题平台里,一次提交要改 Submission、PracticeRecord、UserStat 三张表,Unit of Work 收集所有变更,最后一次事务提交。

讲清楚要点

  1. 事务脚本:一个方法一个事务,过程式,适合简单 CRUD。
  2. 领域模型:对象有行为,业务规则内聚,适合复杂业务。
  3. 表模块:一个类管一张表,数据 + 简单操作,适合报表。
  4. 活动记录:对象自己管 CRUD,Rails/Django 风格,Java 里少用。
  5. Identity Map:缓存已加载对象,避免同一请求内重复查询。
  6. Unit of Work:收集变更,批量提交,保证事务完整性。

你可能会懵的地方

  • 事务脚本和领域模型怎么选? 简单 CRUD 用事务脚本,复杂业务用领域模型。判题平台核心域(提交、判题)用领域模型,边缘域(日志、通知)可以用事务脚本。
  • Identity Map 和缓存什么区别? Identity Map 是请求级缓存,防止同一请求内重复查询;Redis 是跨请求缓存。Identity Map 解决 N+1,Redis 解决热点数据。
  • Unit of Work 和事务什么关系? Unit of Work 收集变更,事务保证提交原子性。Unit of Work 是"收集器",事务是"执行器"。
  • Spring 的 @Transactional 和 Unit of Work 什么关系? @Transactional 是声明式事务,Unit of Work 是变更收集模式。可以结合:Service 方法加 @Transactional,内部用 Unit of Work 收集变更。

错法对照

错法问题正解
一个 Service 方法 200 行事务脚本失控拆成领域模型,规则进对象
同一用户查 10 次N+1 性能问题Identity Map 请求级缓存
多次 save 不统一提交事务边界不清Unit of Work 收集后批量提交
业务规则写在 SQL 里难维护,难测试规则进领域对象,SQL 只做存取
所有表都用领域模型过度设计边缘域用事务脚本,核心域用领域模型

卡点急救

  1. 事务脚本越来越长:找重复的业务规则,抽成领域对象方法;Service 只协调,不实现。
  2. Identity Map 内存泄漏:请求结束清空;或者用 ThreadLocal + try-finally 保证清理。
  3. Unit of Work 和 Spring 事务冲突:Unit of Work 内部不加 @Transactional,由外层 Service 统一加。

面试常问

  • 事务脚本和领域模型什么区别? 答:事务脚本是过程式,一个方法一个事务,业务规则散落;领域模型是面向对象,业务规则内聚在对象里,Service 只协调。判题平台核心域用领域模型,边缘域用事务脚本。
  • Identity Map 解决什么问题? 答:解决同一请求内重复查询同一对象的问题(N+1)。判题平台查提交列表时,同一用户的多个提交只查一次用户表。
  • Unit of Work 和 @Transactional 怎么配合? 答:Unit of Work 收集变更,外层 Service 加 @Transactional 统一提交。Unit 内部不管理事务,只管理变更列表。

自测三问

  1. 判题平台"提交+判题+更新记录"三步,用事务脚本和领域模型分别怎么写?
  2. 查"某课程提交列表"时,同一用户出现 5 次,怎么避免查 5 次用户表?
  3. 一次操作要改 Submission、PracticeRecord、UserStat 三张表,怎么保证要么全成功要么全失败?

小结一句话

PoEAA 是企业应用的"组织代码"方式:事务脚本管简单业务,领域模型管复杂业务,Identity Map 防重复查询,Unit of Work 保事务完整。

延伸

  • 《企业应用架构模式》Martin Fowler
  • 《领域驱动设计》Eric Evans,第 4 章
  • Spring 事务管理
  • 下一课:综合架构练习——C4 模型 + ADR 实战。
加载本节练习…
← 上一课领域驱动设计下一课 →综合架构练习