이번 편의 목표: 연체료 계산과 응답용 책 생성을 서비스로 옮기고, 컨트롤러는 요청과 응답만 남깁니다.

컨트롤러에 계산이 쌓이면 같은 규칙이 화면용 주소와 섞입니다. 서비스는 스프링 빈이되, 주소와 무관한 작업을 맡습니다. @Service는 @Component와 같이 빈으로 등록되며, 역할이 서비스라는 점을 이름에 드러냅니다.

서비스

BookService.java입니다. 04편의 FeePolicy를 생성자로 받습니다.

Java
package com.thinktree.library;

import org.springframework.stereotype.Service;

@Service
public class BookService {
    private final FeePolicy feePolicy;

    public BookService(FeePolicy feePolicy) {
        this.feePolicy = feePolicy;
    }

    public Book sample(Long id) {
        return new Book(id, "샘플", feePolicy.fee(0));
    }

    public Book create(BookRequest request) {
        return new Book(1L, request.title(), request.price());
    }

    public int lateFee(int lateDays) {
        return feePolicy.fee(lateDays);
    }
}

컨트롤러는 서비스를 주입받고 호출만 합니다. 06편의 search는 이 편에서도 남깁니다.

Java
private final BookService bookService;

public BookController(BookService bookService) {
    this.bookService = bookService;
}

@GetMapping("/{id}")
public Book find(@PathVariable Long id) {
    return bookService.sample(id);
}

@PostMapping
public Book create(@RequestBody BookRequest request) {
    return bookService.create(request);
}

FeePolicy를 컨트롤러가 직접 알 필요가 없어졌습니다. 하루 요금을 바꾸는 일은 설정과 FeePolicy에 남고, 책을 만드는 규칙은 BookService에 모입니다.

GET /api/books/4 의 price는 fee(0)이므로 0입니다. 이 응답은 아직 저장소의 책이 아니라 연결을 확인하는 샘플입니다.

직접 해 보기

BookService.lateFee(3)의 결과가 300인지, 컨트롤러가 아니라 서비스 메서드만 보고 확인하세요. application.properties의 하루 요금은 100이어야 합니다.

해설

FeePolicy.fee는 일수 곱하기 dailyFee입니다. 3 곱하기 100은 300입니다. 컨트롤러에 곱셈이 없다면 계층을 나눈 것입니다.