모든 글

리더와 라이터 컴포넌트로 비즈니스 흐름 드러내기

리더와 라이터는 저장소 세부사항과 변경 범위를 감추고 비즈니스 계층에 정책을 남깁니다. 다만 서비스 수명이 그 비용을 정당화해야 합니다.

출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다. gpt-5.6-sol 모델을 사용해 생성·편집했습니다.

리포지토리가 이미 사용자를 읽고 쓰는데 왜 앞에 UserReaderUserWriter를 둘까요? 작은 예제에서는 클래스만 하나 더 감싼 빈 구조처럼 보입니다. 실제로 그런 경우도 있습니다.

저는 소프트웨어가 오래 살아남아 계속 바뀌고 다른 사람에게 인수인계될 것으로 예상할 때 이런 컴포넌트를 사용합니다. 모든 구현 코드를 숨기려는 목적은 아닙니다. 비즈니스 계층에는 비즈니스 흐름이 보이게 하고, 데이터를 얻고 저장하는 세부사항은 더 좁은 변경 이유를 가진 계층에 두려는 것입니다.

이 선택은 유지보수와 인수인계 경험에서 나왔습니다. 원래 개발자가 갑자기 떠난 뒤 설명 없이 코드를 받은 사람이 있을 수 있습니다. 그 사람이 비즈니스 동작을 열었을 때 리포지토리 호출, 통신 세부사항, 매핑과 정책이 길게 섞인 코드보다 의미 있는 단계로 표현된 흐름을 보게 하고 싶습니다.

비즈니스 메서드가 실제 일을 말하게 하기

결제 동작을 예로 들어보겠습니다. 사용자, 상점, 상품을 읽고 규칙을 확인한 다음 결제를 처리합니다. 모든 리포지토리와 클라이언트 호출을 한 메서드에 넣어도 동작은 제대로 할 수 있습니다. 다만 구현 세부사항과 비즈니스 판단이 같은 높이에 놓입니다.

조회 작업을 UserReader, StoreReader 같은 컴포넌트로 감싸면 결제 동작의 모양이 더 잘 보일 수 있습니다. 비즈니스 계층은 무엇이 필요하고 어떤 판단을 하는지 말합니다. 구현 컴포넌트는 그 정보를 어떻게 가져오는지 소유합니다.

트레이드오프가 있습니다. 리더는 세부사항을 감추므로 조회 문제를 디버깅하는 사람은 클래스를 하나 더 열어야 합니다. 전부 인라인으로 두면 구현은 바로 보입니다. 어느 쪽이 언제나 이기는 것은 아닙니다. 저는 비즈니스 흐름의 가독성과 좁은 변경 경계가 탐색 비용보다 클 때 래퍼를 선택합니다.

데이터 원본 변경은 좋은 검토 사례입니다. 상점 정보를 로컬 데이터베이스에서 읽다가 HTTP로 가져와야 한다고 해보겠습니다. 결제 서비스가 리포지토리를 직접 사용하면 통신과 매핑 변경이 비즈니스 클래스에 들어옵니다. StoreReader에 의존하고 있었다면 변경 대부분을 리더 안에 둘 수 있습니다. 비즈니스 동작은 계속 같은 언어로 상점 정보를 요청합니다.

성장하는 서비스에서는 이런 변화가 생각보다 자주 일어납니다. 그렇다고 모든 프로그램에서 일어날 것처럼 미리 구조를 만들 필요는 없습니다.

컴포넌트는 필요한 질문을 만든다

가치는 구현 격리에만 있지 않습니다. 책임이 분명한 컴포넌트를 보면 팀이 논의해야 할 대상도 구체적으로 드러납니다.

Q&A에서 답변을 추가하는 기능이 있다고 해보겠습니다. 블랙리스트에 오른 작성자는 답변을 달 수 없다는 요구사항이 생깁니다. 경계를 고민하지 않으면 유즈케이스 클래스에 블랙리스트 리포지토리가 하나 더 붙습니다. 데이터를 읽고 해석한 뒤 답변까지 추가합니다. 생성자는 커지고 정책과 데이터 접근이 섞입니다.

구현을 컴포넌트로 보기 시작하면 다른 질문을 하게 됩니다. 블랙리스트 검증기가 필요한가? 검증은 답변 생성과 응집되어야 하는가? 여러 곳에서 재사용할 기능인가, 이 정책에만 속하는가? 누가 조회를 소유하고 누가 판단을 소유하는가?

이 작은 예제만으로 올바른 추출을 단정할 수는 없습니다. 그 불확실성이 오히려 유용합니다. 기존 코드를 기계적으로 따라가지 않고 무엇이 함께 있어야 하는지 판단하게 합니다.

리더와 라이터는 제가 이런 사고를 위해 쓰는 이름입니다. “비즈니스 계층”, “구현 계층”도 제 프로젝트에 붙인 표현입니다. 반드시 지켜야 할 공식 용어가 아닙니다. 다른 이름을 써도 컴포넌트의 위치와 책임을 설명할 수 있으면 됩니다.

경계를 향해 점진적으로 키우기

아무 동작도 없는 첫날부터 엔티티마다 리더, 라이터, 검증기와 래퍼를 전부 만들라고 권하지 않습니다. 애플리케이션에 리포지토리 하나면 충분하면 거기서 시작합니다. 서비스가 성장하면서 반복되거나 자주 바뀌는 책임이 보일 때 컴포넌트로 모읍니다.

제가 혼자 관리하는 장기 예제 프로젝트에서는 그 영역에 변경이 많이 올 것이라는 판단 때문에 경계를 조금 일찍 만들 수 있습니다. 맥락에 따른 예상이지 일반 법칙이 아닙니다. 수명과 변경 양상이 다른 팀은 다른 선택을 해야 합니다.

한번 범위를 정했다면 통일성은 필요합니다. 한 모듈은 언제나 리더와 라이터를 쓰는데 옆 클래스는 모든 계층을 바로 가로지르면 다음 유지보수자가 구조를 예상할 수 없습니다. 팀과 느슨한 규칙을 논의하고 모듈이나 프로젝트 단위로 적용합니다. 제 취향에 맞지 않는 관습이라도 일관되고 문서화되어 있으면 여러 개인 스타일이 섞인 코드보다 관리하기 쉽습니다.

새로 입사한 사람이 개인 취향에 맞춰 전체 구조를 다시 만드는 것도 좋은 접근은 아닙니다. 기존 흐름을 이해한 뒤 이득을 보여줄 수 있는 작은 변경부터 시작해야 합니다.

수명이 설계의 양을 정한다

가장 분명한 반례는 일주일만 사용하고 버릴 서비스입니다. 그런 경우에는 로직을 컨트롤러에 바로 두는 선택도 합리적입니다. 일주일짜리 프로그램에 몇 년의 유지보수를 위한 계층을 만들면 시간을 낭비합니다.

고객이 늘고 있고 서비스가 회사에 지속적인 가치를 전달한다면 계산이 달라집니다. 개발자는 떠나고 저장소와 연동 방식은 바뀌며 새 규칙이 기존 흐름에 들어옵니다. 작은 구조가 한 번의 변경이 건드리는 범위를 줄이고 한 사람의 기억에 인수인계를 의존하지 않게 할 수 있습니다.

제가 말하려는 구분은 “리포지토리는 나쁘고 리더는 좋다”가 아닙니다. 사람들이 비즈니스를 바꾸는 계층에서 코드가 비즈니스를 표현하느냐입니다. 구현 책임에 안정된 자리를 주고 흐름을 선명하게 만든다면 리더나 라이터를 사용합니다. 다시 필요할 이유가 없는 호출에 이름만 하나 더 붙인다면 생략합니다.

소프트웨어의 예상 수명까지 설계 조건에 넣어야 합니다. 이 조건을 무시한 규칙은 유용한 컴포넌트를 의식적인 절차로 바꿉니다.