1. Discover the Ubiquitous Language
Start by identifying the domain concepts, using terminology from domain experts:
Action Items:
- List nouns (entities, value objects) and verbs (operations, events) from the domain
- Document domain terms with precise definitions
- Identify synonyms and resolve ambiguity
- Ask: What does the business call this? What are the boundaries of this concept?
Output Format:
Create a glossary section documenting each term:
```markdown
Term (Type: Entity/ValueObject/Event/Command)
- Definition: [Clear, domain-expert-approved definition]
- Examples: [Concrete examples]
- Invariants: [Rules that must always hold]
```
2. Analyze the Existing Domain Model
Before making changes, understand the current state:
Exploration Steps:
- Identify where domain concepts are currently modeled (types, schemas, tables)
- Map out relationships between domain entities
- Find where business logic lives (services, functions, stored procedures)
- Document implicit rules and constraints
- Note inconsistencies in naming or modeling
Questions to Answer:
- What types/classes represent domain concepts?
- What are the invariants? Where are they enforced?
- Which concepts are tangled together that should be separate?
- Are there phantom types or states that shouldn't exist?
3. Identify Inconsistencies and Smells
Common problems to surface:
Naming Inconsistencies
- Same concept with different names (User vs Account vs Customer)
- Different concepts with same name (Order as entity vs Order as command)
- Technical names bleeding into domain language (DTO, DAO suffixes)
Structural Problems
- Illegal states being representable (e.g.,
status: "approved" | "rejected" with separate approved_at and rejected_at fields that can both be set) - Primitive obsession (strings for email, numbers for money)
- Optional fields that are actually required in certain states
- Null/undefined used to represent multiple distinct states
Complected Concerns
- Domain logic mixed with infrastructure (DB access in business logic)
- Multiple responsibilities in one type/module
- Temporal coupling (must call A before B or system breaks)
Missing Concepts
- Domain concepts that exist in conversations but not in code
- Implicit states that should be explicit
- Business rules enforced through comments or conventions rather than types
4. Design the Domain Model
Apply type-driven and data-driven principles:
Data Modeling:
- Start with the data shape; what are the facts?
- Use immutable values for facts that don't change
- Model state transitions explicitly
- Separate identity from attributes
- Consider: what varies together? What varies independently?
Type Design:
- Create sum types for mutually exclusive states:
```
type PaymentStatus =
| Pending
| Approved { approvedAt: Timestamp, approvedBy: UserId }
| Rejected { rejectedAt: Timestamp, reason: string }
```
- Use product types to ensure all required data is present
- Create semantic wrappers for primitives:
```
type EmailAddress = EmailAddress of string // with validation
type Money = { amount: Decimal, currency: Currency }
```
- Make impossible states unrepresentable
Workflow Modeling:
- Model each business workflow as a clear pipeline:
```
ValidateInput β ExecuteBusinessLogic β HandleResult β Persist β Notify
```
- Identify decision points and model them explicitly
- Separate pure business logic from effects (IO, time, randomness)
- Use clear function signatures that document intent
5. Build and Maintain Ubiquitous Language
Consistency Rules:
- Use identical terminology in code, documentation, conversations, and UI
- When domain language changes, update all representations
- Avoid technical jargon in domain code (no "factory", "manager", "handler" unless domain terms)
- Resist the temptation to rename domain concepts for technical convenience
Code Conventions:
- Domain types should mirror domain language exactly
- Function names should use domain verbs
- Module boundaries should follow domain boundaries
- Comments should explain domain rules, not implementation details
Documentation:
- Keep the glossary up to date
- Document why decisions were made (especially constraints and invariants)
- Link code to domain documentation
- Make implicit domain rules explicit
6. Visualize the Domain Model
Use diagrams to communicate domain structure and relationships:
Mermaid for Relationships:
```mermaid
classDiagram
Order --> Customer
Order --> OrderLine
OrderLine --> Product
Order --> PaymentStatus
class Order {
+OrderId id
+CustomerId customerId
+List~OrderLine~ lines
+PaymentStatus status
}
class PaymentStatus {
<>
Pending
Approved
Rejected
}
```
Mermaid for Workflows:
```mermaid
graph LR
A[Receive Order] --> B{Valid?}
B -->|Yes| C[Calculate Total]
B -->|No| D[Return Validation Error]
C --> E[Process Payment]
E --> F{Payment Success?}
F -->|Yes| G[Fulfill Order]
F -->|No| H[Cancel Order]
```
Mermaid for State Transitions:
```mermaid
stateDiagram-v2
[*] --> Draft
Draft --> Submitted: submit()
Submitted --> Approved: approve()
Submitted --> Rejected: reject()
Approved --> Fulfilled: fulfill()
Fulfilled --> [*]
Rejected --> [*]
```
Graphviz/DOT for Complex Relationships:
```dot
digraph domain {
rankdir=LR;
node [shape=box];
Customer -> Order [label="places"];
Order -> OrderLine [label="contains"];
OrderLine -> Product [label="references"];
Order -> Payment [label="requires"];
Payment -> PaymentMethod [label="uses"];
}
```
ASCII for Quick Sketches:
```
Customer
ββ> Order (1:N)
ββ> OrderLine (1:N)
β ββ> Product
ββ> Payment (1:1)
ββ> PaymentMethod
```
When to Use Each:
- Mermaid classDiagram: Entity relationships and type structures
- Mermaid graph/flowchart: Business workflows and decision trees
- Mermaid stateDiagram: State transitions and lifecycle
- Graphviz/DOT: Complex dependency graphs, module boundaries
- ASCII: Quick sketches during discussion, simple hierarchies