FREE COMPUTER SCIENCE STACK LEVEL · 15 OF 20

Applications & software architecture

How do programs model domains and become coherent products?

The question this layer answers

How do programs model domains and become coherent products?

Useful, maintainable products. This layer connects APIs, Interfaces, Testing, Architecture, Identity to the rest of the computing stack.

An application turns a promise into many boundaries

Return to the library. A reader sees a “Reserve” button, but successful reservation spans a user interface, a request, authorization, validation, shared state, and a response. Begin with a precise promise: for a particular copy, at most one active reservation exists, and repeating the same request does not create a second reservation.

Define observable outcomes before implementation:

SituationRequired behavior
Copy available, reader permittedCreate reservation; return its identifier
Copy already held by another readerExplain the conflict; offer a sensible next action
Same request retriedReturn the original outcome where the retry contract applies
Reader not permittedReject without exposing another reader's private details
Result unknown after timeoutSay it is uncertain; reconcile before claiming failure

The interface must distinguish “not sent,” “processing,” “confirmed,” and “outcome unknown.” Re-enabling a button after a timeout is not a complete retry strategy. A request can have committed even when its response was lost.

Keep authority where it can be enforced

Disabling a browser button is a useful interaction choice, but a client can be modified or bypassed. The server must authenticate the caller, authorize the requested operation, validate the input, and enforce the reservation invariant at the shared database boundary. Never accept a request's claimed owner as proof of identity.

Separate policy from transport where practical. A reservation rule can be tested with ordinary values without an HTTP server. Boundary tests then verify that HTTP inputs are parsed, identities are derived correctly, and domain outcomes become appropriate responses. This separation makes failures easier to locate; it does not remove the need for integration tests.

Test behavior that could actually fail

Write a table covering two readers reserving concurrently, a retry after commit, a malformed copy identifier, a cancelled reservation, an unauthorized request, and a database outage. For each case, name the final stored state and the response visible to the reader. Checking only a status code can miss a broken invariant.

Accessibility belongs in the contract. A keyboard user must reach and activate the action, focus should remain meaningful after updates, and status changes need perceivable text rather than color alone. A clear conflict message helps everyone recover. Test the actual flow rather than assuming semantic markup alone guarantees usability.

Collect only the data the task needs

A reservation needs a reader reference, a copy reference, relevant timestamps, and state. It usually does not need a full personal profile copied into every log line. Minimize fields, restrict access, and define retention. Observability should help diagnose failures without casually exposing reading histories.

Capstone artifact: produce the state diagram, the outcomes table, and a set of concurrent and failure tests. Then sketch the smallest implementation that honors them. Framework selection comes after understanding the behavior it must preserve.

Free Applications & software architecture lessons

1. Architecture is change made legible

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.

2. An API is a promise over time

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.

3. Evidence across the lifecycle

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.

4. Application mastery: design for failure and change

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.

Practice lab: Design a reservation people can trust

About 35 minutes. Know functions and state; understand that an API request can fail or be retried. No web framework is required.

Design reserve(book_id, request_id) for a library with one available copy. Users can click twice, connections can drop, and two users may request the same book. The server authenticates the user. Store the reservation and request result durably.

  1. Specify success, unavailable, invalid request, and unknown-outcome states. Define how the same authenticated user retries a request.
  2. Write tests for two users racing, a lost response after commit, and reuse of a request ID with different inputs.
  3. Sketch the interface for keyboard and screen-reader users. Decide how pending, confirmed, and failed outcomes appear without relying on colour. State which personal data is necessary and when it can be removed.
Hint

A disabled button helps the interface but does not protect the database. Couple the reservation and recorded request result atomically.

Worked solution

Scope request IDs to the authenticated user and bind each ID to a request fingerprint. Atomically check availability, reserve the copy, and record the result. A matching retry returns that result; the same ID with different input is rejected. Concurrent attempts for the last copy produce one confirmed reservation and one unavailable result through a database-enforced condition. After a lost response, the client queries or retries the same operation instead of inventing a new ID. Provide a labelled button, visible keyboard focus, text status announced to assistive technology, and a recovery action. Store the user identifier needed to fulfil the reservation; avoid unrelated profile data and define retention by purpose.

  • Put concurrency control on the server, not only in the button.
  • Distinguish an unknown result from a confirmed failure.
  • Include usable error recovery, keyboard access, and data minimization.

Change one assumption

If a reservation expires after 24 hours, distinguish retrying the original request from creating a new reservation. How long must the recorded request result remain meaningful?

Make the contract survive change and failure

Choose boundaries around invariants, then test them with concurrent actions, retries, incompatible clients, and misleading metrics.

1. Design and challenge · allow 45 minutes

The invariant determines the transaction boundary

A store has one unit of an item and two simultaneous buyers. A proposed architecture lets Checkout read stock from Inventory, charge the card, and then tell Inventory to decrement. Construct an oversell execution. Design a reservation protocol with expiry, payment retries, and an explicit state machine. Identify what is atomic locally, what is only eventually resolved, and how a delayed payment response interacts with expiry.

Get a starting hint

A successful stock read is not a reservation. Expiry and payment confirmation need an agreed winner, not two independent booleans.

Compare your reasoning with the worked solution

Both checkouts can read stock=1, both charge, and both decrement, producing oversell or a charged customer with no item. Inventory must atomically reserve against available stock, using a conditional update or transaction that enforces available≥requested. Give each reservation a stable ID and states such as held, confirmed, released. Only one transition out of held can win; checking a timestamp without a conditional state transition is insufficient.

Payment requests need stable idempotency identities and a recoverable pending state. If expiry releases the reservation before a delayed successful payment is reconciled, the system must attempt a new reservation or refund according to an explicit policy. It cannot claim both guaranteed shipment and unconditional release without a coordinating protocol. A local transaction can atomically change inventory and its event/outbox record; it does not atomically commit a remote card processor. Define reconciliation jobs, timeout behavior, audit records, and customer-visible pending states. Test simultaneous reserve, duplicate confirm, confirm-versus-expire, and lost payment acknowledgment. The design is judged by preserved stock and money invariants, not by the number of services.

Check your reasoning

  • Give the concrete oversell interleaving.
  • Make reserve and terminal transitions atomic.
  • Resolve delayed payment after expiry explicitly.

2. Contract evolution · allow 30 minutes

An added enum value can break an old client

Version 1 returns status pending or complete. Version 2 adds cancelled and makes a previously optional reason field required in requests. Explain two ways this can break deployed clients. Propose a migration plan with contract tests and rollback behavior. Is returning HTTP 200 enough to make a request retry-safe?

Get a starting hint

Compatibility is defined by actual old-client behavior and old-server acceptance. Successful transport does not identify the semantic effect.

Compare your reasoning with the worked solution

An old client may use an exhaustive switch with no unknown-status behavior, so a new response enum value can crash or misrender it. Requiring reason rejects old requests that omitted it. Stage the request change by first accepting both shapes, defining a default or maintaining the old path, measuring adoption, and only enforcing the new contract in a negotiated version or after an explicit deprecation process. For response expansion, either preserve the old representation for old clients or first ship clients that tolerate documented unknown values. Test captured old-client contracts against the new server and new clients against the rollback server.

Rollback must account for data already written in the new format; reverting code does not erase cancelled records. Define whether old versions can read them and how any migration is reversed or made forward-compatible. HTTP 200 reports success according to the endpoint contract. It does not make a repeated charge harmless. Retry safety requires idempotent semantics or a durable operation identity, payload consistency checks, and a retention policy.

Check your reasoning

  • Test both directions of version compatibility.
  • Include persisted data in rollback.
  • Separate response codes from effect deduplication.

3. Property and mutation testing · allow 40 minutes

Find the bug that example tests cannot name

A function returns a stable key-sorted copy of a list. Tests only check that the result is nondecreasing. Give an implementation that passes yet is wrong. Specify independent properties that catch element loss, mutation of the input, and unstable ordering of equal-key records. Define a useful shrinking strategy. Then explain what must be pinned or recorded to reproduce a failing build on another machine.

Get a starting hint

The empty list is sorted. Compare multisets, not sets, and distinguish a copy contract from in-place sorting.

Compare your reasoning with the worked solution

Returning [] passes the ordering property for every input. Add multiset preservation, including duplicate counts, and assert that the original input is unchanged and the returned object satisfies the promised aliasing contract. For stable key sorting, attach original indices and assert that equal-key output records retain increasing original index. Also test empty input, one element, repeated keys, reverse order, and values at comparator boundaries. A deliberately broken implementation that drops duplicates should fail multiset preservation; one that sorts in place should fail the input-preservation assertion.

Shrink a failure by removing chunks and simplifying values while retaining the failing property; for stability, preserve at least two equal-key distinguishable records. Store the random seed and minimized counterexample. Reproducing the build needs the source revision, lockfile and package-manager version, runtime/toolchain versions, build commands, relevant non-secret configuration, and platform constraints. A lockfile alone does not pin system libraries, generated inputs, timestamps, network downloads, or nondeterministic steps. Use a clean environment and compare outputs or behavior rather than assuming a successful build proves reproducibility.

Check your reasoning

  • Use multiplicities, not just set equality.
  • Test the documented mutation and stability contracts.
  • Preserve a minimal failing case and enough build inputs to reproduce it.

4. Calculate and decide · allow 35 minutes

Spend an error budget in the correct unit

A request-based SLO requires 99.9% of eligible requests to succeed over a rolling 30-day window. The window has 2,000,000 eligible requests and 1,500 failures. Calculate the allowed failures, remaining budget, and fraction consumed. During an incident, 2% of 10,000 requests fail in one hour. Compute that hour's burn rate relative to the SLO. Why is “43.2 minutes of downtime” not an equivalent statement for this request-based objective? Propose an alert and a release decision with limitations.

Get a starting hint

The budget fraction is 0.001 of eligible requests. Burn rate compares observed failure proportion with the allowed proportion.

Compare your reasoning with the worked solution

Allowed failures are 2,000; remaining budget is 500; consumption is 1,500/2,000=75%. The incident has 200 failures and a burn rate of 0.02/0.001=20. If its traffic volume is representative, that rate spends budget much faster than the long-run allowance, but extrapolation must consider traffic changes and rolling-window replacement.

A time-based 99.9% objective over 30 days permits 0.001×30×24×60=43.2 minutes of bad time. That is a different denominator: a short outage at peak traffic can consume far more request budget than a long low-traffic outage. Define eligible requests and failure classifications so client mistakes do not silently distort server reliability. Use multiple alert windows to distinguish a rapid serious incident from noise, and include low-volume handling. With only 25% of the current budget left, a risky release may warrant postponement or a small monitored rollout, but the decision also depends on urgency, rollback ability, and whether the change repairs the active failure.

Check your reasoning

  • Compute budget and burn rate using request counts.
  • Do not substitute time availability for request success.
  • Tie action to measured risk and rollback, not a threshold alone.

5. Race trace and repair · allow 30 minutes

The older search result arrives last

A user types cat and then caterpillar. Request A for cat starts first; request B starts second and finishes first. The UI writes every arriving response into the results panel. Trace the bug. Propose an implementation that prevents stale results, handles loading and errors for the active query, and remains correct if cancellation does not stop server work. What accessibility feedback should accompany the change?

Get a starting hint

Associate every state update with a generation or request identity, not only successful results.

Compare your reasoning with the worked solution

B first displays caterpillar results; A later overwrites them with cat results while the input still says caterpillar. Increment a request generation whenever the query changes. Capture it for each request and apply success, failure, and loading-finalization updates only if it still equals the active generation. Aborting A can reduce work, but the identity check remains necessary because completion can race cancellation and remote work may continue. On unmount, invalidate the generation or suppress updates through cleanup.

The state should distinguish idle, loading for the current query, success, and error, and define whether previous results remain visible during loading. Do not let A's finally block clear B's loading indicator. Announce meaningful status changes through an appropriate live region without moving focus on every keystroke. Keep the input keyboard-operable and preserve focus. Test reversed completion order, A failing after B succeeds, rapid repeated identical queries, empty input, and component teardown.

Check your reasoning

  • Guard success, failure, and cleanup updates.
  • Treat cancellation as an optimization, not the correctness proof.
  • Preserve focus and provide restrained status feedback.

Study the underlying methods

Section project: Build a reservation service with explicit failure contracts

6 to 8 hours. Implement a local reservation service with a domain layer, persistence boundary, and thin interface. Use Python and SQLite; a command-line JSON interface is sufficient, with an optional localhost HTTP adapter. Inputs are create-resource, reserve, cancel, and inspect commands carrying an explicit local user identifier and request identifier. A resource has a fixed positive capacity. A successful reservation consumes seats for one user, cancellation restores them, and duplicate request identifiers must return the original result without applying the operation twice. Define whether request identifiers are unique globally or per user and reject reuse with different payloads. Treat the supplied user identifier as a test identity, not secure authentication. Outputs are structured responses with stable error categories, reservation identifiers, and remaining capacity. Write contracts before implementation and keep storage details out of the domain API. Inject failures before commit and after commit but before response delivery to investigate retry behavior. Submit a short architecture note explaining transaction boundaries, idempotency storage, and which errors callers can retry safely. Document the result of cancelling an already cancelled reservation. Include a restart test rather than relying only on repeated calls in one process, because persisted retry behavior is part of the contract.

Milestones

  1. Specify command schemas, invariants, identity assumptions, and error responses.
  2. Implement the domain operations and transactional SQLite persistence.
  3. Add persisted idempotency records and payload-conflict detection.
  4. Inject failures around the commit boundary and exercise caller retries.
  5. Document the interface, run a complete reservation-and-cancellation scenario, and review module dependencies.

Acceptance criteria

  • Remaining capacity never falls below zero or exceeds the configured total.
  • Repeating an identical successful request identifier returns the same result without consuming additional seats.
  • Reusing an identifier with a different payload returns a conflict and leaves state unchanged.
  • A failure before commit leaves neither the reservation nor its success record persisted.
  • A simulated lost response after commit can be retried after restart without creating a second reservation.
  • A user cannot cancel another user’s reservation through the domain interface.

Add an audit-log export and a reconciliation command that independently recomputes remaining capacity from persisted reservations and reports any discrepancy.

Practise Applications & software architecture free →