ddd-hexagonal-architecture

DDD / 六边形架构(youteacher_web)

youteacher_web 是 YouTeacher 的前端,基于 Next.js 15 / React 19,代码按六边形架构(端口与适配器)+ 领域驱动内核组织。它想解决的问题很朴素:业务规则永远不知道数据来自哪里,数据管道也永远不知道业务规则。其余一切都由"把这两者隔开"推导而来。

四层,一个依赖方向

src/ 目录分四层,依赖只朝指:

  • domain/ —— 纯业务逻辑,不 import 任何框架。这里放 Account 聚合及其值对象(邮箱、密码、TOTP 码、OAuth 关联)以及 jobs 实体。领域对象是不可变的:一次变更返回新实例,而不是就地修改(例如关联一个 provider 会产出一个新的 Account)。
  • application/ —— 用例(use case)与端口(port)。像 LoginUseCase 这样的用例负责编排领域逻辑;端口是用例所依赖的接口,绝不是具体类。端口刻意做得很窄——LoginGateway 只有 login / logout / fetchProfileStorageGateway 只有 readUser / persistSession / clearSession——这样每个用例只声明它真正需要的能力,别的一概不碰。
  • infrastructure/ —— 针对真实世界实现这些端口的适配器:调后端服务的 HTTP 客户端、各类 auth API 网关、浏览器存储、recaptcha、导航。AuthApiGateway implements AuthGateway,把调用委托给传输层函数;它上面的用例从不直接见到 fetch
  • features/ —— 自包含的 UI 模块(auth、job-search),各自带组件、hooks 和工具函数。

依赖注入放在一个 React hook 里

有意思的一步在于:具体适配器在哪里被绑到抽象用例上。这里没有 DI 容器,组合根(composition root)是一个客户端 hook —— useAuthService。它先把具体网关实例化一次(memo 缓存),再逐个构造用例、把端口传进去——new LoginUseCase(authGateway, recaptcha, storage)——然后把接好线的用例返回给组件。于是 feature 层是唯一同时知道接口与实现的地方;domain 与 application 层对 React 和网络都一无所知。

这也正是它无需 mock 全世界就能测的原因:单元测试用假端口login: vi.fn()、桩存储)构造同一个用例,然后断言编排过程——recaptcha 跑了、网关带着 captcha token 被调用了、会话被持久化了。把真实 HTTP 适配器换成测试替身,用例本身一个字都不用改。

auth 的限界上下文

auth 领域被记录为五个限界上下文,它们都通过单一的 Account 聚合来组合:Identity(账号本身)、Access(会话的接管与吊销)、Credentials(密码规则)、MFA(TOTP / WebAuthn 状态)、Social Linking(OAuth 关联)。传输层 DTO 在边界处被映射成这些领域类型,因此任何一个后端服务的线上格式都不会渗透进内层。有一处架构层面的设计值得点名:会话本身以 httpOnly cookie 存在服务端,客户端本地只保存非敏感的用户信息。

相关

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →