Production Backend API Capstone
Turn Decisions and Incidents Into a Portfolio With No Secrets
One-line summary
An ADR preserves the context, alternatives, choice, and consequences at the time a decision was made, and an incident report records the actual impact, timeline, root cause, detection, and corrective actions that have owners. A portfolio does not copy these originals; it publishes only the permitted summaries.
Why code alone cannot explain a judgment
The ON CONFLICT syntax shows what was chosen, but it does not say why the database boundary was chosen over client-only deduplication. Someone looking at that code six months later will do one of two things: leave it alone because they do not know the reason, or rip it out because they do not know the reason. Both are bad.
An ADR (Architecture Decision Record) records the constraints at the time, the alternatives compared, and the expected cost. The shorter the format, the better.
# ADR-014: 중복 제거를 DB 제약으로 옮긴다
- 상태: 채택 (2026-03-11)
- 맥락: 결제 웹훅이 재시도로 같은 이벤트를 최대 3번 보낸다. 클라이언트 쪽
메모리 캐시로 걸렀는데 파드가 늘면서 캐시가 나뉘어 중복이 새기 시작했다.
- 대안:
1. Redis 분산 락 — 새 의존성이 생기고, 락 해제 실패 시 결제가 멈춘다.
2. 애플리케이션 조회 후 삽입 — 조회와 삽입 사이에 경쟁이 남는다.
3. unique 제약 + ON CONFLICT DO NOTHING — 경쟁이 DB 안에서 끝난다.
- 결정: 3안. 멱등 키를 (provider, event_id) 로 두고 유니크 인덱스를 건다.
- 결과: 중복 결제 0건. 대신 삽입 지연이 p99 기준 4ms 늘었다.
- 되돌리는 조건: 삽입 지연이 p99 20ms 를 넘으면 재검토한다.
An ADR with a "condition for reverting" and one without are different documents. With a condition, you can measure it later and decide; without one, the decision lives forever.
What makes an incident report a learning record
When an incident happens, you look back through the incident report at whether the design's assumptions actually held. It must be a learning record for building the system's next safeguard, not a document for finding someone to blame. Several mechanisms protect this character.
The timeline includes the time zone and distinguishes three times.
| Time | Meaning | If this value is large |
|---|---|---|
| Start | When user impact began | — |
| Detection | When a person noticed | Observability is insufficient |
| Recovery | When the impact ended | The response procedure is slow |
If detection took 40 minutes, then regardless of the root cause, "why did we not know for 40 minutes" is an independent corrective item. Half of all incidents come from here.
The root cause points to a condition, not a person. Not "so-and-so deployed a bad configuration" but "there was no validation on the path by which a bad configuration reaches deployment." The former does not prevent the next incident, and the latter does.
A corrective action has an owner, a due date, and a verifiable completion condition, not "be careful."
- [ ] values 스키마 검증을 CI 에 추가 (담당: 배포팀, 기한: 3/25)
완료 조건: 잘못된 replicas 값을 넣은 PR 이 CI 에서 떨어지는 것을 확인
- [ ] 에러율 경보를 백엔드별로 분리 (담당: 관측팀, 기한: 3/20)
완료 조건: 한 대만 죽였을 때 5분 안에 경보가 울리는 것을 확인
How to build public evidence in practice
A hiring portfolio must not contain raw Authorization headers, tokens, passwords, private IPs, internal .svc.cluster.local addresses, or full terminal output. The original incident report contains customer names and revenue impact, and an ADR reveals internal system structure.
Instead, publish only permitted evidence.
| What not to include | What to include instead |
|---|---|
The full curl -H "Authorization: Bearer ey..." |
"6 HTTP behavior tests passed" |
10.0.3.12, db.internal.svc |
"3-tier layout, DB in a private subnet" |
| "Company A payments down 12 minutes, revenue loss 32 million KRW" | "Payment path down 12 minutes, impact size undisclosed" |
| Pasting the entire log | A list of metric names and one graph |
Pin the schema version and reject unknown fields, so that downstream tools cannot accidentally add the original secrets.
{"schema": "labhub.portfolio/v1",
"evidence": [
{"kind": "test", "name": "http-contract", "passed": 6, "failed": 0},
{"kind": "image", "claim": "non-root, digest-pinned"},
{"kind": "adr", "id": "ADR-014", "one_line": "중복 제거를 DB 제약으로 옮김"},
{"kind": "incident", "id": "INC-2026-03", "one_line": "설정 검증 부재로 12분 중단"}
]}
If you lock the schema with additionalProperties: false, validation fails the moment someone adds a raw_log field for convenience. A tool blocks it instead of relying on human attention.
Practical judgment criteria
A good portfolio shows reproducible contracts and judgment rather than flashy screenshots. This is also what interviews actually ask about.
- Which failure counterexamples did you test? Testing only the success path is not testing.
- How did you divide the data boundary and the authorization boundary? Why you drew the line there is the design.
- What did you change after the outage? A report that stops at root-cause analysis is half a report.
- Did you set a condition for reverting? With one, you are someone who knows how to manage decisions.
The final quiz of this course checks whether the implementation, operations, and documentation evidence connect into a single story.