Contracts — Composing Contract Chains

Why Composition Matters

Real features are never one contract. They are chains — sequences of contracts where each step's output feeds the next step's input. The chain's success depends on every link, and a failure at any point must be handled.

Composing contracts is where design becomes architecture.


The Three Rules of Contract Chains

Rule 1: Output Shape Must Match Input Shape

If Contract A returns a validated_cart and Contract B accepts a validated_cart, they connect. If A returns a cart (not validated) and B expects validated_cart, they don't — and the gap will produce a bug.

What happens if step 3 of 5 fails? Does the whole chain stop? Do steps 1 and 2 need to be undone? Does the chain skip step 3 and continue? The answer must be explicit for every link.

Rule 3: Side Effects Complicate Rollback

If step 1 sends a confirmation email and step 3 fails, you can't "unsend" the email. Side effects are often irreversible, so the chain must account for which steps can be undone and which can't.


Worked Example 1: E-Commerce Checkout

A customer clicks "Place Order." Here's the full chain:

Step 1: ValidateCart
  IN:  cart_id
  OUT: validated_cart (items confirmed in stock, prices confirmed current)
  FAIL: "Item X is out of stock" → stop, show error, suggest alternatives

Step 2: CalculateTotal
  IN:  validated_cart, discount_code (optional), shipping_address
  OUT: order_total (subtotal, discount_amount, tax, shipping, grand_total)
  FAIL: "Invalid discount code" → stop, show error, let customer fix
  FAIL: "Cannot ship to this address" → stop, show error

Step 3: ReserveInventory
  IN:  validated_cart
  OUT: reservation_id (items held for 10 minutes)
  FAIL: "Item X went out of stock since cart was validated" → stop, show 
        error, go back to step 1
  NOTE: This is a TEMPORARY hold. If step 5 fails, reservation is released.

Step 4: ProcessPayment
  IN:  grand_total, payment_method
  OUT: payment_confirmation (transaction_id, status)
  FAIL: "Card declined" → release reservation (undo step 3), show error
  FAIL: "Payment service unavailable" → release reservation, show error,
        suggest retry

Step 5: CreateOrder
  IN:  validated_cart, order_total, payment_confirmation, customer_id
  OUT: order_record (order_id, status = "confirmed")
  FAIL: "System error creating order" → THIS IS CRITICAL. Payment was 
        already processed. Must either: (a) retry order creation, or 
        (b) refund the payment. Never leave money charged without an order.

Step 6: SendConfirmation
  IN:  order_record, customer_email
  OUT: (none — fire and forget)
  FAIL: "Email service unavailable" → log the failure, do NOT undo the 
        order. The order is valid. Email can be resent later.
  SIDE EFFECT: Email sent to customer.

The Failure Cascade

Let's visualize what happens at each failure point:

Fails AtSteps CompletedWhat Must Be UndoneUser Sees
Step 1NoneNothing"Item out of stock" — redirect to cart
Step 2Cart validatedNothing (validation has no side effects)"Invalid discount code" — fix and retry
Step 3Cart validated, total calculatedNothing (no side effects yet)"Item just went out of stock" — back to cart
Step 4Cart validated, total calculated, inventory reservedRelease inventory reservation"Card declined" — try another card
Step 5All above + payment chargedRefund payment + release inventory"System error — please contact support" + automatic refund
Step 6Everything above + order createdNothing to undo — order is validOrder succeeds. Email will be retried later.

This table is the most valuable artifact in the design. It shows exactly what's at risk at each step and what recovery looks like.


Worked Example 2: Employee Onboarding

A new employee is onboarded into a company's systems. This is a multi-system chain involving HR, IT, Facilities, Payroll, and more.

Step 1: CreateEmployeeRecord
  IN:  name, role, department, start_date, manager_id, salary
  OUT: employee_id, employee_record
  FAIL: "Manager not found" → stop, HR fixes manager assignment
  FAIL: "Duplicate employee (matching name + DOB)" → stop, HR investigates

Step 2: SetupPayroll
  IN:  employee_id, salary, tax_withholding_info, bank_account (for direct deposit)
  OUT: payroll_enrollment_confirmation
  FAIL: "Invalid bank routing number" → stop, request corrected info
        (Step 1 persists — employee record exists but payroll isn't set up)
  SIDE EFFECT: Employee added to next payroll cycle

Step 3: CreateIT Accounts
  IN:  employee_id, role, department
  OUT: email_address, system_credentials, access_permissions_list
  FAIL: "Email address conflict (name.lastname already taken)" → 
        auto-generate alternative (name.middle.lastname), proceed
  SIDE EFFECTS: Email created, VPN access granted, software licenses assigned

Step 4: AssignEquipment
  IN:  employee_id, role, department
  OUT: equipment_list (laptop model, monitor, phone, badge)
  FAIL: "Laptop model out of stock" → substitute, proceed with warning
  SIDE EFFECT: Equipment reserved in inventory, shipping initiated

Step 5: SetupWorkspace
  IN:  employee_id, department, start_date
  OUT: workspace_assignment (building, floor, desk number)
  FAIL: "No desks available in department area" → assign temporary desk, 
        add to waitlist
  SIDE EFFECT: Desk reserved in facilities system

Step 6: SendWelcomePackage
  IN:  employee_id, email_address, start_date, workspace, equipment_list
  OUT: (confirmation)
  FAIL: Non-critical — retry later
  SIDE EFFECT: Welcome email sent with first-day instructions

Key Differences From E-Commerce Chain

Not all steps are dependent. Steps 2, 3, 4, and 5 can happen in parallel — they all need employee_id from step 1, but they don't need each other's outputs. This changes the chain from a strict sequence to a fan-out:

                                ┌── Step 2: Payroll
                                ├── Step 3: IT Accounts
Step 1: Create Record ──────────┤
                                ├── Step 4: Equipment
                                └── Step 5: Workspace
                                          │
                         All complete ─────┘
                                          │
                                    Step 6: Welcome

Failures don't cascade backward. If IT can't create an account, that doesn't mean HR needs to delete the employee record. Each step has its own failure handling. This is a design choice — the chain is tolerant of partial completion, unlike the e-commerce chain where payment requires inventory reservation.

Some failures are handled with substitution, not cancellation. "Laptop out of stock" → substitute a different model. "Email conflict" → generate alternative. The chain tries to continue whenever possible.


Worked Example 3: Medical Lab Test Process

A doctor orders a blood test. The sample is collected, processed, and results are delivered.

Step 1: OrderTest
  IN:  doctor_id, patient_id, test_type (e.g., "complete blood count"), 
       urgency ("routine" | "urgent" | "stat"), clinical_notes
  OUT: test_order (order_id, patient_name, test_type, collection_instructions)
  FAIL: "Patient has allergy flagged for this test prep" → warning to doctor
  SIDE EFFECT: Order appears on lab's work queue

Step 2: CollectSample
  IN:  order_id, collector_id (phlebotomist), patient_id_verification 
       (wristband scan or verbal confirmation of DOB)
  OUT: sample_record (sample_id, collection_time, tube_type, volume)
  FAIL: "Patient ID verification failed (wristband doesn't match order)" 
        → HARD STOP. Do not collect. This prevents testing the wrong 
        patient's blood — a potentially fatal error.
  FAIL: "Insufficient sample volume" → recollect
  SIDE EFFECT: Sample labeled with barcode, linked to order_id

Step 3: ProcessSample
  IN:  sample_id
  OUT: processing_record (processing_start_time, analyzer_id, status)
  FAIL: "Sample hemolyzed (damaged)" → error to collector: "Recollection 
        needed. Reason: hemolysis." → back to Step 2
  FAIL: "Analyzer malfunction" → route to backup analyzer
  SIDE EFFECT: Sample processing logged for quality control

Step 4: AnalyzeResults
  IN:  processing_record
  OUT: raw_results (values for each test component, reference ranges, 
       flags for abnormal values)
  FAIL: "Results outside analyzable range" → flag for manual review 
        by lab technician
  SIDE EFFECT: Results stored in lab information system

Step 5: ReviewResults
  IN:  raw_results, patient_history (previous test results for comparison)
  OUT: reviewed_results (same as raw, plus: technician_notes, 
       critical_value_flag)
  FAIL: None — this step always produces a result (even if the result 
        is "requires further testing")
  SIDE EFFECT: If critical value detected (life-threatening result), 
  IMMEDIATE notification to ordering doctor — this must happen within 
  minutes, not hours. This is a regulatory requirement.

Step 6: DeliverResults
  IN:  reviewed_results, doctor_id, patient_id
  OUT: delivery_confirmation
  FAIL: "Doctor not available" → deliver to covering physician
  SIDE EFFECTS: Results appear in patient's medical record, 
  doctor receives notification in their clinical dashboard

Why This Chain Is Unique

Patient safety creates hard stops. Step 2 has a verification check that cannot be bypassed or substituted. If the patient ID doesn't match, the chain stops completely. No alternative, no workaround. This is the highest-stakes failure in the chain — a wrong patient's blood being analyzed means wrong treatment decisions.

Some failures loop backward. "Sample hemolyzed" at step 3 sends the chain back to step 2 (recollect). This isn't a simple linear chain — it has loops.

Time sensitivity varies by step. Routine orders might wait hours at each step. Stat orders bypass the queue at every step. The urgency flag changes the behavior of every contract in the chain without changing the contracts themselves — it's a priority signal that travels through the chain.

Critical value notification is a side effect that overrides normal flow. Normally, results go through all steps sequentially. But if step 5 detects a critical value (e.g., dangerously low blood sugar), the side effect triggers an immediate alert — even before step 6 formally delivers the results. The side effect has higher priority than the main chain.


Composing Contracts: Summary Principles

PrincipleWhat It Means
Map the happy path firstGet the chain right when everything works, then add failure handling
Define the failure point for every stepWhat happens here if this fails? Stop? Undo? Substitute? Skip?
Identify irreversible stepsEmails sent, payments charged, physical actions taken — these can't be undone
Look for parallelizable stepsNot every chain is strictly sequential — find steps that don't depend on each other
Look for loopsSome failures send you back to an earlier step. Map these explicitly.
Time sensitivity shapes the chainA chain that must complete in 200 milliseconds is designed very differently from one that spans 5 business days
The rollback plan is as important as the happy pathFor every step that changes state, document how to reverse it if a later step fails