예약 시스템 리팩토링: 방 도메인 설계와 프로세스 개선
들어가며
모비닥 예약 시스템에 두 가지 문제가 있었다. 예약이 사람(의사/직원)에 묶여 공간 기반 예약이 불가능했고, 슬롯 계산 로직이 복잡해 버그 하나 잡는 데 반나절이 걸렸다.
이 문제를 해결하기 위해 ‘방(Room)’ 도메인을 도입하고, 예약 로직을 전략 패턴으로 개선했다. 이번 글에서는 이 문제를 해결했던 과정과 그 과정 속에서 잘했던 점과 개선할 점을 다루었다.
1. 문제: 예약이 사람에 묶여 있었다
기존 예약 구조는 ‘예약을 처리하는 사람’에게 강하게 결합되어 있었다.
의사가 갑자기 진료를 못 하면, 그 의사의 예약을 다른 의사에게 하나하나 옮겨야 했다. 도수치료실이나 물리치료실처럼 공간 자체에 예약이 필요한 경우엔, 가짜 직원 계정을 만들어 임시로 처리했다. 충격파나 울쎄라 같은 의료 기기도 마찬가지였다. 사람에게 강하게 의존되어 있다 보니, 의사와 직원이 함께 있을 때 우선순위를 판단해야 하고, 여기서 버그가 발생하기도 했다.
이 문제를 해결하기 위해 진료 공간을 뜻하는 ‘방(Room)’이라는 도메인을 만들었다. 예약의 중심을 사람에서 추상화된 진료 공간으로 바꾼 것이다. 이제 진료실·치료실 같은 공간을 기준으로 예약하고, 방에 담당자를 추가/수정하거나 지정하지 않고도 예약을 생성할 수 있게 되었다.
작업 범위가 넓어 두 단계로 나눴다. 먼저 방 도메인을 설계하고 관리 기능을 배포한 뒤, 기존 예약/일정 데이터를 마이그레이션하고 비즈니스 로직을 개선했다.
2. 해결 과정
방 도메인 설계
방을 설계하면서 고민했던 세 가지를 정리했다.
어디서부터 시작할까
새로운 도메인을 설계할 때는 항상 “뭐부터 해야 하지?”라는 고민이 든다. 이번에도 마찬가지였다. 그래서 요구사항 중 가장 단순한 방의 이름부터 테스트 코드를 먼저 작성해보기로 했다.
TDD로 시작하니 개발 방향이 명확해졌다. 테스트를 먼저 작성하다 보니 자연스럽게 테스트하기 쉬운 구조가 됐고, 변경이 생겨도 다른 코드에 미치는 영향을 최소화할 수 있었다.
추상화는 어디에 둘까
방의 호스트 타입에 따라 사용하는 시간표가 다르다. 호스트가 의사라면 의사의 시간표를, 호스트가 없으면 병원의 시간표를 따른다.
이 책임을 서비스가 아닌 레포지토리에 두었다. 서비스는 “방의 타입”을 신경 쓰지 않고 스케줄 조회에만 집중한다. 서비스는 흐름만 다루고, 책임은 도메인 객체와 레포지토리에 위임하는 구조다. 덕분에 별도의 서비스 테스트 없이도 안정성을 확인할 수 있었다.
담당자 없는 예약은 어떻게
환자 앱에서는 “담당 의사 몰라요”를 통해 예약하는 경우가 많다. 기존에는 예약과 연결된 의사나 직원이 없는지를 모두 확인하여 처리하였기 때문에 코드의 복잡도가 높았다.
이번 방 개념을 도입하면서 병원마다 “의사 미지정”이라는 내부에서 사용하는 방을 만들어 사용했다. “의사 미지정 방”이라는 타입을 통해 예약이 어떤 사람과도 연결되지 않은 상태임을 쉽게 알 수 있게 되어 코드의 복잡도는 낮아지고, 가독성은 높아졌다.
예약 슬롯 개선
예약 시스템 개발의 가장 큰 허들은 예약 가능한 시간 슬롯을 계산하고 검증하는 로직의 복잡도였다.
병원 클라이언트와 환자 앱은 예약 슬롯 단위, 조건이 모두 달랐다. 이 차이를 분기문으로 처리하다 보니 조건문이 늘어나면서 코드가 얽히기 시작했다. 중복을 줄이려고 템플릿 메서드 패턴을 적용했지만, 하위 구성 요소가 상위 메서드를 호출하는 꼬인 구조가 되어 복잡도는 여전히 높았다.
최근엔 “의사가 1명인 병원에서 직원이 예약을 등록하자 의사의 시간까지 막히는” 버그도 발생했다. 이런 증상은 리팩토링의 신호라고 판단하고, 구조 개선을 결심했다.
전략 패턴 도입
기존 템플릿 메서드 패턴을 전략 패턴으로 전환했다.
- 의존성 단방향화: 클라이언트는 전략 인터페이스만 참조하고, 전략을 실행만 한다.
- 상속 → 합성: 템플릿 메서드는 상속 기반이라 테스트 시 부모 클래스까지 고려해야 했다. 전략 패턴은 합성이라 전략 객체만 독립적으로 테스트할 수 있다.
- 확장 용이: 새 슬롯 계산 로직은 전략 클래스만 추가하면 된다.
책임 분리
예약 슬롯 객체 하나에 시간 생성, 초기화, 계산 책임이 모두 몰려 있었다. 이 때문에 객체는 비대해졌고, 테스트도 어려웠다. 슬롯 계산 책임은 TimeGenerator로 분리하고, 각 기능을 의미 단위로 나누어 응집도를 높였다.
테스트 코드 개선
기존 테스트는 중복이 많고, 어떤 케이스를 검증하는지도 명확하지 않았다. 이에 따라 도메인 기준으로 테스트를 다시 설계하고, @Nested와 @DisplayName을 적극 활용해 테스트 코드를 이해하기 쉽게 개선하였다.
개선된 구조는 아래와 같다.
3. 잘한 점
1. 문제를 주도적으로 해결했다
영향 범위가 넓어 개발 일정을 2단계로 나눴다. 이해관계자분들과 미팅하며 ‘방’이라는 도메인을 정의하고 코드로 녹여냈다. 프론트엔드 개발자에게도 API 변경 사항과 영향 범위를 미리 공유했다.
협업 병목을 줄이기 위해 잘 사용하지 않던 dev 서버를 적극 활용했다. 작업 내용을 바로 반영해 프론트엔드 개발자가 대기 없이 확인할 수 있도록 했다.
예약 슬롯 계산처럼 복잡한 영역도 피하지 않고 구조적으로 개선했다. 진료의 핵심 기능이라 부담이 컸지만, 덕분에 지금은 이 영역을 주도적으로 다룰 수 있게 되었다.
2. 리뷰로 설계를 검증했다
새로운 진료 공간(방) 도메인을 설계해야 했다. TDD 기반으로 시작했는데, 생각보다 복잡한 도메인이 모델링되었다. 혼자 판단하기 어려워 타팀 개발자분께 코드 리뷰를 부탁드렸다.
처음 설계에서는 “방” 자체를 추상화했다. 그러다 보니 방 선택 전략이 필요해졌고, 구조가 복잡해졌다.
방의 호스트 타입에 따라 방 구현체가 만들어지면서 중복 코드가 많이 발생했다.
리뷰를 통해 “방”이 아닌 “호스트(의사/직원)”를 추상화하는 구조로 변경했다. 그 결과 레포지토리 구현이 훨씬 간결해졌다.
3. 성능 병목을 꾸준히 개선했다
작업 중간중간 성능에 영향을 주는 지점을 점검하고 개선했다.
| 개선 항목 | 내용 | 효과 |
|---|---|---|
| 불필요한 데이터 조회 제거 | 예약 현황판에서 미사용 데이터(파일, 증상 템플릿, 시간표) 제거 | 50~60ms → 20~30ms |
| 예약 불가일 조회 | 6개월치 객체를 메모리에서 DB 쿼리로 이동 | 메모리 20MB → 1MB, 130ms → 40~50ms |
| 인덱스 추가 | 예약 쿼리에 인덱스 추가 및 컬럼 순서 조정 | 8~150ms → 2ms |
4. 개선할 점
1. 협업 커뮤니케이션
환자 앱은 화면 변경이 없어서 영향 범위를 가볍게 봤다. 하지만 백엔드 API 응답이 바뀌면서 환자 앱 쪽에서 예상치 못한 오류가 발생했다. 프론트엔드 개발자와 미리 싱크를 맞추지 않아서 일부 기능을 다시 개발해야 했다. 중요하지 않은 범위는 없다는 걸 느꼈다.
앞으로는 작업 전 영향받는 API와 데이터 흐름을 미리 정리해 공유하려고 한다.
2. 일정 산정
배포 목표일보다 1주일 늦게 배포되었다. 중간에 변경된 요구사항과 프론트엔드 작업 속도를 충분히 고려하지 못했다.
일정이 늘어난 덕분에 테스트를 더 해볼 수 있었지만, 배포 후 예상치 못한 버그 리포트가 들어왔다. 테스트 일정이 길어졌는데도 왜 버그를 잡지 못했을까..
PM분과 이야기를 나눈 결과, 완벽한 테스트는 어려우니 빠르게 수정 가능한 구조를 지향하자는 결론이 나왔다. 앞으로는 수정이 용이한 개발도 함께 고려해야겠다.
끝으로
1. 성장을 체감했다
반년 전 내가 짠 코드를 다시 들여다보니, 그땐 왜 그게 최선이라고 생각했는지 돌아보게 됐다. 넥스트스텝 “자바 클린코드” 과정을 통해 객체지향 설계와 테스트의 중요성을 익힌 덕분에, 이번에는 책임과 역할을 기준으로 구조를 다시 짤 수 있었다.
2. 자신감이 생겼다
그동안 예약 현황판 로직은 복잡해서 손대기 꺼려지는 영역이었다. 하지만 이번에 구조를 개선하면서 오히려 다양한 요구사항을 더 붙여보고 싶다는 생각이 들었다. 어렵게만 느껴졌던 코드가 이제는 주도적으로 다룰 수 있는 영역이 되었다.





