Free Applications & software architecture lessons
Cohesion, coupling, and domain boundaries
Cohesion keeps related rules and data together. Coupling measures how many assumptions cross a boundary. A good boundary exposes a stable capability, hides volatile implementation detail, and keeps its invariants enforceable in one place. Layers, components, and services are tools—not goals.
A checkout handler can parse transport input and call a subscription capability. Eligibility, proration, and renewal rules belong in a domain boundary that can be exercised without HTTP or a particular database. This does not require grand frameworks; it requires dependencies pointing toward the rule owner.
Contracts, identity, errors, and idempotency
Specify valid requests, authorization, identity, invariants, success, failure, retries, ordering, and compatibility. Stable identifiers should not silently inherit mutable display names. Errors should tell callers whether to correct, retry, wait, or stop. Versioning cannot repair an ambiguous contract.
A client sends a request; the server commits; the response is lost. The client sees a timeout but the operation succeeded. Blind retry can duplicate the effect; giving up can mislead the user. Operation identity plus a queryable outcome lets the protocol resolve uncertainty.
Tests, telemetry, and debugging
Unit tests isolate rules; integration tests check boundaries; end-to-end tests exercise a thin critical path; property tests explore invariant-preserving input spaces. In production, logs describe events, metrics summarize populations, and traces connect one request across components. None is a substitute for a clear hypothesis.
Reproduce the user-visible symptom, attach a correlation identifier, and compare expected versus actual state at each boundary. Find the first representation that is wrong, not the loudest downstream exception. Form a hypothesis, predict another observable consequence, and intervene narrowly.
Security, migration, and evolutionary architecture
Authentication establishes an identity claim; authorization decides whether that identity may perform this action on this resource. Data migrations must coexist with old and new code during rollout. Feature flags, compatibility readers, rollback plans, and audit trails make change reversible and attributable.
To rename a database field safely across rolling deployments: first add the new representation and make code tolerate both; backfill and observe; switch writers and readers; only then remove the old form after no compatible consumer needs it. One destructive step cannot safely span mixed versions.