[{"data":1,"prerenderedAt":317},["ShallowReactive",2],{"blog:architecture-decision-records":3},{"id":4,"title":5,"body":6,"date":303,"description":304,"draft":305,"extension":306,"meta":307,"navigation":123,"path":308,"seo":309,"stem":310,"tags":311,"__hash__":316},"blog\u002Fblog\u002Farchitecture-decision-records.md","Architecture Decision Records: The Best Habit My Team Adopted",{"type":7,"value":8,"toc":297},"minimark",[9,13,16,19,26,49,54,57,77,93,97,100,241,249,253,256,283,287,290,293],[10,11,12],"p",{},"Every codebase has decisions baked into it that nobody remembers making.",[10,14,15],{},"Why is this service synchronous instead of async? Why does this module use Repository pattern but that one doesn't? Why are we on PHP 8.3 instead of 8.4?",[10,17,18],{},"The answer is usually \"because someone decided that two years ago and we don't know why.\"",[10,20,21,25],{},[22,23,24],"strong",{},"Architecture Decision Records"," (ADRs) are the fix. They're short documents — often just a page — that record:",[27,28,29,37,43],"ul",{},[30,31,32,33,36],"li",{},"The ",[22,34,35],{},"context"," (what problem were we solving?)",[30,38,32,39,42],{},[22,40,41],{},"decision"," (what did we choose?)",[30,44,32,45,48],{},[22,46,47],{},"consequences"," (what does this mean going forward?)",[50,51,53],"h2",{"id":52},"what-we-actually-did","What we actually did",[10,55,56],{},"When I joined the team, the codebase was brownfield — years of accumulated decisions with no written rationale. That made it the perfect moment to change the habit. I introduced ADRs and wrote the first 11 myself, covering:",[27,58,59,62,65,68,71,74],{},[30,60,61],{},"Module boundary rules (enforced by Deptrac)",[30,63,64],{},"Database interaction patterns (no raw SQL outside Repository classes)",[30,66,67],{},"Naming conventions for handlers, DTOs, and services",[30,69,70],{},"When to use Doctrine vs raw PDO",[30,72,73],{},"How to version the OpenAPI spec",[30,75,76],{},"CI quality gate requirements",[10,78,79,80,84,85,88,89,92],{},"They live in ",[81,82,83],"code",{},"docs\u002Fadr\u002F"," in the repo, numbered sequentially: ",[81,86,87],{},"0001-module-boundaries.md",", ",[81,90,91],{},"0002-database-patterns.md",", etc.",[50,94,96],{"id":95},"the-format-that-stuck","The format that stuck",[10,98,99],{},"I tried multiple ADR formats. The one that the team actually used was the shortest one:",[101,102,107],"pre",{"className":103,"code":104,"language":105,"meta":106,"style":106},"language-markdown shiki shiki-themes one-dark-pro","# ADR 0001: Module boundaries enforced by Deptrac\n\n## Status\n\nAccepted\n\n## Context\n\nWithout explicit boundaries, modules will import from each other freely,\nmaking the codebase increasingly tangled over time.\n\n## Decision\n\nEach module may only depend on its direct dependencies as defined in\ndeptrac.yaml. Cross-module access goes through interfaces.\n\n## Consequences\n\n- New features must think about which module they belong to\n- Deptrac runs in CI; violations fail the build\n- Refactoring across modules requires updating deptrac.yaml explicitly\n","markdown","",[81,108,109,118,125,131,136,143,148,154,159,165,171,176,182,187,193,199,204,210,215,225,233],{"__ignoreMap":106},[110,111,114],"span",{"class":112,"line":113},"line",1,[110,115,117],{"class":116},"sVyAn","# ADR 0001: Module boundaries enforced by Deptrac\n",[110,119,121],{"class":112,"line":120},2,[110,122,124],{"emptyLinePlaceholder":123},true,"\n",[110,126,128],{"class":112,"line":127},3,[110,129,130],{"class":116},"## Status\n",[110,132,134],{"class":112,"line":133},4,[110,135,124],{"emptyLinePlaceholder":123},[110,137,139],{"class":112,"line":138},5,[110,140,142],{"class":141},"sn6KH","Accepted\n",[110,144,146],{"class":112,"line":145},6,[110,147,124],{"emptyLinePlaceholder":123},[110,149,151],{"class":112,"line":150},7,[110,152,153],{"class":116},"## Context\n",[110,155,157],{"class":112,"line":156},8,[110,158,124],{"emptyLinePlaceholder":123},[110,160,162],{"class":112,"line":161},9,[110,163,164],{"class":141},"Without explicit boundaries, modules will import from each other freely,\n",[110,166,168],{"class":112,"line":167},10,[110,169,170],{"class":141},"making the codebase increasingly tangled over time.\n",[110,172,174],{"class":112,"line":173},11,[110,175,124],{"emptyLinePlaceholder":123},[110,177,179],{"class":112,"line":178},12,[110,180,181],{"class":116},"## Decision\n",[110,183,185],{"class":112,"line":184},13,[110,186,124],{"emptyLinePlaceholder":123},[110,188,190],{"class":112,"line":189},14,[110,191,192],{"class":141},"Each module may only depend on its direct dependencies as defined in\n",[110,194,196],{"class":112,"line":195},15,[110,197,198],{"class":141},"deptrac.yaml. Cross-module access goes through interfaces.\n",[110,200,202],{"class":112,"line":201},16,[110,203,124],{"emptyLinePlaceholder":123},[110,205,207],{"class":112,"line":206},17,[110,208,209],{"class":116},"## Consequences\n",[110,211,213],{"class":112,"line":212},18,[110,214,124],{"emptyLinePlaceholder":123},[110,216,218,222],{"class":112,"line":217},19,[110,219,221],{"class":220},"sU0A5","-",[110,223,224],{"class":141}," New features must think about which module they belong to\n",[110,226,228,230],{"class":112,"line":227},20,[110,229,221],{"class":220},[110,231,232],{"class":141}," Deptrac runs in CI; violations fail the build\n",[110,234,236,238],{"class":112,"line":235},21,[110,237,221],{"class":220},[110,239,240],{"class":141}," Refactoring across modules requires updating deptrac.yaml explicitly\n",[10,242,243,244,248],{},"That's it. The key is: write it ",[245,246,247],"em",{},"before"," you implement, while the reasoning is fresh.",[50,250,252],{"id":251},"getting-the-team-to-write-them","Getting the team to write them",[10,254,255],{},"The hardest part is habit formation. What worked for us:",[257,258,259,265,271,277],"ol",{},[30,260,261,264],{},[22,262,263],{},"Led by example"," — I wrote the first batch before asking anyone else to",[30,266,267,270],{},[22,268,269],{},"PR requirement"," — significant architectural changes needed a linked ADR",[30,272,273,276],{},[22,274,275],{},"No perfectionism"," — a rough ADR written in 20 minutes beats a perfect one that never gets written",[30,278,279,282],{},[22,280,281],{},"Reference them in code review"," — \"this violates ADR 0003, here's the link\"",[50,284,286],{"id":285},"the-compounding-return","The compounding return",[10,288,289],{},"Six months in, the value becomes obvious. When a new developer joins and asks \"why do we do X this way?\" — you have an answer. When you're about to make a decision that conflicts with a past one, you catch it. When you're debugging something weird, the decision log is a map.",[10,291,292],{},"The investment is small. The return is compounding. Start your ADR log today.",[294,295,296],"style",{},"html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":106,"searchDepth":120,"depth":120,"links":298},[299,300,301,302],{"id":52,"depth":120,"text":53},{"id":95,"depth":120,"text":96},{"id":251,"depth":120,"text":252},{"id":285,"depth":120,"text":286},"2026-01-20","Why writing down the 'why' behind technical decisions — even briefly — pays compounding returns over time, and how to get a team to actually do it.",false,"md",{},"\u002Fblog\u002Farchitecture-decision-records",{"title":5,"description":304},"blog\u002Farchitecture-decision-records",[312,313,314,315],"Architecture","Engineering Culture","Documentation","ADR","3zBOlesZjrM9yrzPwrRgy77peiULqPIiGOZhkAeOgBM",1776339762988]