9. 모듈

개발 99·2025년 4월 25일

9.1 모듈성

어떤 목적을 수행하기 위해 분리된 작은 구성 단위

  • 모듈이란?
    라이브러리와 프로그램의 구성 요소(객체) 사이에 위치하는 개념인데,
  1. 독립성: 모듈은 독립적이어야 한다.
  2. 은닉성: 모듈의 사용자는 모듈의 내부 구현을 몰라도 된다. 공객된 인터페이스를 이용해 모듈과 통신한다.
    (연관된 코드 묶음이 이 특성을 만족해야만 모듈이라고 부를 수 있다.)
  • 모듈 시스템이란?
    연관된 코드 묶음이 '모듈성'을 갖출 수 있게 도와주는 시스템적인 해결책이며, 다음과 같은 기능을 필수적으로 지원해야 한다.
  1. 의존성 관리 : 모듈을 사용하기 위해 어떤 의존성이 필요한지 명시할 수 있어야 한다.
  2. 캡슐화 관리 : 모듈은 불필요한 구현을 외부로 드러내지 않아야 한다.

정리

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

  • 모듈성에는 독립성과 은닉성이라는 특징이 있다.

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

  • 모듈 시스템은 모듈성 중 독립성을 지원하기 위해 의존성 관리 기능을 지원해야 한다.

  • 모듈 시스템은 모듈성 중 은닉성을 지원하기 위해 캡슐화 관리 기능을 지원해야 한다.

모듈 시스템을 통해 이 모듈의 이름은 무엇이고, 어떤 클래스가 선언돼 있으며, 어떤 외부 모듈에 의존하는지, 내부 컴포넌트 중 어떤 컴포넌트를 외부로 공개할지 정의한다.
(Manager?)

여기서 모듈 시스템에 사용된 import와 export는 어떤 의미인가?

  • import : 이 모듈은 어떤 모듈에 의존하고 있는지 나타낸다.
    (해당 모듈을 사용하기 위해 어떤 부수적인 모듈이나 컴포넌트가 필요한지를 선언하는 역할을 한다.)

  • exports : 이 모듈 중 어떤 컴포넌트만 외부로 노출할 것인지 나타낸다.
    (모듈의 캡슐화를 위해 사용되며, 해당 모듈을 임포트하면 어떤 컴포넌트를 사용할 수 있는지 나열하는 역학을 하고,
    그 덕에 외부로 공개할 컴포넌트를 제한할 수 있다.)

import * as log4js from "log4js";

const logger = log4js.getLogger();

function printFoobar(){
	logger.info("foobar");
}

export fuction printHelloWorld(){
	logger.info("Hello world!");
}

lo4js를 import해서 printFoobar()와 printHelloWorld()를 생성했지만,
사용자는 오로지 printHelloWorld()만 사용가능하다.

따라서 모듈 수준의 의존성 관리와 캡슐화는 어떤 모듈을 정의하면서 해당 모듈에 필요한 의존 모듈이 무엇인지 명시할 수 있고,

해당 모듈을 배포시 모든게 아닌 일부만 배포할 수 있어야 한다.

자바의 패키지는 연관성 높은 코드를 묶는 수단에 불과하므로, 폴더 개념에 더 가깝다.

하지만 module-info.java라는 모듈 디스크럽터로 가능하다.

module myproject.main{
	requires org.apache.httpcomponents.httpclient;
    exports com.myorg.myproject.model;
}

requires를 이용해 외부 의존성을 관리하고,
exports로 외부로 노출할 모듈 내 인터페이스 패키지를 관리한다.
( 패키지 시스템은 모듈 시스템이 될 수 없다. )

연관된 코드를 패키지로 모아둔다고 해서 그것이 모듈이 되는 것이 아니라, 모듈 수준의 의존성 관리와 캡슐화가 반드시 뒤따라야 한다.

정리

  • 모듈은 모듈성을 만족한다.

  • 모듈성은 독립성과 은닉성을 가지고 있다.

  • 모듈 시스템은 코드 묶음이 모듈성을 갖출 수 있게 도와준다.

  • 모듈 시스템은 기능적으로 모듈 수준의 의존성 관리와 캡슐화 관리가 가능해야 한다.

  • 패키지와 모듈은 다르다.

  • 패키지 시스템은 모듈 시스템이 아니다

  • 패키지를 만들 때도 모듈처럼 독립성과 은닉성을 추구해야 한다.

모듈 시스템은 모듈을 만들기 위한 수단 중 하나인데, 모듈은 모듈 시스템 없이도 만들 수 있다.

9.1.1 독립성

모듈이 다른 모듈이나 컴포넌트에 강하게 의존하지 않고 각 모듈을 개별적으로 수정하거나 교체가 가능해야 한다.
(유지보수, 확장성, 재사용성)

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

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

이는 모듈이 독립적이려면 의존성 관리할 수 있어야 함을 의미한다.

Ex)

package com.myproject.account;

public class Account {}
// account보다 상대적으로 독립성이 떨어진다.
package com.myproject.post;

import com.myproject.account.Account;

public class Post{
	Account writer;
}

독립적이다라는 뜻은 다음과 같다.

  • 최대한 내부에서 해결하라

  • 외부에 강하게 의존하지 마라

  • 외부 시스템을 사용한다면 외부 시스템의 사용을 명시하라(핵심!)
    외부 시스템의 사용을 명시하는 것은
    애플리케이션을 실행하는 데 필요한 하위 의존성을 모두 가져오는 것과 동일하다.(의존성 명시와 동일)

그래서 의존성을 명확히 하는 것만으로도 모듈을 독립적으로 만들 수 있다.

(그렇다 해도, 의존성 관리가 핵심이다.)

하위 의존성 추적 기능의 장점

  • 모듈 불러오기 자동화

  • 하위 모듈의 중복 및 불필요한 의존성 검사

  • 하위 모듈 에러시 상위 원인 모듈 파악 가능

  • 동적 로딩 가능

마지막으로 모듈의 시작은 연관된 코드 묶음이고, 높은 응집도, 독립성, 은닉성도 지켜야 한다.

9.1.2 은닉성

모듈은 은닉성을 추구해야 한다.(캡슐화 가능)

  • 자바 패키지 시스템의 문제점
    해당 라이브러리를 가져오면, 그 라이브러리에 담긴 모든 클래스를 사용할 수 있다.

그러나, 모듈을 외부로 공표한다는 것은 모든 사용자를 위한 하위 호환성을 보장해야 함을 의미하므로,
결론적으로 모듈 수준의 인터페이스 관리가 필요함을 뜻한다.

모듈을 사용한다 != 모듈의 모든 기능을 사용할 수 있게 접근한다.

(모듈의 사용자는 모듈이 책임지는 공개된 일부 기능에만 접근할 수 있어야 한다.)

Ex. Micro Service Architecture

  1. MSA의 독립성
  • MSA는 거대한 시스템의 독립된 일부 도메인이다.

  • MSA는 외부 서비스와 협력한다.

  1. MSA의 은닉성
  • MSA의 내부 구현은 외부로 드러나지 않고, 오직 공개된 외부 API만을 통해 외부 서비스와 협력한다.

9.2 패키지 구조

  1. 계층 기반 구조
    com.company.project.layer(presentation, service, ...).*

  2. 도메인 기반 구조
    com.company.project.domain.*

9.2.1 계층 기반 구조

레이어드 아키텍처를 사용하는 프로젝트에 주로 사용.

com.demo.myapp
	.presentation
    	.UserController.java
        .CafeController.java
        .BoardController.java
        
    .business
    	.UserService.java
        .CafeService.java
        .BoardService.java
    	.repository (interface)
        	.UserRepository.java
            .CafeRepository.java
            .BoardRepository.java
    .domain
    	.User.java
        .Cafe.java
        .Board.java
        
    .infrastructure
    	.UserRepositoryImpl.java (repository 패키지 내 인터페이스 구현체)
        .UserJpaEntity.java
        .UserJpaRepository.java
        .CafeRepositoryImpl.java
        .CafeJpaEntity.java
        .CafeJpaRepository.java
        .BoardRepositoryImpl.java
        .BoardJpaEntity.java
        .BoardJpaRepository.java
  1. 장점
    이해하기 쉽고, 사용하기 쉽다

  2. 단점
    도메인이 눈에 들어오지 않는다.
    애플리케이션에 어떤 도메인이 사용되는지 파악하려면 모든 계층을 열어보고 정리해야만 한다.
    (도메인의 관점의 응집도가 떨어지고, 그 결과 비즈니스 코드를 한곳에 모아 볼 수 없다.)

Ex.
UserService는 User 기반은로 작성되고, UserController는 UserService 기반으로 작성되니
하나로 묶는 것이 낫다.

패키지는 논리적으로 연관된 클래스 파일들을 모아 놓는 공간임을 명심해야 한다.

9.2.2 도메인 기반 구조

DDD를 이용하는 프로젝트에 자주 사용

.com.demo.myapp
	.user
    	.UserController.java
        .UserService.java
        .UserRepository.java
        .User.java
        ...
    .cafe
    	.CafeController.java
        .CafeService.java
        .CafeRepository.java
        .Cafe.java
        ...

패키지 최상단에 프로그램이 사용하는 도메인이 오도록 구성한다.

  1. 장점
    비즈니스 코드가 여기저기 산재되지 않는다.
    최상단 도메인을 보고 어떤 도메인을 사용하는 프로젝트인지 알 수 있다.

  2. 단점
    계층이 눈에 들어오지 않는다.

그래서 아래와 같이 각 도메인에 레이어를 나눈다.

.com.demo.myapp
	.user
    	.presentation
        	.UserController.java
        .application
        	.UserService.java
        .repository
        	.UserRepository.java
        .domain
        	.User.java
        .Infrastructure
        	.UserRepositoryImpl.java
            .UserJpaEntity.java
            .User JpaRepository.java
    
    .cafe 이하동문.
  1. 도메인 기반 구조는 계층 기반 구조보다는 복잡하다.

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

  3. 프로젝트를 구성하는 주요 단위를 도메인으로 보고 있다.

프로젝트의 패키지 구조를 선택할 때 중요한 것은 패키지 구조의 생김새가 아니라 모듈화 단위가 무엇인지를 생각하고, 이를 바탕으로 패키지를 구성해 독립성과 은닉성을 제대로 관리하는 것이다.

9.3 패키지와 모듈

그냥 언제 어디서든 독립성과 은닉성을 습관적으로 추구하라.
(패키지든 ,클래스든, API든 다 따져라.)

profile
구구구구구!

0개의 댓글