군만두의 IT 공부 일지

[스터디2] 06. 모듈 본문

프로그래밍/객체지향

[스터디2] 06. 모듈

mandus 2025. 1. 15. 11:58

목차

 
모듈과 패키지의 개념에 대해서 이전에는 예시처럼 무언가 부족한 답변을 가지고 있었습니다. 모듈이 단순히 독립된 코드의 집합이라고만 생각했고, 자바의 패키지 시스템을 모듈 시스템과 동일시하는 오류를 범하기도 했습니다. 이러한 기초 개념에 대한 이해 없이 프로그래밍을 해왔다는 것을 깨닫고, 학습의 필요성을 다시 한번 느꼈습니다.

9. 모듈

시작하기 전 질문은 아래와 같음.

Q. 모듈이나 모듈 시스템이란 무엇일까?
A1. 모듈은 독립된 코드 묶음임. (△)
A2. 자바에서 모듈 시스템은 패키지임. (X)
A3. 자바 9에서 모듈 시스템은 module-info.java임. (△)

 

이번 장을 학습하면 아래 질문에 답변할 수 있음.

Q. 소프트웨어에서 말하는 모듈이나 모듈 시스템이란 무엇일까?
Q. 자바의 패키지는 왜 모듈 시스템이 될 수 없을까?
Q. 자바 9부터 추가된 모듈시스템(module-info.java)은 무엇이고 왜 추가된 것일까?
Q. 자바 9부터 모듈 시스템(module-info.java)이 추가됐다면 자바 9 이전에는 모듈 시스템이 없었다고 봐야 할까? 

9.1 모듈성

  • 모듈: 어떤 목적을 수행하기 위한 분리된 작은 구성 단위
  • 소프트웨어 공학에서 말하는 모듈에 대한 일반적인 답변은 아래와 같음.
    1. 모듈은 프로그램의 기본 구성 요소임. (△) → 의미가 모호함.
    2. 특수한 목적을 가지고 만들어지는 라이브러리임. (△) → 라이브러리가 아니어도 모듈이 될 수 있음.
  • 즉, 모듈이란 라이브러리와 프로그램의 구성 요소 사이에 위치하는 개념임.
  • 소프트웨어 관점에서 모듈(module)이란 독립성(independence)과 은닉성(hiding)을 만족하며 연관된 코드들의 묶음임.
    1. 독립성: 모듈은 독립적이어야 함.
    2. 은닉성: 모듈의 사용자는 모듈의 내부 구현을 몰라도 됨. 공개된 인터페이스를 이용해 모듈과 통신함.
  • 독립성과 은닉성을 모듈성이라고 부르는데, 연관된 코드 묶음은 모듈성을 만족할 때 모듈이 될 수 있음.
  • 모듈 시스템이란 연관된 코드 묶음이 모듈성을 갖출 수 있게 도와주는 시스템적인 해결책임. 모듈성을 지원하기 위해 모듈 시스템은 다음과 같은 기능을 필수적으로 지원해야 함.
    1. 의존성 관리: 모듈을 사용하기 위해 어떤 의존성이 필요한지 명시할 수 있어야 함.
    2. 캡슐화 관리: 모듈은 불필요한 구현을 외부로 드러내지 않아야 함.
  • 모듈이 독립성을 보장하기 위해 모듈 시스템은 '모듈 차원의' 의존성 관리를 할 수 있어야 하고, 모듈이 은닉성을 보장하기 위해 모듈 시스템은 '모듈 차원의' 캡슐화 관리를 할 수 있어야 함.
    • imports 프로퍼티는 '이 모듈은 어떤 외부 모듈에 의존하고 있는지'를 나타냄. 즉, 모듈의 의존성을 관리하는 데 사용함.
    • exports 프로퍼티는 '이 모듈 중 어떤 컴포넌트만 외부로 노출할 것인지'를 나타냄. 즉, 모듈의 캡슐화를 위해 사용됨.

Q. 자바의 패키지 시스템은 모듈 시스템이라고 볼 수 있을까?

A. 아님. 자바의 패키지 시스템은 패키지 수준의 의존성 관리와 캡슐화 관리 기능을 지원하지 않기 때문. 자바의 패키지는 폴더에 가까움. 자바 9 버전부터 module-info.java라는 모듈 디스크립터(module descriptor)가 존재함. 따라서 자바 9가 나오기 전까지는 모듈 시스템이 존재하지 않는다고 평가하기도 함.

 

모듈과 모듈 시스템 정리
- 모듈과 모듈성을 만족함.
- 모듈성은 독립성과 은닉성이라는 특징이 있음.
- 모듈 시스템은 코드 묶음이 모듈성을 갖출 수 있게 도와주는 시스템임.
- 모듈 시스템은 기능적으로 모듈 수준의 의존성 관리와 캡슐화 관리가 가능해야 함.
- 패키지와 모듈은 다름.
- 패키지 시스템은 모듈 시스템이 아님.
- 패키지를 만들 때도 모듈처럼 독립성과 은닉성을 추구하는 것이 좋음.

9.1.1 독립성

  • 모듈이 독립적이어야 하는 이유는 유지보수를 용이하게 하고, 확장성을 높이고, 코드의 재사용성을 높이기 위함.
- 모듈을 사용하기 전에 필요한 의존성을 알 수 있어야 함.
- 모듈의 의존성이 모두 준비된다면 모듈을 사용하는 데 아무런 문제가 없어야 함.
  • 모듈이 독립적이려면 의존성을 관리할 수 있어야 함.
/* 서로 다른 패키지에 위치한 두 코드 비교 */

// account 패키지에 들어있는 Account 클래스, 다른 패키지(또는 클래스)에 의존하지 않음.
package com.myproject.account;

public class Account {

}

// post 패키지에 들어있는 Post 클래스, post 패키지는 account 패키지에 의존함.
package com.myproject.post;

import com.myproject.account.Account;

public class Post {

	Account writer;
}
  • 어떤 시스템을 개발하든 외부 시스템에 의존해야 하는 상황이 생길 수밖에 없음. 따라서 어떤 시스템이 '독립적이어야 한다'는 것은 외부에 의존할 때 강한 의존이 생기는 것을 피하라는 의미임.
- 최대한 내부에서 해결함.
- 외부에는 강하게 의존하지 않음.
-   외부 시스템을 사용한다면 외부 시스템의 사용을 명시함.
  • 모듈의 독립성을 보장하기 위해서는 의존성을 명확히 하고, 연관된 코드만 모이도록 하여 높은 응집도를 추구해야 함.

9.1.2 은닉성

은닉성은 클래스 수준에서만 일어난다고 착각하기 쉽지만 모듈, 패키지, API 서버까지 확장할 수 있음.

  • 은닉성을 추구하는 이유는 작업을 더 편리하게 만들기 위함.
    1. 자바 패키지 시스템의 문제: 라이브러리를 가져오면 그 라이브러리에 있`는 모든 클래스에 접근할 수 있어, 불필요하게 많은 클래스에 의존하거나 의도하지 않은 클래스를 사용할 위험이 있음
    2. 라이브러리 업데이트 문제: 라이브러리가 업데이트되면 기존에 사용하던 클래스가 사라지거나 변경될 수 있어, 코드와의 호환성 문제를 일으킬 수 있음
    3. 파이썬의 몽키 패치(monkey patch) 문제: 런타임 중에 클래스나 모듈의 일부를 동적으로 변경할 수 있는 기술. 한 번 외부로 드러난 인터페이스는 존재하는 사용자가 있으면 삭제하거나 수정하기 어려움. 이는 유지 보수나 업데이트 시 문제를 일으킬 수 있음.
  • 위와 같은 이유로 모듈 수준의 인터페이스 관리가 필요함. 모듈의 사용자는 모듈이 책임지는 공개된 일부 기능에만 접근할 수 있어야 함. 모듈의 내부 구현이 변경되더라도 모듈이 사용자에게 주는 영향 범위가 최소화됨.

9.2 패키지 구조

스프링을 기반으로 하는 프로젝트를 보면 두 가지 방식으로 패키지 구조를 구성하는 것을 볼 수 있음.

스프링 프로젝트의 패키지 구조는 어떤 식으로 구성하는 게 더 좋을까?
- 계층 기반 구조: 계층 이름이 먼저 나오는 패키지 구성 방식
- 도메인 기반 구조: 도메인 이름이 먼저 나오는 패키지 구성 방식

9.2.1 계층 기반 구조

  • 레이어드 아키텍처를 사용하는 프로젝트에서 자주 보임.
  • 최상단 패키지에 레이어드 아키텍처의 계층을 두고, 해당 계층에 대응하는 컴포넌트를 아래에 넣음.
// 계층 기반 구조로 패키지를 구성할 경우
project
└── src/main/java
    └── com.demo.myapp
        ├── presentation
        │   ├── UserController.java
        │   ├── CafeController.java
        │   ├── BoardController.java
        │   └── PostController.java
        ├── business
        │   ├── UserService.java
        │   ├── CafeService.java
        │   ├── BoardService.java
        │   └── PostService.java
        ├── repository
        │   ├── UserRepository.java
        │   ├── CafeRepository.java
        │   ├── BoardRepository.java
        │   └── PostRepository.java
        ├── domain
        │   ├── User.java
        │   ├── Cafe.java
        │   ├── Board.java
        │   └── Post.java
        └── infrastructure
            ├── UserRepositoryImpl.java
            ├── UserJpaEntity.java
            ├── UserJpaRepository.java
            ├── CafeRepositoryImpl.java
            ├── CafeJpaEntity.java
            ├── CafeJpaRepository.java
            ├── BoardRepositoryImpl.java
            ├── BoardJpaEntity.java
            ├── BoardJpaRepository.java
            ├── PostRepositoryImpl.java
            ├── PostJpaEntity.java
            └── PostJpaRepository.java
장점 이해하기 쉽고, 사용하기 쉬움. 패키지 구조가 레이어드 아키텍처의 인지 모델을 그대로 따르고 있음.
단점 도메인이 눈에 들어오지 않음. 도메인 관점의 응집도가 떨어지고, 비즈니스 코드를 한곳에 모아 볼 수 없음.
계층 기반 구조의 특징
1. 계층 기반 구조는 쉬움.
2. 계층 기반 구조는 도메인이 눈에 들어오지 않음.
3. 계층 기반 구조는 프로젝트를 구성하는 주요 단위를 계층으로 보고 있음.

9.2.2 도메인 기반 구조

  • DDD를 이용하는 프로젝트에서 자주 사용됨.
  • 패키지 최상단에 프로그램이 사용하는 도메인이 오도록 구성함. 그리고 각 도메인에 대응하는 코드를 이 패키지의 하위 항목으로 분류함.
// 도메인 기반 구조로 패키지를 구성할 경우
project
└── src/main/java
    └── com.demo.myapp
        ├── user
        │   ├── UserController.java
        │   ├── UserService.java
        │   ├── UserRepository.java
        │   ├── User.java
        │   ├── UserRepositoryImpl.java
        │   ├── UserJpaEntity.java
        │   └── UserJpaRepository.java
        ├── cafe
        │   ├── CafeController.java
        │   ├── CafeService.java
        │   ├── CafeRepository.java
        │   ├── Cafe.java
        │   ├── CafeRepositoryImpl.java
        │   ├── CafeJpaEntity.java
        │   └── CafeJpaRepository.java
        ├── board
        │   ├── BoardController.java
        │   ├── BoardService.java
        │   ├── BoardRepository.java
        │   ├── Board.java
        │   ├── BoardRepositoryImpl.java
        │   ├── BoardJpaEntity.java
        │   └── BoardJpaRepository.java
        └── post
            ├── PostController.java
            ├── PostService.java
            ├── PostRepository.java
            ├── Post.java
            ├── PostRepositoryImpl.java
            ├── PostJpaEntity.java
            └── PostJpaRepository.java
장점 도메인 코드를 한곳으로 모음으로써 비즈니스 코드를 한곳에 모아 볼 수 있음. 패키지 구조만 보고도 프로젝트가 어떤 도메인을 사용하고 잇는지 알 수 있음.
단점 계층이 눈에 들어오지 않음. 계층별로 대응되는 스프링 컴포넌트가 어디에 있는지도 눈에 안 들어옴.

위에서 설명한 단점으로 패키지 구조를 아래처럼 개선함.

  • 최상단을 도메인으로 두고, 바로 아래에 계층을 구분하기 위한 패키지를 오도록 변경함.
  • 도메인 패키지는 독립성을 얻고, 계층에 따라 컴포넌트를 볼 수 있음.
// 도메인 -> 계층 기반 구조로 패키지를 구성할 경우
project
└── src/main/java
    └── com.demo.myapp
        ├── user
        │   ├── presentation
        │   │   └── UserController.java
        │   ├── application
        │   │   └── UserService.java
        │   ├── repository
        │   │   └── UserRepository.java
        │   ├── domain
        │   │   └── User.java
        │   └── infrastructure
        │       ├── UserRepositoryImpl.java
        │       ├── UserJpaEntity.java
        │       └── UserJpaRepository.java
        ├── cafe
        │   ├── presentation
        │   │   └── CafeController.java
        │   ├── application
        │   │   └── CafeService.java
        │   ├── repository
        │   │   └── CafeRepository.java
        │   ├── domain
        │   │   └── Cafe.java
        │   └── infrastructure
        │       ├── CafeRepositoryImpl.java
        │       ├── CafeJpaEntity.java
        │       └── CafeJpaRepository.java
        ├── board
        │   ├── presentation
        │   │   └── BoardController.java
        │   ├── application
        │   │   └── BoardService.java
        │   ├── repository
        │   │   └── BoardRepository.java
        │   ├── domain
        │   │   └── Board.java
        │   └── infrastructure
        │       ├── BoardRepositoryImpl.java
        │       ├── BoardJpaEntity.java
        │       └── BoardJpaRepository.java
        └── post
            ├── presentation
            │   └── PostController.java
            ├── application
            │   └── PostService.java
            ├── repository
            │   └── PostRepository.java
            ├── domain
            │   └── Post.java
            └── infrastructure
                ├── PostRepositoryImpl.java
                ├── PostJpaEntity.java
                └── PostJpaRepository.java
도메인 기반 구조(도메인 계층 기반 구조)의 특징
1. 도메인 기반 구조는 계층 기반 구조보다는 복잡함.
2. 도메인 기반 구조는 도메인이 눈에 들어옴.
3. 도메인 기반 구조는 프로젝트를 구성하는 주요 단위를 도메인으로 보고 있음.

9.2.3 정리

  • 작은 규모의 프로젝트라면 단순하고 직관적인 계층 기반 구조로 개발하는 것이 유리함.
  • 큰 규모의 프로젝트라면 비즈니스 도메인의 복잡성을 관리하기 위해 도메인 기반 구조로 개발하는 것이 유리함.
  • 현재 진행 중인 프로젝트의 주요 단위가 무엇인지를 생각해 보고, 이를 바탕으로 모듈 관리 전략을 수립해서 패키지 구조를 선택하면 됨. 다만, 독립성과 은닉성은 제대로 관리해야 함.
패키지 구조 선택 기준
- 프로젝트의 규모는 어떤가?
- 프로젝트를 어떤 방향으로 모듈화하고 싶은가?

9.3 패키지와 모듈

1. 모듈과 모듈 시스템이 무엇인지 알아야 함.
2. 모듈과 패키지의 차이점을 알아야 함.
3. 필요하다면 패키지도 모듈성을 추구해야 함.
  • 모듈이 추구하는 가치인 독립성과 은닉성은 소프트웨어 개발의 기본 원리임. 시스템의 복잡도를 낮추고 확장성을 높일 수 있음.
  • 따라서 매 개발 단계마다 모듈성을 따지는 것이 좋음. 패키지에서도, 클래스에서도, API 서버에서도 각 단계에서 모듈성을 따지는 것이 좋음.

 

프로젝트의 복잡성에 따라 적절한 패키지 구조를 선택하는 것이 시스템의 유지보수성과 확장성에 영향을 미친다는 것을 이해하게 되었습니다. 그간 도메인 구조를 별다른 고민 없이 사용해왔지만, 앞으로는 프로젝트의 요구사항과 특성을 고려하여 계층적 또는 도메인 중심의 구조를 적절히 선택하려고 합니다.

 

이 글은 『자바/스프링 개발자를 위한 실용주의 프로그래밍』 책을 학습한 내용을 정리한 것입니다.
Comments