Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

전공공부

[Spring] Hexagonal Arichtecture - MVC 패턴과 비교하다. 본문

Study/Spring Boot

[Spring] Hexagonal Arichtecture - MVC 패턴과 비교하다.

monitor 2024. 1. 21. 14:09

MVC 패턴은 일반적으로 Controller, Service, Repository 각 계층의 역할과 책임을 분리하여 애플리케이션 유지보수성을 높이기 위해서 사용합니다. 하지만, MVC 패턴은 비즈니스 로직과 외부 서비스의 로직 결합도를 완전히 분리하지 못한다는 단점이 있는데요. 이를 같이 알아봅시다.

 

MVC

 

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final String filePath;

    public OrderService(OrderRepository orderRepository, @Value("${file.path}") String filePath) {
        this.orderRepository = orderRepository;
        this.filePath = filePath;
    }

    public Order createOrder() {
        String customerName = readFromFile();

        Order order = new Order();
        order.setCustomerName(customerName);
        order.setProduct("Product");

        return orderRepository.save(order);
    }

    private String readFromFile() {
        try {
            return Files.readString(Paths.get(filePath));
        } catch (IOException e) {
            throw new RuntimeException("Failed to read file", e);
        }
    }
}

 

위 코드를 보고 잘 못된 점을 바로 찾을 수 있을까요? 일반적으로 봤을때 DB 접근자와 잘 분리가 된 것 같습니다. 하지만, 자세히 보면 외부 Framework단에서 접근하는 FileReader 부분을 분리하지 못하여서 파일 읽는 것에 대해서 의존성을 가지는 코드가 되었는데요. 이러한 것들이 점차 쌓이게 되면 Configuartion 부분에서 DB를 호출한다던지와 같은 역할을 나누지 못하고 마구잡이의 코드가 될 수 있습니다.

 

아래 Hexagonal로 변경한 코드를 봅시다.

 

Hexagonal Architecture

 

@Service
public class OrderInputPort implements OrderUsecase {
    private final OrderOutputPort orderOutputPort;
    private final FileReaderOutputPort fileReaderOutputPort;

    public OrderInputPort(OrderOutputPort orderOutputPort, FileReaderOutputPort fileReaderOutputPort) {
        this.orderOutputPort = orderOutputPort;
        this.fileReaderOutputPort = fileReaderOutputPort;
    }

    public Order createOrder() {
        String customerName = fileReaderOutputPort.read();

        Order order = Order.builder()
                .customerName(customerName)
                .product("Product TEST")
                .build();
        return orderOutputPort.save(order);
    }
}

 

우선 보다 깔끔하고 이해하기 쉽습니다. 저는 FileSystem에 대해서 신경쓰지 않고 개발을 할 수 있고 그저 메서드를 가져와 호출만 하면 됩니다. 이런식으로 정의하게 되면 후에 TestCase를 @Mock 객체로 생성해서 사용하기도 편합니다.

 

 

package com.test.tester.hexagonal;

import com.test.tester.hexagonal.application.port.input.OrderInputPort;
import com.test.tester.hexagonal.application.port.output.FileReaderOutputPort;
import com.test.tester.hexagonal.application.port.output.OrderOutputPort;
import org.junit.jupiter.api.BeforeEach;
import com.test.tester.hexagonal.domain.Order;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class OrderInputPortTest {
    @Mock
    private OrderOutputPort orderOutputPort;

    @Mock
    private FileReaderOutputPort fileReaderOutputPort;

    private OrderInputPort orderInputPort;

    @BeforeEach
    public void setup() {
        MockitoAnnotations.openMocks(this);
        orderInputPort = new OrderInputPort(orderOutputPort,fileReaderOutputPort);
    }

    @Test
    public void testCreateOrder() {
        // Arrange
        String customerName = "Test Customer";
        String product = "Product";
        Order expectedOrder = Order.builder()
                .customerName(customerName)
                .product(product)
                .build();

        when(fileReaderOutputPort.read()).thenReturn(customerName);
        when(orderOutputPort.save(any())).thenReturn(expectedOrder);

        // Act
        Order actualOrder = orderInputPort.createOrder(product);

        // Assert
        assertEquals(expectedOrder, actualOrder);
    }
}

 

이렇게 만들어두면 단위 테스트에서도 Mock 객체 생성에 무리가 없고 테스트 가능한 코드가 됩니다.

코드 참고

https://github.com/1ComputerMaster/1ComputerMaster/tree/main/Study/tester

 

해당 레포중 src/main/java/com/test/tester/hexagonal , src/test/hexagonal 부분 참고