Service 계층 설계 정리


1. Service의 본질

Service는 Controller의 코드가 너무 많아지니까 영역을 나눈 것이지,
공통 기능을 재사용하려는 목적이 아니다.

공통 기술 기능(파일 처리, 날짜 포맷 등) → Util 클래스
비즈니스 로직 → Service


2. 코드가 같아도 메서드를 분리할지 기준

“이 코드가 나중에 한쪽만 바뀔 가능성이 있는가?”

상황 결정
정책이 바뀌면 두 곳 모두 똑같이 바뀌어야 함 공통 Service로 묶기
겉보기엔 같지만 맥락(Context)이 다름 별도 메서드/서비스로 분리

예시


3. Service가 커지는 문제 (Fat Service)

AService가 a, b, c, d DAO를 전부 가지면:

해결책 → 계층 분리


4. 계층 구조

Controller
    ↓ (1:1 호출)
Facade (상위 Service) ← 시나리오 설계, @Transactional 담당
    ↓
Domain Service (하위 Service) ← 도메인 전문 로직, 공통 부품
    ↓
Repository (DAO)

각 계층의 역할

계층 역할 공통화 비유
Controller 요청/응답만 X 창구 직원
Facade 시나리오 조립, 트랜잭션 보통 X (Controller 전용) 프로젝트 매니저
Domain Service 도메인 규칙 처리 O (적극 재사용) 전문 기술자
Repository DB 접근 - -

5. Facade 패턴

Controller에서 직접 여러 Service 호출하면 안 되는 이유

// ❌ 위험 - 트랜잭션이 각각 따로 동작
public void order() {
    orderService.createOrder();   // 성공
    stockService.decrease();      // 성공
    financeService.update();      // 실패 → 위 둘은 롤백 안 됨!
}

각 Service 메서드마다 트랜잭션이 따로 동작하므로,
중간에 실패해도 이전 작업이 롤백되지 않는다.

Facade로 해결

// ✅ Facade에서 @Transactional → 전체가 하나로 묶임
@Service
@RequiredArgsConstructor
public class OrderFacade {

    private final OrderService orderService;
    private final StockService stockService;
    private final FinanceService financeService;

    @Transactional
    public void placeOrder() {
        orderService.createOrder();         // 주문
        stockService.decreaseStock();       // 재고
        financeService.recordTransaction(); // 재무
        // 셋 중 하나라도 실패 → 전체 롤백
    }
}

6. 같은 Domain Service를 다르게 조합하는 예시

시나리오: 사용자 주문 vs 관리자 강제 주문

// 사용자용 Facade
@Service
public class UserOrderFacade {
    private final OrderService orderService;
    private final StockService stockService;
    private final NotificationService notificationService; // 알림 O

    @Transactional
    public void placeOrder() {
        orderService.createOrder();
        stockService.decreaseStock();
        notificationService.sendSms(); // 사용자에게 알림
    }
}

// 관리자용 Facade
@Service
public class AdminOrderFacade {
    private final OrderService orderService;
    private final StockService stockService;
    private final FinanceService financeService; // 재무 기록 O

    @Transactional
    public void createOrderForce() {
        orderService.createOrder();         // 같은 OrderService 재사용
        stockService.decreaseStock();       // 같은 StockService 재사용
        financeService.recordTransaction(); // 알림 없이 재무만
    }
}

OrderService, StockService는 두 Facade 모두에서 공통으로 재사용
NotificationService, FinanceService필요한 곳에서만 선택적 사용


7. Domain Service 기준

“이 Service는 어디까지 담당하는가?”

// OrderService → 주문 테이블만
@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final OrderHistoryRepository historyRepository; // 주문 히스토리도 주문 영역

    public void createOrder(OrderInfo info) {
        orderRepository.save(info);
        historyRepository.save(new History(info)); // 주문 관련만
    }
}

8. 언제 Facade가 필요한가?

상황 결정
단순 CRUD, 도메인 하나만 건드림 Facade 불필요, Domain Service 직접 사용
여러 도메인이 엮이고 트랜잭션이 중요 Facade 도입
Facade 자체가 너무 커짐 행위 단위로 쪼개기 (PlaceOrderFacade, CancelOrderFacade)

9. 최종 정리

"상위 서비스(Facade)는 컨트롤러의 영역 분리이자 시나리오 설계도,
 하위 서비스(Domain Service)는 비즈니스 규칙을 지키는 공통 부품"