검증, 판단시 - 쿼리 사용 vs 조회 후 서비스에서 판단 #41
Replies: 3 comments
-
|
저는 이 질문이 사이클1에서 깊게 다뤘던 비즈니스 규칙은 어디에 있어야 하는가와 정확히 같은 본질의 질문이라고 생각합니다. 쿼리로 판정하느냐 / 조회 후 Service에서 판정하느냐는 단순한 구현 선택이 아니라, 저는 "조회 후 도메인에서 판단"을 기본으로 두되, (1) 시스템 전체 상태에 의존하거나 (2) 동시성 보호가 필요한 규칙은 DB에 맡긴다를 기준으로 삼고 있습니다. 그래서 둘은 양자택일이 아니라, 같은 규칙을 도메인 + DB 제약의 조합으로 함께 지키는 경우가 많다고 생각합니다. 1. 질문의 본질 — 책임의 위치를 정하는 일표면적으로 이 질문은 "쿼리 한 번이 빠르냐, 조회 후 자바 로직이 명확하냐"의 트레이드오프로 보입니다. 하지만 사이클1 토론에서 정리했듯, 진짜 질문은 이거라고 생각합니다.
이건 단순 성능 비교가 아니라 책임 분배의 문제입니다. 그래서 매번 같은 답이 나올 수 없고, 규칙의 성격에 따라 달라져야 한다고 생각합니다. 2. 쿼리로 판정하는 방식의 강점과 약점// 쿼리에서 판정
boolean exists = reservationRepository
.existsByDateAndTimeIdAndThemeIdAndStatus(date, timeId, themeId, RESERVED);
if (exists) throw new BusinessRuleViolationException(...);강점
약점
3. 조회 후 Service/도메인에서 판정하는 방식의 강점과 약점// 조회 후 도메인에서 판정
List<Reservation> sameDate = reservationRepository.findByDate(date);
Reservations reservations = new Reservations(sameDate);
reservations.validateNotDuplicate(date, timeId, themeId);강점
약점
4. 제 판단 기준 — 규칙의 성격으로 갈라본다사이클1 토론에서 정리한 분류를 그대로 이어가면, 저는 세 갈래로 봅니다. Type 1: 입력값만으로 판정 가능 → 무조건 도메인
Type 2: 좁은 범위 상태에 의존 → 도메인 + DB 안전망
Type 3: 시스템 전체 상태에 의존 → DB에 위임
핵심: 이 분류의 기준은 "쿼리가 빠른가"가 아니라 의존 데이터의 범위와 크기입니다. 데이터가 작고 좁은 범위면 도메인이 다룰 수 있고, 크고 전역이면 DB가 더 잘합니다. 5. 질문에 주신 두 예시를 적용해보면예시 1. "사용자가 이미 해당 슬롯에 예약 또는 대기 중인가?"의존 데이터를 보면:
→ Type 2입니다. 도메인으로 빼서 검증하기 좋다고 봅니다. // 한 사용자의 예약·대기를 모아 도메인 객체로
MyReservationsAndWaitings my = reservationService.findMineWithWaitings(memberId);
my.validateNoConflict(slot);이렇게 하면 "내 예약·대기와 충돌이 무엇인가"라는 규칙이 도메인에 응집되고, 단위 테스트로 다양한 케이스(예약만 있을 때, 대기만 있을 때, 둘 다 있을 때, 취소된 것 제외 등)를 빠르게 검증할 수 있습니다. 다만 단순히 "있나 없나"만 묻는 거라면 규칙이 복잡해질 가능성이 있다면 도메인으로 빼고, 단순 boolean이고 변경 가능성이 적다면 쿼리로 두는 식입니다. 예시 2. "해당 슬롯에 이미 RESERVED 예약이 존재하는가?"이건 본질적으로 같은 형태지만 결이 다릅니다.
→ Type 2이긴 하지만 동시성 위험이 강한 케이스입니다. 저라면 이렇게 합니다.
// 1차: 도메인 검증
boolean alreadyReserved = reservationRepository
.findReservedBySlot(slot)
.isPresent();
if (alreadyReserved) throw new BusinessRuleViolationException(...);
// 2차: DB 제약 (schema.sql)
// UNIQUE(date, time_id, theme_id) WHERE status = 'RESERVED'1차만으로는 동시성에서 뚫리고, 2차만으로는 좋은 에러 메시지를 못 줍니다. 둘은 보완 관계입니다. 사이클1 토론에서 정리한 "어떤 규칙은 도메인 + DB 제약의 조합으로 함께 지켜야 한다"가 정확히 이 상황입니다. 6. 한 가지 더 — 이름의 신호사이클1 토론에서 정리한 통찰 중 하나가 이름이 책임의 위치를 드러낸다는 것이었습니다.
Repository 메서드 이름이 비즈니스 판정 언어에 가까워지면, 그건 책임이 잘못된 위치에 있다는 신호일 수 있습니다. Repository는 "찾아주는" 책임이고, "판정하는" 책임은 도메인입니다. // 책임이 모호한 패턴
reservationRepository.existsByDateAndTimeAndTheme(...) // 판정에 가까운 이름
// 책임이 명확한 패턴
reservationRepository.findReservedBySlot(slot) // 데이터 접근
reservations.hasDuplicate(slot) // 판정이건 두 방식 중 어느 쪽을 고르든 적용되는 원칙입니다. 쿼리로 판정하더라도 메서드 이름을 데이터 접근의 언어로 두고, 판정은 호출하는 쪽에서 명시적으로 하는 게 좋다고 생각합니다. 7. 정리 — 제 기준
제가 따르는 기준:
핵심 사고
질문에 주신 두 예시는 둘 다 Type 2 영역이라 도메인으로 빼는 게 좋다고 봅니다. 다만 "RESERVED 예약 중복"은 동시성 위험이 크므로 DB UNIQUE 제약을 반드시 함께 두고, "본인 예약·대기 중복"은 동시성보다 규칙 자체의 명확성이 더 중요하니 도메인 검증이 핵심이라고 생각합니다. |
Beta Was this translation helpful? Give feedback.
-
|
저는 "이 판정 로직을 나중에 수정, 확장할 때, 어디를 고치는 게 자연스럽고 테스트하기 쉬운가?" 를 고민해 결정하려고합니다. 이번 미션에서의 예시 2가지를 들어보겠습니다. 1. 예약 요청두 가지 검증이 필요합니다.
하지만 "대기자 10명이 넘으면 마감" 요구사항이 추가되는 순간, 쿼리 기반 로직은 변경이 광범위해집니다. 반면 처음부터 "내가 이미 예약한 슬롯인지", "다른 사람이 예약했는지"는 결국 특정 슬롯의 예약+대기 목록을 탐색하는 일입니다. 어차피 같은 범위를 보는 거라면, 쿼리를 여러 번 나눠 쓰는 것보다 한 번 조회 후 2. 예약 취소예약 취소 시 대기 1순위를
"누가 1순위인가"라는 판정은 비즈니스 규칙입니다. 지금은 단순 순번이지만, VIP·등급·예약 이력 등 기준이 얼마든지 복잡해질 수 있는 영역입니다. 그렇기 때문에 그 판정을 SQL에 두는 것보다 도메인에 두는 게 자연스럽습니다. 쿼리 사용이런 관점에서 닉네임 중복 검증과 같이 DB 전체를 확인해야한다면, 쿼리로 판단하는 게 좋겠죠. |
Beta Was this translation helpful? Give feedback.
-
|
앞서 두분이 말씀해주신 내용과 조금은 결이 다른 기준이라고 생각이 드는데요, 저는 유스케이스가 감춰지는가를 기준으로 결정할 것 같습니다. 쿼리에 작성하면, 유스케이스에 감춰지는 경우송송님이 남기신 코멘트를 읽어보니 예약이 존재하는 경우 예약 생성을 보내면 예외가 발생하는 것이 아닌, 자동으로 대기 상태가 되는 것으로 이해했습니다. 이때 진행하는 두가지 검증은 서로 다른 종류이며 SQL에서 한번에 처리하는 경우 유스케이스가 감춰질 수 있다고 생각이 듭니다. 물론 쿼리가 두번 나간다는 측면에서는 불리하지 않은거 아닌가 생각이 들 수 있지만, 인덱스로 걸어둔다면 현재 규모에서 성능적인 차이는 미비할 것 같습니다. 그래서 저는 오히려 유스케이스를 드러내는 것이 좋다고 생각합니다. 쿼리에 작성하더라도, 유스케이스가 감춰지지 않는 경우오히려 반대로, 슬롯을 조회해서, 현재 예약 가능한 시간 목록을 불러온다고 가정해볼게요. 이 상황에서 exist로 reservation을 조회해서, reservation_time에 예약이 하나라도 있는 경우는 해당 reservation_time에 reserved라는 boolean값을 한번에 불러오는 쿼리를 고민해볼 수 있을 것 같습니다. 물론 reservation_time를 모두 불러오고, reservation에 해당 슬롯에 존재하는 모든 time을 불러와 서비스에서 두개를 비교하며 공통되는 경우, 예약된 것으로 판단하는 로직을 작성할 수도 있을 것 같은데요, 이는 SQL에서 한번에 처리하더라도 "현재 예약 가능한 시간을 조회한다."라는 유스케이스가 코드에 명확히 드러나기 때문에 사용해도 무방하다고 생각합니다! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
여러분들은 다음과 같은 검증이나 판단이 필요할 때, sql 쿼리를 사용하나요? 아니면 조회하여 서비스에서 판단하나요?
본인의 기준이 있다면 설명해주세요.
예시
Beta Was this translation helpful? Give feedback.
All reactions