Draw the responsibilities behind a full-stack feature
Map the responsibilities of an export feature across interface, API, worker, storage and operations before dividing the work.

A full-stack feature becomes easier to discuss when you draw the decisions each part owns. My preference is to do that before assigning tasks or choosing services. A component diagram tells you where code runs; an ownership map also tells you who defines the behavior when the request fails, waits or crosses a security boundary.
Consider a hypothetical report-export feature. A user requests a report, waits while a background worker creates it and downloads the result. The interface, API, worker and storage may all be implemented by one person in a learning project. They still have different responsibilities, and leaving one unspecified can produce a feature that looks complete while its recovery path is unclear.
Begin with the user’s promise
Write one sentence about what the user is being offered: “Request a report for the records you may access, then download the completed file when it is ready.” Several decisions are hidden inside that sentence.
Which records may the user access? Does the export use permissions at request time or download time? How long is the file retained? What happens if the worker fails after creating the file but before recording completion? How does the user distinguish a queued request from a lost one?
You do not need to answer every question immediately. Put each question beside the part of the system that can enforce the answer. A product decision about retention belongs in the contract, while storage configuration and cleanup code must implement it. If nobody owns that connection, a discussion about “the storage task” may miss the actual requirement.
Map decisions and evidence together
A first version can map decisions to evidence like this:
| Boundary | Decision it owns | Evidence to inspect |
|---|---|---|
| Interface | How pending, failed and complete states are explained | A visible trace for each state and a usable recovery action |
| API | Who may request which report and what identifier is returned | Authorized and unauthorized request cases |
| Worker | How a job changes state and handles another attempt | A state trace including an interrupted attempt |
| Storage | Who may retrieve the file and when it expires | Access checks and an explicit retention rule |
| Operations | How stuck work is detected and who responds | A signal, an owner and a tested investigation procedure |
Another architecture might move some of these responsibilities elsewhere. The important relationship is between a decision and the evidence that its implementation follows the intended rule.
The extra operations connection matters. A job can remain in a pending state without producing a visible exception. If the interface simply keeps polling, the user may wait indefinitely while each component appears to be running.
Locate the handoffs that need a contract
Suppose the API returns a job identifier before the worker starts. The interface needs to know which states it may receive when it checks that identifier. The worker needs to know whether retrying the job may create another file. Storage needs a rule for objects left behind by interrupted attempts.
Write those agreements in terms of observable behavior. “The worker should be reliable” is too broad. “A repeated attempt for the same completed job must not expose two different download results” is a condition people can inspect, although the implementation still needs design work.
The Google engineering review guidance asks whether a change’s design fits the system and whether its functionality is appropriate. An ownership map helps a reviewer find the surrounding behavior that a local patch may affect. A change to the worker’s state names is also a change to the interface’s interpretation unless a translation layer preserves the contract.
Give the missing work a named owner
An ownership map is useful only if it changes an action. Mark an unresolved decision explicitly rather than placing it in a box labeled “later.” For example, the first release may not yet have an agreed retention period. That is a product and operational question that needs a decision before files are kept indefinitely by accident.
For team work, distinguish the person implementing a change from the person authorized to decide the policy. A developer may implement deletion, while a product or legal owner supplies the retention requirement. In a personal learning project, you can make the policy yourself, but you should still write down the assumption.
The Google SRE chapter on service-level objectives explains the role of indicators and objectives in describing service behavior. For this example, a useful indicator might involve completed exports within an agreed duration. Process uptime alone would not answer whether users receive their files.
Keep the map proportional to the feature
This approach can become bureaucratic if every helper function gets its own responsibility box. Draw boundaries where a promise changes, access changes or failure requires a different response. For a local prototype, one page and a few state traces may be enough.
Before implementation, ask another person to follow a request through the map and point to the owner of a stuck export. If the answer requires guessing, revise that part. After implementation, compare the actual request and job traces with the drawing. The map earns its place when it helps find missing behavior or makes a handoff easier to review.
Test the map with an access change during the job
A responsibility map becomes more useful when you run a difficult timeline through it. Suppose a user requests an export while they belong to a workspace. The job remains queued. Before the worker starts, the user loses membership. The worker later produces a file. Who decides whether the user may download it?
For this fictional product, choose a clear policy: a user whose access has been revoked may no longer retrieve workspace exports. That choice does not automatically determine whether the worker should cancel, finish for other authorized users or delete the result. Those are additional decisions that need an owner.
| Point in the timeline | Decision to make | Boundary that can enforce it |
|---|---|---|
| Request accepted | Was the user allowed to request this report? | API authorization |
| Membership removed | What access ends and when? | Product policy and membership model |
| Worker begins | Is the queued job still useful and permitted? | Job contract and worker behavior |
| Download requested | May this caller retrieve the result now? | Retrieval authorization |
| Result retained | Who can access the object and for how long? | Storage access and retention implementation |
The OWASP authorization guidance recommends permission checks on requests. In this timeline, the initial request check is not enough to implement the chosen download policy. The result path must be examined too.
The figure separates a policy decision from the code that enforces it. A developer can implement a clear rule, but should not silently decide a product’s access semantics merely because the storage library makes one option convenient.
Distinguish a decision owner from an implementer
The person writing the worker may not have authority to decide how long reports containing sensitive data should remain available. The person defining retention may not know which cleanup mechanism can reliably implement it. Both contributions are needed, and the map should make their connection visible.
For a small team, one person may hold several responsibilities. Write the responsibilities separately anyway. It lets the review ask whether the policy was actually chosen and whether the implementation follows it. In a personal project, you can make the decision yourself, but a written rule still prevents a later feature from relying on a different assumption.
A useful handoff contains the rule, the relevant examples and the unresolved edge. For the revoked user, a backend implementer needs to know whether an already-issued download capability can outlive membership and how that possibility affects the chosen policy. The answer depends on the access mechanism; it cannot be inferred from an endpoint name.
Include the result left behind by a failed attempt
Now consider a worker that writes a file and stops before marking the job complete. The interface still shows pending, while storage contains an object. Which component can identify that object, decide whether it is reusable and remove it if necessary?
Attach the object to a stable job identity or another inspectable relationship. Without that relationship, a cleanup process may be unable to distinguish an orphan from a result still in use. A retention rule also needs a starting event: creation time, job completion or last authorized access produce different deletion behavior.
The first implementation can choose a simple rule, such as deleting unreferenced temporary objects after a bounded period in the exercise environment. It still needs a way to recognize those objects and avoid deleting an accepted result. Document the assumption and test a failed attempt separately from an ordinary completed export.
Make the review follow a failure across owners
Ask a reviewer to begin with “the user cannot download the report” and follow the available evidence. Can they identify the job, its attempts and the result object? Can they tell whether the user currently has access? Is there a named owner for a pending job that has no active worker?
If the investigation stops at a team boundary, add the missing handoff or observation. That does not necessarily require another service or dashboard. A stable identifier, a precise event and an agreed owner may be enough. The ownership map should make the difficult request easier to resolve, not create a parallel organization chart that nobody uses during the failure.