서론
저는 이때까지 spring Boot로 개인 프로젝트나 공부를 할 때, 하나의 패키지를 만들었습니다.
src폴더안에 서비스별로 나누고(유저에 대한 Member, 게시판에 대한 board, 설정 파일들이 있는 Configration등등...),
서비스들 안에는 controller, service, dto 등등을 구성했습니다.
하지만 이렇게 하면 폴더를 타고 폴더를 타고.. 이런 과정이 많아지고, 가시성이 안좋을 수도 있으며 테스트하기에도 불리할 수도 있습니다. 이를 위해서 멀티 모듈을 구성해 모듈로서 관심사를 구분하는 멀티모듈 프로젝트를 구성해보고자 합니다.
멀티 모듈 프로젝트의 장점
멀티 모듈은 하나의 프로젝트를 여러 개의 작은 모듈로 나누어서 개발하고 관리하는 방식을 의미합니다. 각각의 모듈은 독립적으로 개발 및 빌드될 수 있고, 필요할 경우 다른 모듈과 의존성을 가질 수 있습니다. 이러한 구성은 단일 모듈보다 몇 가지 장점을 제공합니다.
- 모듈화 : 멀티모듈 프로젝트를 사용하면 프로젝트를 여러 모듈로 나눌 수 있습니다. 각 모듈은 특정 기능 또는 독립적인 부분을 담당하므로 코드의 모듈화와 구조화가 용이해집니다. 이는 코드의 유지보수성을 향상시키고, 팀원들 간의 협업을 더 쉽게 만들어줍니다.
- 의존성 및 빌드 관리: 멀티모듈을 사용하면 각 모듈이 독립적으로 빌드되고 관리될 수 있습니다. 각 모듈은 자체적인 의존성 관리 및 빌드 설정을 가질 수 있으며, 필요한 경우 각 모듈의 빌드를 병렬로 실행하여 전체 빌드 시간을 단축할 수 있습니다.
- 테스트 및 배포 용이성: 각 모듈은 독립적으로 테스트될 수 있으므로 모듈별로 테스트 케이스를 실행하고 관리하기 용이합니다. 또한 배포 시에 필요한 모듈만 선택하여 배포할 수 있어서 전체 시스템의 배포 작업을 세분화할 수 있습니다.
- 재사용성과 확장성: 모듈화된 코드는 다른 프로젝트에서 재사용될 수 있습니다. 필요한 모듈을 다른 프로젝트에 가져와 적용함으로써 코드의 재사용성을 높일 수 있습니다. 또한 새로운 기능을 추가하기 위해 쉽게 모듈을 확장할 수 있습니다.
- 코드 관리와 가독성: 각 모듈은 명확하게 정의된 역할을 담당하므로 코드의 의도를 더 잘 이해하고 파악할 수 있습니다. 또한 모듈 간의 의존성이 분명하게 드러나므로 코드 관리 및 유지보수가 더 쉬워집니다.
IntelliJ에서 멀티모듈 구성하기
인텔리제이를 통해서 간단한 멀티 모듈 프로젝트를 구성해보겠습니다.
요구사항
Java
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk version
sdk install java ll.0.17-zulu
java --version
Java가 설치되어 있지 않다면 설치해줍니다. sdkman으로 설치해주었습니다.
gradle
sdk list gradle
sdk install gradle 7.3.1
gradle을 설치해줍니다. 7.3.1버전을 사용했습니다.
docker-desktop
https://www.docker.com/products/docker-desktop/
DB를 사용할 때 docker를 사용할 겁니다. 필수는 아닙니다.
프로젝트 생성하기
인텔리제이에서 프로젝트를 생성해줍니다.
언어는 Java, 타입은 Gradle로 해줍니다. 나머지는 프로젝트내에서 설정할 것이므로, dependency는 대충 넘겼습니다.
프로젝트로 들어와서 gradle 패키지 > wrapper > gradle-wrapper.properties로 들어가서 gradle의 버전을 7.3.1로 변경해줍니다.
루트경로의 src 패키지는 지워도 됩니다. 여기서는 모듈들을 관리하기만 할 것이므로 소스가 필요없습니다.
root 경로의 build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.9'
}
repositories {
mavenCentral()
}
bootJar.enabled = false
subprojects {
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
apply plugin: 'java'
apply plugin: 'java-library'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
// 관리하는 모듈의 공통 dependencies
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
test {
useJUnitPlatform()
}
}
project(':api') { // 컴파일 시 database 로딩
dependencies {
compileOnly project(':database')
}
}
project(':database') {
bootJar { enabled = false }
jar { enabled = true }
}
root 경로에 있는 build.gradle을 작성해줍니다.
자바 11버전을 사용할거고, 새로 만들 모듈들, subprojects에 기본적으로 있어야 하는 플러그인이나 dependency를 추가해줍니다. 그리고 현재 프로젝트에서 api와 database라는 모듈을 만들건데, api모듈에서 database를 사용할 것이므로 설정해주고, database는 DB를 관리하는 모듈입니다. Applicaiton와 같은 실행파일이 필요없으므로 bootJar를 false로 해줍시다.
모듈 생성
이제 루트 프로젝트를 클릭해서 새로만들기 > 모듈로 들어가서 모듈을 생성해줍니다.
필요에 따라서 새 모듈에서 gradle로 만들거나 spring initializer로 모듈을 생성해줍니다.
모듈의 내용은 Java, Gradle만 맞으면 됩니다. 저는 spring initializer로 생성해주었습니다.
이렇게 controller와 비즈니스 로직 등등을 관리할 api 모듈과 DB관련한 소스를 관리할 database 모듈을 만들었습니다.
settings.gradle로 들어가서 새로 만든 모듈들이 include 되어 있는지 확인합니다.
만약 없다면 추가해주도록 합니다.
api 모듈의 build.gradle
plugins {
id 'java'
}
group = 'com.example'
version = '0.0.1'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.7.6'
implementation "org.springframework.boot:spring-boot-starter-web:2.7.6"
implementation project(":database")
}
test {
useJUnitPlatform()
}
database 모듈의 build.gradle
plugins {
id 'java'
}
group = 'com.example'
version = '0.0.1'
java {
sourceCompatibility = '11'
}
repositories {
mavenCentral()
}
allprojects {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.7.6'
implementation "mysql:mysql-connector-java:8.0.33"
implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.7.6'
}
}
tasks.named('test') {
useJUnitPlatform()
}
각 모듈에 build.gradle을 설정해줍니다.
api에는 starter-web과 DB에 접근할 것이므로 jpa를 넣어줍니다. 그리고 서비스에서 DB의 파일들을 사용할 때가 있습니다. (repository나 entity에 접근해야함) 이를 위해서 database를 의존성에 추가해줍니다.
database에는 jpa과 DB로 mysql을 사용할 것입니다. mysql-connector를 넣어줍니다. 만약 redis를 사용해준다면 넣어줍니다.
Docker로 DB설치하기
docker run -d \
--name test-mysql \
-e MYSQL_ROOT_PASSWORD="test" \
-e MYSQL_USER="test" \
-e MYSQL_PASSWORD="test" \
-e MYSQL_DATABASE="test" \
-p 3306:3306 \
mysql:latest
저는 DB를 docker로 생성해 주었습니다. 다른방식으로 해도 상관없습니다.
프로젝트안에 docker라는 패키지를 만들고, test-mysql.sh라는 sh파일을 만들었습니다.
위의 sh파일로 "test-mysql" 이라는 컨테이너명을 가진 유저, 비밀번호가 "test"인 Mysql DB를 3306포트에 최신버전으로 생성해줍니다.
/bin/sh /mnt/c/Users/{사용자}/Downloads/test_proejct/docker/test-mysql.sh
터미널이나 cmd창에서 해당 sh파일을 실행해줍니다. 루트들은 사람마다 다르므로 pwd나 인텔리제이에서 바로 실행하면 됩니다.
그럼 cmd창에서 "docker ps" 명령어로 확인해도되고, 위처럼 docker-desktop에서 확인해보면 DB가 생성된 것을 확인해 볼 수 있습니다.
인텔리제이 안에서도 DB정보를 입력해서 DB가 활성화 되었는지 확인할 수 있습니다.
좌측 데이터베이스 > +버튼 > MySQL 선택 > 정보입력 > 확인
api 모듈의 application.yml
spring:
profiles:
include:
- database
api모듈안에 resouces안에 application.yml을 만들어줍니다. 안에 위와같은 옵션을 추가해줍니다.
api모듈에서 repository 등 DB관련한 내용을 사용할건데, mysql에 대한 정보들은 이미 database모듈 안에 apllication.yml에 명시되어 있을 겁니다. 그래서 이걸 사용해주기위해 include해줍니다.
database의 appication-database.yml
spring:
jpa:
generate-ddl: false
show-sql: true
hibernate:
ddl-auto: validate
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=Asia/Seoul
username: test
password: test
database안에 resouces안에 application-database.yml을 만들어줍니다.
mysql 연동을 위해 내가 만든 DB의 username, 비번, url 등등의 정보를 넣어줍니다.
주의할 점: 보통 모듈별로 `application.yml`을 사용하려면 `spring.profiles.active`를 설정하여 원하는 프로파일을 활성화할 수 있습니다. 그리고 각 모듈의 프로파일에 따라 각각의 `application-{profile}.yml` 파일을 생성하여 해당 프로파일에 대한 설정을 정의할 수 있습니다. 한마디로, application-database.yml 로 설정해야 api 모듈에서 가져와 쓸 수 있습니다. 만약 database에서 application.yml로 만든다면, api모듈에서 해당 yml 파일을 못찾을 겁니다.
간단한 테스트 해보기
간단하게 데이터를 저장하도록 해본다.
SQL문
create table TEST_USR(
id bigint not null auto_increment primary key,
usr_key varchar(32) not null unique,
pwd varchar(50) not null
);
MySql에 테이블을 생성해줍니다. 간단하게 id, user_key, pwd 3가지만 만들었습니다.
Entity
@Entity
@Data
@Table(name = "TEST_USR")
public class Test {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "usr_key")
String userKey;
@Column(name = "pwd")
String password;
}
Repository
@Repository
public interface TestRepository extends JpaRepository<Test, Integer> {
}
엔티티와 repository를 database모듈에 생성해줍니다.
Controller
@RestController
@RequiredArgsConstructor
public class TestController {
private final TestService testService;
@PostMapping("/test")
public void test(@RequestBody TestInput parameter) {
testService.toSaveData(parameter);
}
}
Service
@Service
@RequiredArgsConstructor
public class TestServiceImpl implements TestService {
private final TestRepository testRepository;
@Override
public boolean toSaveData(TestInput parameter) {
Test test = new Test();
test.setUserKey(parameter.getUser_key());
test.setPassword(parameter.getPassword());
testRepository.save(test);
return true;
}
}
api모듈에 컨트롤러와 서비스를 만들어줍니다.
post요청이 들어오면 body의 내용을 DB에 저장해줍니다.
Application
@EnableJpaAuditing
@SpringBootApplication
@EnableJpaRepositories(basePackages = {"com.example.database"})
@EntityScan(basePackages = {"com.example.database"})
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
api모듈은 실행파일이 필요합니다. ApiApplication이 필요한데, @EnableJpaRepositories, @EntityScan 어노테이션을 추가해줍니다. api모듈에서 Entity와 Repository를 스캔하기 위해서입니다.
이제 apiApplication을 실행한 후 post요청을 보내봅니다.
저는 chorme 확장프로그램으로 했으나 postman이나 curl 명령어로 post요청을 보내볼 수 있습니다.
위처럼 정상적으로 데이터를 받을 수 있습니다.
마치며
https://github.com/Raccoonmen/multi-module-project
이번에는 크고 복잡한 프로젝트에서 유용한 멀티모듈을 구성해 보았습니다.
확실히 가시성이나 어떤 모듈이 어떤 역할을 하는지 분리되어 있으므로 좋았습니다.
나중에는 api, databae 뿐만이 아니라 kafka, consumer 등등 여러가지 모듈을 더 만들 상황이 생길 수도 있습니다. 잘 알아두는 것이 좋을 것 같습니다.
'Backend > 기능 구현' 카테고리의 다른 글
Spring Security JWT - 4. 로그인 세팅 (0) | 2024.01.17 |
---|---|
Spring Security JWT - 2. 프로젝트 생성 및 기본세팅 (0) | 2024.01.16 |
Spring Security JWT - 1. 개념 및 동작 방식 (1) | 2024.01.13 |