Software Architecture and System Design: Turning Requirements Into Decisions
Follow a growing export feature to see when a new boundary earns its operational cost.
Key takeaways
- Architecture concerns consequential structural decisions, while system design works through how requirements will be met.
- Component and data ownership matter even when the application has one deployment.
- Queues and services introduce failure and consistency questions alongside their benefits.
- Record the constraint, consequences, and evidence that would justify revisiting a decision.
Software architecture describes the important structural decisions in a system: where responsibilities belong, how parts communicate, who owns data, and which constraints shape change.
System design works through how the system will meet its requirements. The terms overlap in practice; there is no universal boundary separating an architectural decision from a design decision.
Imagine a reporting application that exports customer records. Initially, an HTTP request reads the database, creates a CSV file, and returns it. As exports grow, requests time out and large jobs interfere with ordinary page loads.
The next design should respond to those observed problems.
Start With Requirements You Can Evaluate
“Make exports scalable” leaves too much unspecified.
A more useful set of requirements might say that customers need confirmation within two seconds, large exports may finish later, records must remain restricted to the requesting organization, and users must be able to retrieve completed files for a defined period.
Now the team can compare designs against something concrete.
Martin Fowler’s architecture guide emphasizes consequential decisions and the ability to change systems. For this application, the important decision is how export work affects request handling, not which diagram style the team chooses.
Establish Ownership Before Splitting Deployments
The application can separate request handling, export generation, and file access into modules without immediately creating independent services.
The export module owns job state. The customer-data module defines which records can be read. The download path checks whether the current user may access the result.
These boundaries reduce the need for unrelated code to modify export tables directly.
A single deployment with clear responsibilities can be easier to operate than several processes with shared internals. Moving a function across the network does not automatically improve the boundary.
Introduce Asynchronous Work for a Specific Reason
To avoid holding the request open, the application can record an export job and arrange for a worker to process it.
The response now means “the job was accepted,” rather than “the file is ready.” The interface must show pending, running, completed, and failed states.
| Decision | Benefit | New responsibility |
|---|---|---|
| Background worker | Keeps long computation out of the request | Job lifecycle and worker operation |
| Queue | Buffers work for processing | Delivery, retry, and backlog handling |
| Separate file storage | Keeps large results outside the database | Access control, expiry, and cleanup |
| More workers | Adds processing capacity | Database load and concurrency limits |
The orchestration and choreography comparison explores communication choices when workflows span more components.
Make Acceptance Durable
Recording the job and publishing a queue message are two operations. If the database write succeeds but publication fails, the customer may see a job that never runs.
The design needs a recovery mechanism. One option is an outbox record written in the same database transaction as the job, then published by a separate process. That publisher may send a message again after an uncertain outcome, so duplicate handling remains necessary.
Another design could have workers claim durable jobs directly from the database.
The appropriate choice depends on throughput, operational constraints, and the delivery guarantees the application needs. The architecture should state how accepted work becomes discoverable after a crash.
Retries Need Business-Level Rules
A worker can finish writing a file and crash before marking the job complete. A later attempt may repeat the work.
Design the operation around a stable job identifier and make repeated processing produce an acceptable outcome. For example, completion could reference one controlled result object rather than create an unlimited series of unrelated files.
AWS’s discussion of idempotent APIs explains why safe retries require an explicit understanding of caller intent.
A retry policy also needs limits and a terminal failure state. Repeating an invalid query forever does not improve reliability.
Define Data Consistency and Authorization
If customer records change while an export runs, should the file reflect one consistent snapshot or whatever each page of the query saw?
That is a product and data-consistency decision, not a queue setting.
Authorization also has a lifecycle. A user may lose access after creating a job but before downloading its result. Decide whether permissions are rechecked during processing and retrieval, and ensure generated files do not become permanently public.
The zero trust model is relevant here: possessing a job identifier should not itself grant access to the underlying records.
Scale the Constraint You Actually Measure
More workers may reduce queue delay while overwhelming the database. If the dominant cost is an inefficient query, increasing concurrency can make the entire application worse.
Observe queue age, processing duration, database load, failures, and download success. Carry a job identifier through logs so an individual export can be reconstructed.
Where the workflow crosses services, distributed tracing can connect related operations. Monitoring should explain customer outcomes, not merely report that worker processes are alive.
Record Why the Decision Was Made
An architecture decision record can capture the request timeout problem, the asynchronous design, alternatives considered, and consequences.
For this feature, it should also record when the choice needs review: sustained backlog, a new consistency requirement, or a need for independent deployment.
The resulting architecture may still be a modular application plus a worker and database. Its quality comes from meeting the export requirements with understandable ownership, recovery, and operating costs.
Every new component should have a reason to exist and a plan for how it will fail.
More Articles Like This
What Is XSD? XML Schema Definition With a Practical Example
