Skip to content

Architecture - MVI & Clean Architecture #39

@nuagenic

Description

@nuagenic

공부하면서 느낀 거 적으려다 보니 PR 리뷰로는 좀 길어질 거 같아서 따로 이슈로 작성해 보겠습니다. 제가 맞게 이해하고 있는지, 혹시 잘못된 부분 있으면 코멘트 남겨주시면 감사하겠습니다.

우리의 목표

Clean Architecture와 MVI 패턴을 결합시키는 것. 현재 우리의 레포지토리는 Clean Architecture만 따르고 있습니다.

Clean Architecture 리뷰

Clean Architecture 자체는 Domain Layer-Application Layer-Adapter Layer로 이루어져 있으며,
우리는 이를 Entity-Usecase-(백엔드 서버와 통신하는) Usecase의 구현체(React Hook)로 대응시켰습니다.

사실 아직 불명확한 것은, Usecase의 구현체를 사용하는 UI 단위의 modules도 Adapter Layer에 포함되는가? 입니다만, Clean Architecture 안에서는 로직만 다룬다고 생각해 별개의 영역으로 생각했습니다.

User의 경우

  1. User Entity가 있다.
    • 유저에 대한 추상화된 객체. 들어가야 하는 속성과 type만 명시해줄 뿐
  2. User를 다루는 usecase가 있다.
    • 여기서 중요한 것은 User가 주어가 아니라 목적어에 해당한다는 점이 아닐까 싶습니다. User가 Video를 보는 시나리오는 Video의 usecase로 분류되지만, User가 User의 정보를 수정하는 시나리오는 User의 usecase일 테니까요. 사실 웹 내에서 이루어지는 거의 모든 시나리오의 주어가 User이기도 하고요.
    • 그렇다면 User를 다루는 usecase에서 주어와 목적어는 고정된 셈입니다. 따라서 각각의 usecase들을 나누는 것은 동사일 것입니다. 따라서 모든 usecase의 시작을 동사로 하는 것이 어떨지 제안합니다. 현재는 UserAccessServiceCommentService 등이 존재하는데, 이들을 LoginServiceGetCommentService로 수정하는 것이 더 직관적이고 바람직해 보입니다.
    • 그렇게 된다면 User Entity의 경우 가능한 usecase는 다음과 같습니다.
      • SigninService : 회원가입을 위한 유즈케이스
      • LoginService : 로그인을 위한 유즈케이스
      • SignoutService : 회원 탈퇴를 위한 유즈케이스
      • LogoutService : 로그아웃을 위한 유즈케이스
      • ReadMeService : 내 유저 정보를 조회하는 유즈케이스 (마이페이지 등)
      • UpdateMeService: 내 유저 정보를 수정하는 유즈케이스
      • ReadFollowerService : 내 팔로워를 조회하는 유즈케이스
      • ReadFollowingService : 내 팔로잉을 조회하는 유즈케이스
    • 사실 이렇게까지 세분화가 되야 할지는 잘 모르겠습니다... 지금은 거의 API 엔드포인트 하나에 하나의 유즈케이스를 대응시킨 셈인데, 보통 이렇게 대응시키는 게 맞을까요? 가령 지금 구현되어 있는 userAccessService의 경우, 로그인과 로그아웃이 하나의 유즈케이스로 구현되어 있습니다. 유즈케이스를 구분하는 기준이 정확히 무엇일지 자문이 필요합니다..
  3. 상황에 맞는 DTO를 주입시켜 유즈케이스의 시나리오를 구현하는 리액트 훅이 있다.

MVI와 어떻게 연결되는가?

MVI를 리액트 내에서 어떻게 구현해야 할까... 여기 Readme를 참고해 보았는데, 간단하게 정리하자면 이렇습니다.

  • model은 (명사에 대한) 객체다. 이것은 Clean Architecture의 Entity와 잘 대응된다.
  • viewmodel을 파라미터로 받고, DOM 구조를 리턴하는 함수다. (즉, 리액트 관점에서는 컴포넌트다)
  • intent는 (동사에 대한) 객체다. 즉, model에 대해 가능한 행위들의 모음이다.
let intents = {
  TICK: 'TICK',
  START: 'START',
  STOP: 'STOP',
  RESET: 'RESET'
};
  • updatemodelintent를 파라미터로 받고, 새로운 model을 리턴하는 함수다.
const update = (model,intent) => {
    console.log(model,intent)
    const updates = {
        'START': (model) => Object.assign(model, { running: true }),
        'STOP': (model) => Object.assign(model, { running: false }),
        'TICK': (model) => Object.assign(model, { time: model.time + (model.running ? 1 : 0 )})
    };
    return updates[intent](model);
}

즉, view 부분에서 이밴트핸들러 등으로 호출하는 것은 intent가 아니라 intent를 파라미터로 가지는 update라는 함수입니다. 요런 식으로요.

let handler = (event) => {
        model = update(model, model.running ? 'STOP' : 'START');
    };

이 구조를 참조해서, 저희 Clean Architecture에 적용을 한다면, 유즈케이스의 구현체인 리액트 훅은 model(현재 우리 아키텍쳐 안에서는 DTO라고 생각됨)을 반환하는 함수 형태여야 할 것이고, 이 함수에 파라미터로 들어가는 intent는 다른 방식으로 정리가 되어야 하지않을까 하는데... 우선 여기까지만 생각해보았고 그렇다면 어떻게 정리할 것인지는 더 고민을 해봐야 할 것 같습니다.

Metadata

Metadata

Labels

enhancementNew feature or requesthelp wantedExtra attention is needed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions