지금까지의 여정은 벽돌을 다듬는 일이었다. 패러다임이라는 제약, SOLID라는 원칙, 컴포넌트라는 응집과 결합의 규칙. 5부에 들어서면 마틴은 그 벽돌들을 쌓아 건물을 세운다. 그 첫 질문은 단순하지만 오해가 두껍게 낀 것이다 — 소프트웨어 아키텍처란 대체 무엇인가, 그리고 아키텍트는 무슨 일을 하는 사람인가.
가장 흔한 오해부터 걷어 내야 한다. 많은 조직에서 아키텍트는 코드에서 손을 뗀 사람, 다이어그램을 그리고 회의에서 결정을 내리는 상급자로 그려진다. 마틴은 이 그림을 정면으로 부순다. 소프트웨어 아키텍트는 최고의 프로그래머이며, 앞으로도 계속 프로그래머로 남는다. 코드를 놓는 순간 아키텍트는 자신이 내리는 결정이 어떤 대가를 치르는지 감각하지 못하게 되고, 그 감각을 잃은 사람이 그리는 설계는 현장에서 작동하지 않는다.
아키텍트는 여전히 프로그래머다
아키텍트가 코드에서 멀어지면 안 되는 이유는 권위의 문제가 아니라 판단의 정확도 문제다. 아키텍처 결정이란 결국 “무엇을 무엇에 의존하게 둘 것인가”, “어디에 경계를 그을 것인가” 같은 물음에 답하는 일인데, 이 답의 좋고 나쁨은 오직 코드를 짜고 유지하는 사람만이 몸으로 안다. 회의실에서 그린 우아한 계층 구조가 실제로는 한 줄을 고치기 위해 다섯 개 모듈을 재컴파일하게 만든다는 사실은, 그 다섯 모듈을 직접 재컴파일해 본 사람에게만 보인다.
그래서 마틴이 그리는 아키텍트는 팀에서 코드를 가장 잘 아는 프로그래머다. 다만 그는 다른 프로그래머보다 한 층 더 멀리 내다보며, 개별 기능을 짜는 시간의 일부를 떼어 시스템 전체의 모양을 설계 문제로 다룬다. 코드를 놓지 않는 이유는, 자신이 내린 설계 결정이 매일의 작업에 얼마의 세금을 매기는지를 스스로 겪어 봐야 그 세금이 감당할 만한지 판단할 수 있기 때문이다.
판단 기준: 어떤 아키텍처 결정을 평가할 때, 그 결정이 “이 코드를 매일 만지는 사람에게 무엇을 더 쉽게 하고 무엇을 더 어렵게 하는가”로 환산되는지 물어라. 환산되지 않는 결정은 대개 현장을 모르는 그림이다. 함정: 아키텍트를 코드 위의 직책으로 승격시키는 순간, 결정의 비용을 감각하는 유일한 회로가 끊긴다 — 그때부터 아키텍처는 현실이 아니라 도표를 근거로 진화한다.
아키텍처의 목적 — 시스템의 생애 전체를 떠받치는 것
그렇다면 좋은 아키텍처가 하는 일은 무엇인가. 마틴의 답은 넓다. 아키텍처의 궁극적 목적은 그 시스템의 개발·배포·운영·유지보수라는 생애 주기 전체를 지원하는 것이다. 그리고 이 넷을 관통하는 하나의 전략은 선택지를 최대한 오래 열어 두는 것이다.
네 국면을 하나씩 짚어 보면 아키텍처가 왜 기능 이상의 것인지 드러난다. 개발의 관점에서, 아키텍처는 팀이 시스템을 만들고 유지하기 쉽게 해야 한다. 다섯 명이 만드는 시스템과 백 명이 만드는 시스템은 근본적으로 다른 구조를 요구한다 — 소수의 팀에 잘 맞는 촘촘한 통합 구조는 대규모 팀에서는 서로의 발을 밟는 지옥이 되고, 그래서 컴포넌트로 갈라 독립적으로 개발할 길을 터야 한다.
배포의 관점에서, 좋은 아키텍처는 시스템을 한 번의 동작으로 배포 가능하게 만든다. 개발 중에는 편했지만 배포하려니 수십 개의 설정 파일을 손으로 맞추고 서비스를 정해진 순서로 띄워야 하는 시스템은, 아키텍처가 배포라는 국면을 배신한 것이다. 운영은 역설적이게도 넷 중 아키텍처에 가장 관대하다. 운영의 어려움은 대개 하드웨어를 더 투입해 돈으로 해결할 수 있기 때문이다. 그래서 마틴은 운영보다 개발·배포·유지보수에 더 무게를 싣는다 — 이들은 사람의 시간을 잡아먹고, 사람의 시간은 하드웨어보다 비싸다.
그리고 유지보수. 마틴은 이것을 넷 중 가장 값비싼 국면이라고 못박는다. 유지보수 비용의 대부분은 새 기능을 짜는 데 드는 게 아니라, 그 기능을 어디에 어떻게 끼워 넣을지 **탐사(spelunking)**하고, 잘못 끼워 넣어 생긴 문제를 감당하는 데 든다. 좋은 아키텍처는 바로 이 탐사의 비용을 낮춘다. 시스템을 잘 갈라 놓아, 새 기능이 들어갈 자리를 찾는 일과 그 변경이 번지는 범위를 좁혀 준다.
판단 기준: 아키텍처의 좋고 나쁨을 “기능이 도는가”가 아니라 개발·배포·운영·유지보수 네 국면에서 각각 무엇을 얼마나 쉽게 만드는가로 채점하라 — 특히 가장 비싼 유지보수를 저울의 무거운 쪽에 두어라. 함정: 데모에서 잘 도는 시스템을 좋은 아키텍처로 착각하기 쉽다. 행위는 첫 배포 시점의 값이고, 아키텍처의 값은 그 뒤 수년의 변경에서야 청구된다(2장에서 본 두 가치처럼).
선택지를 열어 두는 것 — 아키텍처의 본질
네 국면을 지원한다는 목적을 하나의 원리로 압축하면 이렇게 된다. 좋은 아키텍처는 시스템을 부드럽게(soft) 유지한다. 부드럽다는 것은 무엇인가. 아직 결정하지 않아도 되는 것을 결정하지 않은 채로 남겨 두어, 나중에 더 많은 정보를 가지고 더 나은 결정을 내릴 여지를 남기는 것이다. 마틴의 표현으로, 아키텍트의 목표는 내려야 할 결정의 수를 최소화하는 것이 아니라, 아직 내리지 않아도 되는 결정을 최대한 열어 두는 것이다.
여기서 결정적으로 갈리는 두 부류가 있다. 시스템에는 그 시스템이 무엇을 하는지를 정하는 결정 — 업무 규칙, 곧 정책 — 이 있고, 그 정책이 세상과 어떻게 만나는지를 정하는 결정 — 데이터베이스는 무엇을 쓸지, 웹 프레임워크는 무엇일지, 통신 프로토콜은 무엇일지 같은 세부사항 — 이 있다. 시스템의 진짜 가치는 정책에 있다. 세부사항은 그 정책이 잠시 걸치는 옷일 뿐이다.
그런데 우리는 프로젝트 초반, 정보가 가장 적을 때 세부사항부터 못박으려는 유혹에 빠진다. 어떤 데이터베이스를 쓸지, 어떤 웹 서버에 올릴지를 첫 주에 결정하고, 그 결정 위에 시스템을 쌓는다. 마틴은 이것이 거꾸로라고 말한다. 좋은 아키텍트는 그런 결정을 최대한 미루고, 미룬 채로도 개발이 진행되도록 시스템을 배치한다. 데이터베이스를 아직 고르지 않아도 업무 규칙을 짜고 테스트할 수 있다면, 데이터베이스라는 선택지는 열려 있는 것이고, 그 열린 상태 자체가 아키텍처가 만들어 낸 자산이다.
판단 기준: 어떤 결정을 지금 내려야 하는지 물을 때, “이걸 지금 안 정하면 개발이 멈추는가”를 기준으로 삼아라. 멈추지 않는다면 그 결정은 미룰 수 있고, 미룰 수 있는 것은 미루는 편이 거의 항상 낫다 — 나중의 나는 지금의 나보다 더 많이 안다. 함정: 결정을 빨리 내리는 것을 결단력으로, 미루는 것을 우유부단으로 오해하기 쉽다. 세부사항에 관한 한 성급한 확정은 결단이 아니라 정보가 가장 적은 시점에 미래를 저당 잡히는 일이다.
장치 독립성의 교훈 — 미룬 결정이 되사 온 자유
결정을 미루라는 원칙이 추상적으로 들린다면, 마틴이 드는 1960년대의 실화가 그것을 땅에 내려놓는다. 그 시절 프로그래머들은 입출력 장치를 코드에 직접 박아 넣었다. 카드 천공기에 쓰는 코드는 카드 천공기의 명령을 직접 불렀고, 프린터에 쓰는 코드는 프린터의 명령을 직접 불렀다. 코드가 특정 장치에 결혼해 있었던 것이다.
문제는 곧 드러났다. 어떤 업무의 출력을 카드가 아니라 자기 테이프로 바꾸려 하자, 장치를 직접 부르던 코드를 전부 뜯어고쳐야 했다. 장치를 고르는 결정이 업무 규칙 코드 곳곳에 못박혀 있었기 때문에, 장치를 바꾸는 사소한 변경이 시스템 전역으로 번졌다. 그리고 이런 변경은 예상보다 훨씬 자주 일어났다. 이 고통이 운영체제 설계자들에게 하나의 발명을 강요했다 — 장치 독립성(device independence).
해법의 골자는 이렇다. 코드가 특정 장치를 직접 부르는 대신, “표준 입력에 쓴다”, “표준 출력에서 읽는다”는 추상적인 장치에만 대고 말하게 한다. 그 추상적 장치가 실제로 카드인지 테이프인지 프린터인지는 코드가 짜인 뒤, 실행되는 시점에 결정된다. 업무 규칙은 자신이 어떤 물리 장치와 이야기하는지 모른 채로 작성되고, 장치의 선택은 마지막 순간까지 열려 있다.
이 오래된 이야기가 15장의 심장인 이유는, 이것이 곧 좋은 아키텍처가 하는 일의 원형이기 때문이다. 데이터베이스는 오늘날의 자기 테이프이고, 웹 프레임워크는 오늘날의 카드 천공기다. 업무 규칙을 이 세부 장치들에 직접 결혼시키지 않고, 그 사이에 추상적 경계를 세워 두면, 세부사항의 선택은 마지막 순간까지 미뤄지고 언제든 갈아 끼울 수 있는 것으로 남는다. 미뤄 둔 결정이 곧 되사 온 자유다.
판단 기준: 업무 규칙 코드가 특정 데이터베이스·프레임워크·프로토콜의 이름을 직접 부르고 있다면, 그것은 1960년대에 장치를 직접 부르던 코드와 같은 병을 앓는 것이다 — 그 이름을 추상 뒤로 밀어낼 수 있는지 물어라. 함정: “어차피 우리는 이 DB를 절대 안 바꾼다”는 확신은 대개 틀리며, 설령 맞더라도 장치 독립성의 이득은 교체 가능성만이 아니다. 세부사항을 모른 채 정책을 짤 수 있다는 것 자체가, 정책을 세부사항의 방해 없이 독립적으로 개발하고 테스트하게 해 준다.
정책과 세부사항을 가르는 선
그래서 15장이 남기는 아키텍트의 임무는 한 문장으로 요약된다. 시스템에서 정책과 세부사항을 식별해 그 둘 사이에 선을 긋고, 세부사항이 정책에 대해 아무것도 강요하지 못하도록 의존성의 방향을 세우는 것. 정책은 시스템의 존재 이유이고, 세부사항은 그 정책이 세상과 접속하기 위해 필요하지만 얼마든 갈아 끼울 수 있는 부품이다.
이 선을 제대로 그으면, 정책은 세부사항의 변덕으로부터 보호받는다. 웹이 유행에서 물러나고 다른 전달 방식이 오더라도, 관계형 데이터베이스가 다른 저장소로 바뀌더라도, 정책은 그 파도 위에서 흔들리지 않는다. 그리고 선을 긋는 이 능력이야말로 앞서 본 SOLID의 DIP와 컴포넌트 원칙들이 겨냥해 온 지점이다 — 소스 의존성을 세부사항에서 정책으로, 저수준에서 고수준으로 향하게 뒤집는 것.
마틴은 여기서 아키텍트의 태도까지 규정한다. 아키텍트는 어떤 세부사항이 옳은지를 단정하는 사람이 아니라, 그 결정을 최대한 오래 미룰 수 있는 구조를 만드는 사람이다. 옳은 데이터베이스를 고르는 것은 아키텍트의 자랑이 아니다. 데이터베이스를 아직 고르지 않아도 시스템이 자라게 만드는 것, 그것이 아키텍트의 자랑이다.
판단 기준: 설계를 검토할 때 “어디까지가 이 시스템이 하는 일(정책)이고, 어디부터가 그 일을 거들 뿐인 도구(세부사항)인가”를 먼저 갈라라. 이 선이 흐릿한 채로는 어떤 경계도 옳게 그을 수 없다. 함정: 정책과 세부사항의 분리를 “지금 당장 모든 세부사항을 추상 뒤로 숨겨라”로 오독하지 말 것 — 핵심은 세부사항의 결정을 강제로 앞당기지 않는 것이지, 무한한 유연성을 위해 층을 겹겹이 쌓는 것이 아니다(그 과잉은 25장이 YAGNI로 경계한다).
그래서 아키텍처란
- 아키텍트는 코드에서 물러난 상급자가 아니라 여전히, 그리고 계속 프로그래머다 — 결정의 비용은 코드를 만지는 사람만이 감각한다.
- 아키텍처의 목적은 기능을 굴리는 것이 아니라 시스템의 개발·배포·운영·유지보수 생애 전체를 떠받치는 것이며, 가장 비싼 국면은 유지보수다.
- 이 모든 것을 관통하는 전략은 선택지를 열어 두는 것 — 아직 내리지 않아도 되는 결정을 최대한 미뤄, 정보가 더 많은 미래의 나에게 결정을 넘긴다.
- 1960년대 장치 독립성의 교훈: 세부 장치를 추상 뒤로 밀어내자 장치의 선택이 실행 시점까지 미뤄졌다. 오늘의 DB·프레임워크에 대해서도 같은 선을 그어라.
- 아키텍트의 진짜 임무는 정책과 세부사항을 갈라 그 사이에 선을 긋고, 세부사항이 정책에 아무것도 강요하지 못하게 의존성의 방향을 세우는 것이다.
15장은 아키텍처의 목적을 세웠다 — 개발·배포·운영·유지보수를 떠받치고 선택지를 열어 두는 것. 그러나 목적을 알았다고 그것을 어떻게 달성하는지까지 안 것은 아니다. 무엇을 무엇으로부터 독립시켜야 열린 선택지가 실제로 생기는가. 다음 장은 이 “독립성”을 유스케이스·운영·개발·배포라는 네 축으로 구체화하며, 결합을 어디서 끊고 어디서 남길지를 다룬다.
다음장으로 16장