클린아키텍처
프론트엔드에 무슨 비즈니스로직이 있어? 라는 관점도 존재하지만
제가 막상 코드를 짜다보면 프론트엔드에도 비즈니스로직이라고 부를만한 것들이 존재하곤 했고
그런 로직들과 UI를 담당하는 로직들이 뒤섞이게 되면 관리를 하는게 지옥이 되는 경험도 했습니다.
자연스럽게 비즈니스로직 정도는 분리하자. 정도로 생각하면서 코드를 짜왔었는데
좀 더 본격적으로 클린아키텍처를 다룬 레포지토리가 있어서 흥미롭게 코딩을 해봤습니다.
https://github.com/XionWCFM/xion-study
결과물은 위 레포지토리에서 확인하실 수 있습니다.
클린아키텍처란?
클린아키텍처는 애플리케이션 도메인과의 근접성에 따라 책임과 기능의 일부를 분리하도록 하는 방법이라고 할 수 있습니다.
Robert C. Martin의 블로그에서 클린아키텍처에 대한 내용을 찾아볼 수 있습니다.
아키텍처의 세부사항은 다를 수 있지만 결국 이러한 아키텍처를 만드는 이유는 하나입니다.
앞서 정의한 바와 같이 "관심사의 분리"를 이루기 위함이라고 할 수 있습니다.
클린아키텍처는 관심사 분리를 이루기 위해 어떤 방법을 수행할까요?
위 그림에서 알 수 있듯이 소프트웨어를 "계층"으로 나누어 생각하는 것을 통해 분리를 이룹니다.
아키텍처의 가장 바깥쪽에는 쉽게 변경될 수 있는 것들이 존재하며
반대로 아키텍처의 가장 중심부는 쉽게 변경되지 않을 핵심적인 업무규칙들이 존재해야할 것입니다.
또한 각 레이어는 자신의 레이어 바깥을 전혀 몰라야할 것입니다.
이것은 조금만 생각해보면 간단하게 유추할 수 있는 것인데 만약 핵심 업무규칙이
바깥 레이어의 ui에 의존하고있다면 어떨까요?
ui를 구성하는 요소를 바꾸는 순간 그에 의존하고있던 업무규칙도 깨지게될것입니다.
이러한 구조를 선택하게되면 다음과 같은 장점을 가질 수 있습니다.
1. 프레임워크에 독립적이다.
-> 아키텍처는 라이브러리의 존재 여부에 의존하지 않습니다. 대신 도구로 사용할 수 있죠!
2. 테스트 가능하다.
-> 업무 규칙은 UI , 데이터베이스 , 웹 서버 등 외부요소와 상관없이 테스트할 수 있습니다.
-> 철저한 관심사 분리를 통해 테스트하기 더 쉬운 구조로 만들 수 있는거죠!
3. UI 와 독립적이다.
-> UI , DB와 같은 세부사항은 가장 바깥쪽의 레이어에 위치합니다.
-> 따라서 UI, DB의 변경은 업무 규칙에 영향을 줄 수 없으며 이는 UI의 변경을 쉽게합니다.
4. 데이터베이스와 독립적이다.
-> 앞서 설명한 UI 부분과 동일한 이유입니다.
5. 외부 기관으로부터 독립적이다.
-> 업무 규칙은 외부세계를 알필요도없고 알아서도 안되며 알지 못합니다.
-> 따라서 업무 규칙은 외부세계로부터 독립적입니다.
세부 내용 좀 더 보기
네. 아까봤던 그 사진입니다.
엔티티 | 도메인
-> 핵심 업무 규칙을 캡슐화합니다.
-> 메서드를 가지는 객체 / 일련의 데이터구조와 함수의 집합입니다.
-> 가장 변하지 않고 , 외부로부터 영향을 받지 않아야 하는 영역입니다.
즉 도메인은 외부세계와 독립적이어야하며 사용사례가 변경되거나 프레임워크를 바꾸더라도
변경되지 않아야하는 것이라고 생각할 수 있습니다.
유즈케이스 | 애플리케이션
-> 애플리케이션의 특화된 업무 규칙을 포함합니다.
-> 시스템의 모든 유즈케이스를 캡슐화하고 구현합니다.
-> 엔티티로 들어오고 나가는 데이터 흐름을 조정하고 조작합니다.
-> 애플리케이션 계층이라고도 부릅니다.
애플리케이션 계층은 오케스트레이션을 수행하는 계층이라고도 볼 수 있습니다.
애플리케이션이 외부세계와 통신하기를 원하는 방식에 대한 사양을 "포트"라고 정의하곤 합니다.
포트라고하니까 조금 거창해보이는데 그냥 인터페이스라고 생각해도 됩니다.
이 포트는 애플리케이션의 희망사항 - 현실세계 사이의 완충지대 역할을 수행하는데요
입력 포트의 경우에는 애플리케이션이 외부 세계와 어떻게 연결되기를 원하는지를 표현하고
출력 포트의 경우에는 애플리케이션이 외부세계와 통신하는 방법을 표현합니다.
인터페이스 어댑터
-> 컨트롤러, 게이트웨이, 프레젠터가 포함됩니다. 위 사진에선 초록 영역입니다.
-> 외부 인터페이스에서 들어오는 데이터를 유즈케이스, 엔티티가 처리하기 편한 방식으로 변환합니다.
-> 유즈케이스와 엔티티에서 나가는 데이터를 외부 인터페이스에서 처리하기 편하게 변환합니다.
포트가 있으면 어댑터도 있어야겠죠?
이 어댑터 계층에는 외부 서비스에 대한 어댑터가 포함되어 있습니다.
외부 서비스의 api는 우리 애플리케이션이 원하는 바와 다를 가능성이 높습니다.
따라서 이런 호환되지않는 외부 api를 애플리케이션이 원하는 형태로 바꿔야합니다.
즉 어댑터가 필요하다는 것입니다.
아까 포트에도 두가지 종류가 있었던 것 기억하시나요?
그와 마찬가지로 어댑터도 두가지 종류로 구분할 수 있습니다.
driving - 애플리케이션에 신호를 보내는 어댑터입니다.
driven - 반대로 애플리케이션으로부터 신호를 받는 어댑터입니다.
쉽게 예측할 수 있듯이 보통 사용자는 driving 어댑터와 상호작용하게 될것입니다.
사용자가 이벤트를 통해 애플리케이션에 신호를 보내는 행위를 할테니까요
또 다른 관점으로 보면 driving 어댑터는 인프라와 상호작용합니다.
프레임워크 , 드라이버
-> 핵심 업무 규칙과는 관련없는 세부사항입니다.
-> 프레임워크 , 디비, 서버 등이 여기에 해당하게됩니다.
심지어는 우리의 next.js , react 역시도 그저 ui를 위한 세부사항일 뿐입니다.
도메인에 들어갈 수 있는 것은?
사실 프로젝트를 뜯어보면서 이부분이 많이 헷갈렸었는데
스토어 도메인에는 다음과 같은 것들을 포함해도 된다고 표현합니다.
1. 엔티티의 데이터 유형
2. 각 엔티티를 생성하기위한 팩토리함수 or 클래스
3. 데이터에 대한 변환 함수
!!! -> 이때 변환 함수는 도메인 규칙에만 의존해야하며 다른 어떤 것에도 의존하면 안됩니다.
저는 1번만 도메인이라고 생각했는데 역시 절대라는것은 없군요
2,3번 역시 도메인이라고 볼 수 있습니다.
애플리케이션 계층 설계
애플리케이션 레이어에는 use case가 포함된다고 했습니다.
use case에는 항상 다음과 같은 구성요소가 존재하는데요
1. actor
2. action
3. result
당연하다면..당연한것인데 아무튼 그렇습니다.
shaared-kernel
다른 사람 코드를 보다보면 간혹 내 관점에서는 이해하기 힘든 코드를 발견하는 경우가 있습니다.
저같은 경우에는 저자가 작성한 공유커널 개념이 그랬는데요
// shared-kernel.d.ts
type Email = string;
type UniqueId = string;
type DateTimeString = string;
type PriceCents = number;
이러한 형태로 그저 string, number와 같은 primitive한 타입일 뿐인데
그것을 타입으로 래핑해주는 코드를 발견했습니다.
저자는 이를 primitive obsession을 없애기 위해 사용한 기법이라고 설명했는데요
좀 더 어떤 문자열인지에 대한 의도를 잘 드러내기 위한 용도로 사용한다고 보여졌습니다.
의도적인 단순함
여기까지 프로젝트를 보았을 때 애플리케이션의 비즈니스로직이 매우 단순하다는 것을 알 수 있었습니다.
아키텍처의 설계 방법을 보여주기 위한 예제 프로젝트이기 때문에 의도적으로 단순하게 설계한 것이 보이는데요
한번 따라쳐보시는것을 통해 감을 잡아보시고 프로젝트에 맞게 변형해보시면 좋을 것 같습니다.
마치며
우아한형제들의 기술블로그에서 발견했던 댓글인데
저와 같은 고민을 하던 사람이 3년전에도 있었단게 신기했습니다.
저도 변경되면 곤란한 핵심 업무규칙 , 데이터구조가 너무 자주 변경되는 것을 많이 겪었거든요..
이런 경우에는 어떻게 해야하는걸까요?
정말... 궁금합니다......
그리고 추가로 풀리지않는 고민이 있는데 유틸성 함수의 경우
어디서나 중복되는 로직을 간단하게 관리할 수 있다는 점에서 많은 레이어가 유틸에 의존하게되더라구요
그런 경우에 그 유틸함수가 변해야하는 경우 굉장히 곤란한 상황이 연출되곤했었습니다.
이것도 고민이에용...
레퍼런스
https://itchallenger.tistory.com/926
https://github.com/bespoyasov/frontend-clean-architecture
https://dev.to/bespoyasov/clean-architecture-on-frontend-4311
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
https://techblog.woowahan.com/2647/
https://meetup.nhncloud.com/posts/345
https://daryeou.tistory.com/280
https://refactoring.guru/smells/primitive-obsession
'frontend' 카테고리의 다른 글
짧은 zed editor 사용후기 (2) | 2024.01.15 |
---|---|
react-native expo router 시작해보기 (0) | 2024.01.07 |
라이브러리 퍼블리시 시 원본 코드도 첨부해야할까? (1) | 2023.12.15 |
npm 라이브러리를 만들고 publish 하기 (1) | 2023.11.28 |
next.js , tailwindcss, path alias storybook 세팅하기 (2) | 2023.11.07 |