예약 시스템 리팩토링: 방 도메인 설계와 프로세스 개선
들어가며
담당하는 도메인 중 가장 복잡도가 높은 예약 도메인을 2개월에 걸쳐 개선했다. 이번 글에서는 어떤 문제를 해결하고 어떤 방식으로 개선했는지, 그리고 그 과정에서 무엇을 배웠는지를 정리하였다.
1. 프로젝트 배경
기간: 2025.02.17 ~ 2025.04.17 (2개월)
목적:
기존 모비닥의 예약 구조는 예약을 처리하는 사람’(의사/직원)에게 강하게 결합되어 있어 아래와 같은 문제들이 발생했다
- 특정 의사가 갑작스럽게 진료를 못 하게 되면, 모든 예약을 다른 의사에게 하나하나 수작업으로 옮겨야 했다.
- 도수치료, 물리치료와 같은 리소스는 실제 공간에 예약이 필요함에도, 이름만 다른 가짜 직원 계정을 만들어 임시로 예약을 처리했다.
- 충격파, 울쎄라 같은 의료 기기에도 예약이 필요하지만, 기존 구조에서는 이를 자연스럽게 표현하기 어려웠다.
- 예약 처리자가 의사와 직원이 함께 있을 경우엔 누구의 예약으로 처리할지 우선순위를 따로 관리해야 했고, 우선순위 로직이 누락되면 쉽게 버그가 발생했다.
이런 문제를 해결하기 위해‘방(Room)’이라는 도메인을 도입하였다. 이제 의사나 직원이 아니라 진료실이나 치료실과 같은 공간을 기준으로 예약을 하게 되었고 방에서 예약을 처리하는 담당자를 추가하거나 변경할 수 있게 되었다. (없어도 된다!)
범위:
환자 앱과 병원 클라이언트에서 사용하는 예약 기능 전반을 개선하는 작업을 진행했다.
작업 범위가 넓어 아래와 같이 두 단계로 나누어 진행했다:
- 방 도메인 설계 및 방 관리 기능 배포
- 기존 예약 구조의 한계를 해결하기 위해, 진료실이나 치료실 같은 공간 개념인 ‘방(Room)’ 모델을 새로 설계했다.
- 방을 등록·수정·조회할 수 있는 관리 기능을 구현하고 배포했다.
- 기존 예약 및 일정 데이터 마이그레이션 & 비즈니스 로직 개선
- 예약 데이터를 방 중심 구조로 재설계했다.
- 기존 예약 및 병원 일정 데이터를 새로운 방과 연결하고, 예약과 관련된 생성, 검증, 조회 등 비즈니스 로직도 전면 수정했다.
2. 도전과제
복잡한 예약 슬롯 로직 개선
가장 큰 문제는 예약 가능한 시간 슬롯을 계산하고 검증하는 로직의 복잡도였다. 중복된 코드를 줄이기 위해 템플릿 메서드 패턴으로 리팩토링했지만, 복잡도는 여전히 높았다.
가장 큰 원인은 병원 앱과 환자 앱의 예약 처리 방식이 서로 다르고, 각 사이드에서 사용하는 단위 시간이나 조회 조건도 조금씩 달랐기 때문이다. 이 차이를 모두 분기문으로 처리하다 보니 조건문이 점점 많아졌고, 결국 코드 전체가 얽히기 시작했다. 이런 코드는 이해하기도 어렵고, 수정하거나 디버깅하는 데에도 시간이 많이 들었다.
최근엔 “의사가 1명인 병원에서 직원이 예약을 등록하자 의사의 시간까지 막히는” 특수한 버그 케이스도 발생했다. 이런 경험을 통해 단순한 리팩토링으로는 유지보수가 더 이상 어렵다는 판단을 내렸고, 방 도메인 도입과 함께 구조적인 개선을 결심하게 되었다.
전략 패턴 도입
코드를 복잡하게 만들던 또 다른 원인은 템플릿 메서드 패턴 내부에서 하위 구성 요소가 상위 메서드를 직접 호출하는 꼬인 구조 때문이었다. 이처럼 의존성이 뒤섞인 코드는 가독성이 떨어지고, 디버깅을 어렵게 만든다.
이를 해결하기 위해 기존 템플릿 메서드 패턴을 전략 패턴으로 전환하면서 각 예약 슬롯 계산 로직을 전략 단위로 명확하게 분리하고, 상위 모듈이 하위 구현에 의존하지 않도록 했다.(할리우드 원칙을 지켰다!)
책임 분리
예약 슬롯 객체 하나에 시간 생성, 초기화, 계산 책임이 모두 몰려 있었다. 이 때문에 객체는 비대해졌고, 테스트도 어려웠다. 슬롯 계산 책임은 TimeGenerator로 분리하고, 각 기능을 의미 단위로 나누어 응집도를 높였다.
테스트 코드 개선
기존 테스트는 중복이 많고, 어떤 케이스를 검증하는지도 명확하지 않았다. 이에 따라 도메인 기준으로 테스트를 다시 설계하고, @Nested와 @DisplayName을 적극 활용해 테스트 코드를 이해하기 쉽게 개선하였다.
불필요한 코드 제거
과거에는 병원 쪽 ‘의사 미지정’ 예약 슬롯 계산 로직이 존재했지만, 지금은 더 이상 사용되지 않았다. 이런 불필요한 코드를 정리하면서, 쓸모없는 코드를 이해하느라 낭비되는 시간을 줄일 수 있었다.
결과적으로 아래와 같은 구조로 개선되었다.
새로운 방 도메인 설계
이번 프로젝트의 핵심은 ‘방(Room)’이라는 새로운 개념을 정의하는 것이었다. 기존에는 예약이 예약 처리자(의사/직원)와 강하게 연결되어 있었지만, 이제는 진료실이나 치료실처럼 잘 변하지 않는 방이 예약의 중심이 되었다.
1. TDD 기반 설계
새로운 도메인을 설계할 때는 항상 “뭐부터 해야 하지?“라는 고민이 든다. 이번에도 마찬가지였다. 그래서 요구사항 중 가장 단순한 방의 이름부터 테스트 코드를 먼저 작성해보기로 했다. TDD로 시작하면 개발 방향이 훨씬 명확해진다. 기능 구현 전에 먼저 테스트를 작성하다 보니, 자연스럽게 테스트하기 쉬운 구조가 되고 응집력이 높으며 객체 간 결합도가 낮아졌다. 덕분에 변경이 생겨도 다른 코드에 미치는 영향을 최소화할 수 있었다.
2. 방 타입 추상화 위치
방의 호스트 타입에 따라 사용하는 시간표가 다르다. 예를 들어 호스트가 의사라면 방은 의사의 시간표를 따르고, 방의 호스트가 없으면 병원의 시간표를 따른다. 방의 호스트에 따른 시간표 책임은 서비스 레이어가 아닌 레포지토리 레이어에 두었다. 그 결과 서비스 레이어에서는 “방의 타입”을 신경 쓰지 않고 스케줄과 함께 조회하는 것에 집중할 수 있었다. 이와 같이 서비스는 “흐름”만 다루도록 하여 최대한 얇은(Thin) 서비스 레이어를 유지하고, 책임을 도메인 객체와 레포지토리에 위임하였다. 덕분에 별도의 서비스 테스트가 없어도 충분히 안정성을 확인할 수 있었다.
3. ‘의사 미지정 방’ 도입
환자 앱에서는 “담당 의사 몰라요”를 통해 예약하는 경우가 많다. 기존에는 예약과 연결된 의사나 직원이 없는지를 모두 확인하여 처리하였기 때문에 코드의 복잡도가 높았다. 이번 방 개념을 도입하면서 병원마다 “의사 미지정”이라는 내부에서 사용하는 방을 만들어 사용했다. “의사 미지정 방”이라는 타입을 통해 예약이 어떤 사람과도 연결되지 않은 상태임을 쉽게 알 수 있게 되어 코드의 복잡도는 낮아지고, 가독성은 높아졌다.
3. Keep – 잘한 점
1. 코드 리뷰를 적극적으로 요청하고 반영했다
방 도메인 설계를 처음 시작할 때, 코드 리뷰를 통해 구조를 검증받으려 노력했다. 특히 기존에는 “방” 자체를 추상화하다 보니 방 선택 전략 선택이 필요했었는데, 리뷰를 거치며 방의 호스트(의사/직원)를 추상화하는 구조로 변경했고, 그 결과 레포지토리 구현이 훨씬 간결해졌다.
기존 구조 도메인 관계
기존 구조의 레포지토리
to-be 도식화 필요
2. 복잡한 로직에 대한 개선을 끝까지 밀고 갔다
예약 슬롯 계산 로직은 기존에도 워낙 복잡했기 때문에, 변경이 부담스러운 부분이었다. 이번에는 이 복잡함을 무시하지 않고, 구조적인 문제부터 뜯어고치며 끝까지 개선을 시도했다. 덕분에 지금은 디버깅도 훨씬 빨라지고, 새로운 요구사항도 쉽게 적용할 수 있는 상태가 되었다.
3. 성능 병목을 지속적으로 모니터링하고 개선했다
작업 중간중간, 성능에 영향을 주는 지점을 꾸준히 점검하고 개선했다. 특히 아래 세 가지가 가장 큰 개선 효과를 줬다:
- 불필요한 데이터 조회 제거
- 예약 현황판에서 사용하지 않는 파일, 증상 템플릿, 시간표 데이터가 함께 조회되고 있었는데, 이 부분을 제거했다.
- 평균 응답 시간이 50~60ms → 20~30ms로 줄었다.
- 예약 불가일 조회 개선
- 기존에는 6개월치 예약 불가일을 처리하면서 수천 개 객체를 메모리에 올리고 있었다.
- 이를 DB 쿼리에서 처리하도록 개선하면서
- 메모리 사용량은 20MB → 1MB, 처리 시간은 평균 130ms → 40~50ms로 줄었다.
- 예약 현황판 예약 쿼리 인덱스 추가 및 순서 조정
- 예약 현황판의 예약 쿼리에 인덱스를 추가하고, 인덱스 컬럼 순서를 조정했다.
- 기존 8~150ms까지 편차가 컸던 쿼리 수행 시간이 2ms 수준으로 안정화되었다.
이렇게 하나씩 개선하다 보니, 전체적으로 꽤 많은 성능 향상을 이뤄낼 수 있었다.
4. 빠르게 테스트할 수 있도록 자주 배포했다.
작업한 내용은 dev 서버에 바로 반영해서 빠르게 테스트할 수 있도록 했다. 덕분에 작은 버그도 빠르게 고쳐나가 스테이징에서 테스트 시 생각보다 많은 버그가 발생하지 않았다. 프론트엔드 개발자와의 협업에서도 큰 장점이 있었다. 서버 수정 사항을 바로 확인할 수 있어, 서로의 작업을 기다리느라 생기는 병목 없이 빠르게 개발을 이어갈 수 있었다.
4. Problem – 아쉬웠던 점
1. 프론트엔드 개발자와의 소통
이번 작업은 병원 클라이언트를 담당하는 프론트엔드 개발자뿐만 아니라 환자 앱을 담당하는 프론트엔드 개발자와도 함께 진행해야 했다. 환자 앱 사이드는 화면 자체는 변경이 없기 때문에 프론트엔드 개발자와 작업 범위 계산을 덜 면밀하게 계산 했다. 이로 인해 작업 중간에 PM과 개발자 사이에 예상하지 못한 미스 싱크가 발생했고 그로 인해 일부 기능을 다시 개발했다. 동료들에게 어떤 API가 영향을 받고, 어떤 데이터 흐름이 바뀌는지 미리 정리해 공유했더라면 이런 미스 싱크를 최소화 하면서 더 원활하게 협업할 수 있었을 것이다.
다음에는 프론트엔드 개발자와 작업 전에 더 충분히 이야기를 나누어 서로의 싱크를 맞추고, 앞으로 백엔드 변경 사항은 Swagger 문서를 통해 명확히 공유하는 방식으로 소통을 개선해보고자 한다.
2. 일정 지연
이번 프로젝트는 4월 초 배포를 목표로 했지만, 실제 배포는 1주일이 밀린 4월 중순에야 이뤄졌다. 내가 생각하는 프로젝트 지연의 원인은 개발 도중에 갑작스럽게 변경된 요구사항들과 충분하지 못했던 테스트 기간 때문이라 생각한다. 추가로 프론트엔드 개발자의 작업 속도도 고려하여 배포 가능 일정을 공유 했어야 했다.
아무튼 일정을 연기한 덕분에 스테이징 테스트를 더 오래 진행할 수는 있었지만, 워낙 작업 범위가 큰 프로젝트였기 때문에 배포 후 예상치 못한 부분에서 버그 제보가 들어왔다. “왜 일정을 늘려 테스트를 했는데도 버그를 잡지 못했을까?” 라는 주제로 PM분과 이야기를 나누었다. 결론은 소프트웨어에서 발생하는 모든 버그를 잡아 낼 수 없으니 완벽하게 테스트를 못 했다고 자책하지 말고, 빠르게 수정하여 사용자의 신뢰도를 높이는 방향으로 나아가는 것을 목표로 삼자는 것이였다.
5. Try – 다음에 시도할 것
1. 협업 커뮤니케이션 방식 개선
이번 프로젝트를 통해, 시작 단계에서 동료들과 충분히 대화를 나누지 않으면 전체 일정에 영향을 줄 수 있다는 걸 실감했다.
돌아오는 목요일에는 프로젝트 회고를 통해 각자의 생각과 잘한 점, 아쉬웠던 점을 공유하며 협업의 싱크를 맞춰볼 예정이다.
또한 평소 커뮤니케이션 방식을 개선해보고자 한다.
앞으로는 내가 누군가에게 요청하거나 질문할 때, 다음과 같은 구조를 활용하려고 한다:
- 핵심 요청이 무엇인지 먼저 전달하고
- 추가 상황이나 요구사항을 덧붙여 이해를 돕고
- 요청의 배경과 목적을 설명한다
이 방식은 예전에 본 AB180 인턴 후기에서 참고한 것이다.
상대방이 요청의 핵심을 빠르게 이해할 수 있도록, 나부터 정리된 커뮤니케이션을 시도해보려고 한다.
반대로 프론트엔드 개발자가 백엔드 관련 이슈를 문의하는 경우도 잦은데, 이때는 다음과 같은 구조를 통해 소통을 좀 더 원활하게 만들고자 한다:
- Swagger 명세 제공
- 질문 템플릿 제공
- API URL
- 기대한 응답값
- 실제 응답값
서로 빠르고 정확하게 소통할 수 있도록 커뮤니케이션 방식을 개선해나갈 예정이다.
2. 캐시 기반 성능 개선
모비닥 서비스는 트래픽이 적어 본격적으로 캐시를 적용하고 있진 않지만, 앞으로를 대비해 캐싱하기 쉬운 구조를 만들어보려고 한다. 캐싱이 쉬운 구조는 보통 조회와 변경 책임이 분리된 구조(CQRS)에서 출발한다. 특히 조회 전용 서비스에서는 도메인 객체가 아니라 필요한 필드만 담은 DTO를 따로 만들어 캐싱하는 방식이 안정적이다.
지금 코드에서는 서비스 계층에서 도메인 객체를 직접 반환하고 있기 때문에 직렬화 이슈나 불필요한 필드로 인해 캐싱 적용이 까다로운 편이다. 향후 캐시를 도입할 수 있도록 조회용 DTO를 도입하고 조회 로직과 변경 로직을 분리하는 구조로 점진적으로 개선해 나갈 예정이다.
6. 배운 점 & 느낀 점
1. 복잡하다고 피했던 코드, 개선하고 나니 더 자신감이 생겼다
그동안 예약 현황판 로직은 복잡해서 손대기 꺼려지는 영역이었다.
하지만 이번에 구조를 개선하면서 오히려 다양한 요구사항을 더 붙여보고 싶다는 생각이 들었다.
어렵게만 느껴졌던 코드가 이제는 내가 주도적으로 다룰 수 있는 영역이 되었다.
2. 과거의 코드와 비교하며 성장을 체감했다
반년 전 내가 짠 코드를 다시 들여다보니, 그땐 왜 그게 최선이라고 생각했는지 돌아보게 됐다.
넥스트스텝 “자바 클린코드” 과정을 통해 객체지향 설계와 테스트의 중요성을 몸으로 익힌 덕분에,
이번에는 책임과 역할을 기준으로 구조를 다시 짤 수 있었고, 그 결과 버그도 줄고 코드 이해도도 올라갔다.
3. 비즈니스 도메인 지식이 누적되었다
이번 프로젝트뿐만 아니라 환자 로그인 모듈화, 진료 종료 동시성 처리, 예약 슬롯 무한 루프 트러블슈팅 등
다양한 문제를 직접 해결하면서 의료 도메인에 대한 이해도가 많이 높아졌다는 것을 체감했다.
4. 도식화를 통해 복잡함의 본질을 파악할 수 있었다
회고를 위해 예약 슬롯 로직의 개선 전/후 구조를 직접 도식화해보니,
무엇이 복잡했고 왜 그런 구조가 문제가 되었는지를 명확히 이해할 수 있었다.
복잡한 프로세스는 시각적으로 정리해보는 게 효과적이라는 것을 많이 느꼈다.