../

ERD & UML diagrams

How to draw and read the two diagram families software teams still use: entity-relationship diagrams for data (Chen, crow's foot, IDEF1X, and how each mark becomes a PostgreSQL constraint) and UML 2.5.1 for code (class, sequence, state machine, activity, use case, component and deployment diagrams), with the TypeScript each class-diagram symbol becomes and the Mermaid, PlantUML, DBML and D2 that draw them from text. Schema design itself is in DB design & schemas; system-level views (C4) are in Software architecture; the structures these diagrams usually depict are in Design patterns.

Which diagram

Pick the diagram from the question you need answered, and draw one question per diagram.

QuestionDiagramNotes
What data exists and how is it related?ERD, conceptual or logicalthe one diagram almost every team keeps
What tables, keys and constraints ship?ERD, physical (crow's foot)generate it from the live schema; don't hand-maintain it
What types exist and what holds a reference to what?UML classa domain model, or the shape of one pattern
Who calls whom, in what order, for one scenario?UML sequenceAPIs, auth flows, retries, anything async
What states can one thing be in, and what moves it?UML state machineevery status column deserves one
What steps run, where do they branch and run in parallel?UML activityworkflows across people or services
Who uses the system for what?UML use casea scoping index; the value is in the written use cases
What runs where, talking over what?UML deployment, or C4 container/deploymentinfrastructure reviews
How is the system split into parts with interfaces?UML component, or C4 componentmodule and service boundaries
How does the whole system fit its world?C4 system contextnot UML; see Software architecture

The strongest case against all of this: most teams don't use UML, and do fine. In Marian Petre's ICSE 2013 interview study of 50 professional developers, 35 used no UML at all, 11 used it selectively, 3 generated code from it, 1 drew it after the fact, and none used it wholeheartedly. The selective users drew class, sequence and activity diagrams, informally, to think and to explain. That is the defensible use: Martin Fowler's three modes are UML as sketch (to communicate; incomplete on purpose), as blueprint (complete enough to code from) and as programming language (executable models). Sketch by default, blueprint only at boundaries that are expensive to change (public APIs, schemas, protocols), and treat the third as a historical curiosity.

RuleWhy
Title, scope and date on every diagrama diagram without a scope gets read as the whole system
One notation per diagram, with a legend if it isn't standardmixing Chen and crow's foot, or UML and boxes-and-arrows, makes every mark ambiguous
Keep it to one screen (roughly 5–12 boxes)past that, split by scenario or by zoom level
Label every linean unlabeled line says only "something is related"
Store the source next to the code, as text where possiblediffs in review, and it gets updated with the change that breaks it
Generate what can be generated (physical ERDs, dependency graphs)hand-drawn copies of machine facts go stale first

ERD levels

The same model is drawn at three levels of detail for three audiences. Most arguments about "the ERD" are two people looking at different levels.

LevelShowsLeaves outM:N relationshipsAudience
Conceptualentities, relationships, cardinality, business nameskeys, foreign keys, typesdrawn as-isdomain experts, product
Logicalevery attribute, primary and foreign keys, nullability, normalized to 3NFDBMS types, indexes, storageresolved into associative entitiesanalysts, developers
Physicaltables, columns and types, constraints, indexes, schemas, partitionsbusiness narrativejunction tablesdevelopers, DBAs, migration reviews
TermMeaning
Entity (type)a kind of thing with identity: customer, order (an instance is one customer)
Attributea property of an entity: simple or composite, single- or multivalued, stored or derived
Keyattributes whose values identify one instance; the chosen one is the primary key
Relationshipan association between entities, with a verb phrase in each direction
Degreehow many entity types a relationship joins: binary (2), ternary (3), n-ary
Cardinalitythe maximum number of instances on one side per instance on the other: 1 or many
Participation (optionality, modality)the minimum: 0 (partial, optional) or 1 (total, mandatory)
Weak entityan entity identified only together with its owner: an order line within an order
Associative entitya relationship promoted to an entity, usually because it has attributes: a membership with a role
Identifying relationshipthe child's primary key includes the parent's key; non-identifying: it is an ordinary foreign key

Say every relationship as two sentences, one per direction, with "each", a verb and a quantity: "each customer places zero or more orders" and "each order is placed by exactly one customer". If either sentence sounds wrong to the domain expert, the diagram is wrong.

ER notations

Chen 1:N ratio · across Chen (min,max) look here Crow's foot look across UML look across IDEF1X dot: 0, 1 or more PLACES CUSTOMER ORDER 1 N PLACES CUSTOMER ORDER (0,N) (1,1) customer order Customer Order 1 0..* places customer order
One relationship, five notations. Only Chen's (min,max) is read on the near side.
NotationEntityRelationshipMaximumMinimumWhere you meet it
Chen (1976)rectangle; double for a weak entitydiamond; double for an identifying one1, N, M beside the linesdouble line: total participationtextbooks, conceptual models
Chen (min,max)as Chenas Chen(min,max) beside each entitythe minElmasri & Navathe, exams
Crow's foot (Information Engineering)box listing attributesline with a verb phrasebar = one, fork = many (mark touching the box)circle = zero, bar = one (next mark out)ERwin, Lucidchart, dbdiagram, Mermaid, PlantUML, most tools
Barker (Oracle CASE*Method)rounded box; # key, * mandatory, o optional attributeline in two halves, one per endfork = manysolid half = mandatory, dashed half = optionalOracle SQL Developer Data Modeler
IDEF1X (FIPS 184)square corners: independent; rounded: dependentsolid = identifying, dashed = non-identifyingdot = zero or more, P = one or more, Z = zero or one, a number = exactly thatdiamond at the parent end = nullable foreign keyUS government, ERwin
UML class diagramclass boxassociation line1, *, n..m at the far endlower bound of the multiplicitysoftware models; see Class diagrams

Look across vs look here. In crow's foot, UML and Chen's 1:N, the mark at an entity's end tells how many of that entity relate to one instance on the other end: read from the far entity, across the line. Chen's (min,max) is the opposite: the pair beside an entity says how many times that entity takes part in the relationship. Customer (0,N) and Order (1,1) say the same thing as crow's foot ||──o<, written on the other ends. Translating between the two families without flipping the ends is the most common ERD error there is.

Chen introduced the model in "The Entity-Relationship Model: Toward a Unified View of Data" (ACM TODS, March 1976). Gordon Everest drew the "inverted arrow", now called crow's foot, the same year, for maximums only; the circle and bar for minimums came later, and Information Engineering (James Martin and Clive Finkelstein) spread the full notation. Richard Barker's variant shipped with Oracle's CASE*Method (1990). IDEF1X became FIPS 184 in December 1993 and was withdrawn as a federal standard on September 2, 2008; IEEE 1320.2 carries it on.

Crow's foot

mark at B's end reads across UML Mermaid A B exactly one each A has exactly 1 B 1 || A B zero or one each A has at most 1 B 0..1 o| A B one or many each A has at least 1 B 1..* |{ A B zero or many each A has any number of B 0..* o{ the mark touching B is the maximum; the next mark out is the minimum
Each end has two marks: the one touching the box is the maximum, the next one out the minimum.
EndReadsUMLMermaid (left / right)Enforced in SQL by
──┼┼exactly one1|| / ||NOT NULL foreign key on the other table
──o┼zero or one0..1|o / o|nullable foreign key, or UNIQUE foreign key on the other table
──┼<one or many1..*}| / |{nothing declarative: a deferred constraint trigger (Recipes)
──o<zero or many0..*}o / o{the foreign key on this table, nothing more
  • The far end of the line answers the sentence: in customer ||──o< order, the o< at the order end reads "each customer places zero or many orders"; the || at the customer end reads "each order belongs to exactly one customer".
  • Physical ERDs (ERwin, Mermaid, PlantUML) draw identifying relationships solid and non-identifying ones dashed. In Barker notation a dashed half means optional instead. Say which convention a diagram uses.
  • Attributes go in the box, one per line, keys marked PK, FK, UQ. Foreign key columns belong on logical and physical diagrams only: in a conceptual model the line is the relationship.

Chen

EMPLOYEE Ssn Name Phones Age WORKS_FOR StartDate N 1 DEPARTMENT Number Name DEPENDENTS_OF 1 N DEPENDENT Name weak entity identifying relationship Ssn key (dashed: partial key) multivalued attribute derived attribute total participation
Chen notation: attributes are ovals, relationships diamonds, and relationships can carry their own attributes (StartDate).
MarkMeans
rectangle / double rectangleentity / weak entity
diamond / double diamondrelationship / identifying relationship of a weak entity
oval, underlined nameattribute, key attribute
oval, dashed underlinepartial key (discriminator) of a weak entity: unique only within its owner
double ovalmultivalued attribute: becomes a child table
dashed ovalderived attribute: computed, not stored
oval with ovals hanging off itcomposite attribute: its parts become columns
single / double linepartial / total participation
1, N, M on the linescardinality ratio, read across

Chen is verbose, and that is its use: relationship attributes, ternary relationships and weak entities are first-class, so it is the best notation for arguing about a conceptual model before tables exist. Nobody draws a 40-table physical schema in it.

Supertypes & subtypes

Enhanced ER (EER) adds specialization: a supertype with subtypes that inherit its attributes and relationships, drawn as lines from the supertype to a circle and on to each subtype.

ConstraintMarkMeans
disjointd in the circlean instance is at most one subtype (a payment is a card or a transfer)
overlappingo in the circlean instance can be several (a person who is both employee and customer)
totaldouble line from the supertypeevery instance is some subtype
partialsingle linean instance can be just the supertype

Crow's foot tools draw subtypes as boxes nested in the supertype or joined by an "is a" line; UML uses generalization (hollow triangle) with {disjoint, complete} or {overlapping, incomplete}. The tables follow one of Fowler's three mappings:

MappingTablesGoodBadFits
Single tableone table and a kind column; subtype columns nullableno joins; one foreign key targetmany nulls; a CHECK per kind keeps them honestdisjoint, few subtype columns
Class tableone supertype table plus one per subtype whose primary key is also a foreign key to itnormalized; NOT NULL where it belongsa join per read; "exactly one subtype row" needs a triggermany subtype columns
Concrete tableone table per concrete subtype, supertype columns repeatedno joins within a typecross-type queries need UNION; nothing can reference "any subtype"types rarely queried together

Polymorphic references to "any subtype" are covered in DB design & schemas.

From ERD to tables

customers id PK email UQ name orders id PK customer_id FK ship_address_id FK status placed_at products id PK sku UQ name price_cents addresses id PK customer_id FK line1 city order_items order_id FK PK line_no PK product_id FK quantity unit_price_cents solid: identifying (the FK is part of the child's PK) · dashed: non-identifying
A physical ERD. order_items is a weak entity: its key includes order_id, so that line is solid.
ER constructBecomes
strong entitya table; its key becomes the primary key (or a surrogate key plus UNIQUE on the natural key)
weak entitya table whose primary key is the owner's key plus the partial key; foreign key ON DELETE CASCADE
1:N relationshipa foreign key on the N side; NOT NULL if every N-side row must have a parent
1:1 relationshipa foreign key with UNIQUE on the side with total participation, or one merged table
M:N relationshipa junction table; primary key = both foreign keys; relationship attributes become its columns
ternary relationshipa table with one foreign key per participant
multivalued attributea child table: owner foreign key plus value, both in the primary key
composite attributeone column per part (or a composite type)
derived attributenot stored: a view, a generated column or the query
supertype / subtypeone of the three mappings in Supertypes & subtypes
CREATE TABLE orders (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  -- ||..o{ : mandatory parent, non-identifying
  customer_id bigint NOT NULL REFERENCES customers,
  -- |o..o{ : optional parent
  ship_address_id bigint REFERENCES addresses,
  status text NOT NULL DEFAULT 'pending',
  placed_at timestamptz NOT NULL DEFAULT now()
);
 
-- ||--|{ : identifying; the parent's key is in the PK
CREATE TABLE order_items (
  order_id bigint REFERENCES orders ON DELETE CASCADE,
  line_no int,
  product_id bigint NOT NULL REFERENCES products,
  quantity int NOT NULL CHECK (quantity > 0),
  unit_price_cents int NOT NULL,
  PRIMARY KEY (order_id, line_no)
);

A foreign key enforces the parent side of a relationship. It can't enforce a minimum of one on the child side (|{: every order has at least one item), nor a mandatory 1:1 in both directions: those need a deferred constraint trigger or application code, and the diagram should say which.

TrapModelSymptomFix
Fan trapdivision 1:N branch, division 1:N staffwhich branch does this person work at? The path fans out through divisionrelate the entities directly: division 1:N branch 1:N staff
Chasm trapbranch 1:N staff, staff 0..1:N propertya property nobody manages has no path to its branchadd the missing relationship: branch 1:N property
Ternary split into binariessupplier–part, part–project, supplier–projectwhich supplier supplied which part to which project is lostkeep the ternary: one table with three foreign keys
Relationship as attributeorders.customer_namerenames and typos split one customer into severala foreign key to the entity
M:N left on a physical diagrama line with forks at both ends between two tablesno table to put it indraw the junction table

UML at a glance

UML is an OMG standard: Booch, Rumbaugh and Jacobson merged their methods at Rational in 1994–96; OMG adopted UML 1.1 in November 1997, UML 2.0 in 2005, and the current version, 2.5.1, in December 2017 (ISO/IEC 19505:2012 is UML 2.4.1). UML 2.5 defines 14 diagram types.

KindDiagramShowsUse it for
StructureClasstypes, attributes, operations, relationshipsdomain models, pattern structure, design reviews
StructureObjectinstances and links at one momentexplaining a tricky class diagram with an example
StructurePackagenamespaces and their dependencieslayer and module dependency rules
StructureComponentreplaceable parts and their provided/required interfacesmodule and service boundaries
StructureComposite structurethe internal parts, ports and connectors of one classifierrare outside embedded and systems work
StructureDeploymentnodes, execution environments, artifacts, pathsinfrastructure
StructureProfilestereotypes that extend UML itselftool builders
BehaviorUse caseactors, their goals, the system boundaryscoping
BehaviorActivitysteps, decisions, concurrency, who does whatworkflows, business processes
BehaviorState machinethe lifecycle of one object, driven by eventsanything with a status
InteractionSequencemessages between participants over time, for one scenarioAPIs, protocols, async flows
InteractionCommunicationthe same messages, numbered, drawn on the links between objectsrarely; a sequence diagram reads better
InteractionTiminglifeline states along a time axis, with time constraintshardware, real-time, protocols with deadlines
InteractionInteraction overviewan activity diagram whose nodes are interactionsstitching sequence diagrams together; rare

Shared notation, valid on any diagram:

ElementNotationExample
Stereotypeguillemets before the name«interface», «enumeration», «entity», «use»
Constrainttext in braces, natural language or OCL{ordered}, {total = sum of lines}
OCL invariantcontext type inv: expressioncontext Order inv: self.lines->notEmpty()
Notedog-eared rectangle, dashed line to what it explainsfree text
Keywordas a stereotype, built in«include», «extend», «create»
Diagram framerectangle with a pentagon tag in the cornersd checkout, stm Order, act Checkout

Class diagrams

Customer - email: string Product - sku: string - price: Money Order - id: OrderId {readOnly} - status: OrderStatus + /total: Money + add(p: Product, qty: number) + pay(gw: PaymentGateway) + create(c: Customer): Order OrderLine - qty: number - unitPrice: Money + subtotal(): Money «interface» PaymentGateway + charge(m: Money): Receipt StripeGateway - apiKey: string + charge(m: Money): Receipt «enumeration» OrderStatus Pending Paid Shipped Canceled * 1 1 lines 1..* {ordered} * 1 «use»
A class box has three compartments: name, attributes, operations. Underlined is static; a slash marks a derived attribute.

Members

SyntaxExampleMeans
visibility+ public, - private, # protected, ~ packagewho may use the member
attribute- status: OrderStatus = Pendingvisibility name: Type [multiplicity] = default {properties}
multiplicity on an attribute- tags: string [0..*]the attribute holds a collection; omitted means exactly one
derived+ /total: Moneycomputed from other values
static+ create(c: Customer): Order, underlinedclass scope, not per instance
abstractclass or operation name in italics, or {abstract}must be overridden
operation+ pay(in gw: PaymentGateway): Receiptvisibility name(direction param: Type, …): Return {properties}; directions in (default), out, inout
read-only{readOnly}can't change after initialization
identifier{id}part of the object's identity
redefinition / subset{redefines items}, {subsets items}a subclass narrows an inherited property
template classRepository<T> with T in a dashed box on the cornera generic type
{ordered}{unique}CollectionTypeScript
noyes (the default)setSet<T>
yesyesordered setan array kept free of duplicates
no{nonunique}bagMap<T, number> of counts, or an array
yes{nonunique}sequenceT[]

An attribute and a navigable association end are the same thing in the UML metamodel (a property). Convention: draw a line to classes that appear on the diagram, and write an attribute for value types (string, Money, OrderId). Drawing both is the same fact twice.

Relationships

* 1 Order Customer association a field: order.customer * * Team Player aggregation (shared) holds parts it was handed 1 1..* Order OrderLine composition (owned) creates its parts; they die with it Admin User generalization class Admin extends User Stripe «interface» Gateway realization class Stripe implements Gateway «use» Checkout Clock dependency a parameter or local, not a field
Diamonds sit at the whole, triangles at the parent or interface, open arrowheads at the end that can be navigated to.
RelationshipLineEnd markMeansAskTypeScript
Associationsolidopen arrowhead = navigable; × = not navigable; none = unspecifiedinstances are linkeddoes one hold a reference to the other?a field
Aggregation (shared)solidhollow diamond at the wholewhole–part, parts may be sharednone: the spec says its semantics "varies by application area and modeler"a field holding something passed in
Compositionsolidfilled diamond at the wholethe part belongs to one whole at a time and is normally deleted with itis the part ever shared or does it outlive the whole? If not: compositioncreated inside, never handed out for keeping
Generalizationsolidhollow triangle at the parentis-a; the child inheritscould a child be used wherever the parent is?extends
Realizationdashedhollow triangle at the interfaceimplements a contract—implements
Dependencydashedopen arrowhead at the supplieruses, temporarilydoes a change to the supplier force a change here?a parameter, local, or import used in a method
Association classdashed line from a class to an association—the link itself has attributesis there data about the pair (a role, a date)?a class holding both references; a junction table
Qualified associationsmall box at the source end with a key—the key selects the targetlook-up by key?Map<Key, Target>
  • A composite's end has multiplicity 1 or 0..1, never more: a part has at most one owner.
  • Don't draw aggregation. It has no defined semantics, so every reader supplies their own. Use composition when ownership matters, a plain association otherwise.
  • Multiplicities are written at the far end and read across, like crow's foot: Order "*" → "1" Customer is "each order has one customer; each customer has many orders". Write one on every end: a missing one reads as 1 to some people and "unknown" to others.
  • Navigability arrows say who holds the reference. Draw them when the direction is a design decision (it usually is), not on every line.
  • Dependency stereotypes: «use», «create», «call», «instantiate», «import» (packages).
  • Interfaces can be drawn as a class box with «interface», or as a ball (lollipop) the class provides and a socket (half circle) a class requires.

UML to TypeScript

The diagram above as code. TypeScript's types are structural, so a class that never says implements still satisfies an interface it matches: draw realization only for contracts you declare on purpose.

// «enumeration»: a union of literals (or an enum)
type OrderStatus =
  | "Pending" | "Paid" | "Shipped" | "Canceled";
 
// «interface»: no runtime trace; checked by shape
interface PaymentGateway {
  charge(m: Money): Receipt;
}
 
// realization: `implements` makes the contract explicit
class StripeGateway implements PaymentGateway {
  constructor(private readonly apiKey: string) {}
  charge(m: Money): Receipt {
    return callStripe(this.apiKey, m);
  }
}
 
class OrderLine {
  constructor(
    private qty: number,
    private unitPrice: Money,
    readonly product: Product, // association, 1
  ) {}
  subtotal(): Money {
    return times(this.unitPrice, this.qty);
  }
}
 
class Order {
  // composition: created here, never handed out to keep
  readonly #lines: OrderLine[] = []; // 1..* {ordered}
  private status: OrderStatus = "Pending";
 
  private constructor(
    private readonly id: OrderId, // {readOnly}
    private customer: Customer, // association, 1
  ) {}
 
  // underlined in UML: a static (class-scope) member
  static create(c: Customer): Order {
    return new Order(newId(), c);
  }
 
  // derived (/total): computed, not stored
  get total(): Money {
    return sum(this.#lines.map((l) => l.subtotal()));
  }
 
  add(p: Product, qty: number): void {
    this.#lines.push(new OrderLine(qty, p.price, p));
  }
 
  // dependency «use»: a parameter, not a field
  pay(gw: PaymentGateway): void {
    // the 1..* lower bound is an invariant to check
    if (this.#lines.length === 0) throw new Error("empty");
    gw.charge(this.total);
    this.status = "Paid";
  }
}
UMLTypeScriptWatch out
+ publicpublic (the default)—
- privateprivate (compile time) or #field (enforced at run time)UML # means protected; a JavaScript #field is private
# protectedprotected—
~ packageno equivalent; an unexported module member is closest—
{readOnly}readonlyshallow: the array behind a readonly field is still mutable
0..1T | undefined, or an optional property x?: T—
*, {ordered}T[] or ReadonlyArray<T>an array is a sequence: {unique} isn't enforced
*, unordered uniqueSet<T>—
qualified associationMap<K, T>—
abstract class or operationabstract class, abstract m(): T—
«interface»interface or a type aliaserased at run time; no instanceof
template Tgenerics class Repo<T>—

Sequence diagrams

alt [approved] [else] :Browser :CheckoutApi :OrderService :Gateway :Queue POST /checkout POST /checkout placeOrder(cart) placeOrder(cart) validate(cart) validate(cart) charge(total) charge(total) result result OrderPlaced OrderPlaced PaymentFailed PaymentFailed order order 201 Created

1/9

POST /checkout: the browser calls the API and waits. A synchronous call is a solid line with a filled arrowhead; the thin bars on the lifelines are execution specifications, the time each participant is busy.

ElementNotationMeans
Lifelinename : Type in a box, dashed line downone participant; :Type for an anonymous instance
Actorstick figure heading a lifelinea human or external system
Execution specificationthin bar on the lifelinethe participant is busy (optional, but helps with nesting)
Synchronous messagesolid line, filled arrowheada call; the sender waits
Asynchronous messagesolid line, open arrowheada signal or event; the sender carries on
Replydashed line, open arrowheada return value, written name = value or just the value
Createdashed line, open arrowhead, to a lifeline's head drawn lower downthe participant starts existing here
Destroy× at the end of a lifelinethe participant stops existing
Found / lost messagefrom / to a filled dotsender / receiver unknown or out of scope
Self-messagearrow looping back to the same lifelinea call to one's own operation
Duration / time constraint{≤ 2 s} between two marks; {t..t+3}deadlines and timeouts
Gatemessage ending on the frame's edgethe message crosses the diagram's boundary
State invariant[condition] or a state name on the lifelinemust hold at that point
FragmentMeans
altalternatives, each with a guard; at most one operand runs; [else] when no guard holds
optone operand that runs only if its guard holds: an alt with one branch
looprepeats while the guard holds; loop(min, max); bare loop means 0 to unbounded
breakif the guard holds, runs instead of the rest of the enclosing fragment, then leaves it
paroperands run in parallel; their messages interleave in any order
criticalno other messages may interleave on these lifelines: an atomic region
negtraces that must not happen
assertthe only valid continuation
strictstrict order across lifelines, top to bottom
seqweak order (the default): order per lifeline only
ignore / considermessages left out of / the only ones counted in the fragment
refan interaction use: another sequence diagram inlined by name
  • One diagram per scenario; the happy path first, then one diagram per interesting failure.
  • Name participants after roles in the design (:OrderService), not after deployment units, unless the diagram is about deployment.
  • Leave out getters and trivial calls. A sequence diagram that shows every line of code is the code, drawn worse.
  • Async is the point of most sequence diagrams worth drawing: show who waits, who doesn't, and where a reply can be lost.

State machine diagrams

pay [declined] / retry pay [approved] ship deliver cancel, after(30 min) cancel / refund Pending Paid entry / reserveStock Shipped do / track Canceled Delivered
Transitions read trigger [guard] / effect. A self-transition exits and re-enters, so it restarts after(30 min).
ElementNotationMeans
Initial pseudostatefilled dotwhere a region starts; its one transition has no trigger or guard
Final statedot in a ringthe region is done
Staterounded rectanglea condition the object stays in while waiting for events
Transitionarrow labeled trigger [guard] / effecton the trigger, if the guard holds, run the effect and move
Entry / exit actionentry / a, exit / a inside the stateruns on every way in / out
Do activitydo / aruns while in the state; interrupted when it is left
Internal transitiontrigger [guard] / effect inside the statehandled without leaving: no exit or entry actions run
Self-transitionarrow leaving and re-entering the same stateruns exit, then entry; timers restart
Completion transitionarrow with no triggerfires when the state's do activity (or its regions) finish
Composite statea state with a nested machinesubstates share the outer transitions
Orthogonal regionsdashed line splitting a composite stateseveral substates active at once
Choicediamondbranches on guards evaluated after the effects so far (dynamic)
Junctionsmall filled dotchains transitions; guards evaluated before any of it runs (static)
HistoryH (shallow), H* (deep)re-enter the substate that was last active
Fork / joinbarenter / leave several orthogonal regions together
Entry / exit pointsmall circle / circled × on the bordernamed ways into and out of a composite state
Terminate×the machine stops at once
EventWrittenFires when
SignalpaymentReceivedan asynchronous signal arrives
Callcancel()an operation is called
Changewhen(stock = 0)the condition becomes true
Time, relativeafter(30 min)that long after entering the source state
Time, absoluteat(2027-01-01)at that moment
Completionno triggerthe state's own work is finished

The UML state machine is David Harel's statechart (1987) with object-oriented semantics. XState implements statecharts for TypeScript with the same vocabulary (entry/exit actions, guards, delayed transitions, parallel states, history). A plain reducer is enough until timers, nesting or parallel regions appear; the Recipes show one.

Activity diagrams

Customer Shop Warehouse [out of stock] [in stock] Place order Check stock Notify customer Charge card Pick & pack Ship parcel
Partitions (swimlanes) say who acts. Fork and join bars run branches in parallel; a diamond picks one.
ElementNotationMeans
Initial nodefilled dota token starts here
Activity finaldot in a ringthe whole activity ends, all flows with it
Flow finalcircled ×this flow ends; others go on
Actionrounded rectanglea step
Control flowopen arrowthe order of steps
Decision / mergediamondone out of several guarded flows / brings alternatives back together, without waiting
Fork / joinbarsplits into parallel flows / waits for all of them
Object node, pinsrectangle on a flow, small squares on an actiondata passed between actions
Partition (swimlane)a band with a namewho or what performs the actions in it
Send signalconvex pentagonsends an event and goes on
Accept event / time eventconcave pentagon / hourglasswaits for an event / for time to pass
Interruptible regiondashed rounded rectangle; lightning-bolt flow out of itan event aborts everything inside
Call behavioraction with a rake symbolruns another activity
  • Semantics are token flow, like a Petri net: a join waits for a token on every incoming flow, a merge passes each token straight through. A merge where a join was meant runs the next step twice.
  • Every decision's guards must cover every case, and must not overlap; add [else].
  • For processes owned by the business (approvals, handoffs, SLAs), BPMN 2.0 (OMG 2011, ISO/IEC 19510:2013) has richer events and gateways and is what business analysts read. Activity diagrams fit software workflows; a plain flowchart fits a slide.

Use case diagrams

«include» «extend» Shop Browse catalog Track order Place order Pay Apply coupon Customer Payment provider
Actors outside, use cases inside the boundary. «include» points at what is always pulled in; «extend» points at what is optionally added to.
ElementNotationMeans
Actorstick figure (or a box with «actor»)a role outside the system: a person, an organization, another system
Use caseellipse with a verb phrasea goal an actor reaches through the system
Subjectrectangle named for the systemthe boundary
Associationsolid line actor–use casethe actor takes part
Generalizationhollow triangle between actors or use casesa specialized actor or use case
«include»«extend»
Arrow pointsfrom the base to the included use casefrom the extension to the base
Runsalways, at a set point in the baseonly when its condition holds, at an extension point
Is the base complete without it?noyes
Use forsteps shared by several use cases (Pay)optional or exceptional behavior (Apply coupon)

The diagram is a table of contents. The requirements live in the written use cases: goal, actors, preconditions, a numbered main success scenario and its extensions (Alistair Cockburn's format; a template is in the Recipes). Two smells: a use case per screen or per CRUD operation (functional decomposition, not goals), and «include»/«extend» chains deeper than one level.

Component & deployment diagrams

ElementNotationMeans
Componentbox with «component» or the component icona replaceable part behind interfaces: a module, a service
Provided interfaceball (lollipop) on a stickwhat the component offers
Required interfacesocket (half circle) on a stickwhat it needs from elsewhere
Assembly connectorball in socketthis provider satisfies that requirement
Portsmall square on the bordera named interaction point
Delegation connectorarrow from a port to an inner partthe work is passed inside
Node3D box; «device» or «executionEnvironment»hardware, a VM, a container runtime
Artifactrectangle «artifact» with a page icona deployable file: a bundle, an image, a jar
Deploymentartifact drawn inside a node, or a dashed «deploy» arrowwhere it runs
Communication pathline between nodes, labeled with the protocolHTTPS, TCP 5432, AMQP
Packagefolder with a taba namespace; «import» (public) and «access» (private) dependencies

C4 covers the same ground with fewer rules: a container diagram is roughly a component diagram plus a deployment diagram, drawn as labeled boxes with a legend. Use the UML forms when your tools and readers already speak UML, or when interface wiring (ball and socket) is the point. A deployment diagram in PlantUML is in the Recipes.

Other UML diagrams

DiagramReach for it whenNotation in one line
Objecta class diagram's multiplicities confuse peopleboxes named order1 : Order (underlined) with slot values, joined by links
Communicationthe links between objects matter more than the timingobjects joined by links; messages numbered 1, 1.1, 1.2 along them
Timinga protocol has deadlines or a signal changes state over timeone lane per lifeline, its state as a step line along a time axis
Interaction overviewseveral sequence diagrams make up one flowan activity diagram whose nodes are ref frames
Composite structurethe inside of one class is itself a small systemparts as boxes inside the class, connected through ports
Profileyou define stereotypes for a tool or a domain«stereotype» classes extending «metaclass» with a filled-triangle arrow

Diagrams as code

ToolDrawsRendersLayoutUse it for
Mermaidflowchart, class, sequence, state, ER, C4, Gantt and morenatively in GitHub, GitLab, Notion, Obsidian; mmdc CLIautomatic (dagre; ELK optional)docs in Markdown; least control over layout
PlantUMLevery UML diagram above, ER (IE notation), C4 via C4-PlantUML, GanttJava CLI or a server; IDE pluginsGraphviz dot (or the bundled Smetana port); sequence diagrams use their own engine (Teoz with a pragma)complete UML; the most options
D2general diagrams, sql_table, class, sequence_diagram shapesd2 CLIdagre, ELK, or TALA (commercial)good-looking architecture diagrams
DBMLdatabase schemasdbdiagram.io, dbdocs; @dbml/cli converts to and from SQLautomaticERDs that round-trip with DDL
Structurizr DSLC4 modelsStructurizrautomatic or manualone model, many C4 views
Graphviz DOTany directed graphdot CLI, everywheredot, neato, fdpgenerated diagrams (dependency graphs)
Mermaid class arrowMeansMermaid sequence arrowMeans
<|--generalization->>synchronous call
*--composition (diamond at the left)-->>reply
o--aggregation-)asynchronous message
-->association, navigable-xline ending in a cross (failed or lost)
--link-> / -->solid / dotted line, no arrowhead
..>dependency<<->>two-way arrow (recent versions)
..|>realization+ / - after the arrowactivate / deactivate the target
..dashed linkcreate participant, destroylifeline creation and destruction (Mermaid 10.3+)

GitHub renders fenced mermaid blocks in Markdown, issues and pull requests; PlantUML and D2 need a CI step, a server or an editor plugin. Render to SVG on the command line:

npx -p @mermaid-js/mermaid-cli mmdc -i erd.mmd -o erd.svg
java -jar plantuml.jar -tsvg deploy.puml
d2 --layout elk arch.d2 arch.svg
npx -p @dbml/cli dbml2sql schema.dbml --postgres -o out.sql

Diagrams from existing code and schemas:

FromToolGives
a live PostgreSQL databaseDBeaver, DataGrip, pgAdmin's ERD Tool, SchemaSpyphysical ERDs
a SQL dumpsql2dbml (@dbml/cli)DBML for dbdiagram.io
a Prisma schemaprisma-erd-generator (community)a Mermaid ERD on every prisma generate
Python packagespyreverse (ships with Pylint)class and package diagrams as dot, puml or mmd
a TypeScript codebasedependency-cruiser (--output-type dot)module dependency graphs

Common mistakes

MistakeSymptomFix
Reading Chen (min,max) like crow's footevery cardinality comes out reversed(min,max) sits beside the entity it constrains; crow's foot marks sit at the far end
Two notations on one diagrama Chen diamond between crow's-foot tables; UML diamonds on an ERDpick one per diagram and add a legend
Foreign key columns on a conceptual ERDthe relationship appears twice, as a line and as an attributethe line is the relationship; columns arrive in the logical model
Many-to-many lines on a physical ERDthere is nowhere to store the pairsdraw the junction table and its attributes
Minimum of one that nothing enforces"every order has an item" on the diagram, empty orders in productionenforce it (deferred trigger, aggregate root) or draw o{
Composition everywhereevery reference drawn as a filled diamondcomposition only where the part's lifetime is the whole's
Aggregation at allreaders argue about what the hollow diamond meansplain association, or composition
Attribute and association for the same thingcustomer: Customer in the box and a line to Customerone or the other
Navigability arrows on every line, or nonedirection of references unknowable or noisearrows where the direction is a decision
# read as privatea TypeScript #field drawn as UML protectedUML # is protected; a JavaScript #field is private (-)
A sequence diagram of every method call30 lifelines, 200 messagesone scenario, the interesting calls, ref for the rest
Sync arrowheads on async messagesreaders assume the sender waitsopen arrowhead for anything sent and not waited on
State diagram of steps, not states"Validate", "Send email" as statesstates are conditions you wait in; steps belong to an activity diagram
Merge drawn where a join was meantthe step after it runs twicebar (join) to wait for all; diamond (merge) to take whichever comes
«include» / «extend» backwardsarrows point the wrong wayinclude: base → included; extend: extension → base
A use case per screen40 ellipses of "Edit X", "Delete X"one per actor goal; CRUD is one "Manage X" at most
Hand-maintained physical ERDthe diagram disagrees with the schemagenerate it from the database or from the schema source

Recipes

ERD in Mermaid

Renders on GitHub as-is. Relationship tokens are the crow's foot ends; -- is identifying (solid), .. non-identifying (dashed).

erDiagram
  CUSTOMER ||..o{ ORDER : places
  CUSTOMER ||..o{ ADDRESS : has
  ADDRESS |o..o{ ORDER : "ships to"
  ORDER ||--|{ ORDER_ITEM : contains
  PRODUCT ||..o{ ORDER_ITEM : "sold as"
 
  CUSTOMER {
    bigint id PK
    text email UK
    text name
  }
  ORDER {
    bigint id PK
    bigint customer_id FK
    bigint ship_address_id FK "nullable"
    text status
  }
  ORDER_ITEM {
    bigint order_id PK, FK
    int line_no PK
    bigint product_id FK
    int quantity
  }

Class diagram in Mermaid

$ after an operation marks it static, * abstract; cardinalities go in quotes beside the arrow.

classDiagram
  class PaymentGateway {
    <<interface>>
    +charge(m: Money) Receipt
  }
  class Order {
    -OrderId id
    -OrderStatus status
    +total() Money
    +add(p: Product, qty: number) void
    +pay(gw: PaymentGateway) void
    +create(c: Customer)$ Order
  }
  class OrderStatus {
    <<enumeration>>
    Pending
    Paid
    Shipped
    Canceled
  }
  Order "*" --> "1" Customer
  Order "1" *-- "1..*" OrderLine : lines
  OrderLine "*" --> "1" Product
  Order ..> PaymentGateway : «use»
  StripeGateway ..|> PaymentGateway

Sequence diagram in Mermaid

+ and - after an arrow open and close the target's activation bar.

sequenceDiagram
  participant B as Browser
  participant A as CheckoutApi
  participant S as OrderService
  participant G as Gateway
  participant Q as Queue
  B->>+A: POST /checkout
  A->>+S: placeOrder(cart)
  S->>S: validate(cart)
  S->>+G: charge(total)
  G-->>-S: result
  alt approved
    S-)Q: OrderPlaced
  else declined
    S-)Q: PaymentFailed
  end
  S-->>-A: order
  A-->>-B: 201 Created

State machine in Mermaid

stateDiagram-v2
  [*] --> Pending
  Pending --> Pending: pay [declined] / retry
  Pending --> Paid: pay [approved]
  Pending --> Canceled: cancel, after(30 min)
  Paid --> Shipped: ship
  Paid --> Canceled: cancel / refund
  Shipped --> Delivered: deliver
  Delivered --> [*]
  Canceled --> [*]
  Paid: Paid (entry / reserveStock)

State machine as a reducer

One case per state, one if per outgoing arrow; anything unhandled is ignored. Effects (refund, reserve stock, restart the 30-minute timer) run outside, from the old and new state.

type State =
  | "Pending" | "Paid" | "Shipped"
  | "Delivered" | "Canceled";
 
type Ev =
  | { type: "pay"; approved: boolean }
  | { type: "ship" | "deliver" | "cancel" | "timeout" };
 
export function next(s: State, e: Ev): State {
  switch (s) {
    case "Pending":
      // [declined]: self-transition, timer restarts
      if (e.type === "pay") return e.approved ? "Paid" : s;
      if (e.type === "cancel" || e.type === "timeout") {
        return "Canceled";
      }
      return s;
    case "Paid":
      if (e.type === "ship") return "Shipped";
      if (e.type === "cancel") return "Canceled"; // refund
      return s;
    case "Shipped":
      return e.type === "deliver" ? "Delivered" : s;
    default:
      return s; // final states accept nothing
  }
}

At least one child in PostgreSQL

|{ ("one or many") as a deferred constraint trigger: checked at commit, so the order and its items can be inserted in either order inside one transaction.

CREATE FUNCTION order_has_items() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM order_items WHERE order_id = NEW.id
  ) THEN
    RAISE EXCEPTION 'order % has no items', NEW.id;
  END IF;
  RETURN NULL;
END $$;
 
CREATE CONSTRAINT TRIGGER order_needs_items
  AFTER INSERT ON orders
  DEFERRABLE INITIALLY DEFERRED
  FOR EACH ROW EXECUTE FUNCTION order_has_items();

Deleting the last item of an existing order needs a second trigger, on order_items for DELETE, that runs the same check against OLD.order_id.

ERD in DBML

For dbdiagram.io; @dbml/cli turns it into PostgreSQL DDL and back.

Table customers {
  id bigint [pk, increment]
  email text [unique, not null]
  name text
}
 
Table orders {
  id bigint [pk, increment]
  customer_id bigint [not null, ref: > customers.id]
  ship_address_id bigint [ref: > addresses.id]
  status text [not null, default: 'pending']
}
 
Table order_items {
  order_id bigint [ref: > orders.id]
  line_no int
  product_id bigint [not null, ref: > products.id]
  quantity int [not null]
 
  indexes {
    (order_id, line_no) [pk]
  }
}

ERD in PlantUML

Information Engineering notation; * marks a mandatory attribute by convention, -- separates the key.

@startuml
hide circle
skinparam linetype ortho
 
entity customers {
  * id : bigint <<PK>>
  --
  * email : text <<UQ>>
  name : text
}
 
entity orders {
  * id : bigint <<PK>>
  --
  * customer_id : bigint <<FK>>
  ship_address_id : bigint <<FK>>
}
 
customers ||..o{ orders : places
@enduml

Deployment diagram in PlantUML

@startuml
node "app-1" <<device>> {
  node "Bun 1.3" <<executionEnvironment>> {
    artifact "api.js" as api
  }
}
node "db-1" <<device>> {
  database "PostgreSQL 18" as pg
}
queue "orders" as q
 
api --> pg : TCP 5432
api --> q : AMQP
@enduml

Tables in D2

customers: {
  shape: sql_table
  id: bigint {constraint: primary_key}
  email: text {constraint: unique}
}
orders: {
  shape: sql_table
  id: bigint {constraint: primary_key}
  customer_id: bigint {constraint: foreign_key}
}
orders.customer_id -> customers.id

Use case template

Cockburn's fields, trimmed. The extensions are where the requirements hide.

Use case:     Place order
Primary actor: Customer
Scope:        Shop (the system)       Level: user goal
Stakeholders: Customer wants the goods at the shown price;
              Shop wants payment before shipping.
Precondition: the cart holds at least one item
Guarantees:   success: order Paid, stock reserved
              minimal: no charge without an order
Trigger:      Customer chooses "Check out"
 
Main success scenario
  1. Customer confirms the cart and shipping address.
  2. System prices the order and shows the total.
  3. Customer pays (include: Pay).
  4. System reserves stock and confirms the order.
 
Extensions
  2a. Customer enters a coupon (extend: Apply coupon).
  3a. Payment is declined: System keeps the order Pending
      and offers to retry; after 30 minutes it is Canceled.
  4a. An item went out of stock: System refunds and
      notifies the Customer.

Review checklist

[ ] title, scope, date and the question it answers
[ ] one notation; legend if anything is non-standard
[ ] every line labeled; every end has a multiplicity
[ ] cardinalities read aloud as two sentences each way
[ ] ERD level stated: conceptual, logical or physical
[ ] minimums of one: enforced, or not drawn as one
[ ] composition only where lifetimes are shared
[ ] sequence: one scenario; async drawn as async
[ ] state machine: states are waits, not steps
[ ] source stored next to the code, rendered in CI

References