Backend 09
Layered Architecture와 Book 도메인 구조: 패키지는 단순 정리가 아니다
패키지를 여러 개로 나누는 것이 단순 정리인지, 실행 흐름 자체가 달라지는 것인지 헷갈린다.
근거 · 교안 p58-p60
1장: 왜 계층을 나누나요? 단순 정리를 넘어선 책임 분담
솔라는 노트북 화면에 떠 있는 다이어그램을 멍하니 바라보고 있었다. BookController → BookService → BookRepository → DB. 간결한 화살표들이 네 개의 상자를 깔끔하게 연결하고 있었다. 마치 잘 정리된 서랍장 같았다. 하지만 솔라의 머릿속은 뒤죽박죽인 책상 위와 같았다. 아무렇게나 쌓아둔 책, 펜, 노트들이 뒤섞여 있지만, 정작 필요한 물건을 찾는 데는 큰 문제가 없었다.
“언니, 이거 좀 이상해.”
솔라가 입을 열었다. 소파에서 조용히 책을 읽던 루나가 고개를 들었다.
“코드를 controller, service, repository 패키지로 나누는 거 말이야. 그냥 파일들을 폴더별로 정리해두는 거랑 뭐가 달라? 결국 한곳에 다 모아놓고 짜도 프로그램은 돌아가잖아. 이렇게 나누는 게 보기엔 좋을지 몰라도, 실행되는 방식 자체가 바뀌는 건 아니지 않아?”
솔라의 목소리에는 코드를 정리하는 수고로움이 과연 그만한 가치가 있는지에 대한 근본적인 의문이 담겨 있었다. 그냥 한 파일에 전부 넣고 ‘Ctrl+F’로 찾아가며 개발해도 기능은 완성되는데, 굳이 왜 이런 구조를 만들어야 하는 걸까.
루나는 책을 덮고 테이블 옆으로 다가왔다. 노트북 화면의 다이어그램과 혼란스러운 솔라의 표정을 번갈아 보더니, 빈 노트 한 장과 펜을 집어 들었다.
“작은 서점을 하나 연다고 상상해 보자. 솔라 네가 사장이고, 직원은 너 혼자야.”
루나는 노트에 작은 사각형을 그리고 ‘솔라 서점’이라고 적었다.
“손님이 와서 책을 주문하면, 네가 직접 주문을 받아. 창고에 가서 책이 있는지 확인하고, 없으면 출판사에 주문을 넣겠지. 책을 포장하고, 배송 정보를 입력하고, 마지막으로 장부에 매출을 기록하는 일까지 전부 다.”
솔라는 고개를 끄덕였다. “응. 혼자 하니까. 당연히 그래야지.”
“맞아. 하루에 한두 명 올 때는 괜찮아. 그런데 어느 날 갑자기 서점이 대박이 나서 하루에 수백 건의 주문이 밀려 들어오면 어떨까? 전화는 빗발치고, 창고 재고는 뒤죽박죽이 되고, 포장할 책은 산더미처럼 쌓이고. 혼자서 이 모든 걸 감당할 수 있을까?”
“…못하지. 정신없어서 주문을 잘못 받거나 배송을 빠뜨리는 실수를 할 거야.”
“바로 그거야.”
루나는 노트를 돌려 새로운 그림을 그리기 시작했다. 이번에는 여러 개의 사각형을 그리고 선으로 이었다. 각 사각형에는 ‘주문 접수팀’, ‘재고 관리팀’, ‘배송팀’이라고 적었다.
“이제 솔라 서점은 더 이상 너 혼자가 아니야. 손님한테서 주문을 받는 건 ‘주문 접수팀’의 책임이야. 이 팀은 책 제목과 수량이 정확한지만 확인하면 돼. 책이 창고 어디에 있는지, 어떻게 포장하는지는 알 필요가 없어. 그저 확인된 주문서를 ‘재고 관리팀’에 넘겨주면 끝이야.”
루나의 펜이 ‘주문 접수팀’에서 ‘재고 관리팀’으로 향하는 화살표를 그었다.
“재고 관리팀은 주문서에 적힌 책을 창고에서 찾아서 ‘배송팀’에 전달하는 책임을 져. 재고가 부족하면 출판사에 연락하는 것도 이 팀의 몫이지. 손님과 직접 통화할 필요는 없어. 배송팀은 전달받은 책을 안전하게 포장하고 정확한 주소로 보내는 책임을 다하면 되고.”
솔라는 루나가 그린 그림을 가만히 들여다보았다. 각 팀은 자기에게 주어진 명확한 임무만 수행하고, 그 결과물을 다음 팀에게 넘겨주고 있었다. 마치 공장의 생산 라인처럼.
“아…”
솔라의 입에서 나지막한 탄성이 터져 나왔다.
“단순히 일을 나눠서 정리한 게 아니구나. 각자 맡은 ‘책임’이 있었네. 주문 접수팀은 배송팀이 어떻게 일하는지 몰라도 되고, 배송팀도 재고 관리팀의 복잡한 업무를 신경 쓸 필요가 없는 거잖아. 그냥 자기 할 일만 제대로 해서 다음 사람한테 넘겨주면 되는 거였어.”
솔라는 다시 노트북 화면의 다이어그램을 보았다. 아까와 똑같은 그림이었지만, 이제는 전혀 다르게 보였다.
BookController → BookService → BookRepository → DB
이건 단순히 코드를 종류별로 모아둔 폴더 구조가 아니었다. 거대한 ‘책 등록’이라는 요청을 처리하기 위해 각자의 역할을 분담한 전문가 팀이었다. 각 화살표는 단순한 연결선이 아니라, 한 단계의 처리가 끝나고 다음 단계의 책임자에게 업무를 ‘위임’하는 흐름이었다.
“그럼 Controller는 서점의 ‘주문 접수팀’ 같은 거네. 손님, 그러니까 외부의 요청을 가장 먼저 받아서 처리하는 창구.”
솔라의 눈이 반짝였다. 막연했던 그림이 명확한 역할과 책임을 가진 시스템으로 보이기 시작했다. 패키지를 나누는 것은 단순한 정리가 아니라, 복잡한 문제를 해결하기 위한 영리한 ‘책임 분담’ 전략이었던 것이다. 이제 솔라의 머릿속은 더 이상 복잡하지 않았다. 그렇다면 첫 번째 책임자인 컨트롤러는 정확히 어떤 일을 하는 걸까?
“언니, 그럼 저기 첫 번째 관문인 Controller는 손님한테서 뭘 받아서, 어떻게 확인하고, 뭘 다음 팀한테 넘겨주는 거야?”
2장: Controller: 외부 요청을 맞아 책임 넘기기
루나는 아무 말 없이 솔라가 보고 있던 노트북 옆의 노트를 끌어당겼다. 어지러운 서점의 조직도가 그려진 그 위로, 루나의 펜이 조용히 움직였다. ‘주문 접수팀’이라고 적힌 상자 앞에 작은 창구 그림을 하나 더 그리고, 그 옆에 ‘[주문 양식]’이라는 글자를 적었다.
책 제목: (필수)저자: (필수)
루나는 ‘필수’라는 단어 옆에 작은 체크박스를 그려 넣었다. 솔라는 언니의 펜 끝을 가만히 따라갔다. 이전에는 그저 다음 팀으로 화살표만 이어져 있던 그림에, 명확한 ‘조건’이 붙는 순간이었다. 외부와 팀 내부를 가르는 경계선이 더 뚜렷해지는 것 같았다.
솔라는 새로 생긴 체크박스를 손가락으로 콕 찍었다.
“아까 주문 접수팀이라고 했던 Controller의 역할이 바로 이거구나. 주문서 양식을 확인하는 것. 그런데, 그냥 요청이 들어오면 다음 Service한테 ‘이거 처리해!’ 하고 그대로 넘겨주면 안 돼? 어차피 뒤에서 제목이 없으면 등록을 못 할 테니까, 그때 가서 확인해도 되잖아.”
솔라의 말에는 여전히 의문이 남아 있었다. 굳이 첫 관문에서부터 깐깐하게 확인할 필요가 있을까? 마치 모든 일을 한 사람이 처리하던 작은 서점 시절의 습관처럼, 책임의 경계가 아직은 희미하게 느껴졌다.
루나는 대답 대신, 테이블 위에 있던 포스트잇 두 장을 집어 들었다. 그리곤 첫 번째 포스트잇에 무언가를 적어 솔라 앞에 쓱 밀어주었다. 마치 서점 창구에 손님이 주문서를 내미는 것처럼.
{
"title": "모비 딕",
"author": "허먼 멜빌"
}
“자, 솔라 네가 이제부터 BookController야. 외부로부터 ‘책 등록’ 요청이 들어왔어. 어떻게 할래?”
솔라는 포스트잇을 집어 들었다. “음, 책 제목도 있고 저자도 있네. 완벽한 주문서야. 그럼 이제 다음 책임자인 BookService한테 ‘이 책을 우리 서점에 등록해 주세요’ 하고 넘겨주면 되겠지!” 솔라는 자신 있게 말하며 포스트잇을 노트 위에 그려진 ‘재고 관리팀(Service)’ 상자로 옮기려 했다.
그때, 루나가 솔라의 손을 가볍게 막았다. 그리고는 두 번째 포스트잇을 솔라 앞에 밀어 놓았다.
{
"author": "어니스트 헤밍웨이"
}
“이 요청은 어때?”
솔라는 순간 말문이 막혔다. 포스트잇에는 저자 이름만 덩그러니 적혀 있었다. “어… 이건 제목이 없잖아. 이대로 BookService한테 넘기면 ‘도대체 무슨 책을 등록하라는 거죠?’ 하고 다시 되묻거나, 아니면 시스템 전체가 오류를 낼 수도 있겠네. 재고 관리팀은 제목 없는 책을 처리하는 방법을 모르니까.”
솔라는 두 개의 포스트잇을 나란히 놓고 번갈아 보았다. 하나는 명확한 요청, 다른 하나는 불완전한 요청. 그 차이가 한눈에 들어왔다.
“아!”
탄성과 함께 솔라의 눈이 커졌다.
“그냥 넘기면 안 되는 거였어. Controller는 단순히 요청을 전달하는 우체부가 아니었네. 우리 시스템 안으로 들어오기 전에, 요청이 최소한의 양식을 갖췄는지 검사하는 ‘문지기’ 같은 역할이었구나!”
솔라는 제목 없는 요청이 적힌 포스트잇을 옆으로 치웠다. 마치 입장을 거절당한 손님처럼. 그리고 제대로 된 정보가 담긴 첫 번째 포스트잇만 집어 들었다.
“그러니까 컨트롤러의 진짜 책임은 이거야. ‘외부에서 온 모든 요청이 우리 시스템 내부 규칙을 어지럽히지 않도록, 첫 관문에서 지킨다.’ 제목이 없거나, 가격에 숫자가 아닌 문자가 들어오는 등 말도 안 되는 요청은 여기서 다 걸러내고, ‘요청 양식이 잘못되었습니다’라고 밖으로 돌려보내는 거지. 그리고 통과된, 깨끗하고 안전한 요청만 다음 책임자에게 넘겨주는 거야.”
솔라는 비로소 다이어그램의 첫 번째 화살표가 가진 무게를 이해했다. 그것은 단순한 연결이 아니라, 시스템의 안정성을 지키는 책임의 위임이었다. Controller는 외부 세계의 혼란과 내부 시스템의 질서를 구분하는 명확한 관문이었다.
솔라는 이제 깨끗하게 걸러진 ‘모비 딕’ 주문서, 즉 포스트잇을 들고 ‘재고 관리팀(Service)’ 상자 위에 올려놓았다. 문지기의 임무는 끝났다.
“좋아. 이제 문지기가 깐깐하게 검증한 제대로 된 주문서가 Service에 도착했어. 그럼 이 ‘재고 관리팀’은 이 주문서를 받아서 구체적으로 무슨 일을 시작하는 거야?”
3장: Service: 비즈니스 로직의 심장, 책임 위임의 조정자
솔라의 손가락이 ‘재고 관리팀(Service)’ 상자 위에 놓인 ‘모비 딕’ 포스트잇을 톡 건드렸다. 그리고는 망설임 없이, 그 포스트잇을 바로 옆에 그려진 ‘창고/DB’라고 쓰인 상자로 스윽 밀었다. 깔끔하게 검증된 주문서가 다음 부서로 넘어가는, 당연한 흐름이었다.
“자, 주문 접수팀(Controller)이 양식이 완벽한지 확인했으니까. 이제 재고 관리팀(Service)은 이걸 받아서 바로 창고 담당자(Repository)한테 ‘이 책 서가에 꽂아둬!’ 하고 넘겨주면 끝나는 거 아니야?”
솔라는 자신의 논리가 완벽하다고 생각했다. 각 팀은 자기 할 일을 하고 다음 팀에 넘긴다. 문지기가 통과시킨 요청이니, 재고 관리팀은 그저 전달만 하면 될 뿐이다. 솔라에게 ‘재고 관리팀(Service)’은 Controller와 Repository를 잇는 단순한 중간 다리, 컨베이어 벨트의 한 구간처럼 보였다.
하지만 루나는 솔라가 밀어놓은 포스트잇을 다시 ‘재고 관리팀’ 상자 안으로 가져왔다. 그리고는 서점 조직도 옆에 비어 있던 공간에 새로운 목록을 만들기 시작했다.
[솔라 서점 전체 도서 목록]
- 데미안 - 헤르만 헤세
- 어린 왕자 - 앙투안 드 생텍쥐페리
- 모비 딕 - 허먼 멜빌
솔라는 눈을 가늘게 떴다. 루나가 마지막 줄에 ‘모비 딕’을 추가하는 것을 똑똑히 보았다. 그리고 루나는 아무 일 없었다는 듯, 솔라가 아까 검증했던 ‘모비 딕’ 주문서 포스트잇을 가리켰다.
“재고 관리팀장이 이 주문서를 받았어. 이제 창고에 가서 책을 등록하라고 지시하기만 하면 될까?”
“어… 잠깐만.”
솔라는 루나가 새로 만든 도서 목록과, 손에 들린 주문서를 번갈아 보았다. 뭔가 삐걱거리는 느낌이 들었다.
“목록에… 이미 ‘모비 딕’이 있네. 그런데 또 ‘모비 딕’을 등록하라고 하면… 우리 서점엔 똑같은 책이 두 번 등록되는 거잖아? 이건 이상한데.”
“바로 그거야.”
루나가 말했다. “주문 접수팀, 즉 Controller는 주문서의 ‘양식’이 올바른지만 봤어. ‘제목’과 ‘저자’ 칸이 비어있지 않은지만 확인했지. 하지만 그 책이 우리 서점에 이미 존재하는지는 Controller의 책임이 아니야. 그건 우리 서점만의 ‘운영 규칙’, 즉 비즈니스 정책의 문제거든.”
솔라의 머릿속에서 두 가지 종류의 ‘검사’가 분리되기 시작했다. 하나는 요청서의 형식을 보는 ‘표면적인 검사’, 다른 하나는 요청의 내용이 우리 사업의 현재 상황과 규칙에 맞는지를 따지는 ‘본질적인 검사’.
“아! 그럼 재고 관리팀(Service)은 그냥 전달만 하는 게 아니구나. 창고(Repository)에 일을 시키기 전에, 먼저 우리 서점의 규칙을 확인해야 하는 거였어. ‘혹시 이 책, 이미 우리 서점에 있는 건 아닌가?’ 하고 전체 도서 목록을 한번 뒤져봐야 하는 거네!”
솔라는 마치 자신이 재고 관리팀장이 된 것처럼 행동을 시뮬레이션하기 시작했다.
- 먼저 Controller에게서 ‘모비 딕 등록’ 요청서를 받는다.
- 창고(Repository)에 바로 지시하는 대신, 먼저 창고 담당자에게 묻는다. “‘모비 딕’이라는 책이 우리 목록에 있는지 확인 좀 해줘.”
- 창고 담당자가 “네, 이미 있습니다”라고 보고하면, 이 등록 요청은 처리하지 않고 Controller에게 “이미 등록된 책입니다”라고 결과를 돌려준다.
“Service는 단순한 중간 다리가 아니었어. 요청을 처리하기 위한 핵심 규칙들을 판단하고, 여러 단계를 조율하는 ‘지휘자’나 ‘현장 관리자’ 같은 역할이었네.”
솔라는 ‘재고 관리팀’ 상자에 적힌 ‘Service’라는 단어를 손가락으로 쓸었다. 이제 그 단어는 더 이상 비어 보이지 않았다. 그 안에는 ‘중복 확인’, ‘저자 정보 검증’, ‘성인 등급 도서 확인’과 같은 서점만의 고유한 규칙들이 빽빽하게 들어차 있는 것 같았다. Controller가 외부 세계의 혼란으로부터 시스템을 지키는 ‘관문’이었다면, Service는 시스템의 핵심 가치와 정책을 지키는 ‘두뇌’이자 ‘심장’이었다.
“Service의 책임은 이거구나. ‘우리 비즈니스의 핵심 규칙에 따라 요청을 처리하고, 필요한 데이터 작업을 조정한다.’ 창고에 뭘 저장해달라고 명령하기 전에, 뭘 좀 찾아달라고 요청하기도 하는 거야. 단순히 위에서 아래로 흐르는 게 아니라, 필요에 따라 양방향으로 소통하면서 전체 작업의 흐름을 만드는 거지.”
솔라는 비로소 Service가 왜 필요한지, 왜 그저 Controller가 Repository를 직접 호출하지 않는지 깨달았다. 애플리케이션의 진짜 지성은 바로 이 Service 계층에 담겨 있었다.
솔라는 이제 중복이 확인된 ‘모비 딕’ 포스트잇을 옆으로 치웠다. 그리고 새로운 포스트잇을 가져와 ‘노인과 바다 - 어니스트 헤밍웨이’라고 적었다. 이 요청은 중복 검사를 통과했다. 이제 Service는 최종 결정을 내린다.
“좋아. 이제 Service가 모든 비즈니스 규칙 검증을 끝냈어. 그래서 다음 책임자인 Repository한테 말하는 거지. ‘이 ‘노인과 바다’ 객체를 우리 데이터베이스에 영구적으로 저장해 줘.’ 그럼… Repository는 이 ‘객체 저장’이라는 명령을 어떻게 알아듣고 처리하는 거야? DB는 객체 같은 거 모르잖아?”
4장: Repository: 데이터와의 대화, 영속성 책임
솔라의 시선은 ‘노인과 바다’라고 적힌 포스트잇에 고정되어 있었다. 언니, 루나의 손을 떠난 포스트잇은 ‘재고 관리팀(Service)’ 상자를 지나, 드디어 마지막 목적지인 ‘창고/DB’ 상자 위로 옮겨지고 있었다. 하지만 상자에 닿기 직전, 루나의 펜이 그 둘 사이에 단호한 경계선을 그었다. 마치 국경선처럼, 포스트잇은 ‘창고/DB’ 상자 바로 앞, 새로 생긴 ‘창고 관리자(Repository)’라는 작은 상자 위에 멈춰 섰다.
루나는 아무 말 없이 그 경계선 양옆에 다른 종류의 글씨를 적기 시작했다. 현장 관리자(Service)가 창고 관리자(Repository)를 바라보는 쪽에는 이렇게 적었다.
저장해줘( 이 ‘노인과 바다’ 책 객체 )
그리고 창고 관리자(Repository)가 실제 창고(DB)를 바라보는 쪽에는, 전혀 다른 언어로 보이는 명령을 적었다.
INSERT INTO books (title, author) VALUES ('노인과 바다', '어니스트 헤밍웨이');
솔라는 두 문장을 번갈아 보며 미간을 찌푸렸다.
“아, 이제 알겠다.”
솔라는 확신에 찬 목소리로 말했다.
“Service는 그냥 ‘이 객체 저장해 줘’라고 편하게 말하는 거고, Repository가 그걸 받아서 데이터베이스가 알아들을 수 있는 INSERT 같은 SQL 언어로 직접 번역해서 실행하는 거구나. 그럼 Repository는 그냥 SQL 문장 모아놓는 통역사 같은 거네. DB랑 직접 대화하는 역할.”
솔라에게 Repository의 역할은 명확해 보였다. 서비스의 편리한 명령을 실제 데이터베이스의 까다로운 언어로 바꿔주는, 단순하지만 꼭 필요한 번역가. 그 이상도 이하도 아니었다.
루나는 솔라의 말을 부정하지 않았다. 대신, 노트의 빈 공간에 새로운 그림을 그리기 시작했다. 기존의 ‘창고/DB’ 상자 옆에, 전혀 다른 모양의 상자를 하나 더 그렸다. 기존 상자에는 ‘관계형 서가’라고 이름을 붙이고, 새로 그린 상자에는 ‘문서형 보관함’이라고 적었다.
“서점이 아주 잘 돼서 창고를 확장하기로 했어. 기존의 ‘관계형 서가’는 책 제목, 저자, 가격을 각각 다른 칸에 맞춰 정리해야 하는 촘촘한 책장이었어. 그런데 새로 들여온 ‘문서형 보관함’은 그냥 책 정보를 통째로 적은 종이(문서)를 서랍에 휙 던져 넣기만 하면 되는 최신식 시스템이야.”
루나는 ‘문서형 보관함’ 아래에, 아까와는 또 다른 형태의 명령어를 적었다.
db.books.insertOne({ title: '노인과 바다', author: '어니스트 헤밍웨이' });
“자, 솔라. 만약 우리가 내일부터 ‘관계형 서가’ 대신 이 ‘문서형 보관함’을 쓰기로 결정했다고 해봐. 그럼 현장 관리자인 Service는 자기가 내리는 명령을 바꿔야 할까? ‘이 책 객체 저장해줘’가 아니라, 새로운 보관함의 사용법을 배워서 insertOne이라는 새로운 방식으로 명령을 내려야 할까?”
솔라는 잠시 생각에 잠겼다. 현장 관리자(Service)의 역할은 ‘이미 있는 책인지 확인’하는 등 서점의 운영 규칙을 지키는 것이었다. 창고의 선반이 어떻게 생겼는지, 물건을 어떻게 정리하는지는 그의 주된 관심사가 아니었다.
“아니… 그러면 안 될 것 같은데.”
솔라가 조심스럽게 입을 열었다.
“만약 창고를 바꿀 때마다 현장 관리자가 창고 사용법을 새로 배워야 한다면 너무 비효율적이잖아. 나중에 또 다른 종류의 창고가 생기면 그때마다 Service 코드를 다 뜯어고쳐야 하는 거니까. 현장 관리자는 그냥… 똑같이 ‘저장해줘’라고 말할 수 있어야 해.”
바로 그 순간, 솔라의 머릿속에 흩어져 있던 조각들이 맞춰졌다.
“아! 그래서 Repository가 필요한 거구나! Repository는 단순한 통역사가 아니었어. 어떤 창고를 쓰든, 현장 관리자에게는 똑같은 방식으로 보이도록 만들어주는 ‘중간 관리자’이자 ‘방어막’이었던 거야!”
솔라는 흥분하며 자신의 발견을 설명하기 시작했다.
“Service는 창고가 ‘관계형 서가’인지 ‘문서형 보관함’인지 전혀 신경 쓸 필요 없어. 그저 Repository한테 ‘이 Book 객체 저장해줘’라고만 하면 돼. 그럼 Repository가 현재 우리가 사용하는 창고가 어떤 종류인지 파악해서, INSERT 문을 만들든 insertOne 명령을 만들든 알아서 처리해주는 거지. Service는 데이터베이스의 구체적인 기술로부터 완벽하게 보호받는 거야!”
솔라의 눈에는 이제 Repository가 단순히 SQL을 실행하는 곳이 아니라, 비즈니스 로직과 데이터 저장 기술 사이를 분리하는 견고한 벽으로 보였다. 애플리케이션의 핵심인 Service가 데이터베이스 기술의 변화에 흔들리지 않도록 지켜주는, 매우 중요한 책임이었다. 이것이 바로 데이터 영속성의 추상화였다.
“Repository의 진짜 책임은 이거였네. ‘어떤 종류의 데이터베이스를 사용하든, Service가 일관된 방식으로 데이터에 접근할 수 있도록 중간에서 모든 복잡한 과정을 처리한다.’ DB랑 직접 이야기하는 곳이 맞긴 하지만, 그건 책임의 일부일 뿐, 더 중요한 건 Service가 DB 언어를 몰라도 되게 만들어주는 거였어.”
솔라는 이제 ‘노인과 바다’ 포스트잇을 ‘창고 관리자(Repository)’ 상자 위에 확신을 갖고 올려두었다. Repository가 이 요청을 받아, 뒤에 있는 어떤 종류의 창고에든 능숙하게 저장해 줄 것이라고 믿을 수 있었다.
노트 위에는 이제 주문 접수팀(Controller) → 현장 관리자(Service) → 창고 관리자(Repository)로 이어지는 책임의 흐름이 명확하게 그려져 있었다. 각자의 역할이 선명해졌다.
“좋아. 이제 각 팀의 전문가들이 무슨 일을 하는지는 확실히 알겠어. 문지기, 현장 관리자, 그리고 창고 전문가까지. 그럼 이제 정말 궁금한 건데, 외부에서 ‘새 책을 등록해주세요’라는 요청이 하나 툭 들어왔을 때, 이 전문가들이 어떻게 착착 협력해서 일을 끝내는지, 그 전체 흐름을 처음부터 끝까지 따라가 보고 싶어.”
5장: Layered Architecture: 책임의 흐름과 전체 그림 맞추기
솔라는 테이블 위에 펼쳐진 노트를 물끄러미 내려다보았다. 주문 접수팀(Controller), 현장 관리자(Service), 창고 관리자(Repository). 각 팀의 역할과 책임은 이제 제법 선명하게 그려졌다. 마치 각기 다른 전문 기술을 가진 전문가들의 프로필을 읽은 것 같았다. 하지만 솔라의 마음 한구석에는 풀리지 않은 매듭이 남아 있었다.
“각각 따로 일하는 건 알겠는데… 그래서 이 전문가들이 실제로 어떻게 협력하는 거지? 진짜 요청이 들어왔을 때, 일이 어떤 순서로 흘러가는지 한눈에 보고 싶어.”
솔라는 혼잣말처럼 중얼거리며 새 포스트잇 한 장을 집어 들었다. 그리고는 망설임 없이 ‘새로운 책 등록 요청’이라고 적었다. 이전처럼 루나의 설명을 기다리는 대신, 직접 이 요청을 들고 서점의 문을 두드려 보기로 마음먹은 것이다.
솔라는 노트의 빈 페이지에 네 개의 커다란 상자를 그리고, 화살표로 이었다.
[ ] → [ ] → [ ] → [ ]
아직 이름 없는 상자들이었지만, 솔라의 머릿속에는 이미 각자의 역할이 정해져 있었다. 그녀는 첫 번째 상자 위에 ‘Controller’라고 적고, 방금 만든 ‘새 책 등록 요청’ 포스트잇을 그 위에 올려놓았다. 마치 연극의 첫 장면을 준비하는 연출가처럼. 루나는 그런 솔라의 행동을 조용히 지켜보고 있었다.
“자, 시작해볼까.”
솔라가 작은 목소리로 선언했다. 그녀의 시뮬레이션이 막을 올렸다.
1단계: 주문 접수팀 (Controller) 앞
“손님이 ‘총, 균, 쇠 - 재레드 다이아몬드’ 책을 등록해달라고 요청서를 들고 왔어.”
솔라는 포스트잇을 가리키며 말했다.
“나는 이제 Controller, 이 서점의 첫 번째 관문을 지키는 문지기야. 내 책임은 이 요청서의 ‘양식’을 확인하는 것. 제목과 저자 이름이 빠짐없이 잘 적혀 있나? 응, 완벽해. 이상한 글자나 형식이 잘못된 부분도 없어. 그럼 통과!”
솔라는 포스트잇 아래에 작은 글씨로 ‘요청 형식 유효함’이라고 적었다. 그리고는 다음 상자에 ‘Service’라고 이름을 붙이고, 검증이 끝난 포스트잇을 그 위로 옮겼다. 단순한 전달이 아니었다. 외부의 날것 그대로의 요청이, 시스템 내부에서 다룰 수 있는 깨끗한 데이터 조각으로 변환되는 첫 번째 단계였다.
2단계: 현장 관리자 (Service)의 책상 위
“자, 이제 요청서는 현장 관리자인 Service에게 도착했어.”
솔라는 두 번째 상자를 손가락으로 톡 쳤다.
“Service는 단순한 전달자가 아니지. 우리 서점의 핵심 규칙, 즉 ‘비즈니스 로직’을 책임지는 두뇌야. 이 요청을 곧이곧대로 창고에 넘기기 전에, 먼저 처리해야 할 일이 있어. ‘혹시 이 책, 이미 우리 서가에 꽂혀있는 건 아닐까?’”
솔라는 잠시 연기를 멈추고, 세 번째 상자를 힐끗 보았다. 그곳은 Repository의 자리였다.
“Service는 Repository에게 물어봐. ‘혹시 ‘총, 균, 쇠’라는 책이 있는지 확인 좀 해줄래?’ Repository가 데이터베이스를 뒤져보고 대답하겠지. ‘아니요, 없습니다.’ 라고.”
이제 Service는 다음 판단을 내린다. 중복이 아니니 등록을 진행해도 좋다. 솔라는 포스트잇에 ‘중복 없음, 등록 가능’이라고 덧붙였다. 요청은 이제 ‘실제로 데이터베이스에 저장해야 할 대상’이라는 최종 판결을 받은 셈이다. Service의 지휘 아래, 여러 데이터 조회와 규칙 검증이 하나의 트랜잭션으로 묶여 처리되고 있었다.
3단계: 창고 관리자 (Repository)의 작업실
“모든 규칙을 통과한 요청이야. 이제 Service는 Repository에게 최종 명령을 내려. ‘이 Book 객체를 데이터베이스에 영구적으로 저장해 줘.’”
솔라는 세 번째 상자에 ‘Repository’라고 적고 포스트잇을 옮겼다.
“Repository는 Service가 건네준 ‘Book 객체’라는 추상적인 명령을, 데이터베이스가 알아들을 수 있는 구체적인 언어로 번역하는 전문가야. 지금 우리 창고가 ‘관계형 서가(SQL DB)’를 쓴다면 INSERT 구문을 만들 거고, ‘문서형 보관함(NoSQL DB)’을 쓴다면 insertOne 같은 명령을 만들겠지.”
솔라는 네 번째 상자에 ‘DB’라고 적었다. 그리고 Repository 상자에서 DB 상자로 향하는 화살표 위에 ‘INSERT INTO books...’ 라고 작게 적었다. Service는 전혀 몰라도 되는, Repository만의 책임 영역이었다.
4단계: 흐름의 완성
데이터가 DB에 저장되었다. 하지만 이걸로 끝이 아니었다. 솔라는 이번에는 반대 방향으로 흐르는 작은 화살표들을 그리기 시작했다.
“저장이 성공하면, Repository는 Service에게 보고해. ‘저장 완료했습니다.’ 그럼 Service는 Controller에게 보고하지. ‘요청하신 책 등록 작업이 성공적으로 끝났습니다.’ 마지막으로 Controller는 처음 요청을 보냈던 손님에게 응답을 보내주는 거야. ‘책 등록이 완료되었습니다!’”
솔라는 포스트잇의 여정을 처음부터 끝까지, 그리고 다시 돌아오는 흐름까지 눈으로 좇았다. 각자 자기 자리에서 따로 일하는 줄 알았던 전문가들은, 사실 하나의 목표를 위해 유기적으로 연결된 팀이었다. 요청은 각 계층을 지날 때마다 조금씩 다른 모습으로 가공되고, 책임은 명확하게 위임되었다. 화살표는 단순한 연결선이 아니라, ‘책임의 위임’과 ‘결과의 보고’라는 역동적인 상호작용이었다.
솔라는 마침내 노트북 화면에 떠 있던, 처음에는 무미건조하게만 보였던 다이어그램을 다시 보았다.
BookController → BookService → BookRepository → DB
이제 저 화살표들은 살아 움직이는 것 같았다. 외부 세계의 혼돈을 막아내는 첫 번째 방어선, 비즈니스의 핵심 규칙을 지키는 심장, 구체적인 기술의 복잡함을 감싸는 추상화의 벽, 그리고 이 모든 것을 가능하게 하는 유려한 계층별 책임 위임의 흐름. 솔라는 더 이상 헷갈리지 않았다.
“언니, 그럼 만약에 말이야.”
솔라가 새로운 노트를 펼치며 말했다. 그녀의 눈은 이미 다음 설계를 그리고 있었다.
“여기에 ‘책 대여 기능’을 추가한다면… Controller는 사용자 ID와 책 ID를 받고 형식을 검사할 거야. Service는 그 ID로 사용자와 책이 실제로 있는지, 그리고 책이 대여 가능한 상태인지 Repository에게 여러 번 물어보면서 확인한 다음에 ‘대여 처리’라는 비즈니스 로직을 결정하겠지. 그리고 Repository는 Service의 요청에 따라 책의 상태를 ‘대여 중’으로 바꾸는 UPDATE 쿼리를 실행할 거고.”
솔라는 막힘없이 각 계층의 역할을 분담하며 새로운 기능의 뼈대를 세워나갔다. 더 이상 패키지 폴더를 보며 고민하던 솔라는 없었다. 그녀는 이제 복잡한 요구사항을 명확한 책임의 흐름으로 분해하고 조립할 수 있는 설계자의 눈을 갖게 된 것이다.