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/fetchProfile,StorageGateway只有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 存在服务端,客户端本地只保存非敏感的用户信息。
相关
- authentication-and-sessions —— 端到端的认证子系统
- backend-service-integration —— 应用如何触达后端服务
- talent-search-recruitment-flow —— 建在这套骨架上的招聘方功能