디지털 환경이 발전함에 따라 현대 웹사이트의 복잡성도 커지고 있습니다. 더 나은 사용자 경험과 고급 기능에 대한 수요가 증가함에 따라 프런트엔드 개발자는 확장 가능하고 유지 관리가 가능하며 효율적인 아키텍처를 만들어야 하는 과제에 직면해 있습니다.
프런트엔드 아키텍처에 대해 사용할 수 있는 수많은 기사와 리소스 중에서 상당수가 클린 아키텍처와 그 적용에 중점을 두고 있습니다. 실제로 조사된 약 70개의 기사 중 50% 이상이 프런트엔드 개발의 맥락에서 클린 아키텍처에 대해 논의했습니다.
풍부한 정보에도 불구하고 눈에 띄는 문제가 지속됩니다. 제안된 아키텍처 아이디어 중 다수가 실제 프로덕션 환경에서 구현되지 않았을 수 있다는 것입니다. 이는 실제 시나리오에서의 효율성과 적용 가능성에 대한 의구심을 불러일으킵니다.
이러한 우려에 힘입어 저는 프런트엔드에 클린 아키텍처를 구현하기 위한 6개월 간의 여정을 시작했습니다. 이를 통해 이러한 아이디어의 현실을 직시하고 밀과 겨를 분리할 수 있었습니다.
이 기사에서는 이 여정에서 얻은 경험과 통찰력을 공유하고 프런트엔드에서 클린 아키텍처를 성공적으로 구현하는 방법에 대한 포괄적인 가이드를 제공합니다.
이 글은 과제, 모범 사례 및 실제 솔루션을 조명함으로써 프런트엔드 개발자에게 끊임없이 진화하는 웹 사이트 개발 세계를 탐색하는 데 필요한 도구를 제공하는 것을 목표로 합니다.
오늘날 빠르게 발전하는 디지털 생태계에서 개발자는 프런트엔드 프레임워크에 관해 선택의 폭이 넓습니다. 이러한 풍부한 옵션은 수많은 문제를 해결하고 개발 프로세스를 단순화합니다.
그러나 이는 개발자들 사이에서 자신이 선호하는 프레임워크가 다른 프레임워크보다 우수하다고 주장하는 끝없는 논쟁으로 이어지기도 합니다. 사실, 빠르게 변화하는 세상에서 새로운 JavaScript 라이브러리는 매일 등장하고 프레임워크는 거의 매달 소개됩니다.
이러한 역동적인 환경에서 유연성과 적응성을 유지하려면 특정 프레임워크와 기술을 뛰어넘는 아키텍처가 필요합니다.
이는 변화하는 추세와 기술 발전을 수용해야 하는 유지 관리가 필요한 제품 회사나 장기 계약에 특히 중요합니다.
프레임워크와 같은 세부 사항에서 독립하면 작업 중인 제품에 집중하고 제품 수명 주기 동안 발생할 수 있는 변화에 대비할 수 있습니다.
두려워하지 마세요. 이 글은 이 딜레마에 대한 답을 제공하는 것을 목표로 합니다.
프론트엔드에 클린 아키텍처를 구현하려는 과정에서 저는 여러 풀스택 및 백엔드 개발자와 긴밀히 협력하여 프론트엔드 경험이 거의 없는 사람들도 아키텍처를 이해하고 유지 관리할 수 있도록 했습니다.
따라서 우리 아키텍처의 주요 요구 사항 중 하나는 복잡한 프런트엔드에 정통하지 않은 백엔드 개발자는 물론 광범위한 프런트엔드 전문 지식이 없는 풀스택 개발자를 위한 접근성입니다.
프런트엔드 팀과 백엔드 팀 간의 원활한 협력을 촉진함으로써 아키텍처는 격차를 해소하고 통합된 개발 환경을 만드는 것을 목표로 합니다.
불행하게도 멋진 것을 만들려면 배경 지식이 필요합니다. 기본 원칙에 대한 명확한 이해는 구현 프로세스를 용이하게 할 뿐만 아니라 아키텍처가 소프트웨어 개발의 모범 사례를 준수하도록 보장합니다.
이 섹션에서는 아키텍처 접근 방식의 기초를 형성하는 세 가지 주요 개념인 SOLID 원칙 , Clean Architecture (실제로 SOLID 원칙에서 유래) 및 Atomic Design을 소개합니다. 이러한 영역에 대해 강한 느낌이 든다면 이 섹션을 건너뛰어도 됩니다.
SOLID는 개발자가 확장 가능하고 유지 관리가 가능한 모듈식 소프트웨어를 만들도록 안내하는 5가지 설계 원칙을 나타내는 약어입니다.
이 주제를 더 깊이 탐구하고 싶다면(제가 강력히 권장하는), 문제 없습니다. 그러나 지금은 내가 제시한 것만으로도 충분합니다.
그리고 이 기사와 관련하여 SOLID는 우리에게 무엇을 제공합니까?
Robert C. Martin은 SOLID 원칙과 다양한 애플리케이션 개발 경험을 바탕으로 Clean Architecture 개념을 제안했습니다. 이 개념을 논의할 때 아래 다이어그램을 참조하여 해당 구조를 시각적으로 표현하는 경우가 많습니다.
따라서 클린 아키텍처는 새로운 개념이 아닙니다. 이는 기능적 프로그래밍 및 백엔드 개발을 포함한 다양한 프로그래밍 패러다임에서 널리 사용되었습니다.
Lodash와 같은 라이브러리 및 수많은 백엔드 프레임워크는 SOLID 원칙에 뿌리를 둔 이 아키텍처 접근 방식을 채택했습니다.
클린 아키텍처는 시스템을 쉽게 이해하고, 유지 관리하고, 수정할 수 있도록 만드는 것을 주요 목표로 하여 애플리케이션 내에서 우려 사항을 분리하고 독립적이고 테스트 가능한 레이어를 생성하는 것을 강조합니다.
아키텍처는 동심원 또는 레이어로 구성됩니다. 각각에는 명확한 경계, 종속성 및 책임이 있습니다.
클린 아키텍처는 외부 레이어에서 내부 레이어로의 종속성 흐름을 촉진하여 핵심 비즈니스 로직이 사용된 특정 기술이나 프레임워크로부터 독립적으로 유지되도록 보장합니다.
이를 통해 변화하는 요구 사항이나 기술 스택에 쉽게 적응할 수 있는 유연하고 유지 관리 및 테스트 가능한 코드베이스가 생성됩니다.
Atomic Design은 인터페이스를 가장 기본적인 요소로 분해한 다음 더 복잡한 구조로 재조립하여 UI 구성 요소를 구성하는 방법론입니다. Brad Frost는 2008년 "Atomic Design Methodology"라는 제목의 기사에서 이 개념을 처음 소개했습니다.
다음은 Atomic Design의 개념을 보여주는 그래픽입니다.
이는 5개의 서로 다른 레벨로 구성됩니다.
Atomic Design을 수용함으로써 개발자는 모듈성, 재사용성 및 UI 구성 요소의 명확한 구조와 같은 여러 가지 이점을 얻을 수 있습니다. 왜냐하면 디자인 시스템 접근 방식을 따라야 하기 때문입니다. 그러나 이는 이 기사의 주제가 아니므로 계속 진행하세요.
프론트엔드 개발을 위한 클린 아키텍처에 대한 박식한 관점을 개발하기 위해 저는 애플리케이션을 만드는 여정을 시작했습니다. 저는 6개월이라는 기간 동안 이 프로젝트를 진행하면서 귀중한 통찰력과 경험을 얻었습니다.
결과적으로, 이 기사 전반에 걸쳐 제공된 예제는 애플리케이션에 대한 실제 경험을 바탕으로 작성되었습니다. 투명성을 유지하기 위해 모든 예제는 공개적으로 액세스 가능한 코드에서 파생되었습니다.
다음 저장소를 방문하여 최종 결과를 탐색할 수 있습니다.
앞서 언급했듯이 온라인에서 사용할 수 있는 Clean Architecture의 구현이 많이 있습니다. 그러나 이러한 구현에서는 몇 가지 공통 요소를 식별할 수 있습니다.
이러한 공통점을 이해함으로써 우리는 클린 아키텍처의 기본 구조를 이해하고 이를 우리의 특정 요구에 맞게 조정할 수 있습니다.
우리 애플리케이션의 핵심 부분은 다음과 같습니다:
사용 사례 : 사용 사례는 데이터 저장, 업데이트, 가져오기 등 다양한 작업에 대한 비즈니스 규칙을 설명합니다. 예를 들어, 사용 사례에는 Notion에서 단어 목록을 가져오거나 사용자의 일일 학습 단어 연속 증가가 포함될 수 있습니다.
기본적으로 사용 사례는 비즈니스 관점에서 애플리케이션의 작업과 프로세스를 처리하여 시스템이 원하는 목표에 따라 작동하도록 보장합니다.
모델 : 모델은 애플리케이션 내의 비즈니스 엔터티를 나타냅니다. 이는 TypeScript 인터페이스를 사용하여 정의할 수 있으며 요구 사항 및 비즈니스 요구 사항에 부합하도록 보장할 수 있습니다.
예를 들어, 사용 사례에 Notion에서 단어 목록을 가져오는 것이 포함된 경우 적절한 비즈니스 규칙 및 제약 조건을 준수하면서 해당 목록의 데이터 구조를 정확하게 설명하는 모델이 필요합니다.
운영 : 때때로 특정 작업을 사용 사례로 정의하는 것이 불가능할 수도 있고 도메인의 여러 부분에 걸쳐 사용할 수 있는 재사용 가능한 기능을 만들고 싶을 수도 있습니다. 예를 들어, 이름으로 Notion 단어를 검색하는 함수를 작성해야 하는 경우 여기에 해당 작업이 있어야 합니다.
작업은 애플리케이션 내의 다양한 컨텍스트에서 공유하고 활용할 수 있는 도메인별 논리를 캡슐화하는 데 유용합니다.
저장소 인터페이스 : 사용 사례에는 데이터에 액세스하는 수단이 필요합니다. 종속성 역전 원칙에 따라 도메인 계층은 다른 계층에 종속되어서는 안 됩니다(다른 계층은 이에 종속됨). 따라서 이 계층은 저장소에 대한 인터페이스를 정의합니다.
구현 세부 사항이 아니라 인터페이스를 지정한다는 점에 유의하는 것이 중요합니다. 리포지토리 자체는 실제 데이터 소스에 구애받지 않고 해당 소스에서 데이터를 가져오거나 보내는 논리를 강조하는 리포지토리 패턴을 활용합니다.
단일 리포지토리가 여러 API를 구현할 수 있고 단일 사용 사례가 여러 리포지토리를 활용할 수 있다는 점을 언급하는 것이 중요합니다.
이 계층은 데이터 액세스를 담당하며 필요에 따라 다양한 소스와 통신할 수 있습니다. 우리가 프론트엔드 애플리케이션을 개발하고 있다는 점을 고려하면, 이 레이어는 주로 브라우저 API에 대한 래퍼 역할을 할 것입니다.
여기에는 REST용 API, 로컬 스토리지, IndexedDB, 음성 합성 등이 포함됩니다.
OpenAPI 유형과 HTTP 클라이언트를 생성하려는 경우 API 계층이 이를 배치하기에 이상적인 장소라는 점에 유의하는 것이 중요합니다. 이 레이어에는 다음이 포함됩니다.
API 어댑터 : API 어댑터는 당사 애플리케이션에서 사용되는 브라우저 API용 특수 어댑터입니다. 이 구성 요소는 REST 호출과 앱 메모리 또는 사용하려는 기타 데이터 소스와의 통신을 관리합니다.
원하는 경우 자체 객체 스토리지 시스템을 생성하고 구현할 수도 있습니다. 전용 API 어댑터를 사용하면 다양한 데이터 소스와 상호 작용하기 위한 일관된 인터페이스를 유지할 수 있으므로 필요에 따라 데이터 소스를 더 쉽게 업데이트하거나 변경할 수 있습니다.
리포지토리 계층은 여러 API의 통합을 관리하고, API별 유형을 도메인 유형에 매핑하고, 데이터 변환 작업을 통합함으로써 애플리케이션 아키텍처에서 중요한 역할을 합니다.
예를 들어 음성 합성 API를 로컬 저장소와 결합하려는 경우 이것이 완벽한 장소입니다. 이 레이어에는 다음이 포함됩니다.
어댑터 계층은 이러한 계층 간의 상호 작용을 조정하고 서로 연결하는 역할을 담당합니다. 이 계층에는 다음을 담당하는 모듈만 포함됩니다.
프리젠테이션 계층은 사용자 인터페이스(UI)를 렌더링하고 애플리케이션과 사용자 상호 작용을 처리하는 역할을 담당합니다. 어댑터, 도메인 및 공유 레이어를 활용하여 기능적이고 대화형 UI를 만듭니다.
프레젠테이션 계층은 Atomic Design 방법론을 사용하여 구성 요소를 구성하여 확장 가능하고 유지 관리 가능한 애플리케이션을 만듭니다. 그러나 이 레이어는 클린 아키텍처 구현 측면에서 주요 주제가 아니기 때문에 이 기사의 주요 초점은 아닙니다.
중앙 집중식 유틸리티, 구성, 공유 논리 등 모든 공통 요소에는 지정된 장소가 필요합니다. 그러나 이 기사에서는 이 레이어에 대해 너무 깊이 다루지 않을 것입니다.
애플리케이션 전체에서 공통 구성 요소를 관리하고 공유하는 방법에 대한 이해를 제공하는 것만으로도 언급할 가치가 있습니다.
이제 코딩을 시작하기 전에 테스트에 대해 논의하는 것이 중요합니다. 애플리케이션의 신뢰성과 정확성을 보장하는 것이 중요하며 아키텍처의 각 계층에 대해 강력한 테스트 전략을 구현하는 것이 중요합니다.
아키텍처의 각 계층에 대해 포괄적인 테스트 전략을 구현하면 애플리케이션의 안정성, 정확성 및 유지 관리 가능성을 보장하는 동시에 개발 중에 버그가 발생할 가능성을 줄일 수 있습니다.
그러나 소규모 애플리케이션을 구축하는 경우 어댑터 계층에 대한 통합 테스트로 충분합니다.
자, 이제 클린 아키텍처에 대해 확실하게 이해하고 그에 대한 자신의 의견을 형성했으므로 좀 더 깊이 들어가 실제 코드를 살펴보겠습니다.
여기서는 간단한 예만 제시할 것임을 명심하세요. 그러나 더 자세한 예제에 관심이 있다면 이 기사의 시작 부분에 언급된 내 GitHub 저장소를 자유롭게 탐색해 보세요.
"실생활"에서 클린 아키텍처는 대규모 엔터프라이즈 수준 애플리케이션에서 진정으로 빛을 발하는 반면, 소규모 프로젝트에서는 과잉일 수 있습니다. 그 말로 요점을 살펴 보겠습니다.
내 애플리케이션을 예로 사용하여 API 호출을 수행하여 특정 단어에 대한 사전 제안을 가져오는 방법을 보여 드리겠습니다. 이 특정 API 엔드포인트는 두 웹사이트를 웹 스크래핑하여 의미와 예제 목록을 검색합니다.
비즈니스 관점에서 볼 때 이 끝점은 사용자가 특정 단어를 검색할 수 있는 "단어 찾기" 보기에 매우 중요합니다. 사용자가 단어를 찾아 로그인하면 웹에서 스크랩한 정보를 Notion 데이터베이스에 추가할 수 있습니다.
시작하려면 이전에 논의한 레이어를 정확하게 반영하는 폴더 구조를 설정해야 합니다. 구조는 다음과 유사해야 합니다.
client ├── adapter ├── api ├── domain ├── presentation ├── repository └── shared
클라이언트 디렉터리는 많은 프로젝트의 "src" 폴더와 비슷한 용도로 사용됩니다. 이 특정 Next.js 프로젝트에서는 프런트엔드 폴더를 "클라이언트"로, 백엔드 폴더를 "서버"로 명명하는 규칙을 채택했습니다.
이 접근 방식을 사용하면 애플리케이션의 두 가지 주요 구성 요소를 명확하게 구분할 수 있습니다.
프로젝트에 적합한 폴더 구조를 선택하는 것은 실제로 개발 프로세스 초기에 내려야 하는 중요한 결정입니다. 리소스 구성과 관련하여 개발자마다 각자의 선호도와 접근 방식이 있습니다.
일부는 페이지 이름별로 리소스를 그룹화할 수 있고, 다른 일부는 OpenAPI에서 생성된 하위 디렉터리 명명 규칙을 따를 수 있으며, 다른 일부는 해당 솔루션 중 하나를 보증하기에는 애플리케이션이 너무 작다고 생각할 수 있습니다.
핵심은 명확하고 유지 관리 가능한 리소스 구성을 유지하면서 프로젝트의 특정 요구 사항과 규모에 가장 적합한 구조를 선택하는 것입니다.
나는 세 번째 그룹에 속하므로 내 구조는 다음과 같습니다.
client ├── adapter │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ └── supabase ├── api │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ └── supabase ├── domain │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ ├── supabase └── repository ├── local-storage ├── rest ├── speech-synthesis └── supabase
더 깊이 탐구하고 싶은 사람들은 더 많은 정보를 위해 내 저장소를 참조할 수 있다고 믿기 때문에 이 기사에서는 공유 및 프레젠테이션 레이어를 생략하기로 결정했습니다. 이제 몇 가지 코드 예제를 진행하여 Clean Architecture가 프런트엔드 애플리케이션에 어떻게 적용될 수 있는지 살펴보겠습니다.
우리의 요구 사항을 고려해 봅시다. 사용자로서 나는 제안의 의미와 예를 포함한 제안 목록을 받고 싶습니다. 따라서 단일 사전 제안은 다음과 같이 모델링될 수 있습니다.
interface DictionarySuggestion { example: string; meaning: string; }
이제 단일 사전 제안을 설명했으므로 웹 스크래핑을 통해 얻은 단어가 사용자가 입력한 단어와 다르거나 수정되는 경우가 있다는 점을 언급하는 것이 중요합니다. 이를 수용하기 위해 나중에 앱에서 수정된 버전을 사용할 것입니다.
따라서 사전 제안 및 단어 수정 목록을 포함하는 인터페이스를 정의해야 합니다. 최종 인터페이스는 다음과 같습니다.
export interface DictionarySuggestions { suggestions: DictionarySuggestion[]; word: string; }
이 인터페이스를 내보내는 중이므로 export
키워드가 포함되어 있습니다.
우리는 모델을 가지고 있고 이제 그것을 사용할 시간입니다.
import { DictionarySuggestions } from './rest.models'; export interface RestRepository { getDictionarySuggestions: (word: string) => Promise<DictionarySuggestions | null>; }
이 시점에서는 모든 것이 명확해야 합니다. 여기서는 API에 대해 전혀 논의하지 않는다는 점에 유의하는 것이 중요합니다! 저장소 자체의 구조는 매우 간단합니다. 각 메소드가 특정 유형의 데이터를 비동기적으로 반환하는 몇 가지 메소드가 있는 객체일 뿐입니다.
리포지토리는 항상 도메인 모델 형식으로 데이터를 반환한다는 점을 명심하세요.
이제 비즈니스 규칙을 사용 사례로 정의해 보겠습니다. 코드는 다음과 같습니다.
export type GetDictionarySuggestionsUseCaseUseCase = UseCaseWithSingleParamAndPromiseResult< string, DictionarySuggestions | null >; export const getDictionarySuggestionsUseCase = ( restRepository: RestRepository, ): GetDictionarySuggestionsUseCaseUseCase => ({ execute: (word) => restRepository.getDictionarySuggestions(word), });
가장 먼저 주목해야 할 것은 사용 사례를 정의하는 데 사용되는 일반 유형 목록입니다. 이를 달성하기 위해 도메인 디렉터리에 use-cases.types.ts
파일을 만들었습니다.
domain ├── local-storage ├── rest ├── speech-synthesis ├── supabase └── use-cases.types.ts
이를 통해 하위 디렉터리 간에 사용 사례 유형을 쉽게 공유할 수 있습니다. UseCaseWithSingleParamAndPromiseResult
의 정의는 다음과 같습니다.
export interface UseCaseWithSingleParamAndPromiseResult<TParam, TResult> { execute: (param: TParam) => Promise<TResult>; }
이 접근 방식은 도메인 계층 전체에서 사용 사례 유형의 일관성과 재사용성을 유지하는 데 도움이 됩니다.
왜 execute
함수가 필요한지 궁금할 것입니다. 여기에는 실제 사용 사례를 반환하는 팩토리가 있습니다.
이 디자인 선택은 사용 사례 코드에서 직접 저장소 구현을 참조하고 싶지 않으며 가져오기에서 저장소를 사용하는 것을 원하지 않는다는 사실 때문입니다. 이 접근 방식을 사용하면 나중에 종속성 주입을 쉽게 적용할 수 있습니다.
팩토리 패턴과 execute
기능을 사용하면 리포지토리의 구현 세부 정보를 사용 사례 코드와 별도로 유지할 수 있어 애플리케이션의 모듈성과 유지 관리성이 향상됩니다.
이 접근 방식은 도메인 계층이 다른 계층에 의존하지 않는 종속성 역전 원칙을 따르며, 다른 저장소 구현을 교체하거나 애플리케이션 아키텍처를 수정할 때 더 큰 유연성을 제공합니다.
먼저 인터페이스를 정의해 보겠습니다.
export interface RestApi { getDictionarySuggestions: (word: string) => Promise<AxiosResponse<DictionarySuggestions>>; }
보시다시피 인터페이스의 이 함수 정의는 저장소의 정의와 매우 유사합니다. 도메인 유형이 이미 응답을 설명하므로 동일한 유형을 다시 만들 필요가 없습니다.
API가 원시 데이터를 반환하므로 전체 AxiosResponse<DictionarySuggestions>
반환한다는 점에 유의하는 것이 중요합니다. 이를 통해 API와 도메인 레이어 간의 명확한 분리를 유지하여 데이터 처리 및 변환에 더 많은 유연성을 제공합니다.
이 API의 구현은 다음과 같습니다.
export const getRestApi = (axiosInstance: AxiosInstance): RestApi => ({ getDictionarySuggestions: async (word: string) => { const encodedCurrentDate = encodeURIComponent(word); const response = await axiosInstance.get( `${RestEndpoints.GET_DICTIONARY_SUGGESTIONS}?word=${encodedCurrentDate}`, ); return response; } });
이 시점에서 상황은 더욱 흥미로워집니다. 논의할 첫 번째 중요한 측면은 axiosInstance
삽입입니다. 이는 우리의 코드를 매우 유연하게 만들고 견고한 테스트를 쉽게 구축할 수 있게 해줍니다. 이는 쿼리 매개변수의 인코딩이나 구문 분석을 처리하는 곳이기도 합니다.
그러나 여기에서 입력 문자열 자르기와 같은 다른 작업을 수행할 수도 있습니다. axiosInstance
를 주입함으로써 우리는 우려 사항을 명확하게 분리하고 API 구현이 외부 서비스의 다양한 시나리오나 변경 사항에 적응할 수 있도록 보장합니다.
인터페이스가 이미 도메인에 의해 정의되었으므로 저장소를 구현하기만 하면 됩니다. 따라서 최종 구현은 다음과 같습니다.
export const getRestRepository = (restApi: RestApi): RestRepository => ({ getDictionarySuggestions: async (word) => { const { data } = await restApi.getDictionarySuggestions(word); if (!data?.suggestions?.length) { return null; } return formatDictionarySuggestions(data); } });
언급해야 할 중요한 측면은 API와 관련이 있습니다. getRestRepository
하면 이전에 정의된 restApi
전달할 수 있습니다. 앞서 언급했듯이 테스트가 더 쉬워지기 때문에 이점이 있습니다. formatDictionarySuggestions
간략하게 살펴보겠습니다.
export const formatDictionarySuggestions = ({ suggestions, word, }: DictionarySuggestions): DictionarySuggestions => { const cleanedWord = cleanUpString(word); const cleanedSuggestions = suggestions.map((_suggestion) => { const cleanedMeaning = cleanUpString(_suggestion.meaning); const cleanedExample = cleanUpString(_suggestion.example); return { meaning: cleanedMeaning, example: cleanedExample, }; }); return { word: cleanedWord, suggestions: cleanedSuggestions, }; };
이 작업은 도메인 DictionarySuggestions
모델을 인수로 사용하고 문자열 정리를 수행합니다. 즉, 불필요한 공백, 줄 바꿈, 탭 및 대문자를 제거합니다. 숨겨진 복잡성 없이 매우 간단합니다.
주목해야 할 중요한 점은 이 시점에서는 API 구현에 대해 걱정할 필요가 없다는 것입니다. 참고로 저장소는 항상 도메인 모델의 데이터를 반환합니다! 그렇게 하면 종속성 역전의 원칙이 깨지기 때문에 그렇지 않을 수 없습니다.
그리고 지금은 도메인 레이어가 외부에 정의된 어떤 것에도 의존하지 않습니다.
이 시점에서 모든 것이 구현되고 종속성 주입을 위한 준비가 완료되어야 합니다. 나머지 모듈의 최종 구현은 다음과 같습니다.
import { getRestRepository } from '@repository/rest/rest.repository'; import { getRestApi } from '@api/rest/rest.api'; import { getDictionarySuggestionsUseCase } from '@domain/rest/rest.use-cases'; import { axiosInstance } from '@shared/axios.instance'; const restApi = getRestApi(axiosInstance); const restRepository = getRestRepository(restApi); export const restModule = { getDictionarySuggestions: getDictionarySuggestionsUseCase(restRepository).execute, };
좋아요! 우리는 특정 프레임워크에 얽매이지 않고 Clean Architecture 원칙을 구현하는 과정을 거쳤습니다. 이 접근 방식을 사용하면 코드의 적응성이 보장되므로 필요한 경우 프레임워크나 라이브러리를 쉽게 전환할 수 있습니다.
테스트와 관련하여 저장소를 확인하는 것은 이 아키텍처에서 테스트가 어떻게 구현되고 구성되는지 이해하는 좋은 방법입니다.
Clean Architecture의 탄탄한 기반을 바탕으로 다양한 시나리오를 포괄하는 포괄적인 테스트를 작성하여 애플리케이션을 더욱 강력하고 안정적으로 만들 수 있습니다.
입증된 바와 같이 클린 아키텍처 원칙을 따르고 우려 사항을 분리하면 유지 관리, 확장 및 테스트 가능한 애플리케이션 구조가 만들어집니다.
이 접근 방식을 사용하면 궁극적으로 더 쉽게 새로운 기능을 추가하고, 코드를 리팩터링하고, 프로젝트에서 팀과 협력하여 애플리케이션의 장기적인 성공을 보장할 수 있습니다.
예제 애플리케이션에서는 프레젠테이션 레이어로 React가 사용되었습니다. 어댑터 디렉토리에는 나머지 모듈과의 상호 작용을 처리하는 hooks.ts
라는 추가 파일이 있습니다. 이 파일의 내용은 다음과 같습니다.
import { restModule } from '@adapter/rest/rest.module'; import { useAxios } from '@shared/hooks'; export const useDictionarySuggestions = () => { const { data, error, isLoading, mutate } = useAxios(restModule.getDictionarySuggestions); return { dictionarySuggestions: data, getDictionarySuggestions: mutate, dictionarySuggestionsError: error, isDictionarySuggestionsLoading: isLoading, }; };
이 구현을 통해 프레젠테이션 레이어 작업이 매우 쉬워졌습니다. useDictionarySuggestions
후크를 사용하면 프리젠테이션 계층은 기본 기능과 관련되지 않은 데이터 매핑 또는 기타 책임 관리에 대해 걱정할 필요가 없습니다.
이러한 우려 사항의 분리는 클린 아키텍처의 원칙을 유지하는 데 도움이 되어 보다 관리하기 쉽고 유지 관리하기 쉬운 코드로 이어집니다.
무엇보다도 제공된 GitHub 저장소의 코드를 자세히 살펴보고 그 구조를 살펴보시기 바랍니다.
너는 어떤 다른 일을 할 수 있니? 하늘이 한계입니다! 그것은 모두 특정 디자인 요구 사항에 따라 다릅니다. 예를 들어 데이터 저장소(Redux, MobX 또는 맞춤형 항목 - 상관없음)를 통합하여 데이터 계층을 구현하는 것을 고려할 수 있습니다.
또는 RxJS를 사용하여 백엔드와의 비동기 통신을 처리하는 것과 같이 레이어 간의 다양한 통신 방법을 실험할 수 있습니다. 여기에는 폴링, 푸시 알림 또는 소켓(기본적으로 모든 데이터 소스에 대해 준비 중)이 포함될 수 있습니다.
본질적으로 계층화된 아키텍처를 유지하고 역의존성 원칙을 준수하는 한 원하는 대로 자유롭게 탐색하고 실험할 수 있습니다. 항상 도메인이 디자인의 핵심이 되도록 하세요.
이렇게 하면 다양한 시나리오와 요구 사항에 적응할 수 있는 유연하고 유지 관리 가능한 애플리케이션 구조를 만들 수 있습니다.
이 기사에서 우리는 React를 사용하여 구축된 언어 학습 애플리케이션의 맥락에서 클린 아키텍처의 개념을 탐구했습니다.
우리는 계층화된 아키텍처를 유지하고 역의존성 원칙을 고수하는 것의 중요성과 관심사 분리의 이점을 강조했습니다.
Clean Architecture의 중요한 장점은 특정 프레임워크에 얽매이지 않고 애플리케이션의 엔지니어링 측면에 집중할 수 있다는 것입니다. 이러한 유연성을 통해 애플리케이션을 다양한 시나리오와 요구 사항에 맞게 조정할 수 있습니다.
그러나 이 접근 방식에는 몇 가지 단점이 있습니다. 경우에 따라 엄격한 아키텍처 패턴을 따르면 상용구 코드가 증가하거나 프로젝트 구조가 더 복잡해질 수 있습니다.
또한 문서에 덜 의존하는 것은 장점이자 단점이 될 수 있습니다. 이는 더 많은 자유와 창의성을 허용하지만 팀 구성원 간의 혼란이나 잘못된 의사소통을 초래할 수도 있습니다.
이러한 잠재적인 문제에도 불구하고 Clean Architecture를 구현하는 것은 특히 보편적으로 수용되는 아키텍처 패턴이 없는 React의 맥락에서 매우 유익할 수 있습니다.
수년간의 어려움을 겪은 후 문제를 해결하기보다는 프로젝트 시작 시 아키텍처를 고려하는 것이 중요합니다.
Clean Architecture의 실제 사례를 살펴보려면 다음 위치에서 내 저장소를 확인하세요.
와, 아마도 제가 쓴 글 중 가장 긴 글이 될 것 같습니다. 정말 믿을 수 없을 것 같아요!