MVVM 디자인 패턴에 따른 파일 디렉토리 구조 만들기

Addie L.·2020년 5월 26일
6

참고 아티클

MVVM 패턴

MVVM패턴이란, 아키텍쳐에서 entity와 usecase를 포함한 model계층, repository혹은 presenter역할을 맡는 중간 계층인 infrastructure계층에 해당하는 view model, 가장 바깥의 ui를 담당하는 view 계층을 분리해 의존성 규칙에 따라 관심사를 분리한 형태의 디자인 패턴을 말합니다.

계층화 아키텍쳐의 예시

MVVM 패턴의 레이어

대부분의 중/대규모 어플리케이션은 효율적인 개발과 유지보수를 위해 계층을 분리시켜 개발한다. 이러한 계층화 아키텍쳐를 따르는 몇가지 디자인 패턴이 있는데, 그중 하나가 MVVM 패턴이다.

디자인 패턴을 따를때의 장점

가장 바깥쪽 계층인 framework 계층을 수정해도 안쪽 비즈니스 로직이 이 수정사항에 의해 영향받지 않는다. 즉, 독립적으로 뷰계층을 수정하거나 Data source를 바꿀 수 있다.
가장 내부에 위치한 모델 컴포넌트는 다른 컴포넌트들로부터 데이터 구조와 같은 내부적인 사항을 숨길 수 있고, 이때 interface를 선언해준다면 로직 재사용이 가능해진다.
또한, 해당 컴포넌트에 관련된 코드를 분리시켜 다른 컴포넌트로 옮겨가는 것 또한 수월해진다.

interface 선언 후 구현체 만들기

interface를 선언 할 시 주의 할 점은 클라이언트가 필요로 하는 메소드를 기반으로 분리되어야한다는 것이다. 이렇게 인터페이스를 분리하면 세부사항을 분리시킬 수 있다. 이 interface를 상속받아 class를 만들게 되면 추상이 되고, 이 class의 인스턴스를 만들면 구상화 된다.

수준편차에 따른 의존성 규칙 (시스템 상호의존도)

계층사이에서 서로 의존과 간섭이 없을때 clean architecture라고 해주는데, 이러한 클린 아키텍쳐가 동작하기 위해서는 의존성 규칙을 지켜야한다.

이 의존성 규칙에서는 반드시 모든 소스코드 의존성이 외부에서 내부로, 저수준에서 고수준으로 향해야한다.

고수준과 저수준은 추상화의 정도에 따라 분류될 수 있다.
추상화가 많이 되어 있을수록 고수준으로 분류된다.

추상화란, 대상에서 특징을 뽑아낸 것으로 프로그래밍에서는 interface 안에 함수를 쓰는게 추상화를 하는 방법이다. 코드를 썼을 때 어떤 내용을 수행하는지가 한눈에 파악되지않을때 추상적이라고 말할 수 있다.

가장 안쪽의 비즈니스 로직은 바깥쪽 원에서 무엇이 변경되더라도 바뀌지않아야한다.

이 비즈니스 로직을 담당하는 Model 계층은 entity와 usecase로 구성되어있는데,
entity란 비즈니스 로직을 캡슐화한 것으로 가장 일반적이면서도 고수준의 규칙을 캡슐화한 모듈이고, usecase는 entity로부터, 혹은 entity에서의 데이터흐름을 조합한다. 이 계층또한 entity에 영향을 주지 않으며 데이터베이스, UI, 혹은 공통의 프레임워크 변경으로부터 영향받지 않는다.

만약, 이 영향을 주는 쪽 즉, interface를 구현한 구현체가 저수준의 정책을 직접 참조하게된다면 의존성 규칙을 위반한 것으로, 해결책은 의존성 역전의 원칙을 적용해 저수준의 정책이 변경되어도 고수준의 도메인에서는 변경에 대한 영향이 없도록 만들어 줄 수 있다.

MVVM패턴에 따라 계층을 분리한 디렉토리 만들기

실제로 이 MVVM패턴에 따라 계층이 분리된 디렉토리 구조를 만들게 되면,
root폴더의 src밑에 크게 세가지 디렉토리로 나뉘어질 수 있다.
app, data, domain 세가지이다.

작업 순서

entity > usecase > data api > api interactor(repository) > view model > view

interface를 상속한 구현체를 만들때, usecase, data, repository, view model에 해당하는 구현체들을 만든다. View에서만 구현체가 없는 이유는 이 구현체를 가져와 변경해주거나 로직을 넣어주거나 할 계층이 없기때문이다. 구현체 클래스를 만들때는 끝에 Impl을 붙여만든다.

예시)

Domain의 usecase부터 구현체를 만들어주는데,

  1. entity > index.d.ts파일: 비즈니스 로직을 캡슐화시킨 객체형태의 모듈을 만들어준다.
export interface Home {
    head: Head;
    contents: Array<Content>;
    aboutCustomers: AboutCustomers;
}
  1. usecase > index.d.ts에 나중에 구현체를 만들어 줄 interface를 만든다.
    -entity를 import 받아서, 그 entity를 받을 형식을 써준다.
import {
    Home, 
    Service,
    AboutUs,
    PressPage,
    CommonSource,
    PartnerList
} from "domain/entity";
export interface GetHome {
    execute(): Promise<Home>;
}

...

execute()함수에 담길 값은 Promise객체형태인데 그 객체 안에 Home과 관련된 값만 들어갈 수 있다는 의미다.

  1. data > api > index.d.ts에 Home과 관련된 api를 만들기위해 묶어준다.
import {
    Home,
    Service,
    AboutUs,
    PressPage,
    CommonSource
} from "domain/entity";

export interface HomeApi {
    getHome(): Promise<Home>;
}
...
  1. app> viewmodel > index.d.ts에 만들어준 usecase와 api 중간에 view model을 페이지단위로 만들어준다.
import {
    Home,
    Service,
    AboutUs,
    PressPage,
    CommonSource
} from "../../domain/entity";

export interface HomeViewModel {
    displayHome(): Promise<Home>;
    displayPressPage(): Promise<PressPage>;
}

...
  1. view로 넘어와 통신결과를 그려준다.
...

return (
    <>
      <Nav pathName={props.location.pathname} />
      {!data_home ? (
        <div>loading,,,</div>
      ) : (
        <>
          <HeadSection
            backgroundImg={data_home.head.backgroundImage}
            title={data_home.head.title}
            subTitle={data_home.head.subTitle}
            desc={data_home.head.description}
            buttonName={data_home.head.buttonText}
            buttonLink={"/service"}
          />
          {data_home.contents.map((content: any, id: number) => {
            return (
              <ContentSection
                key={id}
                sectionIndex={id + 1}
                contentSection_data={content}
                buttonName={content.buttonText}
                buttonLink={"/service"}
              />
            );
          })}
          <HomeCustomersSection homeCustomers_data={DATA_cumtomers} />
          <PartnersSection />
          {data_press && <PressSection press_data={data_press} />}
        </>
      )}
      <Footer />
    </>
  );
};

주의할점은, 이제 view를 뺀 나머지 계층들은 모두 interactor 즉 interface를 상속받은 구현체를 만들어줘야하는데, 이때 view model의 구현체는 data폴더 아래 repositoryImpl을 달아 만들고, 구현체마다 (구현체라는것을 표시하기위해 Impl을 붙여서 ) 폴더링을해주면된다.

전체 폴더 구조

.
├── app
│   ├── App.tsx
│   ├── view
│   │   ├── CompanyView.tsx
│   │   ├── HomeView.tsx
│   │   ├── PressView.tsx
│   │   ├── PrivacyPolicyView.tsx
│   │   ├── ServiceView.tsx
│   │   ├── ShopListView.tsx
│   │   ├── SurveyView.tsx
│   │   ├── TermOfUseView.tsx
│   │   └── widget
│   │   ├── AccountSurvey.tsx
│   │   ├── Button.tsx
│   │   ├── CarouselSection.tsx
│   │   ├── ContentDetailSection.tsx
│   │   ├── ContentSection.tsx
│   │   ├── DeliverySurvey.tsx
│   │   ├── Footer.tsx
│   │   ├── HeadSection.tsx
│   │   ├── HomeCustomerCard.tsx
│   │   ├── HomeCustomerSection.tsx
│   │   ├── Nav.tsx
│   │   ├── PartnersSection.tsx
│   │   ├── PressCard.tsx
│   │   ├── PressSection.tsx
│   │   ├── PromotionSurvey.tsx
│   │   ├── Style.tsx
│   │   ├── SurveyMenu.tsx
│   │   └── ThreePLSurvey.tsx
│   └── viewmodel
│   ├── AboutUsViewModelImpl.ts
│   ├── CommonSourceViewModelImpl.ts
│   ├── HomeViewModelImpl.ts
│   ├── PressPageViewModelImpl.ts
│   ├── ServiceViewModelImpl.ts
│   ├── ShopViewModelImpl.ts
│   └── index.d.ts
├── data
│   ├── DATA_commonsource.ts
│   ├── DATA_customers.ts
│   ├── DATA_home.ts
│   ├── DATA_partners.ts
│   ├── DATA_press.ts
│   ├── DATA_service.ts
│   ├── api
│   │   ├── AboutUsApiImpl.ts
│   │   ├── CommonSourceImpl.ts
│   │   ├── HomeApiImpl.ts
│   │   ├── PressPageApiImpl.ts
│   │   ├── ServiceApiImpl.ts
│   │   ├── ShopListApiImpl.ts
│   │   ├── index.d.ts
│   │   └── manager
│   │   └── ApiManager.ts
│   └── interactor
│   └── repository
│   ├── AboutUsRepositoryImpl.ts
│   ├── CommonSourceRepositoryImpl.ts
│   ├── HomeRepositoryImpl.ts
│   ├── PressPageRepositoryImpl.ts
│   ├── ServiceRepositoryImpl.ts
│   └── ShopRepositoryImpl.ts
├── domain
│   ├── entity
│   │   └── index.d.ts
│   ├── interactor
│   │   └── repository
│   │   └── index.d.ts
│   └── usecase
│   ├── aboutUs
│   │   └── GetAboutUsImpl.ts
│   ├── commonSource
│   │   └── GetCommonSourceImpl.ts
│   ├── home
│   │   └── GetHomeImpl.ts
│   ├── index.d.ts
│   ├── pressPage
│   │   └── GetPressPageImpl.ts
│   ├── service
│   │   └── GetServiceImpl.ts
│   └── shop
│   └── GetShopListImpl.ts
├── index.tsx
└── injector.ts

3개의 댓글

comment-user-thumbnail
2023년 1월 6일

Doing getting! https://www.bangaloreescortindia.co.in/ Your substance is especially major for us to give such a stunning article.

답글 달기