본문 바로가기

Study

[내일배움캠프 TIL] 9일차 - Controller 단에서 로직 분리 실습 1

728x90
반응형

과제가 주어졌는데, Controller 단에서 모든 동작이 일어나는 코드가 주어지고

service로 로직을 분리해야했습니다.

 

주어진 controller 코드

package com.sparta.miniorder.order.controller;

import com.sparta.miniorder.order.dto.OrderRequest;
import com.sparta.miniorder.order.dto.OrderResponse;
import com.sparta.miniorder.order.entity.Order;
import com.sparta.miniorder.order.repository.OrderRepository;
import com.sparta.miniorder.product.entity.Product;
import com.sparta.miniorder.product.repository.ProductRepository;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.net.URI;

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    @PostMapping
    @Transactional
    public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody OrderRequest request) {
        Product product = productRepository.findById(request.getProductId())
                .orElseThrow(() -> new IllegalArgumentException(
                        "해당 상품이 존재하지 않습니다. id=" + request.getProductId()));

        Order order = new Order(product);
        Order saved = orderRepository.save(order);
        OrderResponse response = new OrderResponse(saved);
        return ResponseEntity.created(URI.create("/api/orders/" + response.getOrderId())).body(response);
    }

    @GetMapping("/{id}")
    @Transactional(readOnly = true)
    public ResponseEntity<OrderResponse> getOrder(@PathVariable Long id) {
        Order order = orderRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException(
                        "해당 주문이 존재하지 않습니다. id=" + id));
        return ResponseEntity.ok(new OrderResponse(order));
    }
}

 

위 코드는 동작은 잘하지만 문제는 controller 단에서 모든 로직을 처리하고 있어서,

추후 추가 기능이나 유지 보수 할 때 힘들어집니다.

3계층 아키텍처에서는 controller, service, repository로 나누라고 되어있는데,

controller는 진입점 역할만하고, 

service는 비즈니스 로직을 담당하고

repository는 주로 db와 연관되는 작업을 합니다.

 

이는 각각 종속성을 느슨하게 하여, 유지보수, 관리를 편하게 만들어줍니다.

 

 

위 코드를 컨트롤러는 이렇게 수정하였습니다.

package com.sparta.miniorder.order.controller;

import com.sparta.miniorder.order.dto.OrderRequest;
import com.sparta.miniorder.order.dto.OrderResponse;
import com.sparta.miniorder.order.entity.Order;
import com.sparta.miniorder.order.respository.OrderRepository;
import com.sparta.miniorder.order.service.OrderService;
import com.sparta.miniorder.product.entity.Product;
import com.sparta.miniorder.product.repository.ProductRepository;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.net.URI;

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @PostMapping
    @Transactional
    public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody OrderRequest request) {
        return orderService.createOrder(request);
    }

    @GetMapping("/{id}")
    @Transactional(readOnly = true)
    public ResponseEntity<OrderResponse> getOrder(@PathVariable Long id) {
        return orderService.getOrder(id);
    }
}

controller 단에서는 경로로 진입만 받고, 바로 service 쪽으로 넘겨주도록 수정하였습니다.

복잡한 비즈니스 로직은 service쪽으로 주고, controller에서는 진입점만 관리하도록 하기 위함입니다.
(controller에서 모든 것을 처리할 경우 점점 코드가 늘어나 유지보수 어려워짐.)

 

 

서비스에는 비즈니스 로직을 추가하였습니다.

package com.sparta.miniorder.order.service;

import com.sparta.miniorder.order.dto.OrderRequest;
import com.sparta.miniorder.order.dto.OrderResponse;
import com.sparta.miniorder.order.entity.Order;
import com.sparta.miniorder.order.respository.OrderRepository;
import com.sparta.miniorder.product.entity.Product;
import com.sparta.miniorder.product.repository.ProductRepository;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.net.URI;

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    public ResponseEntity<OrderResponse> createOrder(@Valid OrderRequest request) {
        Product product = productRepository.findById(request.getProductId())
                .orElseThrow(() -> new IllegalArgumentException(
                        "해당 상품이 존재하지 않습니다. id=" + request.getProductId()));

        Order order = new Order(product);
        Order saved = orderRepository.save(order);
        OrderResponse response = new OrderResponse(saved);

        return ResponseEntity.created(URI.create("/api/orders/" + response.getOrderId())).body(response);
    }

    public ResponseEntity<OrderResponse> getOrder(Long id) {
        Order order = orderRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException(
                        "해당 주문이 존재하지 않습니다. id=" + id));

        return ResponseEntity.ok(new OrderResponse(order));
    }
}

이렇게 수정한 이유도 비즈니스 로직이 수정될때 이부분만 수정할 수 있도록, 유지보수 관점에서 편하게 하기 위함입니다.

 

 

사실 실무에서 일했을 때도, controller, service, repository 로 코드들이 나뉘어져있어서 구조는 익숙한 느낌입니다.
그런데 그때는 이론 같은거는 모르고, 왜 이렇게 되어있지라고 생각하긴 했지만
더 알아볼 생각은 하지 않았었습니다.

그냥 이미 만들어진 방식을 유지하면서 개발을 했었고, 자연스럽게 repository는 db랑 관련 있는 함수들이네,
이런식으로 터득했던 것 같습니다.

그런 구조가 그냥 나온게 아니고, 다 이론이 있고 
많이 쓰는 방식이라는걸 이번에 공부하면서 알게 되었습니다.

다음에 하게 될때는 이론을 이해하고 구조를 짜고, 코딩할 수 있도록 하겠습니다.

 

 

 

 

 

 

 

 

반응형