Architecture) Hexagonal 아키텍처 도입기

박우영·2023년 9월 28일
0

아키텍처

목록 보기
1/1

문제점

우리는 MSA 프로젝트를 진행하며 많은 문제를 겪었고 완벽하지 않지만 성공 시켰습니다.

하지만 배포에서 끝나는게 아닌 서비스를 확장시키고 기존의 코드를 리팩토링 하거나 유지보수 혹은 버그들을 fix 를 해야 합니다.

각 Service마다 많은 메서드 들이 생겼고 저희의 부족함으로 인해 옛날에 작성한 코드를 이해하는데도 시간이 많이 소요가 됐습니다.

계층형 아키텍처의 문제점인진 모르겠지만 저희는 Service가 점점 방대해 짐에따라 서비스를 별도로 둬야하는지 고민을 했고 헥사고날 아키텍처로 리팩토링을 고민 하였습니다.

헥사고날 이란?

여러 소프트웨어 환경에 쉽게 연결할 수 있도록 느슨하게 결합하는것을 목표로 합니다.

즉, 각 application, adapter, domain 이 느슨하게 결합되어 단방향 단방향으로 참조합니다.

더 자세한 설명은 좋은 자료들이 많아 생략하고 밑에 링크를 참조해주시면 감사하겠습니다.

AS-IS

기존의 패키지 구성입니다.

혼자 개발하는것이아닌 2명의 백엔드 끼리 각각의 컨벤션을 가지고 컨벤션이 통일 되지않은 상태에서 개발된 상태입니다.

정말 지저분하고 패키지 개수도 충분히 줄일 수 있을거같습니다.

또한 테스트코드도 많은 의존성이 들어가는데요 코드 예시는 다음과 같습니다.

TestCode Example

@SpringBootTest
@Transactional
@ExtendWith(MockitoExtension.class)
class StudyRuleServiceTests {
    @Autowired
    StudyRuleService studyRuleService;
    @Autowired
    StudyService studyService;
    @MockBean
    RuleClient ruleClient;
    @MockBean
    MemberClient memberClient;

    @BeforeEach
    void setFeign() {
        when(ruleClient.getRule(any()))
                .thenReturn(new RsData<>("S-1", "msg", null));
        when(memberClient.updateMyStudy(any()))
                .thenReturn(new RsData<>("S-1", "msg", null));
        when(memberClient.deleteMyStudy(any()))
                .thenReturn(new RsData<>("S-1", "msg", null));
        when(memberClient.findById(any()))
                .thenReturn(new RsData<MemberResDto>("S-1", "성공", new MemberResDto("leader", "bk1234")));
    }

단위 테스트임에도 불구하고 많은 의존성을 가지는 상태입니다.
Repository에 의존하게 되고 SpringBootTest 로 통합테스트를 진행합니다.

방향성

AS-IS 계층형 아키텍처 -> TO-BE 헥사고날 아키텍처

헥사고날 아키텍처를 첫 도입하는거기때문에 기존의 코드를 전부 바꾸고
아... 계층형이 더 좋은것같네요 하며 계층형으로 다시 바꾸고 싶지않기때문에

새로 추가되는 서비스에 대하여 먼저 도입을 할 예정입니다.

제일 중요시 여기고 얻고자 하는 목표는 다음과 같습니다.

느슨한 결합

  • 계층형 아키텍처로도 느슨하게 할 수 있겠지만 usecase 와 port 등 인터페이스를 활용하여 더 느슨하게 나누고자 합니다.
  • Unit TEST 시 의존성을 최소화한 단위 테스트

TO-BE

패키지구조는 기본을 따랐습니다. adapter, application, domain 으로 나누고

공통사항 및 설정은 global에 두었습니다.

Test Example

public class ChatServiceTest {
    @Mock
    private FindChatPort findChatPort;
    @Mock
    private SendChatPort sendChatPort;
    @InjectMocks
    private ChatService chatService;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
    }
    @Test
    @DisplayName("message 타입의 채팅을 보낸다.")
    void sendMessageType_responseVoid_time1() {
        ChatRequest request = TestInitUtil.requestMessage();
        when(sendChatPort.sendChat(request)).thenReturn(Mono.empty());

        Mono<Void> sendChat = chatService.sendChat(request);

        StepVerifier.create(sendChat).verifyComplete();
        verify(sendChatPort, times(1)).sendChat(request);
    }

    @Test
    @DisplayName("채팅을 조회한다.")
    void findChat() {
        String sender = "1";
        String receiver = "2";
        ChatResponse response = TestInitUtil.responseMessage();
        when(findChatPort.findChatByMemberId(sender, receiver)).thenReturn(Flux.just(response));

        Flux<ChatResponse> responseFlux = chatService.findBySenderAndReceiver(sender, receiver);
        StepVerifier.create(responseFlux)
                .expectNext(response)
                .verifyComplete();

        verify(findChatPort, times(1)).findChatByMemberId(sender, receiver);
    }

}

다른 서비스지만 불필요한 어노테이션이 줄고 Test 시에도 확실한 단위 로 테스트를 진행할 수 있게 됩니다.

기존의 layerd 패턴의 문제점인 Controller -> Service -> Repository 요청이오면 Repository -> Service -> Controller 와 같이 역순으로 Return 을 해야하기 때문에 특정 부분을 수정한다면 전부다 수정해줘야 하는 문제점이 있었습니다.

하지만 헥사고날 아키텍처는 in 과 out 을 기준으로 의존성을 분리하기 때문에 수정을 하더라도 모든 로직을 수정할 필요가 줄어들게 됩니다.

기존에 작성하던 Layerd 뿐만아니라 헥사고날 아키텍처 를 적용해보며
개발자들이 아키텍처를 중요시 하는 이유를 알게되었습니다.
개발이라는것은 개발하고 끝나는것이아닌 수정 및 유지보수가 좋아야 하기때문입니다.

더 좋은코드 좋은아키텍처를 생각해보며 의존성을 최소화하 하는방법에 대해 좀 더 깊은 생각을 해보기로 하며 이만 마치겠습니다.

0개의 댓글