30장이 데이터베이스를 세부사항의 자리로 끌어내렸다면, 31장은 그 옆에 웹을 나란히 앉힌다. 마틴의 주장은 도발적으로 들린다 — 우리가 지난 십수 년간 시스템의 얼굴이자 본체로 떠받든 웹은, 아키텍처의 관점에서 보면 그저 하나의 세부사항이다. 화면을 채우고 요청을 실어 나르는 이 거대한 장치가 어째서 핵심이 아니라 변두리인가. 이 장은 그 물음에 답하기 위해, 먼저 컴퓨팅의 역사가 한 번도 가만히 있지 않았다는 사실부터 상기시킨다.
진자는 결코 멈추지 않는다
컴퓨팅의 역사를 멀리서 내려다보면 하나의 운동이 보인다. 연산 능력이 중앙에 모였다가 가장자리로 흩어지고, 다시 중앙으로 빨려 들어갔다가 또 흩어지는 진자 운동이다. 마틴은 자신이 겪어 온 시대를 이 진자의 왕복으로 되짚는다.
처음에는 거대한 중앙 컴퓨터 한 대에 모든 것이 있었다. 사용자는 멍청한 단말기(dumb terminal) 앞에 앉아 중앙의 연산을 빌려 썼다. 연산은 전적으로 중앙에 집중되어 있었다. 그러다 개인용 컴퓨터가 등장하자 진자는 반대편으로 크게 휘둘렀다 — 연산이 책상마다 흩어졌고, 각자의 기계가 스스로 계산했다. 그다음 웹이 오자 진자는 다시 중앙으로 돌아갔다. 브라우저는 사실상 다시 멍청한 단말기가 되었고, 모든 계산은 서버 팜으로 되돌아갔다. 그러나 그것도 오래가지 않았다. 브라우저 안에서 도는 자바스크립트가 점점 무거워지면서 연산은 또 한 번 가장자리로 새어 나갔고, 리치 클라이언트와 단일 페이지 애플리케이션이 그 자리를 채웠다.
핵심은 이 왕복에 끝이 없다는 것이다. 다음 진동이 어느 쪽으로 얼마나 갈지 아무도 확답하지 못한다. 그리고 바로 이 예측 불가능성이 이 장의 첫 번째 교훈을 낳는다. 당신의 아키텍처가 진자의 현재 위치에 결혼해 있다면, 진자가 움직이는 순간 아키텍처도 함께 뜯겨 나간다.
판단 기준: 어떤 기술 결정을 내릴 때, 그것이 “지금 진자가 여기 있으니까”에 근거한다면 그 결정은 세부사항 층에 격리해 두어야 한다 — 진자는 반드시 다시 움직이기 때문이다. 함정: 현재의 배치가 워낙 지배적이면 그것이 자연 상수처럼 느껴져, 방금 지나온 반대편 극단을 벌써 잊는다. “이제는 모두가 웹으로 한다”는 확신은 십 년 전 “이제는 모두가 데스크톱으로 한다”던 확신과 같은 자리에 서 있다.
GUI는 IO 장치일 뿐이다
진자의 어느 쪽 끝에 서 있든, 사용자가 시스템과 만나는 접점은 결국 화면과 입력이다. 마틴은 이 접점의 정체를 아주 건조하게 규정한다 — GUI는 하나의 세부사항이며, 웹은 GUI다. 그러므로 웹은 세부사항이다. 이 삼단논법이 장 전체의 뼈대다.
이 규정을 몸으로 이해하려면, 화면을 특별한 무엇이 아니라 프린터나 키보드와 같은 반열의 IO 장치로 바라봐야 한다. 우리는 프린터가 시스템의 아키텍처를 결정한다고 생각하지 않는다. 프린터는 결과를 종이에 실어 내보내는 출력 장치일 뿐이고, 어떤 프린터를 쓰느냐는 업무 규칙과 무관하다. 마틴의 요점은 웹 브라우저도 정확히 그 층위에 있다는 것이다. 브라우저는 업무 규칙이 만들어 낸 결과를 사용자의 눈앞에 실어 나르고, 사용자의 손짓을 업무 규칙에게 실어 오는 입출력 장치다. 화려하고 대화적이라는 이유로 이 본질이 바뀌지는 않는다.
역사적으로 우리는 이미 이 교훈을 한 번 배웠다. 초창기 프로그램은 특정 장치(이 프린터, 저 테이프 드라이브)에 코드가 직접 매여 있었고, 장치가 바뀌면 프로그램을 뜯어고쳐야 했다. 운영체제가 장치 독립성이라는 추상을 세우고 나서야 우리는 “표준 출력”에 쓰기만 하면 그것이 프린터든 파일이든 화면이든 상관없어졌다. GUI를 세부사항으로 다룬다는 것은, 바로 그 장치 독립성의 교훈을 화면이라는 장치에도 똑같이 적용하겠다는 뜻이다.
판단 기준: 어떤 컴포넌트가 “화면”이라는 IO 장치를 다른 IO 장치(파일, API 응답, 테스트 하네스)로 갈아 끼워도 그대로 도는지 물어라 — 그럴 수 있다면 화면을 세부사항으로 옳게 격리한 것이다. 함정: 웹이 프린터와 달리 양방향으로 대화한다는 사실에 홀려 그것을 IO 장치 이상으로 격상시키기 쉽다. 상호작용이 풍부하다는 것과 아키텍처적으로 중요하다는 것은 전혀 다른 축의 이야기다.
전달 메커니즘과 업무 규칙을 가르는 선
웹이 세부사항이라는 명제가 실무에서 뜻하는 바는 하나다 — 웹을 전달 메커니즘(delivery mechanism)으로 취급하고, 업무 규칙과의 사이에 경계선을 그어라. 이 선을 긋는 순간 시스템은 두 세계로 갈린다. 한쪽에는 “무엇을 계산하고 어떤 결정을 내리는가”라는 업무 규칙의 세계가 있고, 다른 쪽에는 “그 결과를 어떻게 사용자에게 전달하고 사용자의 요청을 어떻게 받아들이는가”라는 전달의 세계가 있다.
문제는 웹이 이 선을 지우도록 끊임없이 유혹한다는 데 있다. 요청 파라미터를 꺼내는 김에 거기서 바로 계산을 하고, 계산한 김에 응답 HTML을 만들어 버리면 코드는 짧고 빠르게 완성된다. 그러나 이렇게 짠 코드는 업무 규칙이 HTTP 요청과 응답의 형태에 녹아들어, 웹이라는 전달 방식과 한 몸이 된다. 진자가 다음 위치로 움직여 전달 방식이 바뀌는 날 — 웹에서 모바일 네이티브로, 혹은 서버 렌더링에서 리치 클라이언트로 — 업무 규칙도 함께 끌려 나온다. 1장의 급여 시스템이 그러했듯, 나와 상관없어야 할 변경이 나를 흔드는 것이다.
마틴이 21장에서 말한 “소리치는 아키텍처”가 여기서 되울린다. 잘 갈라 놓은 시스템의 중심을 들여다보면 그것이 웹 애플리케이션이라고 외치지 않는다. 그것은 은행 업무를, 재고 관리를, 대출 심사를 외친다. 웹은 그 업무를 사용자에게 배달하기 위해 바깥에 꽂아 둔 어댑터일 뿐, 시스템의 정체가 아니다.
판단 기준: 컨트롤러나 핸들러에서 HTTP 요청 객체를 걷어 냈을 때 업무 규칙이 온전히 살아남는지 확인하라 — 요청/응답 객체를 만지는 코드와 업무를 결정하는 코드가 분리되어 있으면 전달 메커니즘을 옳게 갈라낸 것이다. 함정: 프레임워크가 제공하는 컨트롤러가 요청 파싱·유효성 검사·업무 처리·응답 생성을 한자리에서 하도록 자연스럽게 이끌기 때문에, 아무 저항 없이 짜면 선은 저절로 지워진다. 편한 길이 곧 결합의 길이다.
UI와 업무 규칙 사이에는 추상이 있다
그렇다면 갈라 놓은 두 세계는 어떻게 다시 만나 협력하는가. 완전히 격리하되 소통은 해야 한다면, 그 사이에는 무언가가 놓여야 한다. 마틴의 답은 명확하다 — UI와 업무 규칙 사이에는 추상이 있다. 이 추상 덕분에 여러 종류의 사용자 인터페이스가 동일한 업무 규칙 위에 꽂힐 수 있다.
이 추상의 정체는 22장의 동심원에서 이미 예고되었다. 업무 규칙(유스케이스)은 자신을 호출하는 것이 무엇인지 알지 못한 채, 오직 입력 데이터를 담은 요청 모델을 받고 출력 데이터를 담은 응답 모델을 내놓는다. 이 요청/응답 모델이 바로 UI와 업무 규칙 사이에 놓인 추상이다. 그것은 HTTP도 HTML도 JSON도 모르는, 순수한 데이터 구조다. 웹 어댑터는 들어온 요청을 이 추상적 입력 모델로 번역해 안쪽으로 밀어 넣고, 업무 규칙이 돌려준 추상적 출력 모델을 다시 웹의 언어로 번역해 사용자에게 실어 보낸다.
이 추상이 놓이는 순간 무엇이 가능해지는가. 같은 업무 규칙 위에 웹 UI를 꽂을 수도, 데스크톱 UI를 꽂을 수도, 다른 프로그램이 호출하는 API를 꽂을 수도, 심지어 테스트 코드가 직접 호출하는 하네스를 꽂을 수도 있다. UI는 여러 가지 형태로 갈아 끼울 수 있는 플러그인이 되고, 그 아래의 업무 규칙은 어떤 UI가 꽂히든 흔들리지 않는다. 진자가 다시 움직여도 안쪽 세계는 고요한 이유가 여기에 있다 — 움직이는 것은 언제나 추상 바깥의 어댑터일 뿐이다.
판단 기준: 업무 규칙과 UI 사이에 오가는 데이터가 HTTP·HTML·프레임워크 타입을 전혀 모르는 단순한 구조체인지 확인하라 — 그 데이터가 벤더 타입을 모른다면 추상이 제자리에 놓인 것이다. 함정: 요청/응답 모델이라며 만든 객체가 실은 프레임워크의 요청 객체를 그대로 품고 있거나 그 필드 구조를 베껴 오면, 이름만 추상이고 실체는 웹에 매인 구체다 — 추상은 형태를 흉내 내는 게 아니라 의존성을 끊어야 추상이다.
테스트가 경계를 드러낸다
웹을 세부사항으로 밀어냈을 때 따라오는 가장 값진 배당은 테스트 용이성이다. 브라우저를 거치지 않고, HTTP 서버를 띄우지 않고, 업무 규칙을 곧바로 호출해 검증할 수 있게 되기 때문이다. 23장의 험블 객체 패턴이 말했듯, 테스트하기 어려운 것(화면 렌더링)과 테스트하기 쉬운 것(업무 결정)을 갈라 두면, 시스템의 대부분을 차지하는 후자를 빠르고 안정적으로 검증할 수 있다.
거꾸로, 테스트를 짜려는데 자꾸만 웹 서버를 띄우고 브라우저를 흉내 내야 한다면 그것은 경계가 무너져 있다는 신호다. 업무 규칙이 전달 메커니즘에 얽혀 있어 웹을 통하지 않고는 규칙에 닿을 수 없다는 뜻이기 때문이다. 이런 의미에서 테스트는 아키텍처의 진단 도구다. 테스트가 아프다고 호소하는 자리가 곧 경계가 흐려진 자리다.
판단 기준: 핵심 업무 규칙 하나를 검증하는 데 웹 계층 없이 단위 테스트로 닿을 수 있는지 물어라 — 닿을 수 있으면 웹이 세부사항 자리에 옳게 물러나 있는 것이다. 함정: 통합 테스트로 전 구간을 훑으면 다 커버된다고 여겨 이 경계 신호를 놓치기 쉽다. 통합 테스트는 배관이 새는지 보지만, 업무 규칙이 웹에서 분리됐는지는 말해 주지 않는다.
무엇을 지불하고 무엇을 사는가
물론 이 분리는 공짜가 아니다. 웹 어댑터를 따로 두고, 요청/응답 추상을 정의하고, 번역 코드를 양쪽에 두는 일은 그저 컨트롤러에서 다 처리하는 것보다 손이 더 간다. 작은 도구, 수명이 짧은 스크립트, 진자가 움직일 일이 없는 내부용 화면이라면 이 대가가 순손실일 수 있다. 30장의 데이터베이스가 그러했듯, 세부사항을 격리하는 경계에도 값이 매겨져 있고 그 값이 이득을 넘어서는 지점이 있다.
그러니 이 장의 결론은 “언제나 웹을 완벽히 격리하라”가 아니다. 이 시스템의 수명이 얼마나 긴가, 전달 방식이 바뀔 가능성이 얼마나 실재하는가, 같은 업무 규칙 위에 다른 UI를 꽂을 미래가 그려지는가 — 이 질문들의 답에 따라 경계에 지불할 값이 정해진다. 다만 마틴이 힘주어 말하는 것은, 진자의 역사가 증언하듯 그 “바뀔 가능성”이 우리가 흔히 짐작하는 것보다 훨씬 크다는 사실이다. 오래 살 시스템일수록, 웹이 세부사항이라는 명제에 걸어 두는 편이 옳을 확률이 높다.
- 컴퓨팅은 중앙 집중과 분산 사이를 오가는 진자 운동을 멈춘 적이 없다 — 아키텍처를 진자의 현재 위치에 매면 진자가 움직일 때 함께 뜯긴다.
- GUI는 하나의 IO 장치일 뿐이고, 웹은 GUI다. 상호작용이 풍부하다는 것과 아키텍처적으로 중요하다는 것은 다른 축이다.
- 웹은 전달 메커니즘(세부사항)이므로 업무 규칙과의 사이에 경계선을 그어, “무엇을 결정하는가”와 “어떻게 전달하는가”를 갈라야 한다.
- UI와 업무 규칙 사이에는 HTTP·HTML을 모르는 순수한 요청/응답 추상이 놓이고, 그 덕분에 웹·데스크톱·API·테스트가 같은 규칙 위에 꽂히는 플러그인이 된다.
- 경계에도 값이 있으니 언제나 완벽히 격리할 필요는 없다 — 다만 진자의 역사는 “안 바뀐다”는 짐작이 대개 틀렸음을 증언한다.
다음장으로 32장