본문 바로가기
독서/자바,스프링 개발자를 위한 실용주의 프로그래밍

9. 모듈

by Thinking 2024. 11. 13.

9.1 모듈성

모듈이란 무엇일까요? 소프트웨어 공학에서 모듈은 프로그램의 기본 구성 요소이면서 라이브러리를 포괄하는 조금 더 큰 규모의 용어입니다. 즉 모듈이란 라이브러리와 프로그램의 구성 요소 사이에 위치하는 개념입니다. 하지만 이는 좀 모호한데 더 구체적으로는 독립성과 은닉성을 만족하며 연관된 코드들의 묶음입니다.

1) 독립성 : 모듈은 독립적이어야 한다.

2) 은닉성 : 모듈의 사용자는 모듈의 내부 구현을 몰라도 된다. 공개된 인터페이스를 이용해 모듈과 통신한다.

 

이때 독립성과 은닉성 같은 특성을 모듈성이라고 부릅니다. 즉 연관된 코드 묶음은 모듈성을 만족할 때 모듈이 될 수 있습니다. 모듈 시스템이란 연관된 코드 묶음이 '모듈성'을 갖출 수 있게 도와주는 시스템적인 해결책입니다. 그래서 다음과 같은 기능이 필수적이죠.

1) 의존성 관리 : 모듈을 사용하기 위해 어떤 의존성이 필요한지 명시할 수 있어야 한다.

2) 캡슐화 관리 : 모듈은 불필요한 구현을 외부로 드러내지 않아야 한다.

 

모듈은 독립성과 은닉성을 만족하며, 모듈 시스템은 모듈을 만들기 위한 시스템으로서 모듈에 대한 의존성 관리와 캡슐화 관리가 제공되어야 합니다. 모듈 시스템이 이 같은 기능을 제공해야 하는 이유는 각각 독립성과 은닉성에 대응해서 생각할 수 있습니다.

 

모듈이 독립성을 보장하기 위해 모듈 시스템은 '모듈 차원의' 의존성 관리를 할 수 있어야 한다는 말입니다. 그리고 모듈이 은닉성을 보장하기 위해 모듈 시스템은 '모듈 차원의' 캡슐화 관리를 할 수 있어야 한다는 말이죠.

 

1) 모듈은 연관된 코드의 묶음이 모듈성을 갖춘 경우 모듈이라고 부를 수 있다.

2) 모듈성에는 독립성, 은닉성이라는 특징이 있다.

3) 모듈성을 갖출 수 있게 도와주는 시스템이 모듈 시스템이다.

4) 모듈 시스템은 모듈성 중 독립성을 지원하기 위해 의존성 관리 기능을 지원할 수 있어야 한다.

5) 모듈 시스템은 모듈성 중 은닉성을 지원하기 위해 캡슐화 관리 기능을 지원할 수 있어야 한다.

6) 패키지와 모듈은 다릅니다.

7) 패키지 시스템은 모듈 시스템이 아닙니다.

8) 패키지를 만들 때도 모듈처럼 독립성과 은닉성을 추구하는 것이 좋습니다.

 

모듈 수준의 의존성 관리와 캡슐화란 이처럼 개발자가 어떤 모듈을 정의하면서 해당 모듈에 필요한 의존 모듈이 무엇인지 명시할 수 있고, 해당 모듈을 배포할 때도 모든 내용이 아닌 일부만 배포할 수 있는 것을 의미합니다. 이를 위해 모듈 시스템은 모듈이 가진 독립성과 은닉성을 보장하기 위해 의존성 관리와 캡슐화 관리를 모듈 수준에서 지원할 수 있어야 합니다.

 

자바의 패키지 시스템은 모듈 시스템이라고 볼 수 있을까요? 라는 질문에 답은 '아니요'입니다. 자바의 패키지 시스템은 연관성 높은 코드를 묶는 수단에 불과합니다. 자바 9가 나오기 전까지는 모듈 시스템이 존재하지 않는다고 평가하기도 합니다. 모듈 디스크립터라는 시스템이 도입된 이유도 알 수 있죠. 바로 패키지와 JAR 시스템만으로 모듈다운 모듈을 만들기 어려웠기 때문입니다. 모듈 시스템이 추구하는 가치를 조금 더 깊게 이해하고 '패키지를 모듈에 가깝게 써야 한다'라는 말을 하고 싶습니다.

 

 

9.1.1 독립성

'모듈은 독립적이어야 한다'라는 의미는 모듈이 다른 모듈이나 컴포넌트에 강하게 의존하지 않고 각 모듈을 개별적으로 수정하거나 교체할 수 있어야 한다는 뜻입니다.

1) 모듈을 사용하기 전에 필요한 의존성을 알 수 있어야 한다.

2) 모듈의 의존성이 모두 준비됐다면 모듈을 사용하는 데 아무런 문제가 없어야 한다.

 

이 같은 해석은 '모듈이 독립적이라면 의존성을 관리할 수 있어야 한다'라는 명제로 이어집니다. 모듈의 독립성은 왜 '의존성을 관리해야 한다'로 이어질까요? 다른 패키지에 의존한다고 해서 '이 패키지는 독립적이지 않다'라고 평가한다면 이는 곧 모든 패키지를 원시 타입만 이용해서 개발하라는 의미가 됩니다. '어떤 시스템이 독립적이어야 한다'라는 말은 대상이 외부 시스템과 완전히 격리돼야 한다는 말이 아니라 독립적인 무언가를 만들어야 한다면 이는 시스템 간의 협력 자체를 부정하는 말이 됩니다. '독립적이다'라는 말은 외부에 의존하는 상황이 생기는 것 자체를 부정하지 않습니다. 외부에 의존할 때 강한 의존이 생기는 것을 피하라는 의미이죠

1) 최대한 내부에서 해결하라

2) 외부에는 강하게 의존하지 마라

3) 외부 시스템을 사용한다면 외부 시스템의 사용을 명시하라

 

모듈의 하위 의존성을 명시하는 것이 어떻게 모듈의 독립성을 보장하는 결과로 이어질까요? 많이 사용하는 spring-boot-starter, 이 라이브러리는 독립적이라고 평가할 수 있죠. Maven, Gradle 같은 패키지 관리 도구로 쉽게 이용할 수 있고, 어려운 설정 작업이 추가로 필요하지 않죠. 해당 라이브러리에도 정말 다양한 하위 모듈들이 사용됩니다. 그렇다면 이러한 도구들은 하위 의존성을 어떻게 추적할 수 있는 것일까요?

 

이것은 우리가 pom.xml, build.gradle에 의존할 라이브러리를 나열하는 것처럼 각 라이브러리도 자신의 하위 의존성을 어딘가에 명확히 정의하고 있기 때문입니다. pom.xml, build.gradle에 의존성을 추가하는 행위는 애플리케이션 개발에 필요한 라이브러리를 가져오기 위한 것뿐만 아니라 애플리케이션이 필요로 하는 외부 의존성이 무엇인지 설명하기 위한 목적도 있습니다. 애플리케이션의 사용법을 알려주는 것과 같고, '이 애플리케이션을 사용하려면 실행하는 데 필요한 하위 의존성을 모두 가져와라'라고요.

 

이는 모듈에도 그대로 적용할 수 있고, 의존성을 명시하는 행위는 '이 모듈을 사용하고 싶다면 모듈을 실행하는 데 필요한 하위 의존성을 모두 가져와라'라고 적어두는 것과 같습니다. 모듈의 의존성을 추적할 수 있게 된다면, 모듈을 불러오는 것을 자동화할 수 있을뿐더러 하위 모듈의 중복 검사와 불필요한 의존성이 있는지도 검사할 수 있죠. 더 나아가 동적 로딩도 가능합니다.

 

마지막으로 모듈의 시작은 연관된 코드 묶음이었다는 사실도 잊지 말아야 합니다. 모듈이 높은 응집도를 추구해야 하는 것은 당연합니다. 모듈에 필요한 의존성을 명시한다고 무조건 독립적인 모듈이 되는 것은 아니고, 모듈의 독립성을 보장하기 위해 의존하는 모듈이 무엇인지 명확히 하면서도 연관된 코드만 모이도록 해야 합니다.

 

 

9.1.2 은닉성

모듈이 은닉성을 추구해야 한다는 말은 클래스가 은닉성을 추구하는 것처럼 모듈 수준의 캡슐화가 가능해야 한다는 말입니다. 우리는 모듈을 외부에 공유하더라도 공개된 인터페이스 외에 불필요한 정보를 숨길 수 있길 원합니다. 하이럼 법칙으로 'API를 사용하는 사용자가 충분히 많다면 개발자의 설계 의도는 더 이상 중요하지 않습니다.'라고 의역할 수 있습니다. 자바 패키지 시스템의 문제점으로 어떤 라이브러리를 가져오면 해당 라이브러리에 있는 모든 클래스를 사용할 수 있다는 점이죠.

 

결론적으로 모듈 수준의 인터페이스 관리가 필요하다고 할 수 있습니다. 모듈을 사용한다는 의미가 모듈의 모든 기능을 사용할 수 있게 된다는 의미가 돼서는 안 됩니다. 모듈의 사용자는 모듈이 책임지는 공개된 일부 기능에만 접근할 수 있어야 하고, 이렇게 되어야 모듈의 내부 구현이 변경되더라도 모듈의 사용자에게 주는 영향 범위를 최소화할 수 있게 됩니다.

 

 

9.2 패키지 구조

스프링을 기반으로 하는 프로젝트를 보면 크게 2가지 방식으로 패키지 구조를 구성하는 것을 볼 수 있습니다. 하나는 패키지를 구성할 때 계층이 먼저 나오는 '계층 기반 구조', 또 다른 하나는 '도메인 기반 구조'입니다. 각 전략의 장단점과 사용시기를 살펴보죠.

 

 

9.2.1 계층 기반 구조

레이어드 아키텍처를 사용하는 프로젝트에서 자주 보이는 구성 방식입니다. 최상단 패키지에 레이어드 아키텍처의 계층을 두고 해당 계층에 대응하는 컴포넌트를 아래에 넣는 방식으로, 프로젝트의 패키지를 다음과 같이 구성한다는 의미입니다. 장점은 이해하기 쉽고 사용하기 쉽다는 점입니다. 패키지 구조가 레이어드 아키텍처의 인지 모델을 그대로 따르고 있죠. 반면 단점은 도메인이 눈에 들어오지 않는다는 점입니다. 패키지 구조를 봐도 이 애플리케이션이 어떤 애플리케이션인지 알 수 없습니다. 더불어 어떤 도메인이 사용되는지 파악하려면 모든 계층을 열어보고 정리해야 합니다. 도메인 관점의 응집도가 떨어지고, 그 결과 비즈니스 코드를 한 곳에 모아 볼 수 없습니다.

 

패키지는 논리적으로 연관된 클래스 파일들을 모아 놓는 공간입니다. 그런데 도메인이라는 단위만큼 확실한 연관 관계가 존재할까요? 그래서 응집도가 떨어진다고 평가할 수 있습니다.

1) 계층 기반 구조는 쉽습니다.

2) 계층 기반 구조는 도메인이 눈에 들어오지 않습니다.

3) 계층 기반 구조는 프로젝트를 구성하는 주요 단위를 계층으로 보고 이습니다.

 

 

9.2 도메인 기반 구조

도메인 기반 구조에서는 패키지 최상단에 프로그램이 사용하는 도메인이 오도록 구성합니다. 각 도메인에 대응하는 코드를 이 패키지의 하위 항목으로 분류하죠. 패키지 구조만으로 도메인을 강조할 수 있기 때문에, 이러한 구조는 DDD를 이용하는 프로젝트에서 자주 사용됩니다. 단점도 분명 있습니다. 계층이 눈에 들어오지 않죠. 최상단을 도메인으로 두고, 바로 아래에 계층을 구분하기 위한 패키지를 오도록 변경해 보죠. 이로써 '도메인 -> 계층' 기반 구조가 탄생했습니다. 도메인 계층 기반 구조의 특징은 아래와 같습니다.

1) 도메인 기반 구조는 계층 기반 구조보다는 복잡해집니다.

2) 도메인 기반 구조는 도메인이 눈에 들어옵니다.

3) 도메인 기반 구조는 프로젝트를 구성하는 주요 단위를 도메인으로 보고 있습니다.

 

 

정리

작은 규모의 프로젝트라면 단순하고 직관적인 계층 기반 구조로 개발하는 것이 유리하며, 큰 규모의 프로젝트라면 비즈니스 도메인의 복잡성을 관리하기 위해 도메인 기반 구조로 개발하는 것이 유리합니다. 은탄환은 존재하지 않습니다. 

 

1) 모듈과 모듈 시스템이 무엇인지 잘 알아야 합니다.

2) 모듈과 패키지의 차이점을 잘 알아야 합니다.

3) 필요하다면 패키지도 모듈성을 추구해야 합니다.

 

모듈이 추구하는 가치인 독립성, 은닉성은 소프트웨어 개발의 기본 원리입니다. 시스템의 복잡도를 낮추고 확장성을 높일 수 있다는 것이 검증된 확실한 전략이죠. 어떤 대상을 모듈화 하겠다 보다는 언제 어디서든 독립성, 은닉성을 습관적으로 추구하는 것이 좋습니다. 

 

은탄환 : 소프트웨어 공학에서 일관되게 발생하는 문제를 바로 해결할 수 있는 보편적이고 완벽한 가상의 해결책을 의미한다.