[Clean Architecture] 클린 아키텍처를 읽고

2025. 5. 13. 14:39·개발서적

이번 개발서적 스터디에서 클린 아키텍처 책을 읽게 되었다.

https://www.yes24.com/product/goods/77283734

이번에 이 책을 읽게 된 계기는 ‘기존 프로젝트의 아키텍처 개선’을 목표로 선정하였다. 프로젝트를 시작했을 때도 아키텍처에 대해 팀원들과 고민하였기 때문에, 이에 대한 궁금증을 해소하고 최종적으로 도메인 주도 설계로 나아가기 위한 단계가 되길 바라며 책을 읽기 시작했다.

Clean Architecture?

클린 아키텍처란 무엇일까?

클린 아키텍처를 간단히 설명하면 엔티티로부터 세부사항을 분리하고, 엔티티를 세부사항으로부터 독립적으로 유지하는 것이라 생각한다. 클린 아키텍처를 생각하면, 아래의 그림이 생각날 것이다.

위 그림처럼 엔티티를 가장 안쪽에 배치하고 의존성의 방향이 한 방향으로 이루어지게 한다.

클린 아키텍처의 목표를 달성하기 위해 우리는 관심사를 분리하고, 의존성 규칙을 세워야한다.

관심사 분리

관심사 분리란 어떠한 관심사를 기준으로 프로그램을 분리하여, 각 프로그램은 하나의 관심사와 관련이 있게하는 것을 말한다.

그렇다면 관심사 분리를 통해서 얻을 수 있는 장점은 무엇일까?

한 가지로 말하면 유지보수하기 수월하다는 것이다. 관심사 분리를 통해 변경 사항이 발생하더라도, 변경 사항에 해당하는 부분만 고치면 된다.

말만 보면 당연히 그래야한다고 느껴지지만, 그러지 않는 상황도 충분히 발생한다. 내 경험을 비롯해 말하자면, Mybatis를 사용하다가 JPA로 변경할 일이 생겼다. 그런데 서비스에서 Mybatis라는 데이터베이스에 대해서 직접적으로 알고 있다면 어떻게 될까? 유스 케이스에 대한 변경은 전혀 없지만, 서비스 코드가 대폭 수정된다.

그런데 이를 인터페이스를 통한 의존성 역전을 이용한다면, 이에 대한 세부사항은 몰라도 된다. 즉, 세부사항의 변경이 세부사항 수정으로 끝나게 된다.

결국 우리는 소프트웨어를 계층으로 분리함으로써 관심사의 분리라는 목표를 달성해야 한다.

의존성 규칙

클린 아키텍처 그림에서 각각의 동심원은 소프트웨어에서 서로 다른 영역을 표현한다. 이때 보통 안으로 들어갈수록 고수준의 소프트웨어가 된다. 주로 바깥쪽 원은 세부사항이고, 안쪽 원은 정책이다.

이러한 아키텍처가 동작하도록 하는 가장 중요한 규칙은 의존성 규칙인데, 소스 코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다.

내부의 원에 속한 요소는 외부의 원에 속한 어떤 것도 알지 못한다. 또한 외부의 원에 선언된 데이터 형식도 내부의 원에서 절대로 사용해서는 안된다.

패키지

아키텍처를 생각하면 패키지 구조를 떠올리게 된다. 물론 패키지 구조 = 아키텍처인 것은 아니다. 하지만 패키지를 보았을 때, 아키텍처는 어떤 시스템인지에 대해 소리쳐야 한다. 즉, 스프링이라고 소리치는 것이 아니라 “재고 관리 시스템이야”라고 소리쳐야 한다.

계층 기반 패키지

단순하게 사용할 수 있는 설계 방식은 계층 기반 패키지, 즉 레이어 아키텍처이다. 주로 레이어 아키텍처는 웹, 업무 규칙, 영속성 코드에 대한 계층이 각각 존재한다. 또한 각 계층은 반드시 바로 아래 계층에만 의존해야 한다.

처음 시작하기에는 계층형 아키텍처가 적합하다. 복잡함을 겪지 않고도 무언가를 작동시키기에 빠른 방법이기 때문이다. 하지만 스프트웨어가 커지면, 큰 그릇 세 개만으로 모든 코드를 담기엔 부족하다.

또한 계층형 아키텍처는 업무 도메인에 대해 아무것도 말하지 않는다. 전혀 다른 업무 도메인이라도 코드를 계층형 아키텍처로 만들어서 나란히 놓고보면, 다른 점이 보이지 않는다.

기능 기반 패키지

서로 연관된 기능, 도메인 개념, 또는 애그리거트 루트에 기반하여 수직의 얇은 조각으로 코드를 나누는 방식이다.

이는 코드의 상위 수준 구조가 업무 도메인에 대해 무언가를 알려주게 된다. 즉, 주문과 관련한 무언가를 한다는 것을 확인할 수 있다.

또한 주문 유스케이스가 변경될 경우 변경해야 할 코드가 여러 군데 퍼져 있지 않고 한 패키지에 담겨있다.

포트와 어댑터

포트와 어댑터 또는 헥사고날 아키텍처 등의 방식으로 접근하는 이유는 업무/도메인에 초점을 둔 코드가 프레임워크나 데이터베이스 같은 기술적인 세부 구현과 독립적이며 분리된 아키텍처를 만들기 위해서이다. 이러한 코드 베이스는 내부와 외부로 구성할 수 있다.

내부 영역은 도메인 개념을 모두 포함하는 반면, 외부 영역은 외부 세계와의 상호작용을 포함한다.

이때 중요한 규칙은 바로 외부가 내부에 의존하며, 절대 그 반대로는 안된다.

기존 패키지를 위 방식대로 다시 재구성해보자.

com.my.app.domain 패키지가 내부이며, 나머지 패키지는 모두 외부이다. 의존성은 내부를 향해 흐르는 모습을 볼 수 있다.

OrdersRepository가 Orders라는 간단한 이름으로 바뀌었는데, 이는 도메인 주도 설계라는 세계관에서 비롯된 명명법으로, 도메인 주도 설계에서는 내부에 존재하는 모든 것의 이름은 반드시 유비쿼터스 도메인 언어 관점에서 기술하라고 조언한다. 바꿔 말하면, 도메인에 대해 논의할 때 우리는 ‘주문’에 대해 말하는 것이지, ‘주문 리포지토리’에 대해 말하는 것이 아니다.

컴포넌트 기반 패키지

컴포넌트 기반 패키지는 강제성을 부여한다. 컴파일러를 사용해 아키텍처를 강제하는 것이다. 사용자 인터페이스를 큰 단위의 컴포넌트로부터 분리해서 유지한다.

이 접근법에서는 업무 로직과 영속성 관련 코드를 하나로 묶는데, 이 묶음을 컴포넌트라 부른다. 여기서 컴포넌트는 인터페이스로 감싸진 연관된 기능들의 묶음으로 애플리케이션과 같은 실행 환경 내부에 존재한다.

이때 각 컴포넌트가 개별 jar 파일로 분리될지 여부는 독립적인 관심사, 즉 한 요소의 변경이 다른 변경에 영향을 미치지 않는 것이다.

이 방식의 이점은 주문과 관련된 무언가를 코딩할 때 OrdersComponent만 둘러보면 된다. 이 컴포넌트 내부에서 관심사의 분리는 여전히 유효하며, 업무 로직은 데이터 영속성과 분리되어 있다. 모놀리식 애플리케이션에서 컴포넌트를 잘 정의하면 마이크로 서비스 아키텍처로 가기 위한 발판으로 삼을 수 있다.

조직화만 신경쓰지 말자.

지금까지 이야기한 방식에서 모든 타입을 public으로 지정한다면, 패키지는 단순히 조직화를 위한 메커니즘으로 전락한다. 즉, 단순히 풀더 구조로 활용되는 것이다. public 타입을 코드 베이스 어디에서도 사용한다면, 패키지를 사용하는 데 따른 이점이 거의 없다.

위 4가지 접근법들은 매우 다르다. 하지만 모든 타입을 public으로 선언한다면, 계층형 아키텍처를 표현하는 네 가지 방식에 지나지 않는다.

자바의 접근 지시자를 적절하게 사용하여, 타입을 패키지로 배치하는 방식에 따라서 각 타입에 접근할 수 있는 정도가 실제로 크게 달라질 수 있다. 위 네 가지 다이어그램을 패키지 구조를 살려서 제한적인 접근 지시자를 사용해보자.

가장 왼쪽의 계층 기반 패키지 접근법에서 OrdersService와 OrdersRepository 인터페이스는 외부 패키지의 클래스로부터 자신이 속한 패키지 내부로 들어오는 의존성이 존재하므로 public으로 선언되어야 한다. 반면, 구현체 클래스는 더 제한적으로 선언할 수 있다. 이들 클래스는 누구도 알 필요가 없는 구현 세부사항이다.

두 번째, 기능 기반 패키지 접근법에서는 OrdersController가 패키지로 들어올 수 있는 유일한 통로를 제공하므로 나머지는 모두 패키지 protected로 지정할 수 있다.

세 번째, 포트와 어댑터 접근법의 경우, OrdersService와 Orders 인터페이스는 외부로부터 들어오는 의존성을 가지므로 public을 지정해야 한다. 이 경우에도 구현 클래스는 패키지 protected로 지정하며, 런타임에 의존성을 주입할 수 있다.

마지막 컴포넌트 기반 패키지 접근법에서는 컨트롤러에서 OrdersComponent 인터페이스로 향하는 의존성을 가지며, 그 외의 모든 것은 패키지 protected로 지정할 수 있다. public 타입이 적으면 적을수록 필요한 의존성의 수도 저어진다.

클린 아키텍처에서는 컴포넌트 기반 패키지와 같이, 아키텍처 원칙을 강제할 때 단순한 규율이나 컴파일 후처리 도구보다는, 컴파일러에 의지할 것을 권장한다. 이에 대한 방법으로 모듈을 통한 분리가 될 수도 있다.

결론

결국 우리는 최적의 설계를 만들었다고 하더라도, 구현 전략에 얽힌 복잡함을 고려하지 않으면 설계가 순식간에 망가질 수 있다.

어떻게 설계해야 원하는 코드 구조로 매핑할 수 있을지, 어떻게 조직화할지, 런타임과 컴파일 타임에 어떤 결합 분리 모드를 적용할지 고민해야 한다.

선택사항을 열어두고 실용주의적으로 행동하면서, 팀 규모, 기술 수준, 해결책의 복잡성, 일정을 동시에 고려해야 한다. 아키텍처 스타일을 강제하는 데 컴파일러의 도움을 받을 수 있을지 고민하고, 데이터 모델과 같은 다른 영역에 결합되지 않도록 주의해야 한다.

위 내용들처럼 아키텍처를 고민한다는 것은 수많은 고려사항과 주의사항들이 있다. 그만큼 잠재적인 변경 요인이 상당히 많다는 것이다. 모든 것을 관통하는 아키텍처는 없고, 그때그때 변화할 수 있어야 한다. 결국 우리는 유동적으로 진화할 수 있어야 한다고 생각한다.

'개발서적' 카테고리의 다른 글

[도메인 주도 개발 시작하기] 애그리거트에 대해서  (1) 2025.06.08
[도메인 주도 개발 시작하기] 도메인 모델 시작하기  (0) 2025.05.30
[가상 면접 사례로 배우는 대규모 시스템 설계 기초] 사용자 수에 따른 규모 확장성  (0) 2025.05.05
[Real MySQL 8.0] 인덱스  (0) 2025.03.14
[Real MySQL 8.0] 트랜잭션과 잠금  (0) 2025.02.27
'개발서적' 카테고리의 다른 글
  • [도메인 주도 개발 시작하기] 애그리거트에 대해서
  • [도메인 주도 개발 시작하기] 도메인 모델 시작하기
  • [가상 면접 사례로 배우는 대규모 시스템 설계 기초] 사용자 수에 따른 규모 확장성
  • [Real MySQL 8.0] 인덱스
g-hwang
g-hwang
g-hwang 님의 블로그 입니다.
  • g-hwang
    g-hwang 님의 블로그
    g-hwang
  • 전체
    오늘
    어제
    • 분류 전체보기 (40)
      • 데브코스 (7)
      • 스프링 (7)
      • 자바 (3)
      • 아키텍처 (3)
      • 트러블 슈팅 (4)
      • 알고리즘 (0)
      • 개발서적 (6)
      • 인프런 워밍업 스터디 (7)
      • 오픈소스 기여 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    인프런워밍업클럽
    도메인
    레이어 아키텍처
    자바
    다중 컬럼 인덱스
    카프카
    가상 면접 사례로 배우는 대규모 시스템 설계 기초
    jpa 순환참조
    JPA
    워밍업 스터디
    트랜잭션
    스터디3기
    스프링
    virtual thread
    래퍼 클래스
    real mysql 8.0
    인덱스
    인프런 워밍업 스터디
    코드래빗
    이벤트 스토밍
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
g-hwang
[Clean Architecture] 클린 아키텍처를 읽고
상단으로

티스토리툴바