architecture
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseProject Architecture and File Organization
项目架构与文件组织
This file is the SINGLE SOURCE OF TRUTH for folder structures and file naming conventions.
本文件是文件夹结构与文件命名规范的唯一权威来源。
1. High-Level Architecture
1. 高层架构
This project MUST be structured as a Modular Monolith built with Next.js 16+ (App Router). It strictly adheres to Domain-Driven Design (DDD) and Clean Architecture principles.
Instead of organizing files exclusively by technical responsibilities (e.g., all controllers together, all models together), you MUST divide the root of into Bounded Contexts (feature modules) such as , , , , and .
src/authfoldersshort-urlsusersworkspaces本项目必须采用基于Next.js 16+(App Router)构建的Modular Monolith架构,严格遵循领域驱动设计(DDD)和Clean Architecture原则。
不能仅按技术职责组织文件(例如将所有控制器放在一起、所有模型放在一起),必须将根目录划分为Bounded Contexts(功能模块),如、、、和。
src/authfoldersshort-urlsusersworkspaces2. Bounded Context Structure (The 4 Layers)
2. Bounded Context结构(四层架构)
Every module inside (except and ) MUST follow a strict 4-layer architecture. Dependencies MUST always point inwards toward the Domain.
src/app/shared/text
src/<bounded-context>/
├── domain/ # 1. CORE: Pure typescript. No frameworks. No DB imports.
│ ├── entities/ # Rich domain models with getters and .toSnapshot() rules.
│ ├── value-objects/ # Immutable primitives (e.g., folderName.ts) with validation.
│ ├── errors/ # Domain errors (e.g., folderAlreadyExistsError.ts).
│ └── interfaces/ # Contracts (Abstract Classes) for Repositories.
│
├── use-cases/ # 2. APPLICATION: Orchestration layer.
│ ├── createFolder.ts # Specific business flows. Uses repositories and domain models.
│ └── createFolder.test.ts # Co-located unit tests.
│
├── infrastructure/ # 3. INFRASTRUCTURE: External concerns. Database, Email, etc.
│ ├── postgresFolderRepository.ts # Implementation of domain interfaces.
│ └── postgresFolderRepository.integration.test.ts # Co-located integration tests.
│
└── presentation/ # 4. PRESENTATION: Next.js and UI concerns.
├── actions/ # Server Actions (e.g., createFolderAction.ts).
├── components/ # React Components (e.g., FolderList.tsx).
└── hooks/ # React Hooks.src/app/shared/text
src/<bounded-context>/
├── domain/ # 1. 核心层:纯TypeScript代码,无框架依赖,无数据库导入。
│ ├── entities/ # 包含getter和.toSnapshot()规则的富领域模型。
│ ├── value-objects/ # 带验证逻辑的不可变基础类型(如folderName.ts)。
│ ├── errors/ # 领域错误(如folderAlreadyExistsError.ts)。
│ └── interfaces/ # 仓库接口(抽象类)。
│
├── use-cases/ # 2. 应用层:编排层。
│ ├── createFolder.ts # 特定业务流程,使用仓库和领域模型。
│ └── createFolder.test.ts # 同目录单元测试。
│
├── infrastructure/ # 3. 基础设施层:外部依赖相关,如数据库、邮件服务等。
│ ├── postgresFolderRepository.ts # 领域接口的实现。
│ └── postgresFolderRepository.integration.test.ts # 同目录集成测试。
│
└── presentation/ # 4. 表现层:Next.js与UI相关内容。
├── actions/ # Server Actions(如createFolderAction.ts)。
├── components/ # React Components(如FolderList.tsx)。
└── hooks/ # React Hooks。3. Naming Conventions and Locations
3. 命名规范与位置
You MUST adhere to the following file naming and location conventions across all Bounded Contexts:
| Concept | Location | Naming Convention | Example |
|---|---|---|---|
| Bounded Context | | | |
| Value Objects | | | |
| Entities | | | |
| Domain Errors | | | |
| Interfaces | | | |
| Use Cases | | | |
| Infrastructure | | | |
| Server Actions | | | |
| Components | | | |
所有Bounded Context必须遵循以下文件命名与位置规范:
| 概念 | 位置 | 命名规范 | 示例 |
|---|---|---|---|
| Bounded Context | | | |
| 值对象 | | | |
| 实体 | | | |
| 领域错误 | | | |
| 接口 | | | |
| 用例 | | | |
| 基础设施实现 | | | |
| Server Actions | | | |
| 组件 | | | |
4. The Dependency Rule & Inversion of Control
4. 依赖规则与控制反转
- Domain is isolated: The folder MUST NOT import from
domain/,use-cases/, orinfrastructure/.presentation/- Pragmatic Exception: You MAY import focused libraries like (for complex validations like emails/URLs),
zod(for UUIDv7 generation), anduuid(forneverthrowtypes) inside the Domain. We delegate these tasks because manually parsing emails or generating UUIDv7s is too complex, error-prone, and provides no business value to reinvent.Result
- Pragmatic Exception: You MAY import focused libraries like
- Dependency Inversion: Use Cases MUST depend on Repository Interfaces (from ), not on concrete implementations (like Postgres).
domain/interfaces/ - No Use Case Chaining: A Use Case MUST NOT instantiate, inject, or call another Use Case.
- Service Container: Instantiation is centralized in (and exported as
src/shared/infrastructure/bootstrap.ts).serviceContainer- UI Components and Server Actions MUST NOT instantiate Repositories or Use Cases directly.
- They MUST import the pre-configured use case instance from .
serviceContainer - Example:
serviceContainer.folders.createFolder.execute(params)
- 领域层隔离:文件夹禁止从
domain/、use-cases/或infrastructure/导入内容。presentation/- 实用例外:可在领域层导入特定库,如(用于邮箱/URL等复杂验证)、
zod(用于生成UUIDv7)和uuid(用于neverthrow类型)。我们使用这些库是因为手动解析邮箱或生成UUIDv7过于复杂、易出错,且没有业务价值需要重新实现。Result
- 实用例外:可在领域层导入特定库,如
- 依赖反转:用例必须依赖仓库接口(来自),而非具体实现(如Postgres)。
domain/interfaces/ - 禁用用例链式调用:一个用例不得实例化、注入或调用另一个用例。
- 服务容器:实例化集中在中(并以
src/shared/infrastructure/bootstrap.ts导出)。serviceContainer- UI 组件和Server Actions不得直接实例化仓库或用例。
- 必须从导入预配置的用例实例。
serviceContainer - 示例:
serviceContainer.folders.createFolder.execute(params)
5. The src/app/
Layer (Routing)
src/app/5. src/app/
层(路由)
src/app/The directory MUST be treated strictly as a Routing and Entry-Point Layer. It MUST contain minimal logic.
src/app/- It MUST wire HTTP requests to your .
presentation/components/ - : Public-facing pages supporting the custom i18n implementation (e.g., Landing, Login).
app/[locale]/ - : Authenticated application views. MUST NOT use the
app/dashboard/segment natively in the URL but still supports i18n through[locale].LocaleProvider - You MUST NOT place domain logic or direct database queries inside or
page.tsx. You MUST calllayout.tsxuse cases or Server Actions instead.serviceContainer
src/app/- 必须将HTTP请求关联到。
presentation/components/ - :支持自定义i18n实现的公开页面(如着陆页、登录页)。
app/[locale]/ - :已认证的应用视图。不得在URL中原生使用
app/dashboard/段,但仍通过[locale]支持i18n。LocaleProvider - 不得在或
page.tsx中放置领域逻辑或直接数据库查询,必须调用layout.tsx用例或Server Actions。serviceContainer
6. Shared and Specialized Modules
6. 共享与专用模块
The src/shared/
Module
src/shared/src/shared/
模块
src/shared/The context MUST contain cross-cutting concerns that apply to the entire application:
shared- : Base classes, generic utilities (
shared/domain/wrappers), and shared branded errors (likeResult).RepositoryError - :
shared/infrastructure/- and
bootstrap.ts(Dependency Injection).serviceContainer.ts - (Zod environment validation).
load-env.ts - (Database schema and setup).
drizzle-postgres/ - (Custom translation logic).
i18n/
- : Reusable Components (e.g., UI library
shared/presentation/,shadcn).LocaleProvider - : Reusable testing utilities like
shared/test/and database seeder helpers.txTest
shared- :基类、通用工具(
shared/domain/包装器)和共享标记错误(如Result)。RepositoryError - :
shared/infrastructure/- 和
bootstrap.ts(依赖注入)。serviceContainer.ts - (Zod环境变量验证)。
load-env.ts - (数据库Schema与配置)。
drizzle-postgres/ - (自定义翻译逻辑)。
i18n/
- :可复用组件(如UI库
shared/presentation/、shadcn)。LocaleProvider - :可复用测试工具,如
shared/test/和数据库种子数据助手。txTest
The src/auth/
Module (Auth)
src/auth/src/auth/
模块(认证)
src/auth/The context MUST act as a specialized Bounded Context that integrates the Better Auth library. Unlike standard 4-layer modules, it MUST act as an infrastructural bridge:
auth- MUST contain (Server Config) and
auth.ts(Client Config).auth-client.ts - MUST provide for injecting session state into Server Actions and Use Cases.
getSession.ts - MUST house specific auth UI in and React Email templates in
presentation/components/.templates/
auth- 必须包含(服务端配置)和
auth.ts(客户端配置)。auth-client.ts - 必须提供,用于将会话状态注入Server Actions和用例。
getSession.ts - 必须在中存放特定认证UI,在
presentation/components/中存放React Email模板。templates/
7. Environment Variables (Zod Validation)
7. 环境变量(Zod验证)
You MUST NOT access directly in application code. All environment variables MUST be defined, validated, and exported from a single file: .
process.envsrc/shared/infrastructure/load-env.ts- Zod Validation: We MUST use a helper that takes the environment variable name and a
retrieveEnvVarschema. This MUST throw an immediate, descriptive error on startup if an environment variable is missing or malformed.z.ZodType - Type Coercion: For numbers or booleans, you MUST use Zod's coercion (e.g., ).
z.coerce.number().int().positive() - Value Object Integration: Variables MAY be mapped directly into a Value Object on load (e.g., ) so the rest of the application gets a valid, typed primitive.
AppUrl.from(value)._unsafeUnwrap() - Public Variables Prefix: Any exported constant in that will be exposed to the client/browser MUST have a
load-env.tsprefix in its TypeScript variable name (e.g.,PUBLIC_). This visually distinguishes client-safe variables from server-only secrets within the codebase. (The actual environment variable name must still follow Next.js conventions, e.g.,export const PUBLIC_APP_NAME).NEXT_PUBLIC_APP_NAME - App Usage: Anywhere in the codebase that needs an environment variable, you MUST import the exported constant directly from .
load-env.ts
不得在应用代码中直接访问。所有环境变量必须在单个文件中定义、验证并导出。
process.envsrc/shared/infrastructure/load-env.ts- Zod验证:必须使用助手,传入环境变量名称和
retrieveEnvVarschema。如果环境变量缺失或格式错误,必须在启动时立即抛出描述性错误。z.ZodType - 类型转换:对于数字或布尔值,必须使用Zod的转换功能(如)。
z.coerce.number().int().positive() - 值对象集成:变量可在加载时直接映射到值对象(如),以便应用其他部分获取有效的类型化基础值。
AppUrl.from(value)._unsafeUnwrap() - 公开变量前缀:中任何将暴露给客户端/浏览器的导出常量,其TypeScript变量名必须带有
load-env.ts前缀(如PUBLIC_)。这在代码库中直观区分客户端安全变量与服务端专属密钥。(实际环境变量名称仍需遵循Next.js规范,如export const PUBLIC_APP_NAME)。NEXT_PUBLIC_APP_NAME - 应用使用:代码库中任何需要环境变量的地方,必须直接从导入导出的常量。
load-env.ts
Example from load-env.ts
:
load-env.tsload-env.ts
示例:
load-env.tstypescript
import { z } from "zod";
import { AppUrl } from "@/shared/domain/value-objects/appUrl";
// Base helper
function retrieveEnvVar<T>(params: { name: string; schema: z.ZodType<T> }): T {
const value = process.env[params.name];
const parsed = params.schema.safeParse(value);
if (parsed.success) return parsed.data;
throw new TypeError(`Environment variable ${params.name} is invalid...`);
}
// 1. Standard string validation
export const PUBLIC_APP_NAME = retrieveEnvVar({
name: "NEXT_PUBLIC_APP_NAME",
schema: z.string().min(3),
});
// 2. Coercion (e.g., port to number)
export const POSTGRES_PORT = retrieveEnvVar({
name: "POSTGRES_PORT",
schema: z.coerce.number().int().positive(),
});
// 3. Wrapping in a Value Object
export const PUBLIC_APP_BASE_URL = AppUrl.from(
retrieveEnvVar({
name: "NEXT_PUBLIC_APP_BASE_URL",
schema: z.url(),
}),
)._unsafeUnwrap({ withStackTrace: true });typescript
import { z } from "zod";
import { AppUrl } from "@/shared/domain/value-objects/appUrl";
// 基础助手函数
function retrieveEnvVar<T>(params: { name: string; schema: z.ZodType<T> }): T {
const value = process.env[params.name];
const parsed = params.schema.safeParse(value);
if (parsed.success) return parsed.data;
throw new TypeError(`Environment variable ${params.name} is invalid...`);
}
// 1. 标准字符串验证
export const PUBLIC_APP_NAME = retrieveEnvVar({
name: "NEXT_PUBLIC_APP_NAME",
schema: z.string().min(3),
});
// 2. 类型转换(如端口转为数字)
export const POSTGRES_PORT = retrieveEnvVar({
name: "POSTGRES_PORT",
schema: z.coerce.number().int().positive(),
});
// 3. 包装为值对象
export const PUBLIC_APP_BASE_URL = AppUrl.from(
retrieveEnvVar({
name: "NEXT_PUBLIC_APP_BASE_URL",
schema: z.url(),
}),
)._unsafeUnwrap({ withStackTrace: true });8. Testing Strategy & Co-location
8. 测试策略与同目录存放
Tests MUST live right next to the code they verify.
- : Fast, isolated Unit Tests logic (Domain Models, Use Cases). MUST be handled by Vitest's unit worker.
*.test.ts - : Slower tests requiring infrastructure (Postgres/Testcontainers). MUST be placed inside
*.integration.test.tsdirectories and run via theinfrastructure/helper to rollback changes.txTest - : For testing components in
*.ui.test.ts.presentation/
测试必须与被验证的代码放在同一目录下。
- :快速、隔离的单元测试逻辑(领域模型、用例),必须由Vitest的单元工作器处理。
*.test.ts - :需要基础设施的慢速测试(Postgres/Testcontainers),必须放在
*.integration.test.ts目录下,通过infrastructure/助手运行以回滚更改。txTest - :用于测试
*.ui.test.ts中的组件。presentation/
9. Control Flow Summary (A Complete Request)
9. 控制流程总结(完整请求)
The control flow MUST be as follows:
- User interacts with a Client Component ().
presentation/components/PascalCase.tsx - Component MUST call a Server Action ().
presentation/actions/camelCaseAction.ts - Server Action MUST validate raw inputs (via ), authenticate the user, and call a Use Case via
Zod.serviceContainer - The Use Case () MUST orchestrate logic:
use-cases/camelCase.ts- MUST use on Value Objects to create valid domain primitives.
static from() - MUST call Repository Interfaces () to fetch/save data.
domain/interfaces/ - MUST return a (
Result<Data, DomainError>).neverthrow
- MUST use
- Server Action MUST receive the . If error, it MUST use
Resultto exhaustively map it to a UI-friendly error response string. If success, it MUST return data mapped viaassertNever(). Layer boundaries MUST be maintained..toBranded()
控制流程必须如下:
- 用户交互:用户与客户端组件()交互。
presentation/components/PascalCase.tsx - 组件必须调用**Server Action**()。
presentation/actions/camelCaseAction.ts - Server Action必须通过验证原始输入,认证用户,并通过
Zod调用**用例**。serviceContainer - 用例()必须编排逻辑:
use-cases/camelCase.ts- 必须使用**值对象**的方法创建有效的领域基础值。
static from() - 必须调用**仓库接口**()来获取/保存数据。
domain/interfaces/ - 必须返回(
Result<Data, DomainError>)。neverthrow
- 必须使用**值对象**的
- Server Action必须接收。如果是错误,必须使用
Result将其全面映射为UI友好的错误响应字符串。如果是成功,必须通过assertNever()返回映射后的数据。必须保持层边界。.toBranded()