Domain modeling with Mermaid class diagrams
Class diagrams have a reputation problem. In many teams they're associated with heavyweight UML tooling, enterprise process, and documentation that's out of date before it's published. That reputation is deserved — but it's a tooling problem, not a concept problem.
The concept is genuinely useful: a shared picture of your domain model accelerates design discussions, surfaces missing relationships, and gives new engineers a map of the codebase before they open a single file. Mermaid's classDiagram syntax keeps the concept without the baggage. The diagram is text, it lives in your repository, and it gets reviewed alongside the code it describes.
Class diagram basics
A class has three compartments: name, attributes, and methods. In Mermaid, you declare them one per line inside curly braces:
classDiagram
class Order {
+String id
+String status
+Decimal total
+DateTime createdAt
+place() void
+cancel() bool
+calculateTotal() Decimal
}
Visibility prefixes follow UML convention:
| Prefix | Visibility |
|---|---|
+ | Public |
- | Private |
# | Protected |
~ | Package / internal |
Relationships
The real value of a class diagram is in the lines between classes. Mermaid supports all the standard UML relationship types:
| Syntax | Relationship | When to use |
|---|---|---|
--> | Association | One class uses another |
*-- | Composition | Child cannot exist without parent |
o-- | Aggregation | Child can exist independently |
<|-- | Inheritance | Is-a relationship |
..|> | Implementation | Implements an interface |
..> | Dependency | Uses temporarily |
Multiplicity labels go in quotes beside the class name on each end of the arrow:
classDiagram
Customer "1" --> "0..*" Order : places
Order "1" *-- "1..*" LineItem : contains
LineItem "0..*" --> "1" Product : references
Order "1" --> "0..1" Payment : paid by
Skip multiplicity when the cardinality is obvious; add it when it's not.
A practical example: e-commerce domain
Here's a domain model for a small e-commerce system — the kind of diagram you'd sketch in the first week of a project:
classDiagram
class Customer {
+String id
+String email
+String name
+register() void
}
class Order {
+String id
+OrderStatus status
+Decimal total
+place() void
+cancel() bool
+calculateTotal() Decimal
}
class LineItem {
+String productId
+int quantity
+Decimal unitPrice
+subtotal() Decimal
}
class Product {
+String id
+String name
+Decimal price
+int stockLevel
+isAvailable() bool
}
class Payment {
+String id
+PaymentMethod method
+Decimal amount
+process() bool
}
Customer "1" --> "0..*" Order : places
Order "1" *-- "1..*" LineItem : contains
LineItem "0..*" --> "1" Product : references
Order "1" --> "0..1" Payment : paid by
This diagram won't match the code exactly — the code has more fields, more methods, and more edge cases. That's intentional. A domain diagram is a map, not a mirror.
Interfaces and inheritance
Use <<interface>> to mark abstract types and ..|> to show implementation:
classDiagram
class PaymentProcessor {
<<interface>>
+charge(amount: Decimal) bool
+refund(transactionId: String) bool
}
class StripeProcessor {
-String apiKey
+charge(amount: Decimal) bool
+refund(transactionId: String) bool
}
class PayPalProcessor {
-String clientId
+charge(amount: Decimal) bool
+refund(transactionId: String) bool
}
PaymentProcessor <|.. StripeProcessor : implements
PaymentProcessor <|.. PayPalProcessor : implements
The dotted line on <|.. signals implementation rather than inheritance — the distinction matters when you're communicating with engineers coming from statically typed languages where the two are explicit in the syntax.
Enumerations
Enums are first-class in Mermaid class diagrams and make valid state values explicit:
classDiagram
class OrderStatus {
<<enumeration>>
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
class Order {
+String id
+OrderStatus status
}
Order --> OrderStatus : has status
Naming the enum in the diagram prevents the inevitable "wait, what values can status actually hold?" question in code review.
Generics
Use tilde syntax for generic types — useful when documenting collection-heavy APIs:
classDiagram
class Repository~T~ {
+findById(id: String) T
+findAll() List~T~
+save(entity: T) T
+delete(id: String) void
}
class OrderRepository {
}
Repository~Order~ <|-- OrderRepository : extends
Tips for useful class diagrams
Model the domain, not the database. A class diagram should reflect the business concepts your code works with, not the table structure underneath. Foreign keys belong in an ER diagram; relationships between domain objects belong here.
Don't model every class. Pick the ten or fifteen classes that carry the most business logic. Leave out DTOs, value objects, and persistence entities unless they're the point of the diagram.
Draw before you code. Class diagrams find missing concepts. Trying to draw the relationship between Order and Discount and realizing you haven't worked out stacking rules is a five-minute design session, not a two-day refactor.
Keep it in the same PR as the code change. A class diagram in docs/architecture/domain.md earns its keep only if it's updated when the model changes. The habit of reviewing diagram alongside code is what keeps it accurate.
Class diagrams vs. ER diagrams
Both show relationships between named things, which makes them easy to confuse. The key distinction: an ER diagram models your storage layer — tables, columns, foreign keys, cardinalities as the database enforces them. A class diagram models your object layer — classes, methods, inheritance, and the associations your code actually traverses.
In a well-layered system, they won't look the same. An ER diagram may have a join table your domain model abstracts away. A class diagram may have a polymorphic type your schema represents with nullable columns. Both are right; they're describing different things.
If you can't describe what a relationship arrow means in plain English, it doesn't belong on the diagram.
Start with the five or six most important classes in your system. Draw the relationships between them. The gaps you find in the first ten minutes are worth more than the finished diagram.
Related posts
Common Mermaid diagram errors and how to fix them fast
Syntax errors, rendering glitches, and layout surprises break diagramming workflows. Learn to spot and fix the 10 most common Mermaid mistakes before they slow you down.
Flowchart vs sequence diagram: when to use each in Mermaid
Both show process flows, but flowcharts model decisions and branches while sequence diagrams trace interactions over time. Here's how to pick the right one.