Java/학습 정리

[클린 아키텍처] 계층과 모듈은 다르다. / Layer != Module

Chipmunks 2025. 7. 25.
728x90

성장력이 뛰어난 친구가 클린 아키텍처에서 포트(Port)의 적절한 위치를 물었다.

 

- 도메인 모듈에 Input / Output 포트를 정의하는 건 어떻게 생각해?

- Input 포트는 유즈케이스 인터페이스 명세야. Output 포트는 외부 기술(영속성 등)의 인터페이스 명세고.

- 유즈케이스를 애플리케이션 모듈에 인터페이스 선언과 구현체를 두던데, 의미가 없어보여.

 

Project/
- project-api/
- project-application/

- project-domain/
-- domain1/
--- port/
---- in/
----- XXXUseCase.kt
---- out/
----- XXXRepository.kt
--- service/
---- ...
--- model/
---- ...

- project-infrastructure/
...

- 첨부한 프로젝트 구조 일부

 

처음 든 생각은 이렇다.

'용어가 명확한 구분 없이 혼용되어 있다.'

'어떻게 해야 오해가 있는 지점을 찾을 수 있을까?'

 

나 또한 클린 아키텍처의 숙련도가 높은 편은 아니다.

실무에 도입해 본 적도 없다.

1~2달 운영한 사이드 프로젝트로 멀티 모듈 구조와 함께 잠깐 적용해 본 점이 전부다.

어떻게 어디서부터 정리해야 할까?

 

클린 아키텍처의 대표적인 계층 그림부터 찾아보았다.

The Clean Coder - https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

 

먼저, 용어를 하나씩 파헤쳐 보자.

 

1. 도메인 모듈

'도메인'과 '모듈'을 나누어보자.

 

도메인은 소프트웨어가 해결하고자 하는 문제 영역, 즉 비즈니스 영역 또는 문제 영역 따위를 뜻한다.

모듈은 독립적인 기능 단위 / 개별 구성 요소를 뜻한다. 자바에선 독립적으로 모듈 배포가 가능하다.

즉, 도메인 모듈은 특정 문제 영역을 구분하는 독립적이고 물리적인 구성 요소를 뜻한다.

 

질문을 다시 한 번 보자.

- 도메인 모듈에 Input / Output 포트를 정의하는 건 어떻게 생각해?

 

질문의 맥락상 'project-domain/domain1' 의 물리적인 위치의 모듈을 뜻한다.

또는 그와 유사한 역할을 하는 모듈.

 

위 모듈은 어떤 역할을 하는가?

위 모듈은 어떻게 구성되어 있는가?

 

순수 코틀린 객체로 이루어진 모듈이다.

코틀린 객체는 비즈니스 규칙을 담고 있다.

 

클린 아키텍처에서 이 모듈에 해당하는 계층은,

바로 '엔티티' 계층이다.

즉, '도메인 모듈'은 엔티티 계층을 담았다.

 

2. Input / Output 포트

포트는 어디서 나온 단어일까?

포트는 어댑터와 함께 헥사고날 아키텍처에서 나온 용어다.

 

헥사고날 아키텍처는 클린 아키텍처를 따른다.

포트는 도메인 로직과 외부 요소를 연결하는 인터페이스 역할을 한다.

(어댑터는 포트로 정의한 인터페이스를 실제 외부 요소의 구현에 맞게 변환하는 역할을 한다.)

 

포트 설명에 '도메인 로직' 이라는 용어가 나왔다.

이 '도메인 로직'은 무엇을 의미하는 걸까?

 

3. 유즈케이스 / 애플리케이션 모듈

유즈케이스는 클린 아키텍처의 계층을 뜻한다.

애플리케이션은 이 계층을 담은 모듈의 일반적인 이름이다.

 

유즈케이스는 '논리적인' 계층이고,

애플리케이션은 '물리적인' 모듈이다.

 

즉, '계층'은 논리적이고

'모듈'은 물리적이다.

 

---

 

위 용어 설명을 보고, 위 질문을 다시 한 번 봐보자.

위화감의 정체를 대략 눈치챌 수 있다.

 

1. 클린 아키텍처와 헥사고날 아키텍처를 혼용한다.

클린 아키텍처의 계층을 명확히 구분하지 않고, 헥사고날 아키텍처와 혼용했다.

 

헥사고날 아키텍처에서는 ‘포트가 정의되었다’는 것은 '어댑터가 해당 포트를 구현할 수 있는 조건이 마련되었다'는 뜻이다.

즉, 위 프로젝트는 헥사고날 아키텍처를 따르는 셈이다.

 

포트의 정의와 쓰임새는 올바르다.

다만, 계층을 올바르게 구분하지는 못했다.

 

Input (내지 Inbound) 포트는 외부(컨트롤러, 리졸버 등) 구성요소가 유즈케이스 실행을 호출하도록 도와주는 인터페이스다.

( 구성요소가 특정 논리적인 계층, 물리적인 모듈을 뜻하는 게 아니라는 점을 주의하라. )

 

도메인 모듈은 엔티티 계층이다.

Input 포트는 유즈케이스의 계약이므로, 유즈케이스를 알고 있다.

 

그렇다면, 도메인 모듈 안에 Input 포트가 있다는 건 어떤 의미일까?

엔티티 계층이 유즈케이스 계층을 알고 있다는 의미가 된다.

 

즉, 단방향 의존성이 특징인 계층 설계에서 역방향으로 의존성이 걸리게 된다.

이는 클린 아키텍처의 목적에 위반한다.

 

유즈케이스를 호출하는 예시로 생각해보자.

 

인터페이스 어댑터 계층(초록색 계층)의 컨트롤러 구성요소가 유즈케이스를 호출하려고 한다.

유즈케이스 호출의 명세로 Input 포트를 찾는다.

자바의 패키지 또는 모듈로 Input 포트를 가져올 때, 도메인 모듈 하위에서 가져온다.

 

즉, 인터페이스 어댑터 계층은 엔티티 계층에 직접 참조한다.

이는 유즈케이스를 뛰어넘은 의존성으로,

계층간 단방향 의존성이 특징인 설계의 원칙에 위반한다.

 

Input 포트는 유즈케이스 계층이어야 한다.

따라서 엔티티 계층인 위 도메인 모듈에 두는 건, 올바르지 않다.

 

위에서 언급한 포트의 도메인 로직이란, 도메인 객체를 조작하고 편집하는 모든 로직을 뜻한다.

클린 아키텍처로선, 유즈케이스 계층 / 엔티티 계층을 의미한다.

클린 아키텍처에선 단방향 의존성을 추구하고, Input 포트는 인터페이스 어댑터 계층에서 사용하므로

도메인 로직은 대부분 엔티티 계층을 의미하게 된다.

 

2. 클린 아키텍처의 목적을 놓치고 있다.

엉클밥 블로그에 클린 아키텍처의 목적이 자세히 나와있다.

 

The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle.

- by Robert C. Martin, 2012/08/13

 

 

클린 아키텍처의 주요 목적은 의존성 제어다.

정확히 하면, 소스 코드의 의존 방향을 제어하는 것이다.

 

소스 코드를 논리적인 계층으로 나누고,

특정 의존 방향을 강제하는 것이 설계의 목적이다.

 

아키텍처 그림에서 '구성 요소'와 '화살표 방향'만 있는 이유다.

 

안쪽 원으로만 의존성이 있어야 하며,

안쪽 원은 바깥쪽 원을 알아선 안된다.

 

1번의 마지막 문단과 마찬가지로

위 사례에서 엔티티 계층이 유즈케이스 계층을 알고 있으므로,

클린 아키텍처에 위반한다고 판단했다.

 

 

마지막 질문의 내용은 아래와 같다.

 

- 유즈케이스를 애플리케이션 모듈에 인터페이스 선언과 구현체를 두던데, 의미가 없어보여.

 

의미가 없다, 라는 건 무엇일까?

유즈케이스 인터페이스를 정의한 곳과 구현한 곳의 위치가 동일한 걸 문제로 삼았다.

 

곧바로 대답하기 어려웠다.

무엇이 나와 싱크가 안맞았던 거였을까?

 

이는 인터페이스를 선언한 것과 구현한 것의 의미와 목적을 놓치고 있는 점에서 기인했다.

 

유즈케이스 인터페이스(Input Port)를 선언한 건,

외부에서 그 유즈케이스를 의존하기 위해서다.

그리고 외부와의 의존성을 신경 쓰지 않고, 구현할 수 있게 된다.

 

즉, 의존성 역전 원칙(DIP)의 원리를 잠시 놓쳤다고 볼 수 있다.

물리적인 위치를 어디에 두느냐의 문제가 아니다.

논리적으로 어떤 목적이 있는가를 따졌어야 했다.

 

또한 3번과 마찬가지로, 계층과 모듈을 혼용한 점도 있다.

유즈케이스 인터페이스를 정의한 모듈이 꼭 애플리케이션 모듈이 아니어도 된다.

 

'애플리케이션'은, 일반적으로 유즈 케이스를 구현하는 구성요소의 대표적인 이름일 뿐이고

'모듈'은 물리적인 위치일 뿐이다.

 

예컨데, 애플리케이션 모듈 하위에 별도 포트만을 담은 포트 모듈에 위치시켜도 된다.

이러면 구현체와 물리적으로 구분되어 있게 된다. ( 내 사이드 프로젝트가 이런 구조다. )

실제로 포트만 별도로 관리하고 싶은 경우, 애플리케이션 하위가 아닌 독립 모듈로 분리하는 방식도 가능하다.
이 경우에도 중요한 건 위치가 아니라, 여전히 외부가 포트(interface)만 알고, 내부 구현은 감춰져 있다는 점이다.

 

재차 강조하는 건, 물리적인 위치와는 관계 없다는 점이다.

유즈케이스가 필요한 외부 구성요소(컨트롤러, 리졸버 등의 인터페이스 어댑터 계층)가 유즈케이스 인터페이스만 알고 있으면 된다는 점이다.

그리고 그 구현은 유즈케이스 계층에 속하는, 애플리케이션을 포함한 수많은(!) 이름을 가질 수 있는 물리적인 모듈 / 패키지에서 구현하면 된다.

핵심은 물리적인 위치가 아니라, 소스 코드상 논리적인 의존성 역전 원칙을 지켰는지를 판단해야 한다.

 

 

3. 계층과 모듈을 혼용한다. / Layer != Module

계층은 논리적인 역할이며,

모듈은 물리적인 위치 / 구성요소 / 배포 단위를 뜻한다.

 

전체 질문을 다시 한 번 봐보자.

 

- 도메인 모듈에 Input / Output 포트를 정의하는 건 어떻게 생각해?

- Input 포트는 유즈케이스 인터페이스 명세야. Output 포트는 외부 기술(영속성 등)의 인터페이스 명세고.

- 유즈케이스를 애플리케이션 모듈에 인터페이스 선언과 구현체를 두던데, 의미가 없어보여.

 

계층과 모듈이 혼용되어 있다.

모듈 관점에서 이야기 되어야 하는 점이, 계층 관점에서 이야기 되고 있다.

반대로 계층 관점에서 이야기되어야 하는 점이, 모듈 관점에서 이야기 되고 있다.

 

계층은 보통 알고 있다.

우리에게 필요한 건, '어떤 모듈이 어떤 계층에 속하는가'를 정의하는 것이다.

만약 모듈 단위가 아니라면, 패키지 단위라도 어떤 계층에 속하는지를 명확히 밝혀야 한다.

 

위 질문에서 계층의 맥락을 보충해보자.

 

- (도메인 모듈은 엔티티 계층에 속하는데), 도메인 모듈에 (유즈케이스 계층의) Input 포트 / (인터페이스 어댑터 계층과 의존성 역전을 할) Output 포트를 정의하는 건 어떻게 생각해?

 

- 유즈케이스를 (유즈케이스 계층의) 애플리케이션 모듈에 인터페이스 선언과 구현체를 두려고 해, 의미가 있을까?

 

4. 클린 아키텍처와 도메인 주도 개발의 도메인 설계를 혼용한다.

'도메인'이라는 용어를 DDD(도메인 주도 개발, Domain-Driven-Development) 관점에서 바라보고 있었다.

 

클린 아키텍처 관점에선 '도메인'(또는 어떤 종류든)이라고 붙인 모듈은 어떤 계층인가, 어떤 의존성 방향을 가지는가만 따지면 된다.

이야기 하다보니, DDD 도메인의 개념이 강하게 자리 잡아, '계층'에 대한 생각을 지워버렸다.

 

DDD의 도메인 설계는 계층 설계와 완전히 다른 영역이다.

현실 세계의 문제의 경계를 나누고 이를 객체화 시키는 것이다.

(이 또한 DDD의 도메인 설계는 물리적인 특정 디렉토리 구조를 강제하지 않는다.
DDD의 핵심은 코드 배치가 아니라, 비즈니스 개념을 정확하게 모델링하는 논리적인 설계 전략이다.)

 

즉, 관심사 자체가 다른 것이다.

관심사가 다르다고 해서, 한 쪽의 관심사를 소홀히 하면 안된다.

관심사를 명확히 구분하고 설계를 진행해야 한다.

 

예컨데,

'domain1 모듈'은 도메인 주도 개발에서 도출한 도메인을 넣은 모듈이고,

클린 아키텍처의 엔티티 계층이야, 라고 자신있게 말할 수 있어야 한다.

 

만약, domain1 모듈이 controller 모듈 / usecase 모듈 / entity 모듈 / adapter 모듈을 가지고

( 재차 강조하지만, 물리적인 모듈/패키지의 위치나 이름이 중요한게 아니다! )

controller -> usecase -> entity -> (output port) <- adapter 의존성만 지킨다면,

이는 클린 아키텍처 설계로서 의미와 가치를 충분히 가진다.

 

 


 

물리적인 구성요소(모듈 / 패키지 / 포트 / 어댑터 등)와

논리적인 계층을 명확히 나누는 것 만으로도,

계층형 아키텍처와 관련한 소통이 원할히 될 수 있다.

 

사실 비단 클린 아키텍처, 헥사고날 아키텍처의 문제만은 아니다.

3-tier 아키텍처, 프레젠테이션 - 애플리케이션(로직) - 데이터베이스(데이터), 같은 계층형 아키텍처와

스프링 프레임워크의 컨트롤러 / 서비스 / 레포지토리 어노테이션과 혼용하는 사례도 많이 봤다.

 

MVC와 같은 '디자인 패턴'을 '아키텍처'로 혼용하는 사례도 적지 않다.

이는 아래에 자세히 서술한 적이 있다.

2024.03.25 - [자바/자바 강의] - [Java] MVC 패턴의 오해와 고찰 | feat. Smalltalk-80, Dolphin Smalltalk MVP

 

[Java] MVC 패턴의 오해와 고찰 | feat. Smalltalk-80, Dolphin Smalltalk MVP

모두가 다르게 말하는 디자인 패턴이 있다?벌써 5개월 전이다.온라인으로 자바 과제를 하는 코스에 참가해, 많은 학생과 코드 리뷰를 활발히 했었다.자바 과제는 입력 / 비즈니스 요구사항 처리

itchipmunk.tistory.com

 

같은 이름이라도 철학이 다르면 역할이 다르고,

설계의 본질은 겉으로 보이는 이름이 아니라 '의도된 책임'이다.

 

논리 - 아키텍처(설계)

물리 - 컴포넌트(구성요소), 모듈, 패키지

 

오늘의 고찰은 논리적인 것과, 물리적인 것의 차이를 구분하는 연습이 필요하다는 것이었다.

댓글