시스템을 설계한다는 것은 경계선을 긋는 일이다.
앞선 시리즈가 객체 하나의 책임에 붙은 이름을 다뤘다면, 이번 시리즈는 그 위 층위를 다룹니다. 모듈과 모듈 사이, 서버와 브라우저 사이, 지금과 나중 사이에는 선이 그어집니다. 그 선마다 값이 매겨져 있고, 설계는 그 값을 알고 선을 긋는 것입니다.
경계에는 값이 매겨져 있다
시스템 설계를 이야기할 때 우리는 보통 구성 요소를 먼저 말합니다. 어떤 서비스가 있고, 어떤 데이터베이스를 쓰고, 프론트엔드는 무엇으로 만드는가. 그러나 시스템의 성격을 실제로 결정하는 것은 구성 요소가 아니라 그 사이에 그은 선입니다.
같은 코드를 두 파일에 나눠 두면 모듈 경계가 생깁니다. 같은 함수를 서버에서 실행하면 프로세스 경계가 생깁니다. 같은 값을 두 곳에 저장하면 캐시 경계가 생깁니다. 그리고 경계가 생길 때마다 무언가가 청구됩니다.
- 지연이 청구됩니다. 네트워크 경계를 넘는 호출은 로컬 호출보다 수천 배 느립니다.
- 복잡도가 청구됩니다. 경계를 넘는 데이터는 직렬화되고, 검증되고, 다시 조립됩니다.
- 일관성이 청구됩니다. 두 곳에 사본이 생기면 둘 중 하나는 낡습니다.
- 수명이 청구됩니다. 경계는 곧 약속이 되고, 약속은 바꾸기 어려워집니다.
- 가시성이 청구됩니다. 경계 너머에서 벌어진 일은 이쪽에서 보이지 않습니다.
경계를 긋지 않으면 이 비용을 내지 않습니다. 대신 다른 비용을 내게 됩니다. 모든 것이 모든 것에 연결되어 있어 한 곳을 고치면 다른 곳이 무너집니다.
설계는 단순히 경계를 긋는 일이 아니라, 어느 비용을 낼지 고르는 일입니다.
버튼 하나가 통과하는 경계들
추상적으로 들리는 이야기를 구체적으로 만들어 봅니다.
사용자가 결제 버튼을 누릅니다. 이 한 번의 클릭이 화면을 떠나 다시 돌아오기까지, 요청은 열세 개의 경계를 지납니다. 이 모든 것을 결정해야 합니다.
버튼 컴포넌트가 어느 모듈에 속하는지가 이미 결정입니다. 그 컴포넌트가 결제 API의 응답 형태를 직접 알아야 하는지가 결정입니다. 검증을 브라우저에서 할지 서버에서 할지가 결정이고, 결제 요청이 실패했을 때 재시도할지가 결정이고, 결제와 주문 생성이 하나의 사건인지 둘인지가 결정입니다. 그 결과를 어디에 캐시할지, 이 버튼이 다른 화면과 함께 배포되는지, 실패했을 때 무슨 일이 있었는지 나중에 알 수 있는지, 모두 결정입니다.
각 결정에는 이미 이름이 있습니다. 그 이름들을 순서대로 따라가는 것이 이 시리즈의 구성입니다.
이 시리즈에서 다룰 것들
경계를 네 층위로 나눴습니다. 코드가 자리에 있을 때, 코드가 움직일 때, 값이 흐를 때, 시스템이 살아 있을 때입니다.
각 편은 다음 흐름으로 진행합니다.
- 이 경계는 무엇이고 무엇을 청구하는가
- 이 경계에 이미 붙어 있는 이름들: 구조, 코드, 안티패턴
- 이 경계가 화면에 드러나는 자리: 디자인·UX 결정
코드 예제는 앞선 시리즈와 같이 TypeScript로 통일했습니다.
I. 구조의 경계 - 코드가 자리에 있을 때
- ep.01 모듈 경계: 이 코드들은 왜 같은 덩어리에 있는가. 폴더 구조는 취향이 아니라 결합에 대한 선언입니다.
- ep.02 의존 방향: 누가 누구를 알아야 하는가. 화살표의 방향 하나가 시스템의 교체 가능성을 결정합니다.
- ep.03 계약 경계: 경계를 넘은 데이터는 무엇을 보장하는가. 타입은 컴파일 타임에 사라집니다.
II. 실행의 경계 - 코드가 움직일 때
- ep.04 실행 경계: 이 작업은 언제, 어디서 실행되어야 하는가. 빌드 타임과 런타임, 서버와 브라우저의 사분면입니다.
- ep.05 네트워크 경계: 호출이 실패한다는 사실을 어디까지 인정할 것인가. 로딩과 에러 화면은 이 인정의 결과물입니다.
- ep.06 시간의 경계: 지금 처리할 것인가, 나중으로 미룰 것인가. 응답 시점과 완료 시점이 갈라지는 순간의 이야기입니다.
III. 데이터의 경계 - 값이 흐를 때
- ep.07 상태의 경계: 이 값의 소유자는 누구인가. 상태를 어디에 두는지가 곧 화면의 성격을 결정합니다.
- ep.08 캐시 경계: 이 값의 사본은 몇 개이고 누가 낡았는가. 캐시는 최적화가 아니라 사본을 만드는 결정입니다.
- ep.09 일관성 경계: 어디까지가 하나의 사건인가. 트랜잭션이 닿지 않는 곳에서 벌어지는 일입니다.
- ep.10 신뢰 경계: 이 입력은 어디서 검증되어야 하는가. 클라이언트 검증은 UX이고 서버 검증은 보안입니다.
IV. 수명의 경계 - 시스템이 살아 있을 때
- ep.11 배포 경계: 무엇이 함께 배포되어야 하는가. 배포 단위는 코드 구조가 아니라 릴리스 속도가 결정합니다.
- ep.12 변경 경계: 어느 부분이 약속이고 어느 부분이 구현인가. 한 번 공개한 것은 되돌리기 어렵습니다.
- ep.13 관측 경계: 경계 너머에서 무슨 일이 있었는지 어떻게 아는가. 앞의 열두 경계가 만든 사각지대를 되짚습니다.
부록
- ep.14 조직 경계: 앞의 열세 경계는 누가 결정했는가. 아키텍처 다이어그램은 조직도라고 할 수 있습니다.
이 시리즈를 읽는 법
열네 편은 적은 분량이 아닙니다. 순서대로 읽지 않아도 되도록 만들었습니다.
- 처음부터 읽는다면: I군부터 순서대로 읽으시면 됩니다. 뒤로 갈수록 앞의 경계를 전제하지만, 각 편에 필요한 만큼 되짚습니다.
- 프론트엔드 작업 중이라면: ep.04 실행 경계에서 시작하세요. ep.05 네트워크, ep.07 상태, ep.08 캐시로 이어지는 네 편이 화면을 그리는 일과 가장 가깝습니다.
- 백엔드 작업 중이라면: ep.02 의존 방향에서 시작해 ep.06 시간, ep.09 일관성, ep.13 관측으로 가는 동선이 자연스럽습니다.
- 디자이너라면: 각 편의 3번 단락만 이어 읽으셔도 됩니다. 로딩 화면, 에러 문구, 낡은 데이터 표시, 권한에 따른 UI 분기: 모두 시스템 경계가 화면에 드러난 자리입니다.
- 선행 편 없이 읽을 수 있는 편: ep.01, ep.04, ep.05, ep.06, ep.11은 완전히 독립적입니다. 검색으로 어느 편에 들어오셔도 그 자리에서 읽힙니다.
앞선 시리즈를 읽지 않으셔도 됩니다. 필요한 자리에 링크만 남기고, 내용을 다시 설명하지는 않습니다.
시리즈 전체 목차
- ep.00 - 시스템의 형태는 경계가 결정한다
- ep.01 - 모듈 경계와 폴더 구조 전략
- ep.02 - 의존 방향과 헥사고날 아키텍처(Hexagonal Architecture)
- ep.03 - 계약 경계와 런타임 타입 검증
- ep.04 - 실행 경계와 렌더링 전략
- ep.05 - 네트워크 경계와 회복 탄력성
- ep.06 - 시간의 경계와 이벤트 기반 처리
- ep.07 - 상태의 경계와 서버 상태 분리
- ep.08 - 캐시 경계와 무효화 전략
- ep.09 - 일관성 경계와 Saga 패턴
- ep.10 - 신뢰 경계와 검증의 위치
- ep.11 - 배포 경계와 마이크로 프론트엔드
- ep.12 - 변경 경계와 Strangler Fig
- ep.13 - 관측 경계와 분산 추적
- ep.14 - 부록: 조직 경계와 콘웨이의 법칙
참고 자료
- Fowler, M. (2002). Patterns of Enterprise Application Architecture. Addison-Wesley.
- Cockburn, A. (2005). Hexagonal Architecture. https://alistair.cockburn.us/hexagonal-architecture/
- Nygard, M. (2018). Release It! (2nd ed.). Pragmatic Bookshelf.
- Richardson, C. Microservice Architecture Patterns. https://microservices.io/patterns/
- Kleppmann, M. (2017). Designing Data-Intensive Applications. O’Reilly.
다음 편 예고
폴더를 나누는 일은 취향의 문제로 보입니다. 그러나 폴더 구조는 무엇이 함께 바뀌는지에 대한 선언이고, 그 선언이 틀리면 매 작업마다 비용을 냅니다. 첫 번째 경계는 가장 조용한 경계입니다.