#3 - 사고방식을 코드로 옮긴다는 것
중년개발자
@loxo
24일 전
사고방식을 코드로 옮긴다는 것
Spring 전문가 관점에서 본
"프론트를 믿지 않는 백엔드"를 Spring Boot 4로 구현하는 방법
1. Spring Boot는 구조를 강요하지 않는다 (그래서 더 위험하다)
Spring Boot는 빠르게 만들 수 있게 해주지만,
올바르게 만들게 강제하지는 않는다.
그래서 초보 단계에서는 흔히 이런 구조가 나온다.
controller
└── service
└── repository
그리고 모든 검증, 모든 판단, 모든 예외가 Service 하나에 쌓이기 시작한다.
👉 이 순간부터 사고방식과 구조가 어긋난다.
2. 사고방식 → 구조 변환의 핵심 원칙
백엔드 사고방식을 Spring Boot 구조로 옮길 때 전문가들이 공유하는 핵심 원칙은 단순하다.
✅ 원칙 1. "의심은 가장 바깥에서 시작한다"
- HTTP 요청은 가장 위험하다
- 따라서 Controller 레벨에서 1차 방어가 필요하다
✅ 원칙 2. "비즈니스 규칙은 중앙에 둔다"
- Service는 흐름 제어자가 아니다
- 규칙의 소유자다
✅ 원칙 3. "데이터는 스스로를 지킨다"
- Entity는 단순한 DTO가 아니다
- 불변성과 상태 전이를 책임진다
3. Controller: 신뢰하지 않는 입구
Controller의 역할은 단 하나다.
"이 요청을 시스템 안으로 들일 자격이 있는가?"
3.1 Controller에서 해야 할 것
- 요청 구조 검증 (형태)
- 필수 값 검증
- 인증/인가 컨텍스트 확보
@RestController
@RequiredArgsConstructor
class OrderController {
private final OrderService orderService;
@PostMapping("/orders")
public ResponseEntity<Void> create(@Valid @RequestBody CreateOrderRequest req) {
orderService.create(req);
return ResponseEntity.ok().build();
}
}👉 @Valid는 프론트를 믿지 않겠다는 선언이다.
3.2 Controller에서 하면 안 되는 것
- 비즈니스 판단
- 상태 변경 로직
- 계산
Controller가 똑똑해질수록, 시스템은 빠르게 무너진다.
4. DTO: 거짓을 담는 그릇
전문가 관점에서 DTO는 이렇게 정의된다.
"아직 믿지 않기로 한 데이터"
DTO 설계 원칙
- Entity와 절대 1:1 매핑하지 않는다
- 의미 없는 필드는 허용하지 않는다
public record CreateOrderRequest(
@NotNull Long productId,
@Positive int quantity
) {}DTO에 검증이 많아질수록, Service는 더 순수해진다.
5. Service: 최종 심판
Service는 흐름을 처리하는 곳이 아니다.
Service는 비즈니스 규칙의 최종 심판이다.
@Service
@RequiredArgsConstructor
@Transactional
class OrderService {
private final ProductRepository productRepository;
private final OrderRepository orderRepository;
public void create(CreateOrderRequest req) {
Product product = productRepository.findById(req.productId())
.orElseThrow(() -> new BusinessException("상품 없음"));
Order order = Order.create(product, req.quantity());
orderRepository.save(order);
}
}👉 Service는 **"이게 가능한가?"**만 묻는다.
6. Entity: 가장 신뢰받는 영역
Entity는 시스템에서 가장 안쪽에 있다.
그래서 오히려 가장 엄격해야 한다.
6.1 Entity는 setter를 갖지 않는다
@Entity
class Order {
@Id @GeneratedValue
private Long id;
private int quantity;
protected Order() {}
public static Order create(Product product, int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("수량 오류");
}
return new Order(product, quantity);
}
}Entity가 스스로를 방어하지 못하면, 그 어떤 레이어도 안전하지 않다.
7. 예외 전략: 실패를 설계한다
초보자는 예외를 "처리"하려 하고, 전문가는 예외를 "설계"한다.
글로벌 예외 핸들링
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
ResponseEntity<String> handle(BusinessException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}👉 예외는 정책이다.
8. Spring Boot 4에서 이 사고방식이 더 중요해진 이유
- API 증가
- 비동기 처리
- 멀티 클라이언트 (Web / App / 외부)
이제 프론트는 하나가 아니다.
그래서 백엔드는 더 이상 가정하면 안 된다.
9. 전문가의 한 문장 정리
- Controller는 의심한다
- Service는 판단한다
- Entity는 거부한다
Spring Boot 4는 이 역할 분리를 방해하지도, 대신해주지도 않는다.
결국 구조는 사고방식의 그림자다.
다음 단계에서는 이 구조를 기반으로
테스트, 트랜잭션 경계, 비동기 처리까지 확장한다.