Living Pattern Playbook: Rust, Laravel, Nuxt
A practical, evolving guide to design patterns, reliability tactics, and modeling techniques you can drop into real services. Code is organized in appendices so the core narrative stays clean.
Table of Contents {#table-of-contents}
Living Pattern Playbook — Rust, Laravel, Nuxt
2. Principles that guide every service
5. Modeling with statecharts and contracts
10. Security and trust as code
11. Team workflow and decision records
Appendix B. Laravel blueprints
Appendix D. TLA+ and Alloy starter specs
Appendix F. Property based testing recipes
Appendix G. CRDT and real time collaboration
Appendix H. Pure Reducer Patterns (Deep Dive)
3) Architecture: Functional core, imperative shell
5) Testing reducers (fast + powerful)
Rust — Pure reducer + effects pattern
Laravel (PHP) — Reducer-as-service and fold
Nuxt/TypeScript — Slice reducers + combine
Appendix I. Testing Strategies for Reducers
10. Migration & Interoperability
11. Checklist for shipping reducer-based features
1. Purpose and scope {#1.-purpose-and-scope}
Give teams a single source of truth for modern patterns that trade complexity for leverage. The goal is to make correctness and evolution natural.
Out of scope: exhaustive academic treatment. We target implementable slices with references to deeper sources when useful.
2. Principles that guide every service {#2.-principles-that-guide-every-service}
- Information hiding and stable interfaces
- High cohesion with low coupling
- Explicit state and transitions
- Idempotency by default
- Time budget and deadlines propagate
- Backpressure instead of buffers
- Schemas are contracts with version rules
- Security decisions are centralized and testable
- Everything important is observable
- Small controlled experiments behind a flag
3. Pattern index at a glance {#3.-pattern-index-at-a-glance}
Architecture
- Hexagonal ports and adapters
- Modular monolith with bounded contexts
- Event sourcing and projections when audit or time travel is required
- Orchestrated sagas for multi step money or entitlement flows
- Workflow engines for durable timers and compensation
Concurrency and flow control
- Actor model and CSP style channels
- Reactive streams with backpressure
- Bulkheads, circuit breakers, hedged requests
Data and evolution
- Append only event logs
- Data contracts with compatibility gates
- Change data capture driven projections
Collaboration and offline
- CRDT based sync for local first experiences
- Causality aware UX
Testing and assurance
- Design by contract
- Property based and model based testing
- Deterministic simulation for workflows
- Mutation testing to harden test suites
4. How to choose patterns {#4.-how-to-choose-patterns}
Use a quick decision table at design time. Pick the dominant force and apply the smallest pattern that resolves it.
| Force | Symptom | Minimal pattern | When to avoid |
|---|---|---|---|
| Temporal complexity | Many branches and retries | Statechart with guards and invariants | When flow is a single step |
| Multi service money flow | Needs compensation and audit | Saga with orchestrator plus outbox | When two steps and same database |
| Collaboration | Concurrent edits and offline | CRDT document plus event log | When strict linear history is required |
| Scale and latency | Queue storms and fan out | Backpressure with bounded queues | When throughput is tiny |
| Auditability | Who did what and when | Event sourcing with projections | When history is irrelevant |
5. Modeling with statecharts and contracts {#5.-modeling-with-statecharts-and-contracts}
Model work as a hierarchical statechart with explicit events. Then state invariants as contracts. Keep the model text based and close to the code. See the code in Appendix A and B for Rust and Laravel implementations, and Appendix C for Nuxt UI state.
Checklist
- All states listed with entry and exit actions
- Events listed with guards and expected effects
- Illegal states are unrepresentable in data types
- Invariants captured as runtime contracts and property tests
6. Reliability toolkit {#6.-reliability-toolkit}
Use defaults that become shared code in every service.
- Idempotency
- Carry an idempotency key in every external call and command
- Store attempt ledger with status and checksum
- Retries
- Exponential backoff with jitter and a hard cap
- Retry only on safe conditions and idempotent operations
- Timeouts and deadlines
- One default time budget, propagate deadline through calls
- Log remaining time in spans
- Outbox and inbox tables
- Write business change and message in one transaction
- A background relay publishes from outbox
- Circuit breaker and bulkheads
- Trip on error rate and latency budget breach
- Reserve capacity per consumer pool
- Sagas
- Explicit steps with compensation and a durable state store
7. Observability plan {#7.-observability-plan}
- Metrics
- RED for services and SLI per critical endpoint
- Traces
- One span per request with business attributes and idempotency key
- Logs
- Structured, sampled at the edge, correlated by trace id
- SLOs and error budgets
- Publish targets and budget policy in repo
8. Delivery and rollout plan {#8.-delivery-and-rollout-plan}
- Feature flags
- Dark launch and shadow traffic
- Canary then linear ramp
- Rollback plan in the pull request template
- Post release verification playbook
9. Data and schema evolution {#9.-data-and-schema-evolution}
- Version schemas and review compatibility
- Additive changes first, then migrations with dual write or backfill
- CDC stream for projections and caches
- Archive policy for append only stores
10. Security and trust as code {#10.-security-and-trust-as-code}
- Threat model per feature with STRIDE
- Policy engine for authorization rules
- Secrets and keys rotated with short TTL
- Signed events for audit with key provenance
11. Team workflow and decision records {#11.-team-workflow-and-decision-records}
- One ADR per significant decision with alternatives and tradeoffs
- Design review checklist in repo
- Runbooks co located with services
12. One month adoption plan {#12.-one-month-adoption-plan}
Week 1
- Pick one flow and model it as a statechart
- Add property based tests for invariants
Week 2
- Add outbox and idempotency middleware to one service
- Add deadlines and a retry policy as a shared library
Week 3
- Move a real saga into a workflow engine
- Add standard metrics and traces with exemplars
Week 4
- Pilot CRDT based sync or WASM plugin behind a flag
- Shadow traffic and compare causally
13. Appendices {#13.-appendices}
Appendix A. Rust blueprints {#appendix-a.-rust-blueprints}
- State machine with enums and pattern matching
- Command handler with outbox pattern
- Property based test with proptest
- Tower layer for idempotency and deadlines
- TLA+ sketch for a saga
See code in this appendix section.
Appendix B. Laravel blueprints {#appendix-b.-laravel-blueprints}
- Module structure for bounded contexts
- Domain events and outbox relay
- Idempotency middleware
- Horizon ready queue setup with retries and jitter
- Saga coordinator options and Temporal interop
- Pest tests with generators and mutation testing with Infection
Appendix C. Nuxt blueprints {#appendix-c.-nuxt-blueprints}
- Statecharts with XState
- Event modeled UI with projections
- Offline first with Yjs and repair loops
- Edge function adapter for ports and adapters style
Appendix D. TLA+ and Alloy starter specs {#appendix-d.-tla+-and-alloy-starter-specs}
- Minimal specs to model order capture and payment saga
Appendix E. ADR template {#appendix-e.-adr-template}
- Copy friendly template for quick decisions
Appendix F. Property based testing recipes {#appendix-f.-property-based-testing-recipes}
- Patterns for generators and shrinking
Appendix G. CRDT and real time collaboration {#appendix-g.-crdt-and-real-time-collaboration}
- Choose a document shape per feature: map for forms, array for ordered lists, text for editors
- Store events in an append only log with causal metadata
- Reconcile with a repair loop that compares projection to source of truth
Appendix H. Pure Reducer Patterns (Deep Dive) {#appendix-h.-pure-reducer-patterns-(deep-dive)}
1) What is a pure reducer? {#1)-what-is-a-pure-reducer?}
A reducer is a total, deterministic function of the form:
(State, Event) -> State- Pure: no IO, no time, no randomness, no hidden reads/writes.
- Total: defined for all valid
(state, event)pairs; if an event is illegal for a state, return the same state (no-op) or a well-defined error value (sum type). - Deterministic: same inputs, same output — this unlocks replay, time travel, and property-based testing.
Think: the reducer is the mathematical heart of your domain; everything else (IO, DB, queues) is the shell.
2) Laws & invariants {#2)-laws-&-invariants}
If you log events e1..en and fold them:
fold(State0, [e1, e2, ..., en]) = reduce(...reduce(reduce(State0, e1), e2)..., en)Useful laws (context-dependent):
- Idempotency (per event type): applying the same event twice should either be impossible (guarded by command-layer) or a no-op.
- Monotonicity (for some domains): certain counters or sets only grow (append-only). Model as such.
- Compositionality: global state can be a product of slices; reducers compose via
productandsumtypes. - Illegal states unrepresentable: encode with ADTs/enums and typed fields.
3) Architecture: Functional core, imperative shell {#3)-architecture:-functional-core,-imperative-shell}
- Core: reducers + pure decision logic (validation, pricing, transitions). No framework/IO.
- Shell: adapters that translate commands to events, persist events, call reducers, publish side effects.
- Boundary: the shell never mutates domain state directly — it feeds events to the reducer.
Pattern: handle(command) -> [events, effects] where the reducer stays (state, event) -> state. The command handler is allowed to do IO to validate and then emit events; the reducer folds them.
4) Composition patterns {#4)-composition-patterns}
- Slice reducers (product types): split state into slices, each with its reducer, then combine.
- Layered reducers: base reducer + decorators that enforce cross-cutting invariants (e.g., quotas, limits).
- Event upcasting: support schema evolution by mapping old events to new ones before reducing.
- Lens-based updates: use lenses/paths to focus updates inside nested state without mutation.
5) Testing reducers (fast + powerful) {#5)-testing-reducers-(fast-+-powerful)}
- Example tests: given state + event => expected state.
- Property-based: generate arbitrary event sequences; assert invariants always hold and no panics occur.
- Replay tests: a production event log must reproduce production projections bit-for-bit.
- Metamorphic tests: inserting a no-op event, or reordering commutative events, does not change final state.
6) Performance & ergonomics {#6)-performance-&-ergonomics}
- Prefer persistent data structures or structural sharing (Rust:
Arc, smallvec, custom; JS: Immer or immutable libs) to keep copying cheap. - Normalize collections (ids -> maps) to keep reducer operations O(1) or O(log n).
- Derive views with selectors and memoization; reducers stay ignorant of derived data.
7) Concurrency & Distribution {#7)-concurrency-&-distribution}
Reducers shine when scoped correctly — especially under concurrency and distributed conditions.
Aggregate boundaries {#aggregate-boundaries}
- Scope each reducer to a single aggregate key (e.g.,
order_id,account_id). - Within that boundary, events are processed serially, guaranteeing consistency.
- Concurrency across aggregates is safe because each reducer instance is isolated.
Serialization strategies {#serialization-strategies}
- Actor mailbox: each aggregate reducer runs inside an actor, processing one event at a time.
- DB row lock: lock per aggregate row while applying events, ensuring serial consistency.
- Partition key: use Kafka/SQS/stream partitions keyed by aggregate ID to guarantee event ordering.
Cross-aggregate coordination {#cross-aggregate-coordination}
- Use a saga or workflow orchestrator to coordinate reducers across multiple aggregates.
- Reducers themselves stay pure and local; the saga emits events to each aggregate’s reducer.
Distribution patterns {#distribution-patterns}
- Event sourcing with sharded logs: reducers replay from partitions assigned per aggregate.
- Snapshotting: store snapshots alongside events to reduce replay cost.
- Leader election: for reducers with global scope (rare), ensure only one active instance processes events.
Invariants under concurrency {#invariants-under-concurrency}
- Keep invariants scoped to the aggregate whenever possible.
- For global invariants (e.g., unique usernames), enforce at command level with locks or consensus, not inside reducers.
8) Anti-patterns {#8)-anti-patterns}
Things to avoid when designing reducers:
- Impure reducers: calling time, random, DB, or network APIs.
- Hidden mutations: modifying global singletons, static caches, or shared state.
- Transport leakage: mixing HTTP, headers, or gRPC details into domain events.
- Side effects inside reducer: reducers must only return new state, not perform I/O.
- Overloaded reducers: too many responsibilities; split into slices or layers.
9) Blueprints {#9)-blueprints}
Rust — Pure reducer + effects pattern {#rust-—-pure-reducer-+-effects-pattern}
#[derive(Clone, Debug)]pub enum OrderState { Draft, Pending { total: u64 }, Paid { txn: String, total: u64 }, Cancelled { reason: String } }
#[derive(Clone, Debug)]pub enum OrderEvent { Submitted { total: u64 }, PaymentReceived { txn: String }, Cancelled { reason: String } }
pub fn reduce(state: &OrderState, ev: &OrderEvent) -> OrderState { use OrderEvent::*; use OrderState::*; match (state, ev) { (Draft, Submitted { total }) if *total > 0 => Pending { total: *total }, (Pending { total }, PaymentReceived { txn }) => Paid { txn: txn.clone(), total: *total }, (Draft, Cancelled { reason }) | (Pending { .. }, Cancelled { reason }) => Cancelled { reason: reason.clone() }, (s, _) => s.clone(), // no-op for illegal transitions }}
pub fn fold(mut s: OrderState, events: &[OrderEvent]) -> OrderState { for e in events { s = reduce(&s, e); } s}Laravel (PHP) — Reducer-as-service and fold {#laravel-(php)-—-reducer-as-service-and-fold}
final class OrderReducer { public function reduce(OrderState $s, array $e): OrderState { return match([$s, $e['type'] ?? null]) { [OrderState::Draft, 'Submitted'] => $e['total'] > 0 ? OrderState::Pending : $s, [OrderState::Pending, 'PaymentReceived'] => OrderState::Paid, [OrderState::Draft, 'Cancelled'], [OrderState::Pending, 'Cancelled'] => OrderState::Cancelled, default => $s, }; }
public function fold(OrderState $s, array $events): OrderState { foreach ($events as $e) { $s = $this->reduce($s, $e); } return $s; }}Nuxt/TypeScript — Slice reducers + combine {#nuxt/typescript-—-slice-reducers-+-combine}
export type OrderState = | { tag: 'Draft' } | { tag: 'Pending'; total: number } | { tag: 'Paid'; total: number; txn: string } | { tag: 'Cancelled'; reason: string };
export type OrderEvent = | { type: 'Submitted'; total: number } | { type: 'PaymentReceived'; txn: string } | { type: 'Cancelled'; reason?: string };
export function reduce(s: OrderState, e: OrderEvent): OrderState { switch (s.tag) { case 'Draft': if (e.type === 'Submitted' && e.total > 0) return { tag: 'Pending', total: e.total }; if (e.type === 'Cancelled') return { tag: 'Cancelled', reason: e.reason || 'unknown' }; return s; case 'Pending': if (e.type === 'PaymentReceived') return { tag: 'Paid', total: s.total, txn: e.txn }; if (e.type === 'Cancelled') return { tag: 'Cancelled', reason: e.reason || 'unknown' }; return s; default: return s; // terminal states are idempotent }}
export const fold = (s0: OrderState, events: OrderEvent[]) => events.reduce(reduce, s0);10) Migration & Interoperability {#10)-migration-&-interoperability}
- Wrapping legacy systems: translate DB mutations into domain events, fold them with reducers to cross-check state.
- Event upcasting: evolve event schemas by mapping old events into new forms before reduction.
- Time travel debugging: reducers + event logs allow replaying history step by step.
- Golden log tests: keep canonical logs to ensure replay results remain consistent after refactors.
11) Checklist for shipping reducer-based features {#11)-checklist-for-shipping-reducer-based-features}
12) Key Takeaways {#12)-key-takeaways}
- Reducers are the simplest truth-preserving core for domain logic.
- By keeping reducers pure, you enable replay, testing, and deterministic debugging.
- Reducers compose naturally across slices and aggregates.
- Paired with event sourcing, reducers provide infinite observability into past states.
- Anti-patterns usually involve breaking purity or hiding side effects.
13) Next Steps {#13)-next-steps}
- Apply reducers to a production flow (orders, payments, certificates).
- Establish golden event logs for replay.
- Extend reducer approach with property-based testing.
- Pilot reducer + event sourcing in a small bounded context before rolling wider.
Appendix I. Testing Strategies for Reducers {#appendix-i.-testing-strategies-for-reducers}
1) Property-based testing {#1)-property-based-testing}
- Generate random sequences of domain events.
- Fold them through the reducer.
- Assert invariants (e.g., totals never negative, illegal states unreachable).
- Use shrinking to isolate minimal counterexamples.
2) Replay testing {#2)-replay-testing}
- Record actual production event logs.
- Replay them through the reducer to reconstruct state.
- Compare against the persisted state (DB snapshot) for consistency.
- Golden logs: preserve a canonical set of event logs to ensure deterministic replay across refactors.
3) Metamorphic testing {#3)-metamorphic-testing}
- Insert no-op events and verify final state remains unchanged.
- Reorder commutative events (e.g., adding items in a cart) and assert same final state.
- Append redundant cancellations and ensure idempotency.
4) Simulation testing {#4)-simulation-testing}
- Use deterministic schedulers to interleave events from concurrent aggregates.
- Validate that per-aggregate invariants hold regardless of scheduling order.
5) Mutation testing {#5)-mutation-testing}
- Apply mutations to reducer logic (invert conditions, alter comparisons).
- Run property and replay tests to ensure they catch regressions.
- Ensures your test suite is strong enough to detect subtle bugs.