BindProject

스튜디오 예약 시스템의 설계와 구현 : 유연한 운영 시간 설계와 MSA의 시너지

dding-shark 2025. 7. 27. 14:26
728x90

스튜디오 예약 시스템의 핵심: 유연한 운영 시간 설계와 MSA의 시너지

현제 개발 중인 바인드 프로젝트에서 핵심적인 기능 중 하나인 '운영 시간 관리' 로직을 어떻게 설계하고 구현했는지, 그리고 MSA(Microservices Architecture) 구조가 이 설계에 어떤 강력한 유연성을 더해주는지 상세하게 작성 하겠습니다.

 

 

 

 


 

 

 

1. 문제의 시작: 스튜디오 운영 시간은 생각보다 복잡하다

예약 시스템을 만든다고 할 때, 가장 먼저 떠오르는 질문은 "이 시간에 예약이 가능한가?"일 것입니다. 간단해 보이지만, 현실 세계의 운영 정책은 매우 복잡합니다.

  • 평일과 주말의 운영 시간이 다르다.
  • 특정 요일은 정기 휴무일이다.
  • 운영 시간, 시간대 마다 가격이 다르다.
  • 명절이나 국경일에는 문을 닫는다.
  • 시설 보수나 개인 사정으로 특정 날짜에만 임시 휴업을 할 수 있다.

단순히 스튜디오 정보에 startTime, endTime 필드 몇 개를 추가하는 것만으로는 이러한 다이나믹한 요구사항을 감당할 수 없습니다. 사용자는 정확한 정보를 원하고, 스튜디오 운영자는 자신의 운영 정책을 시스템에 쉽고 유연하게 반영할 수 있어야 합니다.

저희는 이 문제를 해결하기 위해 두 가지 핵심 개념을 도입했습니다.

  1. 정기 운영 스케줄 (Operation Schedule): 요일별로 반복되는 일반적인 운영 시간 (예: 매주 월요일 09:00 ~ 22:00)
  2. 임시 휴일 (Temporary Holiday): 정기 운영일이지만 특정 날짜에 예외적으로 운영하지 않는 날 (예: 2025-08-15 광복절)

이 두 가지를 조합하여 아무리 복잡한 운영 정책이라도 시스템에 간단히 반영하고 정확하게 가용성을 판단할 수 있도록 설계했습니다.

 

 

 

 

 


 

 

 

 

2. 설계의 중심: 데이터 모델과 엔티티

문제 해결의 실마리는 데이터 모델링에 있습니다. Studio 엔티티를 중심으로 OperationScheduleTemporaryHoliday가 각각 1:N 관계를 맺고, 실제 '예약' 정보를 담는 Booking 엔티티가 별도로 존재하는 구조입니다.

특히 저희는 MSA 구조를 채택하여 각 도메인의 책임과 데이터를 명확히 분리했습니다.

erDiagram
    subgraph "운영시간 서비스 (Operation Hour Service)"
        Studio ||--o{ OperationSchedule : "has"
        Studio ||--o{ TemporaryHoliday : "has"
    end

    subgraph "예약 서비스 (Booking Service)"
        Studio ||--o{ Booking : "has"
        User ||--o{ Booking : "books"
    end

    Studio {
        Long studioId PK
        String name
        (기타 스튜디오 정보)
    }

    OperationSchedule {
        Long scheduleId PK
        Long studioId FK
        DayOfWeek dayOfWeek "요일"
        LocalTime startTime "시작 시간"
        LocalTime endTime "종료 시간"
    }

    TemporaryHoliday {
        Long holidayId PK
        Long studioId FK
        LocalDate holidayDate "휴일 날짜"
        String description "휴일 설명"
    }

    Booking {
        Long bookingId PK
        Long studioId FK
        Long userId FK
        LocalDateTime startTime "예약 시작 시간"
        LocalDateTime endTime "예약 종료 시간"
        String status "예약 상태 (CONFIRMED 등)"
    }

이러한 설계를 바탕으로 다음과 같이 JPA 엔티티를 구현했습니다.

OperationSchedule.java

// package operationhour.entity;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import studio.entity.Studio;

import java.time.DayOfWeek;
import java.time.LocalTime;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OperationSchedule {

    @Id @GeneratedValue
    @Column(name = "schedule_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "studio_id")
    private Studio studio;

    @Enumerated(EnumType.STRING)
    private DayOfWeek dayOfWeek;

    private LocalTime startTime;
    private LocalTime endTime;
}

TemporaryHoliday.java

// package operationhour.entity;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import studio.entity.Studio;

import java.time.LocalDate;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TemporaryHoliday {

    @Id @GeneratedValue
    @Column(name = "holiday_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "studio_id")
    private Studio studio;

    private LocalDate holidayDate;
    private String description;
}

 

 

 

 

 

 


 

 

 

 

 

3. 실시간 예약 가능 시간 조회: 3단계 필터링 로직

사용자가 특정 날짜의 예약 가능한 시간을 조회할 때, 시스템은 단순히 '운영 시간'만 알려주는 것이 아니라 '이미 예약된 시간'을 제외한 '진짜 예약 가능한 시간' 목록을 제공해야 합니다. 이는 다음과 같은 3단계 필터링 로직으로 구현됩니다.

graph TD
    A[Start: 사용자가 특정 날짜의<br>예약 가능 시간 조회 요청] --> B{1. 임시 휴일 확인};

    B --▶|휴일 O| F[X 예약 가능 시간 없음];
    B --▶|휴일 X| C{2. 정기 운영 스케줄 확인};

    C --> D(해당 날짜의 전체<br><b>운영 가능 시간대</b> 목록 생성);

    D --> E{3. <b>확정된 예약(Booking)</b><br>시간대 조회};

    E --> G[운영 가능 시간대 - 확정된 예약 시간대];

    G --> H[V 최종 <b>예약 가능 시간</b> 목록 반환];

    subgraph "데이터베이스 / 서비스 조회"
        B -->|운영시간 서비스| Holiday_DB[TemporaryHoliday 테이블];
        C -->|운영시간 서비스| Schedule_DB[OperationSchedule 테이블];
        E -->|예약 서비스| Booking_DB[Booking 테이블];
    end

1단계: 운영 여부 판단 (임시 휴일 → 정기 스케줄)

  • 먼저 해당 날짜가 임시 휴일인지 체크합니다. 휴일이라면 즉시 빈 목록을 반환합니다.
  • 휴일이 아니라면 정기 운영 스케줄을 가져와 그날의 전체 '운영 가능 시간대'를 계산합니다. (예: 09:00, 09:30, 10:00, ..., 21:30)

 

 

 

2단계: 예약된 시간 조회
- Booking 테이블(또는 예약 서비스)에서 해당 날짜에 이미 '확정(CONFIRMED)'된 예약 목록을 모두 가져옵니다.

 

(예:

14:00

16:00, 19:00

20:00)

 

 

 

3단계: 차집합 계산
- 1단계에서 구한 '운영 가능 시간대' 전체 목록에서 2단계의 '예약된 시간대'를 제외합니다. 이 결과가 바로 사용자가 실시간으로 예약할 수 있는 최종 시간 목록입니다.

 

 

 

 

 

 


 

 

 

4. MSA가 가져다주는 유연함: 확정된 예약과 독립적인 운영 시간 변경

"만약 이미 다음 주 예약이 꽉 차 있는데, 사장님이 갑자기 다음 주부터 운영 시간을 바꾸고 싶으면 어떻게 하죠?"

바로 이 지점에서 MSA 구조의 진정한 이점이 드러납니다. 저희 시스템은 운영시간 서비스예약 서비스가 명확히 분리되어 있습니다.

  • 운영시간 서비스: 스튜디오의 미래 운영 정책(OperationSchedule, TemporaryHoliday)을 관리합니다. "앞으로 우리 가게는 이렇게 운영될 것이다"라는 계획을 담당합니다.
  • 예약 서비스: 사용자와 스튜디오 간의 계약, 즉 Booking 정보를 관리합니다. 이는 과거에 확정된 사실의 기록입니다.

이러한 분리 덕분에, 운영자는 이미 확정된 예약의 존재 유무와 상관없이 언제든지 미래의 운영 시간을 자유롭게 변경할 수 있습니다.

예를 들어, 다음 주 월요일 19시-21시 예약이 이미 확정된 상태에서 운영자가 다음 주부터 저녁 운영을 18시까지만 하도록 OperationSchedule을 변경했다고 가정해 봅시다.

  • 시스템 동작: 운영시간 서비스의 스케줄 정보만 변경됩니다. 예약 서비스의 '월요일 19시 예약' 데이터는 전혀 영향을 받지 않습니다.
  • 운영자 측면: 복잡한 조건 확인 없이 즉시 미래의 운영 계획을 바꿀 수 있습니다. 시스템이 "이미 예약이 있어서 변경할 수 없어요"라며 운영자를 막지 않습니다.
  • 사용자 측면: 이미 확정된 나의 예약은 안전하게 유지됩니다. 만약 운영자가 변경된 정책으로 인해 기존 예약을 취소해야 한다면, 이는 시스템의 자동 변경이 아닌 별도의 커뮤니케이션(전화, 알림 등)을 통해 처리되어야 하는 비즈니스 정책의 영역입니다.

이처럼 각 서비스가 자신의 책임과 데이터를 명확하게 분리하여 소유함으로써, 시스템은 유연성과 안정성을 동시에 확보하게 됩니다. 저희는 서비스 간의 통신을 위해 Kafka와 같은 메시지 큐를 활용한 이벤트 기반 아키텍처를 적극적으로 사용합니다. KafkaEventSerializerTest에서 볼 수 있듯이, 각 도메인 이벤트는 정해진 형식으로 직렬화되어 다른 서비스에 전달되며, 이를 통해 서비스 간의 결합도를 낮추고 독립적인 배포와 확장을 가능하게 합니다.

 

 

 

 

 

 


 

 

 

5. 결론: 유연한 설계와 아키텍처의 시너지

단순한 운영 시간 관리를 넘어, 정기/예외를 분리한 데이터 모델링, 실시간 가용성 체크 로직, 그리고 도메인별 책임을 분리한 MSA 구조를 결합함으로써 저희는 운영자와 사용자 모두에게 편리하고 안정적인 예약 경험을 제공하는 시스템을 구축할 수 있었습니다. 이 견고한 기반 위에서 앞으로는 시간대별 차등 요금제나 특별 할인 이벤트 같은 더욱 고도화된 기능들을 유연하게 확장해 나갈 계획입니다.

긴 글 읽어주셔서 감사합니다.

728x90