域 第一个项目结构
少花时间设置文件 。 花更多的时间建立你的域名.
这是什么?
在大多数TypeScript代码库中,您在方便和架构之间选择. 围绕域的清晰边界和角色意味着更多的文件,更多的合同和更多的接线,因此在最后期限的压力下,逻辑在最快速写出的地方就结束. 边界模糊了一条捷径.
域首工程结构去除此选择 : 最干净的道路也是最短的交互式的 CLI 询问新代码的上下文、上层和作用是什么,并用尊重边界的合同、执行、测试骨架和接线来代替它。 只有在抚养规则允许的情况下,才提供抚养权。 做正确的事情需要一些提示;做错误的事情需要额外的努力。 它知道这个 @domain-first 包:类型、出错、处理器、接线和处理器。 生成的文件属于您的工程; 您会写商业规则 .
npm i -D @domain-first/project-structure
npx df-init
npx df-scaffold
API 密码
整个技术规格存在于 API.md (英语)。 。 。 .
指南
代理工具的非交互模式
df-scaffold, (中文(简体) ), df-react 财务报告和审定财务报表 df-integration 接受JSON的请求。 人类可以继续使用交互式菜单. 特工可以先发现计划 和项目ID:
npx df-scaffold --describe
npx df-scaffold --request '{"operation":"query","context":"sales","name":"orders","implementation":{"type":"prisma","save":false},"dependencies":{"PrismaOrdersQuery":["shared-kernel:PrismaInfrastructureService"]}}'
npx df-react --request '{"operation":"widget","name":"admin/orders","widgetType":"separated"}'
npx df-integration --request '{"operation":"add","integrations":["storybook"]}'
--request - 从Stdin读JSON. dryRun: true 不写入预览更改; overwrite: true 明确允许替换已更改的现有生成文件。 答复是JSON,有档案清单、下一步和诊断。 错误返回 ok: false 和非零退出代码而不返回到提示;无效的请求丢弃阶段更改 。 初始化通过 df-init。 。 。 .
这个 API 参考 描述请求字段、发现ID和结果语义.
如何初始化项目
运行 df-init 在项目中 package.json. 安排访问 项目-后端-前端 以顺序。 每个段落询问是否现在配置它: 回答否然后配置它 。 跳过工程保存源文件夹 src; 跳过后端或前端时,该部分不在此列.
npx df-init
# Return to one section later:
npx df-init --section backend
npx df-init --section frontend
工程要求源文件夹 。 后端请求 @domain-first 软件包 envBranchedWire 分支 与 wire,共享内存 DB 和测试库,然后添加所需的共享克内尔文件. 与 @domain-first/handlers-rest,它会添加 端点生成器前端报价 反应 或者说 下一个.js然后是"故事书" 启用故事本运行现有的 React + Vite 集成: 它创建 .storybook 配置、软件包脚本和构建输出 .gitignore 输入。 选择配置后端的 Next.js 询问 对 REST 端点使用 @ domain- first/ handlers- next 一次 一次吗 添加为 " 是 " handlers-rest 连接到后端软件包 nextAdapter 并创造出全程路线。 这以前是可行的 next 已安装。 随着后端被推迟,REST的设置等待到后端配置. 丢失的软件包被打印为安装命令;没有自动安装.
所有设置和保存的执行类型都生活在 domain-first.project-structure.config.json数字 :
{
"project": { "rootFolder": "src" },
"backend": {
"domainFirstPackages": ["@domain-first/wire", "@domain-first/handlers"],
"withInMemoryDb": true,
"testingLibrary": "vitest",
"implementations": {
"repositories": { "primary": ["prisma", "mongo"], "test": ["stub"] },
"commands": { "primary": ["sql"], "test": [] },
"queries": { "primary": ["elastic"], "test": ["fake"] },
"ports": { "primary": ["s3"], "test": [] }
},
"rules": {
"dependencies": {
"domain-services": ["domain-services"],
"repositories": ["domain-services", "application-ports", "infrastructure-services"],
"application-ports": ["repositories", "domain-services", "application-ports", "infrastructure-services"],
"application-services": ["repositories", "domain-services", "application-ports", "commands", "queries"],
"use-cases": ["repositories", "domain-services", "application-ports", "application-services", "commands", "queries"],
"commands": ["infrastructure-services"],
"queries": ["infrastructure-services"],
"infrastructure-services": ["repositories", "domain-services", "application-ports", "infrastructure-services"],
"presentation-services": ["application-ports", "application-services", "use-cases", "commands", "queries", "presentation-services"]
},
"lint": {
"context-isolation": true,
"shared-kernel": true,
"layers": true,
"dependencies": true,
"react": true,
"clock": true,
"config": true
}
}
},
"frontend": {
"react": { "framework": "react", "storybookFramework": "@storybook/react-vite" }
}
}
将配置交给你的团队 重运行 df-init 提议 项目, (中文(简体) ), 后端, (中文(简体) ), 前端 财务报告和审定财务报表 所有款次,以当前值作为默认值。 单节更新保存了其他部分并保存了选择. 后端设置将所有标准依赖性列表写入并启用 backend.rules,因此可以直接编辑。 在重新配置时,只填写缺失的规则;现有名单, add编号 :remove 补丁和已停用的检查被保留. 后端设置保存了现有的支持文件并恢复了缺失的文件. 更改源文件夹会影响未来的生成;现有源不会被移动. 反应通过存在 frontend.react; 其编号 framework 实值 react 或者说 next,所以Next.js使用相同的反应发电机. 前端选择从所保存的值预填入, 返回 Next.js 当 next 已声明为 package.json两种选择 @storybook/react-vite 从"故事书"集成;没有单独的"故事书"框架提示. 框架设置仍按 frontend.<framework>。 。 。 .
先前主要版本的配置不读:命令停止并请求您运行 df-upgrade。缺失后端或反应部分生成一条指向相应消息 df-init --section 命令。 初始化和配置由项目所有者进行;代理技能只读取这些设置并请求所有者配置缺失的段落.
节省的执行类型
仓库、命令、查询和端口生成器提供执行类型的菜单. 输入执行类型并保存供未来使用 接受一个新的 kebab 大小写名称并将其保存在 backend.implementations 在共享配置中. 只输入此运行的执行类型 生成代码而不添加保存的选择。 这两项行动都适用于初级和测试实施,包括增加现有合同.
每个类别及其主要/测试选择是分开的,由项目的所有背景共同选择。 普通类型,例如: api 财务报告和审定财务报表 mock 非预设: 手动输入或保存到以后。 菜单包含被保存的选择,可用的系统执行和两个人工输入动作.
系统实施有明确标记:
- 系统: 交叉语句( 在另一个限定语境中的调用处理器) —— 端口适配器,只有在另一个有界限上下文存在时才使用.
- 系统:内存(使用共享的内存数据库) ——寄存器,命令和查询,只有当共享的内存 DB 集成被启用,其文件已经准备好.
第一个可用的保存类型是默认选择的,然后是可用的系统类型;否则将选择保存条目动作,其名称输入为空. 在现有合同中添加一项执行,既显示主要选择,也显示测试选择。 通过保存动作,普通类型会按照其选定的环境被记住;只运行的动作从不注册.
更新至 backend.implementations 保存所有其他配置区域,并与生成的文件一起保存;取消同时丢弃。 Prisma 合并增加 prisma 到寄存器、命令和查询选择。 没有全球持续违约设置.
如何创建有界限的上下文
选择 脚手架新上下文 并输入名称, 如 sales。它开始为空:在第一个错误之前没有错误命名空间.
对每个名字使用小写 kebab 大小写 : place-order 变成 PlaceOrderUseCase。 。 。 .
如何添加总和
选择 范围:销售 / 领域 / 新的总量名称 order,并添加一个带有 prisma 执行情况和实施情况 in-memory 测试一.
src/bounded-contexts/sales/
├── domain/aggregates/order/
│ ├── index.ts
│ ├── order.aggregate-root.ts
│ └── order-repository.ts
├── infrastructure/repositories/
│ ├── prisma/prisma-order-repository.ts
│ └── in-memory/in-memory-order-repository.ts
└── wiring/repositories/wire-order-repository.ts
实体和价值对象进入一个集合(新实体, (中文(简体) ), 新建值对象)或进入上下文 domain/。 。 。 .
如何添加使用大小写、命令或查询
选择 应用程序 – 新使用大小写, (中文(简体) ), 新命令 或者说 新建查询与 @domain-first/handlers班级有计划占位符和一个 handle 与 defineHandler。 。 。 .
export class PlaceOrderUseCase {
constructor() {}
static inputSchema = {};
static outputSchema = {};
handle = defineHandler({
inputSchema: PlaceOrderUseCase.inputSchema,
outputSchema: PlaceOrderUseCase.outputSchema,
handler: async (input) => {
return {};
},
});
}
命令或查询在 application/ 执行情况 infrastructure/就像一个仓库 查询只读取数据,绝不更改数据;任何更改都是命令.
如何切换执行
与 @domain-first/wire,存储器、命令、查询和端口每个执行一个工厂,由 NODE_ENV数字 :
export const wireOrderRepository = envBranchedWire({
test: wireInMemoryImplementation,
development: wirePrismaImplementation,
production: wirePrismaImplementation,
});
测试得到测试的执行,其他的都得到初级.
您的环境可能不同: 选择 自定义分支 在后端设置时输入, 例如 . local, staging, production,然后选择测试分支,让我们 df-init 添加 test,或者不设一个:然后每个分支得到初级执行,不需要测试执行。 生成的电线使用这些树枝. 如何选择当前分支 : env-branched-wire.ts 获得空 currentBranch 函数,直到您写入它才编译.
如何连接依赖关系
作为最后一步,装有电线的发电机提供新单元可能依赖的模块,这些模块来自相同的界限上下文和共享核心。 跨文本端口适配器也参见相邻上下文的应用程序处理器. 工厂和建筑厂都增加了每个选择:
export class PlaceOrderUseCase {
constructor(private readonly orderRepository: OrderRepository) {}
}
export const wirePlaceOrderUseCase = wireClass(PlaceOrderUseCase, [
wireOrderRepository,
]);
只提供了尊重地层的依赖性:一个用例不能取另一个用例,域不能取应用程序. 以后要添加依赖,请选择 电线依赖 在上下文菜单中。 有十个以上选择的菜单,如模块列表,取一个搜索:将名称的一部分打入过滤.
如何添加执行内容
选择 基础设施 + 新执行 以给一个现有的寄存器、命令、查询或端口一个额外的执行,比如 local a 的适配器 FileStorage 旁边的端口 s3 财务报告和审定财务报表 in-memory与 wire,选择使用它的环境: test 继续 in-memory, (中文(简体) ), development 移动到 local, (中文(简体) ), production 继续 s3。 。 。 .
如何移动或重命名模块
选择 移动或重命名 在上下文菜单中:重命名一个集合、实体、值对象、服务、端口、查询、命令或使用大小写,将其移动到另一个有边框的上下文或共享克内尔上下,或将一个实体在集合中或外移动。 其文件、执行、电线、规格以及项目中的每个导入和衍生名称都随之而来: OrderRepository, (中文(简体) ), PrismaOrderRepository, (中文(简体) ), wireOrderRepository,则 orderRepository 参数,一个使用大小写,内嵌 DB 部分。 将禁止进口引入上下文之间的举动,连同将跨越边界的进口清单被拒绝.
如何删除模块
选择 删除 在上下文菜单中选择模块。 其所有档案包括:合同、执行、电线和规格、一个装有整个文件夹的集合,加上其他地方为其添加的线条生成器(一个实体的再出口总量,一个集合的内膜DB部分)。 虽然另一个文件仍然导入它,但没有任何内容被删除,CLI列出了这些导入,因此没有留下任何破损.
如何添加错误
选择 域 – 出错 在上下文命名空间中添加错误,创建嵌入式命名空间,或者添加出错。 上下文命名空间( E)SalesErrors = errorNamespace('sales') 内 domain/errors/index.ts)是在没有命名空间的情况下添加第一个错误后创建的.
export class OutOfStock extends SalesErrors.define('out-of-stock') {}
名称不重复上下文:错误 payment 名称空间 sales 实值 PaymentDeclined在共享核心中,它们以 SharedKernelError数字 : TimeoutSharedKernelError。 。 。 .
如何在上下文之间共享代码
将每个上下文的域和应用模型保持本地化. 对于另一个上下文的数据或行为,创建 应用程序端口 b. 消费背景下的应用程序 + 新端口选择 系统: 交叉语句( 在另一个限定语境中的调用处理器) 为它的适配器。 不需要运输或终点.
端口适配器菜单包含已保存的端口类型和手动输入动作,加 系统: 交叉语句( 在另一个限定语境中的调用处理器) 当另一个受约束的上下文存在时。 没有自动 api, (中文(简体) ), mock 或者说 in-memory 端口选择。 已保存的主端口和测试端口类型仍然分开;添加另一个适配器可以显示两个列表. 从寄存器、命令和查询中的选择不添加到端口菜单中.
与 wire, (中文(简体) ), 交叉连接的依赖性.. 财务报告和审定财务报表 电线依赖 提供查询、命令和使用来自每个相邻上下文、带有上下文标记的大小写,例如: InvoiceQuery (billing). CLI将每个操作器注入适配器并用接线导入工厂. 名称包括避免相撞的上下文: BillingInvoiceQuery, (中文(简体) ), wireBillingInvoiceQuery。 。 。 .
定义您端口中的能力方法并在适配器中执行:绘制您的输入图,调用被注入的处理器,并将其结果和出错映射到您自己的模型. 分开 mock 或者说 stub 测试适配器, 因此测试不会运行相邻上下文 。 无 wire将操作器通过构造器注入,然后自己编成接线.
仅在下端口适配器 infrastructure/application-adapters/cross-context/ 可能导入邻居的处理器。 电线只能将它们注入那些适配器;查询/命令执行和其他适配器类型仍然孤立. 这个 cross-context 类型无法用于共享内核端口。 共享的普遍模式、技术港口和服务属于 共享核心 (单位:千美元)src/shared-kernel) (中文(简体) ).
如何添加 REST 端点
与 @domain-first/handlers-rest选择 演示文稿 → 新处理器- REST 端点,选择或创建控制器、方法和相对路径。 A. 背景情况 sales控制器 orders路径 recent 财务报告和审定财务报表 get 给:
src/bounded-contexts/sales/presentation/rest/orders/recent/GET.ts // GET /sales/orders/recent
对于上下文本身,选择上下文根控制器 (根)-/社会-网络它生活在 presentation/rest/(root)/,不包含路径的文件夹:空路径和 get 给 GET /social-networks 内 src/bounded-contexts/social-networks/presentation/rest/(root)/GET.ts (单位:千美元)SocialNetworksGetEndpoint),路径 :id 给 GET /social-networks/:id路径 from-scratch 与 post 给 POST /social-networks/from-scratch。一个路线属于一个控制器,所以 (root)/from-scratch 和控制器 from-scratch 相互排斥。 以8.5为背景端点的项目 npx df-upgrade。 。 。 .
每个端点都使用项目的一个端点生成器, RestEndpointGeneratorPresentationService 内 src/shared-kernel/presentation/services/初始化脚手架,作为常规的共享克内尔演示服务(类,谱和线):
/**
* The adapter of your HTTP framework.
*/
const adapter = undefined as unknown as Adapter<any[], any>
export class RestEndpointGeneratorPresentationService {
get withoutGuards(): EndpointGenerator {
return createEndpoint({
errors: [invalidRequest()]
})(adapter) as EndpointGenerator
}
}
设置模块级别 adapter 常数高于您的 HTTP 框架的适配器; 类通过关闭使用。 如果您连接了下一个适配器, 文件导入 nextAdapter 从 @domain-first/handlers-next 相反,没有设置,和 app/api/[...path]/route.ts (单位:千美元)src/app/… 当文件夹存在时)服务端点 nextEndpoints。通过接线,线路服务 restHandlers,所以每一个新的终点都工作而不碰它;在第一个终点之前,路线是不编译的,CLI也这么说. 每个生成方法都是返回的取出器 EndpointGenerator: 每个终点增加一个(例如) withAuth,带有 context 和错误绘图。 保留 invalidRequest() 联 合 国 @domain-first/handlers-rest 无效请求( e)400)并添加自己的绘图: errors: [httpError(MyError, 404), invalidRequest()]。一个新的终点呼叫 this.endpointGenerator(...) 并且不是故意编译的: 选择自己在它的 get handle(),比如说. this.endpointGenerator.withoutGuards(...)。 。 。 .
通过接线,端点将生成器和从上下文的询问中选择的处理器,命令和使用大小写,所有端点被收集为 restHandlers 内 src/presentation/rest/index.ts连接到您的 HTTP 服务器 .
脚手架反应模块
与 frontend.react 配置, 运行 npx df-react: pages,部件(monolithic,有分离的UI,或无头),有故事本故事的UI-kit组件,以及共享的钩子. 名称可包括文件夹:
admin/panel/users → pages/admin/panel/admin-panel-users-page.tsx (AdminPanelUsersPage)
随着Next.js,一个新页面也得到了它的App路由器路线, app/admin/panel/users/page.tsx,它使得 AdminPanelUsersPage。现有路径永远不会被覆盖.
移动或重命名页面 财务报告和审定财务报表 移动或重命名部件 移动文件并更新项目中的每个导入. 删除页面 财务报告和审定财务报表 删除部件 一旦没有导入它们, 删除它们, 如果您同意的话, 将包含 Next.js 页面的路径 。 UI-kit组件和共享钩可以移动,更名并同样删除. 随着Next.js,移动一页 也提出移动它的路线, app/admin/users/ → app/backoffice/users/,因为它的URL随它而改变.
如何添加工具
运行 npx df-integration 来配置一个或几个 REST 端点生成器、 共享的相模 DB、 故事本、 Prisma 8、 Vitest、 Playwright 、 在 Testcontainers 、 OpenAPI 生成器或 Orval API 客户端( 类型客户端和 React 查询钩头) 上的集成测试。 集成添加配置文件、软件包脚本和 .gitignore 条目,然后打印一个安装命令复制。 没有为你安装或运行.
端点生成器和内膜DB是什么 df-init 脚手架;作为集成,它们会在后期添加或还原缺失的文件,并且永远不覆盖已存在的文件.
使用Testcontainers, CLI 询问哪些执行类型获得集成测试(prisma 对, stripe 也许不是。 从那时起,每一次新的执行这种类型,比如: PrismaOrdersQuery中,获取一个字符 specs/integration/ 它通过它的电线 并运行对 PostgreSQL 在容器; 基础设施服务 当你问。 单位规格与域名模式,服务及用例一致.
如何检查规则
npx df-lint
它报告了违反域-第一规则的情况:禁止在有界上下文之间进口、禁止从地层进口、使用依赖性规则不允许的模块(在进口和接线中)、进口它们不应进口的模块、阅读没有时钟和配置端口的时间或环境变量。 当发现某物时,它与1一起退出,这样你就可以在CI中运行.
df-init 脚手架上所有标准规则 backend.rules 在后端设置中。 团队可以直接编辑这些条目:每种模块可能取决于什么(backend.rules.dependencies)和哪些检查运行(backend.rules.lint) (中文(简体) ). df-scaffold 根据同样的规则提供依赖性。 对于较旧或手动缩短的配置 backend.rules,默认值和每一次检查运行;类型和检查您不提及保留其默认值.
- 进来吧
backend.rules.dependencies(作为钥匙和列表):repositories, (中文(简体) ),domain-services, (中文(简体) ),application-ports, (中文(简体) ),application-services, (中文(简体) ),use-cases, (中文(简体) ),commands, (中文(简体) ),queries, (中文(简体) ),infrastructure-services, (中文(简体) ),presentation-services。 。 。 . - 类型值: 替换默认值的列表, 或
{ "add": [...], "remove": [...] }。 。 。 . - 报到
backend.rules.lint数字 :context-isolation, (中文(简体) ),shared-kernel, (中文(简体) ),layers, (中文(简体) ),dependencies, (中文(简体) ),react, (中文(简体) ),clock, (中文(简体) ),config。 。 。 .
默认和细节 : 扶养规则, (中文(简体) ), df-lint。 。 。 .
{
"backend": {
"rules": {
"dependencies": {
"use-cases": {
"add": [
"use-cases"
]
}
},
"lint": {
"clock": false
}
}
}
}
如何看待结构
npx df-graph # bounded contexts and the Shared Kernel
npx df-graph --context sales # the modules of one context by layer
npx df-graph --html # open the rendered charts in the browser
npx df-graph --out docs/ARCHITECTURE.md
df-graph 读取线条并绘制人鱼图:哪些上下文通过跨文本端口互相联系,这些上下文使用共享克内尔,在上下文中,每个模块都依赖于什么. 它会打印图表,打开已渲染的一页 --html (美人鱼来自CDN),或写作 --out 以扩展格式: .md 对于GitHub, 它使图表, .mmd, (中文(简体) ), .json 或者说 .html。 。 。 . --json 给工具和代理人同样的图表.
如何升级为新的主要版本
在将软件包升级为新的主要版本后, 运行 :
npx df-upgrade --dry-run # review the plan
npx df-upgrade
它发现项目需要什么,显示每个变化,并在一次确认后一起应用; --yes 跳过问题。 在此之前,其他命令, df-lint 包含, 请停止 run npx df-upgrade再次运行它打印 Everything is up to date.
从 7.x 将配置拆分为 project, (中文(简体) ), backend 财务报告和审定财务报表 frontend,记录 frontend.react.framework 明确(next 何时 next 进入 package.json,否则 react)和移动 defaultPersistenceLayerImplementation 到第一个保存的主类型寄存器、命令和查询。 然后更新代理技能 npx df-skills。 。 。 .
如何教你的编码代理
包船技巧 教编码剂 规划特征,脚手架与 df-scaffold,用 df-react 并执行生成的代码.
npx df-skills --claude
在升级了软件包以获得新版本后再运行. 添加 --principles 还将方案拟订原则与 CLAUDE.md 财务报告和审定财务报表 AGENTS.md。 。 。 .
许可证
麻省理工学院