본문 바로가기

FrontEnd

헥사고날 아키텍처와 관심사의 분리를 이용한 클린 코드

반응형

1. 어니언, 헥사고날 아키텍처

헥사고날, 어니언 아키텍처는 port, adaptor 패턴의 별칭일 뿐입니다.

https://blog.ploeh.dk/2016/03/18/functional-architecture-is-ports-and-adapters/

 

Functional architecture is Ports and Adapters

Functional architecture tends to fall into a pit of success that looks a lot like Ports and Adapters. In object-oriented architecture, we often struggle towards the ideal of the Ports and Adapters architecture, although we often call it something else: lay

blog.ploeh.dk

hexagonal architecture

포트는 인터페이스, 어댑터는 실제 구현체일 뿐입니다.

즉 포트(인터페이스)를 통해 어댑터(부수효과)를 격리하는 것이 핵심입니다.

https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture/

 

지속 가능한 소프트웨어 설계 패턴: 포트와 어댑터 아키텍처 적용하기

2022-LINE-engineering-site

engineering.linecorp.com

프론트엔드는 포트, 어댑터 패턴을 어떻게 구현하죠?

컨테이너 컴포넌트와 컨텍스트 API를 IOC 처럼 사용하여 구현합니다.

참고 : 리액트는 UI 런타임입니다. https://overreacted.io/react-as-a-ui-runtime/

 

React as a UI Runtime

An in-depth description of the React programming model.

overreacted.io

그런데 포트 / 어댑터 패턴을 사용하는 이유는 뭘까요?


 

2.  AOP,  레이어드 아키텍처

백엔드 애플리케이션 프레임워크를 배울때 가장 어려웠던 것은,

각 계층(레이어)의 라이프사이클과 책임, 코드 실행 순서였던것 같습니다.

이는 요청 / 응답이라는 백엔드 애플리케이션의 핵심 기능 / 라이프사이클 관점에서 바라봐야 합니다.

앱이 구현하는 feature, domain은 그 다음 문제입니다.

다음은 nestjs의 요청, 응답 생명 주기입니다.

(출처 : https://wikidocs.net/158658)

nestjs의 요청 생명 주기

(Todo : 각 레이어의 관심사에 대해서도 나중에 정리하여 추가 예정)

위 그림은 사실 아래 그림과 같습니다.

즉 인터페이스를 통해 아래는 위만 알며, 세부 구현은 모릅니다.

각 레이어는 파라미터와 리턴값을 통해 소통할까...요?

사실 숨은 경로가 있는데요, 바로 예외 처리입니다.

이 경우 에러 처리 로직을 최상단 레이어에 위임할 수 있습니다.

 

즉, 파리미터 기반으로 레이어가 소통하는 것은 맞지만,

리턴값 기반으로 소통은 아닐 수도 있습니다.

(데코레이터의 시그니처를 봐도 알 수 있죠)

해당 레이어가 하나의 책임을 담당하는 것,

이것이 레이어드 아키텍처고 진정한 AOP의 힘입니다.

각 레이어 별로 변화를 캡슐화하고, 자신의 관심사를 담당하는 것 (단일 책임 원칙, 횡단관심사)이죠.

다른 점이 있다면, 데코레이터는 하나만 런타임에 바꾸는 것이 불가능하고,

파라미터, 생성자 주입을 이용하면 런타임에 의존성을 쉽게 바꿀 수 있다는 점입니다.


3. 대수적 효과와 AOP

이전 문단에서 오류 필터를 볼 수 있었는데요,

마치 IOC를 담당하는 스프링, nestJS 런타임이 존재하는 것처럼,

리액트는 최상단에 리액트 런타임이 존재하는 것처럼 생각할 수 있습니다.

https://overreacted.io/ko/algebraic-effects-for-the-rest-of-us/

 

모두를 위한 대수적 효과

부리또가 아니다.

overreacted.io

이는 일반적인 개발자들이 평소와 같이 코딩하면서, 매우 프로그레시브한 프로그래밍 패턴을 사용할 수 있게 해줍니다.

  • 서스펜스를 사용하여 비동기 성공 / 실패 상태관리를 리액트에 위임합니다.
    • 개발자는 실패 / 성공 시 UI만 개발하면 됩니다.
  • 에러바운더리를 사용하여 오류 처리를 리액트에 위임합니다.
    • 개발자는 에러 발생 시 UI만 개발하면 됩니다.

위는 AOP의 사례입니다.

 

다른 예도 들어보죠

도메인 서비스 로직에

  • 트랜잭션 관리 로직이 섞이는게 맞나요?
  • 에러처리 로직이 섞이는게 맞나요? (에러는 비즈니스 적으로 발생할 수 있으니 throw 하는건 괜찮죠)
  • 서비스가 레포지토리에서 데이터를 가져오는 방법을 알아야 하는게 맞나요?
    • (물론 "저장, 기록"은 비즈니스의 일부일 수 있으니 해당 로직이 들어가는건 적절합니다.)

위에 있는 모든 로직이 섞이지 않게 하려면 AOP를 사용해야 합니다.

이는 내 관심 아니야~ 하고 상단 레이어로 throw 하는 것처럼 생각할 수 있습니다.

혹은 이후 실행될 함수가 중간과정을 모르게 데이터를 트랜스폼 하거나 실행을 차단합니다.

대신 최상단에서 의존성을 제공하는 것처럼, 해당 관심사를 처리하는 방법을 알아야겠죠.

 

아쉬운 점은 throw한 위치에서 재개는 불가능하단 점입니다.

대신 함수를 잘게 쪼개서 이 아쉬움을 달랠 수는 있습니다. (ex 리덕스)

 

또 비슷한 사례로 chain of responsibility 패턴(미들웨어)을 들 수 있습니다.

각 미들웨어는 자신의 관심사만 처리하고, 아닌 경우는 다른 미들웨어에 처리를 위임하죠.

4. DI, DIP

헥사고날 아키텍처란

  • 인터페이스와 구현체의 분리
    • 포트와 어댑터
  • 레이어 별 관심사의 분리
    • 오른손이 하는 일을 왼손이 모르게 하기
  • 대수적 효과
    • ex 미들웨어, 에러필터, 서스펜스, 

이 3가지 요소가 핵심이라는 건데요 (제 생각에)

 

AOP와 포트/어댑터 패턴을 구현하려면 DIP 개념과 이를 실현하기 위한 DI가 필요합니다.

자세한 설명은 아래 글에서 확인하세요 

https://itchallenger.tistory.com/766?category=1057505 

 

의존성 역전 원칙과 NestJS(Dependency Inversion Principle with NestJS)

최근에 Remix를 써보면세 백엔드를 복습하기로 했습니다. 그 일환으로 NestJS를 공부하고 있는데요, NestJS 창시자가 속해있으면서 풀스택 컨설팅(이라고 하고 SI라 읽는) 회사의 공식 블로그에 양질

itchallenger.tistory.com

 

 

 

 

 

 

 

 

 

반응형