16장이 좋은 아키텍처는 유스케이스·개발·배포·운영을 저마다 독립적으로 지탱해야 한다고 말했다면, 17장은 그 독립을 실제로 만들어 내는 단 하나의 행위를 이름 붙인다 — 선긋기다. 마틴의 명제는 도발적일 만큼 단순하다. 소프트웨어 아키텍처란 선을 긋는 기술이고, 그 선을 **경계(boundary)**라 부른다. 경계는 소프트웨어의 요소들을 서로 갈라놓고, 한쪽이 다른 쪽을 알지 못하도록 막는다. 질문은 하나로 좁혀진다 — 그 선을 어디에, 언제 그어야 하는가.
아키텍처는 결국 선을 긋는 일이다
먼저 왜 선을 긋는지부터 분명히 해야 한다. 마틴의 답은 인력 비용이다. 좋은 아키텍처의 목표는 1장에서 못박았듯 시스템을 만들고 유지하는 데 드는 인력을 최소로 낮추는 것이고, 인력을 잡아먹는 주범은 결합(coupling), 그중에서도 성급하고 부주의한 결정에 시스템이 통째로 묶여 버리는 결합이다.
부주의한 결정이란 무엇인가. 업무 규칙과 아무 상관도 없는 것 — 프레임워크의 버전, 데이터베이스의 종류, 웹 서버의 설정, 의존성 주입 라이브러리 — 에 핵심 업무 규칙이 손발이 묶이는 것이다. 이런 것들에 이른 시점에 결혼해 버리면, 나중에 이혼하려 할 때 시스템 전체를 뜯어야 한다. 선을 긋는다는 것은 곧 이 결혼을 미룰 수 있도록, 업무 규칙과 부주의해도 되는 것들 사이에 벽을 세우는 일이다.
판단 기준: 어떤 결정이 “업무 규칙이 성립하는 데 꼭 필요한가”를 물어라. 답이 아니오라면 — 대개 프레임워크·DB·웹이 그렇다 — 그것은 선 바깥으로 밀어낼 후보다. 함정: “어차피 이 DB를 쓸 거니까 지금 붙여 두면 편하다”는 편의가 결합의 씨앗이다. 지금의 사소한 편의가 나중의 거대한 이혼 비용으로 청구된다.
조기 결정을 미루는 것이 곧 선긋기다
마틴이 드는 이야기가 있다. 어느 회사가 자신들의 방대한 소프트웨어에 세 가지 스키마의 데이터베이스를 붙여 팔았다. 시스템을 아키텍처 없이 짰기 때문에, 업무 규칙이 데이터베이스에 직접 말을 걸었고, 데이터베이스 접근 코드가 업무 규칙 전역에 흩뿌려져 있었다. 결과는 유지보수 지옥이었다. 어떤 스키마 하나만 바꿔도 업무 규칙 곳곳이 함께 깨졌다.
만약 이 회사가 선을 그어 두었다면 어땠을까. 업무 규칙과 데이터베이스 사이에 경계가 있었다면, 업무 규칙은 데이터베이스가 세 종류인지 한 종류인지조차 몰랐을 것이다. 데이터베이스라는 결정은 경계 바깥의 세부사항으로 격리되고, 스키마를 바꾸는 일은 업무 규칙을 건드리지 않은 채 경계 안쪽에서만 끝났을 것이다.
여기서 마틴의 반직관적인 처방이 나온다. 좋은 아키텍트는 결정을 최대한 미룬다. 데이터베이스를 고를지, 웹 프레임워크를 고를지, REST를 쓸지 — 이런 결정을 프로젝트 초기에 못박을수록, 검증되지 않은 가정 위에 시스템 전체를 세우게 된다. 선을 잘 그으면 이 결정들을 나중으로, 정보가 더 많아진 시점으로 미룰 수 있고, 심지어 미룬 채로도 시스템을 개발하고 테스트할 수 있다.
판단 기준: “이 결정을 지금 내리지 않으면 개발이 막히는가”를 물어라. 막히지 않는다면 미뤄라 — 미룰 수 있다는 것 자체가 선이 제대로 그어졌다는 증거다. 함정: 결정을 미루는 것을 우유부단이나 회피로 오해하지 말 것. 미룸은 게으름이 아니라, 아직 오지 않은 정보를 기다리는 적극적 전략이다. 반대로 서둘러 못박은 결정 대부분은 가장 정보가 적을 때 내린 최악의 추측이다.
GUI와 DB는 업무 규칙에 꽂는 플러그인이다
선을 어디에 긋는가. 마틴의 답은 명료하다 — 중요한 것과 중요하지 않은 것 사이에 긋는다. 업무 규칙은 중요하다. GUI는 업무 규칙에 중요하지 않다. 데이터베이스도 업무 규칙에 중요하지 않다. 그러니 GUI와 업무 규칙 사이에, 데이터베이스와 업무 규칙 사이에 선이 지나간다.
이 관계를 마틴은 **플러그인(plugin)**이라는 은유로 못박는다. 데이터베이스는 업무 규칙에 꽂혀 있는 플러그인이다. GUI 역시 업무 규칙에 꽂혀 있는 또 다른 플러그인이다. 업무 규칙은 자신이 어떤 데이터베이스에 꽂혀 있는지, 어떤 화면에 꽂혀 있는지 몰라야 한다 — 콘솔이든 웹이든, 오라클이든 파일이든, 그것을 갈아 끼워도 업무 규칙은 미동도 하지 않아야 한다.
이 은유의 진짜 힘은 의존성의 방향에 있다. 플러그인은 본체에 의존하지 그 반대가 아니다. 이어폰이 스마트폰에 꽂히지, 스마트폰이 이어폰의 존재를 알고 설계되지 않는다. 마찬가지로 데이터베이스 코드가 업무 규칙을 향해 의존해야지, 업무 규칙이 데이터베이스를 향해 의존해서는 안 된다. 소스 코드 의존성은 언제나 중요한 것(업무 규칙)을 향하도록, 즉 덜 중요한 것에서 더 중요한 것 쪽으로 흐르도록 선을 넘어야 한다. 5장에서 본 다형성을 통한 의존성 역전이 바로 이 방향을 강제하는 도구다.
코드로 보면 방향이 손에 잡힌다. 아래는 업무 규칙이 데이터베이스를 향해 의존하는 흔한 형태다.
// 업무 규칙 안에서 데이터베이스 구현을 직접 붙든다 — 선이 없다.
public class OrderService {
public void placeOrder(Order order) {
MySqlOrderTable table = new MySqlOrderTable(); // 구체 DB에 결혼
table.insert(order);
}
}OrderService는 업무 규칙인데도 MySqlOrderTable이라는 세부사항을 안다. MySQL을 버리는 순간 업무 규칙을 뜯어야 한다. 선은 인터페이스로 긋는다.
// 업무 규칙 쪽이 인터페이스를 소유한다. 구현은 선 바깥에서 꽂힌다.
public interface OrderGateway { // 업무 규칙 영역이 정의한다
void save(Order order);
}
public class OrderService {
private final OrderGateway gateway; // 추상에만 의존한다
public OrderService(OrderGateway gateway) { this.gateway = gateway; }
public void placeOrder(Order order) {
gateway.save(order); // 어떤 DB인지 모른다
}
}
// 선 바깥, 세부사항 영역 — DB는 여기서 업무 규칙에 "꽂힌다"
public class MySqlOrderGateway implements OrderGateway {
public void save(Order order) { /* MySQL insert */ }
}이제 소스 의존성은 MySqlOrderGateway → OrderGateway, 즉 세부사항이 업무 규칙을 향한다. 제어 흐름은 여전히 OrderService에서 DB로 가지만, 소스 의존성은 그와 반대로 뒤집혔다. 이 역전이 만든 선 위에서 MySQL은 언제든 파일이나 인메모리 구현으로 갈아 끼울 수 있는 플러그인이 된다.
판단 기준: 어떤 컴포넌트가 다른 컴포넌트에 대해 인터페이스(추상)를 소유하는 쪽이 “본체”이고, 그 인터페이스를 구현하는 쪽이 “플러그인”이다. 인터페이스를 누가 소유하는지가 곧 누가 더 중요한지를 선언한다. 함정: 데이터베이스는 시스템의 중심처럼 느껴진다 — 모든 데이터가 거기 있으니까. 그 착시에 넘어가 업무 규칙을 데이터베이스 스키마에 맞춰 설계하면, 꼬리가 몸통을 흔드는 아키텍처가 된다. 데이터는 중요하되, 데이터베이스라는 도구는 세부사항이다.
입출력은 언제나 관련 없다
선긋기가 어려운 이유 하나는, 우리가 시스템을 입출력의 관점에서 상상하는 습관 때문이다. 사람들은 GUI가 곧 시스템이라고 느낀다. 화면이 시스템이고, 그러니 화면을 먼저 설계하고 화면에 맞춰 나머지를 짜야 한다고 믿는다. 마틴은 이 직관을 정면으로 뒤집는다 — 입출력은 관련 없다(IO is irrelevant).
GUI는 하나의 입출력 장치일 뿐이다. 업무 규칙의 관점에서 보면, 화면에 버튼이 있든 콘솔에 명령어를 치든 음성으로 말하든, 그것은 규칙을 실행시키는 신호가 어떤 껍데기를 입고 도착했느냐의 차이일 뿐이다. 규칙 자체는 그 껍데기를 몰라야 한다. 웹 역시 마찬가지다 — 웹은 업무 규칙을 사용자에게 전달하는 하나의 전달 메커니즘이며, 그 이상도 이하도 아니다(이것이 31장에서 “웹은 세부사항”으로 다시 확장된다).
그래서 마틴은 시스템을 두 종류의 컴포넌트로 가른다. 하나는 업무 규칙을 담은 컴포넌트, 다른 하나는 업무 규칙과 무관하되 필요한 것들 — 데이터베이스, 사용자 인터페이스, 각종 유틸리티 — 을 담은 컴포넌트다. 이 둘 사이에 선을 긋고, 소스 의존성이 업무 규칙 쪽으로만 흐르게 하면, 입출력은 언제든 갈아 끼울 수 있는 플러그인의 자리로 물러난다.
판단 기준: 어떤 코드를 두고 “이게 없으면 업무 규칙 자체가 무의미해지는가, 아니면 규칙을 세상에 드나들게 하는 통로일 뿐인가”를 물어라. 후자라면 입출력이고, 입출력은 선 바깥이다. 함정: 화면이 화려하고 사용자가 매일 그것만 보기 때문에, GUI가 시스템의 본질처럼 과대평가된다. 사용자가 보는 것과 시스템이 존재하는 이유는 다르다 — 화면은 규칙이 잠깐 입는 옷이다.
경계는 진짜 변경축·가치축 위에 긋는다
여기까지 오면 오해가 하나 생긴다. 선이 좋은 것이라면 많이 그을수록 좋은 것 아닌가. 마틴은 단호히 아니라고 답한다. 모든 경계에는 값이 매겨져 있다. 경계를 하나 그으면 그것을 넘나드는 데이터 구조, 양방향의 인터페이스, 그 사이를 오가는 변환이 필요하고, 이 모든 것이 개발·유지보수 비용이 된다. 아무 데나 그은 선은 이득 없이 비용만 물린다 — 이것이 과잉 엔지니어링이다.
그렇다면 어디에 그어야 하는가. 마틴의 답은 변경의 축이다. 서로 다른 이유로, 서로 다른 속도로 변하는 것들 사이에 선을 긋는다. 데이터베이스와 업무 규칙 사이에 선을 긋는 이유는, 이 둘이 서로 다른 이유로 변하기 때문이다 — DB는 성능이나 벤더 사정으로 바뀌고, 업무 규칙은 사업의 필요로 바뀐다. 이것은 7장 SRP(하나의 액터에 대해서만 책임진다)와 8장 OCP(변경을 축으로 격리한다)가 컴포넌트 수준에서 되울리는 원리다. 같은 이유로 함께 변하는 것은 한쪽에 모으고, 다른 이유로 변하는 것 사이에만 선을 긋는다.
그러니 선을 긋는 일은 예언이다. 어떤 축을 따라 변경이 실제로 올지를 내다보고, 그 축 위에만 선을 놓는다. 오브젝트 1장이 “예상되는 변경의 축을 골라 그 방향으로만 유연성을 배치하는 것이 설계”라 말했던 그 절제가, 여기서 경계의 언어로 반복된다. 축을 잘못 짚으면 정작 변경이 오는 자리에는 선이 없어 시스템이 통째로 흔들리고, 변경이 오지 않는 자리에는 쓰지도 않을 선이 비용만 물린다.
판단 기준: 선을 긋기 전에 “이 둘이 서로 다른 이유로, 다른 시점에 변하는가”를 물어라. 그렇다면 진짜 축이니 그어라. 함께 변한다면 선은 낭비다. 함정: 아름다운 대칭이나 교과서적 계층을 위해 선을 긋는 것 — “원래 서비스 계층과 리포지토리 계층은 나누는 거니까”. 변경축과 무관한 선은 장식일 뿐이며, 장식에도 데이터 변환과 유지보수라는 실비가 청구된다.
언제 선을 실체화할 것인가
마지막 긴장이 남는다. 선을 그어야 할 자리를 알아챘다 해도, 그것을 지금 당장 완전한 경계로 만들어야 하는 것은 아니다. 완전한 경계는 비싸고(24장에서 부분적 경계로, 25장에서 그 판단으로 다시 다뤄진다), 아직 변경이 실제로 오지 않은 자리라면 선을 그어 둘 자리를 지켜보는 것만으로 충분할 때가 있다.
그래서 노련한 아키텍트는 경계를 두 단계로 다룬다. 먼저 어디에 잠재적 선이 있는지를 식별해 둔다 — 업무 규칙과 DB 사이, 규칙과 GUI 사이. 그다음 그 선을 완전한 인터페이스로 실체화할지, 아니면 더 가벼운 형태로 남겨 둘지를 비용과 위험을 저울질해 결정한다. 너무 이르면 쓰지 않을 유연성에 값을 치르고, 너무 늦으면 이미 결합이 굳어 선을 긋는 데 큰 수술이 든다. 이 타이밍을 읽어 내는 것이 아키텍트의 실력이다.
판단 기준: 선의 위치를 아는 것과 선을 완전히 구현하는 것을 분리하라 — 위치는 일찍 식별하되, 실체화는 변경 압력이 실제로 차오르는지 지켜보며 결정한다. 함정: 양극단 모두 위험하다. “언젠가 필요할 테니 지금 다 추상화하자”는 과잉이고, “지금 안 급하니 그냥 붙여 두자”는 과소다. 아키텍처는 이 둘 사이에서 매번 다시 저울질하는 판단이지, 한 번 정하면 끝나는 규칙이 아니다.
- 아키텍처는 곧 경계(선)를 긋는 일이며, 선의 목적은 업무 규칙과 무관한 결정(프레임워크·DB·웹)에 시스템이 성급히 결합되는 것을 막는 데 있다.
- 좋은 아키텍트는 결정을 미룬다. 선이 제대로 그어져 있으면 DB·웹 같은 결정을 정보가 많아진 시점까지 미룬 채로도 개발·테스트할 수 있다.
- GUI와 DB는 업무 규칙에 꽂는 플러그인이다. 소스 의존성은 언제나 덜 중요한 것에서 더 중요한 것(업무 규칙)을 향하고, 그 방향은 인터페이스를 누가 소유하느냐로 강제된다.
- 입출력은 관련 없다 — 화면이 시스템의 본질처럼 보이는 것은 착시다. 규칙이 잠깐 입는 옷일 뿐이다.
- 선은 진짜 변경축·가치축 위에만 긋는다. 모든 경계에는 값이 붙으므로, 서로 다른 이유로 변하는 것들 사이에만 그어야 이득이 비용을 넘는다.
경계라는 선은 정적인 지도 위의 국경선처럼 보이지만, 실제로는 실행 중인 시스템에서 함수 호출이 넘나드는 살아 있는 통로다. 다음 장은 이 선을 해부해, 경계를 넘는 일이 런타임에 정확히 무엇인지 — 소스 수준의 분리에서 서비스 수준의 분리까지, 그 스펙트럼과 각각의 값을 들여다본다.
다음장으로 18장