[클린 아키텍처] 03. 코드 구성하기

Jimin Lim·2023년 5월 2일
0

Architecture

목록 보기
3/23
post-thumbnail

✅ 03. 코드 구성하기

🔗 패키지 구조

1️⃣ 계층으로 구성

  • web -> domain <- persistence (domain만 의존하도록)
    • domain 패키지에 AccountRepository 인터페이스를 두고, persistence에 AccountRepositoryImpl을 둠으로써 의존성을 역전시킨다.
buckpal
|- domain
|   |- Account
|   |- Activity
|   |- AccountRepository
|   |- AccountService
|- persistence
|   |- AccountRepositoryImpl
|- web
    |- AccountController

하지만 이 방법은 아래의 세 가지 이유로 최적의 구조가 아니다.

  1. 애플리케이션의 기능 조각이나 특성을 구분 짓는 패키지 경계가 없다.
    기능을 추가한다면 아래와 같이 코드를 추가하게 된다.
    web - controller
    domain - UserService, UserRepository, User
    persistence - UserRepositoryImpl
    User, Account에 대한 경계가 없어 엉망진창 묶음으로 변모할 가능성이 크다.

  2. 어떤 유스케이스들을 제공하는지 파악할 수 없다.
    AccountService, AccountController가 어떤 유스케이스를 구현했는지 파악하기 어렵고 해당 서비스 내의 어떤 메서드가 그에 대한 책임을 수행하는지 찾아야 한다.

  3. 목표로 하는 아키텍처를 파악할 수 없다.
    incoming 포트와 outgoing 포트가 코드 속에 숨겨져있다.

2️⃣ 기능으로 구성

  • 계층을 없애고 기능별로 묶은 패키지다. 계좌와 관련된 모든 코드를 최상위 account 패키지에 넣는다.
buckpal
| - account
    |- Account
    |- AccountController
    |- AccountRepository
    |- AccountRepositoryImpl
    |- SendMoneyService

패키지 경계를 package-private 접근 수준을 이용해 패키지 간 경계를 강화시킬 수 있다.

하지만 위의 방식은 계층형보다 가시성을 훨씬 더 떨어뜨린다. 어댑터를 나타내는 패키지명이 없어, 인커밍 포트/아웃 고잉 포트를 확인하기 어렵다.

3️⃣ 아키텍처적으로 표현력 있는 패키지 구조

헥사고날 아키텍처에서 핵심적인 요소는 (1) 엔티티, (2) 유스케이스, (3) 인커밍/아웃고잉 포트, (4) 인커밍/아웃고인 어댑터다.

buckpal
  |- account
      |- adapter
      |   |- in
      |   |  |- web
      |   |      |- AccountController
      |   |        
      |   |       
      |   |- out
      |   |  |- persistence
      |   |      |- AccountPersistenceAdapter
      |   |      |- SpringDataAccountRepository
      |- domain
      |   |- Account
      |   |- Activity
      |- application
          |- SendMoneyService
          |- port
          |  |- in
          |      |- SendMoneyUseCase
          |  |- out
          |      |- LoadAccountPort
          |      |- UpdateAccountStatePort
  • 최상위: account 패키지, Account와 관련된 유스케이스 구현한 모듈임을 나타냄
  • application 패키지: 서비스 계층 포함, SendMoneyService는 SendMoneyUseCase(in)를 구현하고, LoadAccountPort, UpdateAccountStatePort(out)를 사용한다.
  • adapter 패키지: application 계층의 인커밍 포트를 호출하는 인커밍 어댑터와, application 계층의 아웃고잉 포트에 대한 구현을 제공하는 아웃고잉 어댑터를 포함한다.

패키지가 많다면 모두 public으로 만들어 다른 패키지끼리 접근이 가능하도록 해야한다는 것처럼 보인다. 하지만 어댑터 패키지에 대해서는 그렇지 않다. adapter의 모든 클래스들은 application 내에 있는 포트 인터페이스를 통하지 않고는 외부에서 호출되지 않기에 package-private 접근 수준으로 둬도 된다.

하지만 application 패키지와 domain 패키지에서 의도적으로 어댑터에서 접근 가능해야 하는 포트는 public이어야 한다. 서비스는 인터페이스 뒤에 숨겨질 수 있으므로 public 일 필요가 없다.

🔗 의존성 주입의 역할

애플리케이션 계층이 인커밍/아웃고잉 어댑터에 의존성을 갖지 않는 것이 본질적인 요건이다.

  • 애플리케이션 계층: 인터페이스 (포트)
  • 어댑터 계층: 해당 인터페이스를 구현한 클래스

애플리케이션 계층에 어댑터에 대한 의존성을 추가하고 싶지 않기에, 애플리케이션 계층에 포트 인터페이스를 구현한 실제 객체를 제공하기 위해선 의존성 주입을 활용할 수 있다.

  • AccountController 는 SendMoneyuseCase 인터페이스를 필요, 의존성 주입을 통해 SendMoneyService 클래스의 인스턴스 주입

✨ 참고자료

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=283437942

https://velog.io/@yhlee9753/%EB%A7%8C%EB%93%A4%EB%A9%B4%EC%84%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-3.-%EC%BD%94%EB%93%9C-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0#%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%EC%9D%98-%EC%97%AD%ED%95%A0

profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글