npx skills add https://github.com/wondelai/skills --skill domain-driven-design通过围绕业务领域建模代码来应对软件复杂性的框架。基于一个基本事实:软件的最大风险不是技术故障——而是构建了一个不能反映业务实际运作方式的模型。
模型即代码;代码即模型。 软件应体现对业务领域的深刻、共享的理解。当领域专家和开发者使用同一种语言,并且这种语言直接在代码库中表达时,复杂性就变得可管理,需求被精确捕获,系统随着业务变化而优雅地演进。
目标:10/10。 在评审或创建领域模型时,根据对以下原则的遵循程度,按 0-10 分进行评分。10/10 表示完全符合所有指导原则;较低的分数表示存在需要解决的差距。始终提供当前分数以及达到 10/10 所需的具体改进措施。
核心概念: 开发者和领域专家之间共享的、严谨的语言,在对话、文档和代码中一致使用。当语言改变时,代码也随之改变。当代码揭示出命名不当之处时,语言也随之精炼。
为何有效: 歧义是大多数建模失败的根源。当开发者说“订单”而领域专家指的是“采购请求”时,错误就不可避免。通用语言强制对齐,使得每个类、方法和变量名都映射到业务认可和验证的概念。
关键见解:
DataProcessor 对比 ClaimAdjudicator)隐藏了领域逻辑代码应用:
| 上下文 | 模式 |
|---|
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 示例 |
|---|
| 类命名 | 以领域概念命名类 | LoanApplication,而不是 RequestHandler |
| 方法命名 | 使用业务使用的动词 | policy.underwrite(),而不是 policy.process() |
| 事件命名 | 过去时态的领域动作 | ClaimSubmitted,而不是 DataSaved |
| 模块结构 | 按领域概念组织 | shipping/,billing/,而不是 controllers/,services/ |
| 代码审查 | 拒绝纯技术性名称 | 将 Manager、Helper、Processor、Utils 标记为命名异味 |
核心概念: 限界上下文是一个明确的边界,在此边界内定义并适用特定的领域模型。同一个词(例如“客户”)在不同的上下文中可能意味着不同的事物。上下文映射定义限界上下文之间的关系和转换策略。
为何有效: 试图维护单一统一模型的大型系统不可避免地会陷入不一致。限界上下文承认业务的不同部分有不同的模型,并使边界明确。然后,上下文映射管理集成,使每个上下文保持其内部一致性。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 服务集成 | 防腐层 | 在边界处将外部 API 响应转换为你的领域对象 |
| 团队协作 | 共享内核 | 两个团队共同拥有一个小的 Money 值对象库 |
| 遗留系统迁移 | 顺从者 / ACL | 将遗留系统包装在使用你的领域语言的适配器后面 |
| API 设计 | 开放主机服务 + 发布语言 | 公开一个具有规范模式、文档完善的 REST API |
| 模块边界 | 每个上下文单独的包 | myapp.shipping 和 myapp.billing 包,带有明确的转换 |
核心概念: 实体具有跨越状态变化而持续存在的标识。值对象完全由其属性定义且不可变。聚合是实体和值对象的集群,具有一个强制执行一致性边界的单一根实体。
为何有效: 没有这些区分,系统会将所有事物都视为具有标识、可变且具有数据库级关系的对象,导致状态混乱、更新不一致和脆弱的并发性。聚合划定了一致性边界:边界内的一切保证一致;边界外的一切最终一致。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 标识跟踪 | 带 ID 的实体 | Order 由 orderId 标识,在状态变化中持续存在 |
| 不可变属性 | 值对象 | Address(street, city, zip) —— 替换,永不改变 |
| 一致性边界 | 聚合根 | Order 是根;OrderLine 项仅通过它存在 |
| 跨聚合引用 | 通过 ID 引用 | Order 存储 customerId,而不是 Customer 对象 |
| 并发控制 | 根上的乐观锁 | Order 上的版本字段;如果两个编辑竞争则冲突 |
核心概念: 领域事件捕获了领域中发生的、领域专家关心的事情。事件以过去时命名(OrderPlaced,PaymentReceived),代表已经发生的事实。
为何有效: 领域事件将原因与结果解耦。当 OrderPlaced 被发布时,运输上下文、计费上下文和通知上下文都可以独立响应,而订单上下文无需了解它们中的任何一个。这减少了耦合,实现了最终一致性,并创建了自然的审计追踪。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 状态转换 | 在领域动作上引发事件 | order.place() 引发 OrderPlaced 事件 |
| 跨上下文集成 | 发布集成事件 | OrderPlaced 在运输上下文中触发 ShippingLabelRequested |
| 审计追踪 | 将事件存储为历史 | 事件日志:OrderPlaced -> PaymentReceived -> OrderShipped |
| 最终一致性 | 异步事件处理器 | InventoryReserved 处理器在 OrderPlaced 后异步更新库存 |
| 事件溯源 | 从事件重建状态 | 重放所有 Account* 事件以推导当前账户余额 |
核心概念: 仓储提供领域对象内存集合的假象,隐藏持久化细节。工厂封装复杂的对象创建逻辑,确保聚合总是在有效状态下创建。
为何有效: 领域逻辑永远不应依赖于对象如何存储或构造。仓储抽象掉 SQL、ORM 和数据访问,使领域代码读起来像业务逻辑。工厂确保从聚合诞生那一刻起就满足不变量,防止无效对象的存在。
关键见解:
findPendingOrders(),而不是 getByStatusCode(3)add/remove;面向持久化的仓储使用 saveOverdueInvoiceSpecification代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 数据访问抽象 | 仓储接口 | 领域层中的 OrderRepository.findByCustomer(customerId) |
| 复杂创建 | 工厂方法 | Order.createFromQuote(quote) 从 Quote 聚合验证和组装 |
| 查询封装 | 规约 | spec = OverdueBy(days=30); repo.findMatching(spec) |
| 重建 | 仓储加载聚合 | 仓储将 Order + OrderLines 从数据库行组装成完整的聚合 |
| 端口和适配器 | 接口在领域层,实现在基础设施层 | 领域层中的 interface OrderRepository;基础设施层中的 PostgresOrderRepository |
核心概念: 并非系统的所有部分都同等重要。战略设计识别核心领域——提供竞争优势的部分——并将其与支撑子域(必要但无差异性)和通用子域(商品化,购买或使用现成的)区分开来。
为何有效: 对每个模块都应用相同严格性的团队会分散他们最优秀的人才,并对商品化功能进行过度设计。通过识别核心领域,组织将最优秀的开发者、最深入的建模和最谨慎的设计投入到最重要的地方,而在其他地方使用更简单的方法或第三方解决方案。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 构建与购买决策 | 分类子域类型 | 构建自定义定价引擎(核心);使用 Stripe 进行支付(通用) |
| 团队分配 | 最优秀的开发者在核心领域 | 高级工程师对承保规则建模;初级工程师集成电子邮件服务 |
| 代码组织 | 将核心与通用分离 | domain/pricing/(深度模型)对比 infrastructure/email/(薄适配器) |
| 简化 | 精炼核心概念 | 从单体 InsuranceService 中提取 PolicyRatingEngine |
| 文档 | 领域愿景声明 | 一页文档:“我们的竞争优势是使用...进行实时风险评分” |
| 错误 | 为何失败 | 修复方法 |
|---|---|---|
| 使用技术名称而非领域语言 | 领域逻辑隐藏在 DataManager 和 ProcessorService 后面;专家无法验证模型 | 重命名为领域术语:ClaimAdjudicator,PolicyUnderwriter;如果没有领域术语,概念可能错了 |
| 一个模型统治一切 | 一个单一的 Customer 类服务于计费、运输和营销,变得臃肿且矛盾 | 定义限界上下文;每个上下文都有自己的 Customer 模型,只包含它需要的属性 |
| 具有许多嵌套实体的巨型聚合 | 并发冲突、加载缓慢、事务瓶颈 | 保持聚合小;通过 ID 引用其他聚合;在聚合之间使用最终一致性 |
| 贫血领域模型(所有逻辑都在服务中) | 领域对象是数据袋;业务规则分散在服务类中;重复和不一致 | 将行为移入实体和值对象;服务仅编排,从不包含领域逻辑 |
| 集成点没有防腐层 | 外部模型渗入你的领域;你的代码与外部模式和命名耦合 | 将每个外部系统包装在转换层后面,该层转换为你的通用语言 |
| 将限界上下文视为微服务 | 过早的服务提取;没有好处的分布式系统复杂性 | 限界上下文是模型边界,不是部署单元;从单体中的模块开始 |
| 跳过领域专家协作 | 开发者发明了一个不符合业务现实的模型;昂贵的返工 | 与领域专家定期进行建模会议;精炼模型直到专家说“是的,这就是它的工作方式” |
| 问题 | 如果否 | 行动 |
|---|---|---|
| 领域专家能读懂你的类名并理解它们吗? | 代码使用技术术语而非领域语言 | 重命名类、方法和事件以使用通用语言 |
| 限界上下文边界是否明确定义? | 模型跨越边界;同一个术语有不同的含义 | 绘制上下文映射图;定义明确的边界和转换策略 |
| 聚合是否小(一个根 + 最小集群)? | 聚合大、慢且有并发问题 | 分解为更小的聚合;通过 ID 引用;接受最终一致性 |
| 领域对象是否包含行为,而不仅仅是数据? | 贫血模型;逻辑分散在服务类中 | 将业务规则移入实体和值对象 |
| 是否使用领域事件进行跨聚合通信? | 聚合之间紧耦合;同步链 | 引入领域事件;让聚合异步响应事件 |
| 每个外部集成点是否有防腐层? | 外部模型污染你的领域 | 在每个集成边界添加转换层 |
| 你是否识别出哪个子域是核心? | 对所有事物付出同等努力;最优秀的人才分散 | 分类子域;将深度建模集中在核心领域 |
此技能基于 Eric Evans 开发的领域驱动设计方法论。要了解完整的方法论、模式和更深入的见解,请阅读原著:
Eric Evans 是一位软件设计顾问,也是领域驱动设计的创始人。他曾在金融、保险和物流等行业的大型系统上工作,并在此过程中开发了后来成为 DDD 的模式和实践。他于 2003 年出版的《领域驱动设计:软件核心复杂性应对之道》一书被广泛认为是有史以来最具影响力的软件架构书籍之一。Evans 创立了 Domain Language 咨询公司,帮助团队将 DDD 应用于复杂的软件项目。他是全球软件会议的常任主旨演讲者,并通过研讨会、社区参与以及与从业者的合作,不断完善和发展 DDD 概念。他的工作塑造了现代微服务、事件溯源和战略软件设计的方法。
每周安装量
264
代码仓库
GitHub 星标数
260
首次出现
2026年2月23日
安全审计
安装于
codex255
opencode254
gemini-cli251
kimi-cli251
cursor251
github-copilot251
Framework for tackling software complexity by modeling code around the business domain. Based on a fundamental truth: the greatest risk in software is not technical failure -- it is building a model that does not reflect how the business actually works.
The model is the code; the code is the model. Software should embody a deep, shared understanding of the business domain. When domain experts and developers speak the same language and that language is directly expressed in the codebase, complexity becomes manageable, requirements are captured precisely, and the system evolves gracefully as the business changes.
Goal: 10/10. When reviewing or creating domain models, rate them 0-10 based on adherence to the principles below. A 10/10 means full alignment with all guidelines; lower scores indicate gaps to address. Always provide the current score and specific improvements needed to reach 10/10.
Core concept: A shared, rigorous language between developers and domain experts that is used consistently in conversation, documentation, and code. When the language changes, the code changes. When the code reveals awkward naming, the language is refined.
Why it works: Ambiguity is the root cause of most modeling failures. When a developer says "order" and a domain expert means "purchase request," bugs are inevitable. A ubiquitous language forces alignment so that every class, method, and variable name maps to a concept the business recognizes and validates.
Key insights:
DataProcessor vs. ClaimAdjudicator) hides domain logicCode applications:
| Context | Pattern | Example |
|---|---|---|
| Class naming | Name classes after domain concepts | LoanApplication, not RequestHandler |
| Method naming | Use verbs the business uses | policy.underwrite(), not policy.process() |
| Event naming | Past-tense domain actions | ClaimSubmitted, not DataSaved |
| Module structure |
See: references/ubiquitous-language.md
Core concept: A bounded context is an explicit boundary within which a particular domain model is defined and applicable. The same word (e.g., "Customer") can mean different things in different contexts. Context maps define the relationships and translation strategies between bounded contexts.
Why it works: Large systems that try to maintain a single unified model inevitably collapse into inconsistency. Bounded contexts accept that different parts of the business have different models and make the boundaries explicit. Context maps then manage integration so that each context preserves its internal consistency.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| Service integration | Anti-Corruption Layer | Translate external API responses into your domain objects at the boundary |
| Team collaboration | Shared Kernel | Two teams co-own a small Money value object library |
| Legacy migration | Conformist / ACL | Wrap legacy system behind an adapter that speaks your domain language |
| API design | Open Host Service + Published Language | Expose a well-documented REST API with a canonical schema |
| Module boundaries | Separate packages per context | myapp.shipping and myapp.billing packages with explicit translation |
See: references/bounded-contexts.md
Core concept: Entities have identity that persists across state changes. Value Objects are defined entirely by their attributes and are immutable. Aggregates are clusters of entities and value objects with a single root entity that enforces consistency boundaries.
Why it works: Without these distinctions, systems treat everything as a mutable, identity-bearing object with database-level relationships, leading to tangled state, inconsistent updates, and fragile concurrency. Aggregates draw a consistency boundary: everything inside is guaranteed consistent; everything outside is eventually consistent.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| Identity tracking | Entity with ID | Order identified by orderId, survives state changes |
| Immutable attributes | Value Object | Address(street, city, zip) -- replace, never mutate |
| Consistency boundary | Aggregate Root | Order is root; OrderLine items exist only through it |
| Cross-aggregate reference | Reference by ID | Order stores , not a object |
See: references/building-blocks.md
Core concept: A domain event captures something that happened in the domain that domain experts care about. Events are named in past tense (OrderPlaced, PaymentReceived) and represent facts that have already occurred.
Why it works: Domain events decouple the cause from the effect. When OrderPlaced is published, the shipping context, billing context, and notification context can each react independently without the ordering context knowing about any of them. This reduces coupling, enables eventual consistency, and creates a natural audit trail.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| State transitions | Raise event on domain action | order.place() raises OrderPlaced event |
| Cross-context integration | Publish integration event | OrderPlaced triggers ShippingLabelRequested in shipping context |
| Audit trail | Store events as history | Event log: OrderPlaced -> PaymentReceived -> OrderShipped |
See: references/domain-events.md
Core concept: Repositories provide the illusion of an in-memory collection of domain objects, hiding persistence details. Factories encapsulate complex object creation logic, ensuring that aggregates are always created in a valid state.
Why it works: Domain logic should never depend on how objects are stored or constructed. Repositories abstract away SQL, ORMs, and data access so that domain code reads like business logic. Factories ensure that invariants are satisfied from the moment an aggregate is born, preventing invalid objects from ever existing.
Key insights:
findPendingOrders(), not getByStatusCode(3)add/remove; persistence-oriented repositories use saveOverdueInvoiceSpecificationCode applications:
| Context | Pattern | Example |
|---|---|---|
| Data access abstraction | Repository interface | OrderRepository.findByCustomer(customerId) in domain layer |
| Complex creation | Factory method | Order.createFromQuote(quote) validates and assembles from a Quote aggregate |
| Query encapsulation | Specification | spec = OverdueBy(days=30); repo.findMatching(spec) |
| Reconstitution | Repository loads aggregate | Repository assembles Order + from DB rows into a complete aggregate |
See: references/repositories-factories.md
Core concept: Not all parts of a system are equally important. Strategic design identifies the Core Domain -- the part that provides competitive advantage -- and distinguishes it from Supporting Subdomains (necessary but not differentiating) and Generic Subdomains (commodity, buy or use off-the-shelf).
Why it works: Teams that apply the same rigor to every module spread their best talent thin and over-engineer commodity functionality. By identifying the Core Domain, organizations invest their best developers, deepest modeling, and most careful design where it matters most, while using simpler approaches or third-party solutions elsewhere.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| Build vs. buy decision | Classify subdomain type | Build custom pricing engine (core); use Stripe for payments (generic) |
| Team allocation | Best developers on Core Domain | Senior engineers model the underwriting rules; juniors integrate the email service |
| Code organization | Separate core from generic | domain/pricing/ (deep model) vs. infrastructure/email/ (thin adapter) |
| Simplification | Distill core concepts | Extract a PolicyRatingEngine from a monolithic InsuranceService |
| Documentation | Domain Vision Statement |
See: references/strategic-design.md
| Mistake | Why It Fails | Fix |
|---|---|---|
| Using technical names instead of domain language | Domain logic is hidden behind DataManager and ProcessorService; experts cannot validate the model | Rename to domain terms: ClaimAdjudicator, PolicyUnderwriter; if no domain term exists, the concept may be wrong |
| One model to rule them all | A single Customer class serving billing, shipping, and marketing becomes bloated and contradictory | Define bounded contexts; each context gets its own Customer model with only the attributes it needs |
| Giant aggregates with many nested entities |
| Question | If No | Action |
|---|---|---|
| Can a domain expert read your class names and understand them? | Code uses technical jargon instead of domain language | Rename classes, methods, and events to use ubiquitous language |
| Are bounded context boundaries explicitly defined? | Models bleed across boundaries; the same term means different things | Draw a context map; define explicit boundaries and translation strategies |
| Are aggregates small (one root + minimal cluster)? | Aggregates are large, slow, and have concurrency issues | Break into smaller aggregates; reference by ID; accept eventual consistency |
| Do domain objects contain behavior, not just data? | Anemic model; logic scattered in service classes | Move business rules into entities and value objects |
| Are domain events used for cross-aggregate communication? | Tight coupling between aggregates; synchronous chains | Introduce domain events; let aggregates react to events asynchronously |
| Is there an Anti-Corruption Layer at every external integration? | Foreign models pollute your domain | Add a translation layer at each integration boundary |
| Have you identified which subdomain is core? | Equal effort on everything; best talent spread thin |
This skill is based on the Domain-Driven Design methodology developed by Eric Evans. For the complete methodology, patterns, and deeper insights, read the original book:
Eric Evans is a software design consultant and the originator of Domain-Driven Design. He has worked on large-scale systems in industries including finance, insurance, and logistics, where he developed the patterns and practices that became DDD. His 2003 book Domain-Driven Design: Tackling Complexity in the Heart of Software is widely regarded as one of the most influential software architecture books ever written. Evans founded Domain Language, a consulting firm that helps teams apply DDD to complex software projects. He is a frequent keynote speaker at software conferences worldwide and continues to refine and evolve DDD concepts through workshops, community engagement, and collaboration with practitioners. His work has shaped modern approaches to microservices, event sourcing, and strategic software design.
Weekly Installs
264
Repository
GitHub Stars
260
First Seen
Feb 23, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
codex255
opencode254
gemini-cli251
kimi-cli251
cursor251
github-copilot251
Flutter状态管理教程:MVVM与Provider实现单向数据流和架构模式
1,100 周安装
| Organize by domain concept |
shipping/, billing/, not controllers/, services/ |
| Code review | Reject technical-only names | Flag Manager, Helper, Processor, Utils as naming smells |
customerIdCustomer| Concurrency control | Optimistic locking on root | Version field on Order; conflict if two edits race |
| Eventual consistency | Async event handlers | InventoryReserved handler updates stock asynchronously after OrderPlaced |
| Event sourcing | Rebuild state from events | Replay all Account* events to derive current account balance |
OrderLines| Ports and adapters | Interface in domain, impl in infra | interface OrderRepository in domain; PostgresOrderRepository in infrastructure |
| One-page doc: "Our competitive advantage is real-time risk scoring using..." |
| Concurrency conflicts, slow loads, transactional bottlenecks |
| Keep aggregates small; reference other aggregates by ID; use eventual consistency between aggregates |
| Anemic domain model (all logic in services) | Domain objects are data bags; business rules scatter across service classes; duplication and inconsistency | Move behavior into entities and value objects; services only orchestrate, never contain domain logic |
| No Anti-Corruption Layer at integration points | Foreign models leak into your domain; your code becomes coupled to external schemas and naming | Wrap every external system behind a translation layer that converts to your ubiquitous language |
| Treating bounded contexts as microservices | Premature service extraction; distributed system complexity without the benefit | A bounded context is a model boundary, not a deployment unit; start with modules in a monolith |
| Skipping domain expert collaboration | Developers invent a model that does not match business reality; expensive rework | Regular modeling sessions with domain experts; refine the model until experts say "yes, that is how it works" |
| Classify subdomains; focus deep modeling on the Core Domain |