Home [football] 멀티 모듈 구조를 통한 서비스 단위 서버 구분
Post
Cancel

[football] 멀티 모듈 구조를 통한 서비스 단위 서버 구분

# 문제점


API 서버와 웹소켓 서버를 구분한 아키텍처로 구현했지만 하나의 모듈 내에서 전부 구현되어 하나의 포트에서 동작하는 한계가 있었습니다. 같은 프로젝트를 두개 이상의 서버로 실행시키면서 제한적인 기능만 활용하는 모양이 되었고 이러한 구조는 서버를 분리하면 할수록 매우 비효율적으로 동작하게 되었습니다.

결국 현재 프로젝트 구조는 제가 의도했던 완벽한 서버의 분리가 젼혀 안된 구조였고 이를 각각의 프로세스로 동작하도록 나눠줄 필요가 있다고 판단했습니다.

# 해결 방법


Spring 프로젝트 자체를 분리?

api서버와 웹소켓 서버를 각각 구현한 두개의 프로젝트로 나눠서 구현한다면 위에서 말한 문제점은 해결된다고 볼 수도 있겠지만 두 서버에서 공통적으로 사용되는 코드를 반복 생성해야 되고 IDE도 여러개로 띄워놔야 하는 또 다른 문제점들을 발생시킬 수 있습니다.

이는 완벽한 해결방법이라 생각되지 않았고 하나의 프로젝트에서 각 서버의 기능을 구현한 모듈을 분리할 수 있는 방법을 찾아보기로 했습니다.

멀티 모듈

Spring에서는 하나의 프로젝트 내에서 여러개의 모듈을 생성해 독립적으로 프로세스를 동작시킬 수 있어 멀티 모듈 구조로 현재 프로젝트를 다음과 같이 나눠봤습니다.

  • api-server 모듈
  • chatting-server 모듈
  • core-server 모듈

이렇게 각각의 모듈에 해당하는 코드를 분리시키고 공통적으로 사용되는 클래스에 대해선 core 모듈에 구성함으로써 공유되도록 프로젝트를 재구성해보기로 했습니다.

구현 모습

자세한 멀티 모듈 구성 방법은 링크를 참고하세요.

생성된 각 모듈의 모습

위 사진처럼 세개의 모듈을 생성한 모습을 볼 수 있습니다.

Root Project의 build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
buildscript {
	ext {
		springBootVersion = '2.7.1'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
		classpath "io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE"
	}
}

// 하위 모든 프로젝트 공통 세팅
subprojects {
	apply plugin: 'java'
	apply plugin: 'idea'
	apply plugin: 'org.springframework.boot'
	apply plugin: 'io.spring.dependency-management'

	group 'org.example'
	version '1.0-SNAPSHOT'

	sourceCompatibility = '11'
	compileJava.options.encoding = 'UTF-8'

	repositories {
		mavenCentral()
	}

	// 하위 모듈에서 공통으로 사용하는 의존성도 추가할 수 있습니다. 다만 모듈별 확실한 구분을 위해 미사용합니다.
	dependencies {
	}

	test {
		useJUnitPlatform()
	}
}

project(':football-core') {
	bootJar { enabled = false } // main()으로 실행시키지 않을 모듈
	jar { enabled = true } // 하지만 jar 파일이 만들어여야만 다른 모듈에서 의존 가능
}

project(':football-api-server') {
	bootJar { enabled = true }
	jar { enabled = false }
}

project(':football-chatting-server') {
	bootJar { enabled = true }
	jar { enabled = false }
}

루트 프로젝트의 build.gradle 파일에선 모든 모듈에서 사용될 공통 세팅을 할 수 있고 의존성도 추가할 수 있습니다. 다만 각 모듈의 확실한 분리를 위해 해당 파일에선 최대한 선언하지 않고 각 모듈들의 build.gradle 파일에서 의존성을 추가하는 방향으로 구성했습니다.

Api-server 모듈의 build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
dependencies {

    // MySql
    implementation 'mysql:mysql-connector-java'

    // Flyway
    implementation 'org.flywaydb:flyway-core:5.2.4'

    // Redis
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'

    // JPA
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // Valid
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    // Spring Security
    implementation 'org.springframework.boot:spring-boot-starter-security'

    // Lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    // JUnit5
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'

    // etc
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter'
    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

    implementation project(':football-core')
}

Chatting-server 모듈의 build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dependencies {

    // Web Socket
    implementation 'org.springframework.boot:spring-boot-starter-websocket'

    // Spring Security
    implementation 'org.springframework.boot:spring-boot-starter-security'

    // Lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    // JUnit5
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'

    // etc
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter'
    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

    implementation project(':football-core')
}

core-server 모듈의 build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
dependencies {

    // JWT
    implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2'

    // Spring Security
    implementation 'org.springframework.boot:spring-boot-starter-security'

    // Lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    // JUnit5
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'

    // etc
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter'
    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

}

위 모듈별 의존성이 추가된 라이브러리만 확인해본다면 websocket 모듈에는 웹소켓에 대한 기능만 수행할 수 있도록 구성했고, 그 외 DB, Redis와 연동된 작업은 Api 서버가 담당하도록 분리시켰고 Spring Security에 대한 구성 클래스만 core 모듈에 위치시켜 다른 두 모듈에서 같은 설정을 가지고 공유할 수 있도록 구성했습니다.

common 모듈의 저주라는 말이 있듯이, core 모듈에 너무 많은 코드나 의존성이 추가되어 있는 것은 위험할 수 있습니다.

그래서 추후에는 core 모듈에 구성된 Spring Security에 대한 책임도 다른 모듈로 분리시켜 core 모듈 내에는 순수 자바 코드에 대한 공유만 이뤄질 수 있도록 리팩토링 할 예정입니다.

# 마치며


이처럼 Scale Out을 고려한 설계를 통해 Api 서버와 채팅 서버에 대한 분리를 고려했고 이를 실제로 독립적인 프로세스로 동작할 수 있도록 멀티 모듈을 적용시켰습니다. 멀티 모듈 구조를 통해 서버 확장에 대한 실제 코드 구성이 가능하도록 할 수 있었으며 추가적으로 공통된 코드도 효율적으로 관리할 수 있는 구조로 변경할 수 있었습니다.

# 참고자료


  • https://velog.io/@soyeon207/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EB%A9%80%ED%8B%B0-%EB%AA%A8%EB%93%88-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0
  • https://techblog.woowahan.com/2637/
This post is licensed under CC BY 4.0 by the author.

[football] Redis Cluster와 Replication 구조를 통한 분산 저장이 가능한 설계 구현

[football] Github Actions를 활용한 CI/CD