์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Spring Boot 3.0๊ณผ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜ ๊ตฌํ˜„ ์ „๋žต
โ˜• Java ยท ํ”„๋กœ๊ทธ๋žจ๊ฐœ๋ฐœ

๐Ÿš€ Spring Boot 3.0๊ณผ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜ ๊ตฌํ˜„ ์ „๋žต

์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ณ  ์žฌ๋ฏธ์žˆ๊ฒŒ ๋ฐฐ์šฐ๋Š” MSA์˜ ๋ชจ๋“  ๊ฒƒ! ๐ŸŽ‰

๋ชจ๋†€๋ฆฌ์‹ ํ•˜๋‚˜์˜ ๊ฑฐ๋Œ€ํ•œ ๋ฉ์–ด๋ฆฌ ์•ฑ ๐Ÿ˜… ์ฃผ๋ฌธ ๊ฒฐ์ œ ํšŒ์› โ†’ ๋ถ„๋ฆฌ! ์ฃผ๋ฌธ ์„œ๋น„์Šค Order Service ๊ฒฐ์ œ ์„œ๋น„์Šค Payment Service ํšŒ์› ์„œ๋น„์Šค User Service ์•Œ๋ฆผ ์„œ๋น„์Šค Notify Service ๐ŸŒ API Gateway ๋ชจ๋“  ์š”์ฒญ์˜ ๊ด€๋ฌธ โ˜• Spring Boot 3.0 Java 17+ ยท GraalVM ยท Jakarta EE

๐Ÿค” Spring Boot 3.0, ๋ญ๊ฐ€ ๋‹ฌ๋ผ์กŒ์–ด?

์•ผ, ์†”์งํžˆ ๋งํ•ด์„œ Spring Boot 2.x์—์„œ 3.0์œผ๋กœ ๋„˜์–ด๊ฐ€๋Š” ๊ฑด ๋‹จ์ˆœํ•œ ๋ฒ„์ „์—…์ด ์•„๋‹ˆ์•ผ.
์ด๊ฑด ํŒจ๋Ÿฌ๋‹ค์ž„์˜ ์ „ํ™˜์ด๋ผ๊ณ  ๋ด์•ผ ํ•ด. ๐Ÿ”ฅ

2022๋…„ 11์›”์— ์ •์‹ ์ถœ์‹œ๋œ Spring Boot 3.0์€ Java 17์„ ์ตœ์†Œ ์š”๊ตฌ ๋ฒ„์ „์œผ๋กœ ์„ค์ •ํ–ˆ๊ณ ,
๊ธฐ์กด์˜ javax.* ํŒจํ‚ค์ง€๊ฐ€ ์ „๋ถ€ jakarta.*๋กœ ๋ฐ”๋€Œ์—ˆ์–ด.
์ด๊ฒŒ ์ฒ˜์Œ์—” "์•„ ๊ท€์ฐฎ์•„..." ์‹ถ์ง€๋งŒ, ์‚ฌ์‹ค ์ด ๋ณ€ํ™”๊ฐ€ ์—„์ฒญ๋‚œ ๋ฏธ๋ž˜๋ฅผ ์—ด์–ด์ค€๋‹ค๊ณ !

๐Ÿ“Œ Spring Boot 3.0 ํ•ต์‹ฌ ๋ณ€๊ฒฝ์‚ฌํ•ญ ์š”์•ฝ Java 17 ์ตœ์†Œ ์š”๊ตฌ Jakarta EE 9+ GraalVM Native Image Micrometer Observation API HTTP/2 ๊ธฐ๋ณธ ์ง€์› Spring Security 6.0 Hibernate 6.x

ํŠนํžˆ GraalVM Native Image ์ง€์›์€ ์ง„์งœ ๊ฒŒ์ž„์ฒด์ธ์ €์•ผ.
๊ธฐ์กด JVM ์œ„์—์„œ ๋Œ๋ฆฌ๋˜ Spring ์•ฑ์„ ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ์ปดํŒŒ์ผํ•˜๋ฉด
์‹œ์ž‘ ์‹œ๊ฐ„์ด ์ˆ˜์‹ญ ๋ฐ€๋ฆฌ์ดˆ๋กœ ์ค„์–ด๋“ค๊ณ , ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰๋„ ํ™• ์ค„์–ด๋“ค์–ด.
๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ ์ด๊ฒŒ ์–ผ๋งˆ๋‚˜ ์ค‘์š”ํ•œ์ง€ ์•Œ์ง€? ์ปจํ…Œ์ด๋„ˆ ์ˆ˜๋ฐฑ ๊ฐœ ๋„์šธ ๋•Œ ์ฐจ์ด๊ฐ€ ์–ด๋งˆ์–ด๋งˆํ•ด! ๐Ÿš€

๐Ÿ’ก Tip! Java 17์˜ ๋ ˆ์ฝ”๋“œ(Record), ๋ด‰์ธ ํด๋ž˜์Šค(Sealed Class), ํŒจํ„ด ๋งค์นญ ๋“ฑ์˜ ๊ธฐ๋Šฅ์ด
Spring Boot 3.0๊ณผ ํ•จ๊ป˜ ์“ฐ๋ฉด ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•˜๊ณ  ์•ˆ์ „ํ•ด์ ธ. ๊ผญ ๊ฐ™์ด ๊ณต๋ถ€ํ•ด๋ด!

๐Ÿ—๏ธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜(MSA)๋ž€ ๋ญ”๋ฐ?

MSA๋ฅผ ํ•œ ์ค„๋กœ ์„ค๋ช…ํ•˜๋ฉด ์ด๋ž˜:
"ํ•˜๋‚˜์˜ ํฐ ์•ฑ์„ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ž‘๊ณ  ๋…๋ฆฝ์ ์ธ ์„œ๋น„์Šค๋กœ ์ชผ๊ฐœ์„œ ์šด์˜ํ•˜๋Š” ๋ฐฉ์‹"

์˜ˆ์ „์—” ์‡ผํ•‘๋ชฐ ํ•˜๋‚˜ ๋งŒ๋“ค๋ฉด ์ฃผ๋ฌธ, ๊ฒฐ์ œ, ํšŒ์›, ๋ฐฐ์†ก, ์•Œ๋ฆผ ๊ธฐ๋Šฅ์ด ์ „๋ถ€ ํ•˜๋‚˜์˜ ๊ฑฐ๋Œ€ํ•œ ํ”„๋กœ์ ํŠธ ์•ˆ์— ์žˆ์—ˆ์ž–์•„.
์ด๊ฑธ ๋ชจ๋†€๋ฆฌ์‹(Monolithic) ์•„ํ‚คํ…์ฒ˜๋ผ๊ณ  ํ•ด.

๊ทผ๋ฐ ์ด๊ฒŒ ์ปค์ง€๋ฉด ์ปค์งˆ์ˆ˜๋ก ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒจ. ๊ฒฐ์ œ ๊ธฐ๋Šฅ ํ•˜๋‚˜ ์ˆ˜์ •ํ–ˆ๋Š”๋ฐ ์ „์ฒด ์„œ๋น„์Šค๊ฐ€ ๋‹ค์šด๋˜๊ฑฐ๋‚˜,
๋ฐฐํฌ ํ•œ ๋ฒˆ ํ•˜๋ ค๋ฉด ๋ช‡ ์‹œ๊ฐ„์”ฉ ๊ฑธ๋ฆฌ๊ฑฐ๋‚˜... ๊ฐœ๋ฐœ์ž๋“ค์˜ ์•…๋ชฝ์ด์ง€ ๐Ÿ˜ฑ

๐Ÿ˜ฑ ๋ชจ๋†€๋ฆฌ์‹์˜ ๋ฌธ์ œ์  โ‘  ํ•˜๋‚˜๊ฐ€ ์ฃฝ์œผ๋ฉด ์ „์ฒด๊ฐ€ ์ฃฝ๋Š”๋‹ค (๋‹จ์ผ ์žฅ์• ์ )
โ‘ก ํŠน์ • ๊ธฐ๋Šฅ๋งŒ ์Šค์ผ€์ผ์•„์›ƒ์ด ๋ถˆ๊ฐ€๋Šฅ
โ‘ข ๊ธฐ์ˆ  ์Šคํƒ ๋ณ€๊ฒฝ์ด ๊ฑฐ์˜ ๋ถˆ๊ฐ€๋Šฅ
โ‘ฃ ํŒ€์ด ์ปค์งˆ์ˆ˜๋ก ๋ฐฐํฌ ์ถฉ๋Œ ๋นˆ๋ฒˆ
โ‘ค ์ฝ”๋“œ๋ฒ ์ด์Šค๊ฐ€ ๊ฑฐ๋Œ€ํ•ด์ ธ์„œ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์›€
๐ŸŽ‰ MSA์˜ ์žฅ์  โ‘  ์„œ๋น„์Šค๋ณ„ ๋…๋ฆฝ ๋ฐฐํฌ ๊ฐ€๋Šฅ
โ‘ก ํ•„์š”ํ•œ ์„œ๋น„์Šค๋งŒ ์Šค์ผ€์ผ์•„์›ƒ
โ‘ข ์„œ๋น„์Šค๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ธฐ์ˆ  ์Šคํƒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
โ‘ฃ ์žฅ์•  ๊ฒฉ๋ฆฌ (ํ•˜๋‚˜ ์ฃฝ์–ด๋„ ๋‹ค๋ฅธ ๊ฑด ์‚ด์•„์žˆ์Œ)
โ‘ค ์ž‘์€ ํŒ€์ด ๊ฐ ์„œ๋น„์Šค๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐœ๋ฐœ

๋ฌผ๋ก  MSA๊ฐ€ ๋งŒ๋Šฅ์€ ์•„๋‹ˆ์•ผ. ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์˜ ๋ณต์žก์„ฑ, ๋„คํŠธ์›Œํฌ ์ง€์—ฐ, ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ฌธ์ œ ๋“ฑ
์ƒˆ๋กœ์šด ๋„์ „๊ณผ์ œ๋“ค์ด ์ƒ๊ธฐ๊ฑฐ๋“ . ๊ทธ๋ž˜์„œ Spring Boot 3.0์˜ ์ƒํƒœ๊ณ„๊ฐ€ ์ด ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ์—„์ฒญ ๋„์›€์ด ๋ผ!

๐Ÿ—๏ธ MSA ํ•ต์‹ฌ ๊ตฌ์„ฑ์š”์†Œ ๐Ÿ“ฑ ํด๋ผ์ด์–ธํŠธ ์›น / ๋ชจ๋ฐ”์ผ / ์™ธ๋ถ€ API ๐ŸŒ API Gateway ์ธ์ฆยท๋ผ์šฐํŒ…ยท๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑยทํ•„ํ„ฐ๋ง ๐Ÿ‘ค ํšŒ์› ์„œ๋น„์Šค User Service :8081 ๐Ÿ›’ ์ฃผ๋ฌธ ์„œ๋น„์Šค Order Service :8082 ๐Ÿ’ณ ๊ฒฐ์ œ ์„œ๋น„์Šค Payment Service :8083 ๐Ÿ”” ์•Œ๋ฆผ ์„œ๋น„์Šค Notify Service :8084 ๐Ÿ“จ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค Kafka / RabbitMQ ๐Ÿ” ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ Eureka / Consul โš™๏ธ Config Server ์ค‘์•™ ์„ค์ • ๊ด€๋ฆฌ ๊ฐ ์„œ๋น„์Šค๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ๋ฐฐํฌยทํ™•์žฅ ๊ฐ€๋Šฅ ๐ŸŽฏ

โš™๏ธ Spring Boot 3.0์œผ๋กœ MSA ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ํ•˜๊ธฐ

์ž, ์ด์ œ ์ง„์งœ ์‹ค์ „์œผ๋กœ ๋“ค์–ด๊ฐ€๋ณด์ž! ๐Ÿ”ฅ
Spring Boot 3.0 ๊ธฐ๋ฐ˜ MSA ํ”„๋กœ์ ํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ•˜๋Š”์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ์‚ดํŽด๋ณผ๊ฒŒ.

1
ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์„ค๊ณ„
๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ๋ฐฉ์‹ ๋˜๋Š” ๋…๋ฆฝ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋ฐฉ์‹ ์ค‘ ์„ ํƒ. ์ดˆ๊ธฐ์—” ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ์ด ๊ด€๋ฆฌํ•˜๊ธฐ ํŽธํ•ด.
2
Spring Initializr๋กœ ๊ฐ ์„œ๋น„์Šค ์ƒ์„ฑ
start.spring.io์—์„œ Spring Boot 3.x, Java 17+, ํ•„์š”ํ•œ ์˜์กด์„ฑ ์„ ํƒ
3
๊ณตํ†ต ์˜์กด์„ฑ ์„ค์ • (build.gradle / pom.xml)
Spring Cloud, Spring Security, Actuator ๋“ฑ MSA ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€
4
API Gateway ๊ตฌ์„ฑ
Spring Cloud Gateway๋กœ ๋ผ์šฐํŒ… ๊ทœ์น™ ์„ค์ •
5
์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ ์„ค์ •
Eureka Server ๋˜๋Š” Consul๋กœ ์„œ๋น„์Šค ๋“ฑ๋ก/๋ฐœ๊ฒฌ ๊ตฌํ˜„


์‹ค์ œ build.gradle ์˜ˆ์‹œ๋ฅผ ๋ณด๋ฉด ์ด๋Ÿฐ ์‹์ด์•ผ:

// build.gradle (Spring Boot 3.x MSA ๊ธฐ๋ณธ ์„ค์ •)
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

dependencies {
    // Spring Boot ๊ธฐ๋ณธ
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    
    // Spring Cloud (MSA ํ•ต์‹ฌ!)
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
    
    // ๋ณด์•ˆ
    implementation 'org.springframework.boot:spring-boot-starter-security'
    
    // ๋ชจ๋‹ˆํ„ฐ๋ง
    implementation 'io.micrometer:micrometer-registry-prometheus'
    
    // ํ…Œ์ŠคํŠธ
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.0"
    }
}
๐Ÿ’ก Spring Cloud ๋ฒ„์ „ ์ฃผ์˜! Spring Boot 3.x๋Š” Spring Cloud 2022.0.x (Kilburn) ์ด์ƒ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ด.
๋ฒ„์ „ ํ˜ธํ™˜์„ฑ ํ‘œ๋Š” spring.io/projects/spring-cloud์—์„œ ๊ผญ ํ™•์ธํ•˜์ž!

๐ŸŒ API Gateway ๊ตฌํ˜„ - ๋ชจ๋“  ์š”์ฒญ์˜ ๊ด€๋ฌธ

API Gateway๋Š” MSA์˜ ํ”„๋ก ํŠธ ๋„์–ด์•ผ.
ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง์ ‘ ๊ฐ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์— ์ ‘๊ทผํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ,
๋ชจ๋“  ์š”์ฒญ์ด Gateway๋ฅผ ํ†ตํ•ด ๋“ค์–ด์˜ค๊ณ  ์ ์ ˆํ•œ ์„œ๋น„์Šค๋กœ ๋ผ์šฐํŒ…๋˜๋Š” ๊ตฌ์กฐ์ง€.

Spring Cloud Gateway๋Š” WebFlux ๊ธฐ๋ฐ˜์˜ ๋น„๋™๊ธฐยท๋…ผ๋ธ”๋กœํ‚น ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ด์„œ
์„ฑ๋Šฅ์ด ๊ธฐ์กด Zuul๋ณด๋‹ค ํ›จ์”ฌ ๋›ฐ์–ด๋‚˜. Spring Boot 3.0๊ณผ ์ฐฐ๋–ก๊ถํ•ฉ์ด์•ผ! ๐Ÿก

// application.yml - API Gateway ๋ผ์šฐํŒ… ์„ค์ •
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE        # ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ๋ฅผ ํ†ตํ•ด Eureka์—์„œ ์ฐพ์Œ
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1             # /api ์ œ๊ฑฐ ํ›„ ์ „๋‹ฌ
            - name: CircuitBreaker      # ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค ์ ์šฉ
              args:
                name: userServiceCB
                fallbackUri: forward:/fallback/user
                
        - id: order-service
          uri: lb://ORDER-SERVICE
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Request-Source, gateway  # ํ—ค๋” ์ถ”๊ฐ€
            
        - id: payment-service
          uri: lb://PAYMENT-SERVICE
          predicates:
            - Path=/api/payments/**
            - Method=POST,GET           # ํŠน์ • HTTP ๋ฉ”์„œ๋“œ๋งŒ ํ—ˆ์šฉ
          filters:
            - StripPrefix=1

Gateway์—์„œ JWT ์ธ์ฆ ํ•„ํ„ฐ๋ฅผ ์ „์—ญ์œผ๋กœ ์ ์šฉํ•˜๋ฉด
๊ฐ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ ์ธ์ฆ ๋กœ์ง์„ ์ค‘๋ณต ๊ตฌํ˜„ํ•  ํ•„์š”๊ฐ€ ์—†์–ด. ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์˜ ๋ํŒ์™•! ๐Ÿ’ช

// JwtAuthenticationFilter.java
@Component
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {

    private final JwtTokenProvider jwtTokenProvider;
    
    // ์ธ์ฆ ๋ถˆํ•„์š” ๊ฒฝ๋กœ
    private static final List<String> WHITELIST = List.of(
        "/api/users/login",
        "/api/users/register",
        "/actuator/health"
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();
        
        // ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๊ฒฝ๋กœ๋Š” ํ†ต๊ณผ
        if (WHITELIST.stream().anyMatch(path::startsWith)) {
            return chain.filter(exchange);
        }
        
        // Authorization ํ—ค๋”์—์„œ JWT ์ถ”์ถœ
        String token = extractToken(exchange.getRequest());
        
        if (token == null || !jwtTokenProvider.validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        
        // ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ—ค๋”์— ์ถ”๊ฐ€ํ•ด์„œ ํ•˜์œ„ ์„œ๋น„์Šค๋กœ ์ „๋‹ฌ
        String userId = jwtTokenProvider.getUserId(token);
        ServerHttpRequest mutatedRequest = exchange.getRequest()
            .mutate()
            .header("X-User-Id", userId)
            .build();
            
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    @Override
    public int getOrder() {
        return -1; // ์ตœ์šฐ์„  ์‹คํ–‰
    }
}

๐Ÿ” ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ - ์„œ๋น„์Šค๋“ค์ด ์„œ๋กœ๋ฅผ ์ฐพ๋Š” ๋ฐฉ๋ฒ•

MSA์—์„œ ์„œ๋น„์Šค๋“ค์€ ๋™์ ์œผ๋กœ IP๊ฐ€ ๋ฐ”๋€Œ๊ณ , ์ธ์Šคํ„ด์Šค ์ˆ˜๋„ ์ˆ˜์‹œ๋กœ ๋ณ€ํ•ด.
๊ทธ๋Ÿผ ์„œ๋น„์Šค A๊ฐ€ ์„œ๋น„์Šค B๋ฅผ ์–ด๋–ป๊ฒŒ ์ฐพ์•„? ๐Ÿค”
๋ฐ”๋กœ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ๊ฐ€ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด์ค˜!

Spring Cloud Netflix Eureka๊ฐ€ ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š”๋ฐ,
๊ฐ ์„œ๋น„์Šค๊ฐ€ ์‹œ์ž‘๋  ๋•Œ Eureka Server์— ์ž์‹ ์„ ๋“ฑ๋กํ•˜๊ณ ,
๋‹ค๋ฅธ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ Eureka์—์„œ ์ฃผ์†Œ๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฐฉ์‹์ด์•ผ.

// EurekaServerApplication.java
@SpringBootApplication
@EnableEurekaServer  // ์ด ํ•œ ์ค„๋กœ Eureka Server ์™„์„ฑ!
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

// application.yml (Eureka Server)
server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false   # ์ž๊ธฐ ์ž์‹ ์€ ๋“ฑ๋ก ์•ˆ ํ•จ
    fetchRegistry: false
  server:
    enableSelfPreservation: false  # ๊ฐœ๋ฐœํ™˜๊ฒฝ์—์„œ๋Š” ๋น„ํ™œ์„ฑํ™”
// ๊ฐ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค application.yml (Eureka Client)
spring:
  application:
    name: ORDER-SERVICE   # ์„œ๋น„์Šค ์ด๋ฆ„ (๋Œ€๋ฌธ์ž ๊ถŒ์žฅ)

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    preferIpAddress: true
    instanceId: ${spring.application.name}:${server.port}
โšก Spring Boot 3.x์—์„œ Eureka ์ฃผ์˜์‚ฌํ•ญ Spring Boot 3.0๋ถ€ํ„ฐ spring-cloud-starter-netflix-eureka-client์—์„œ
Spring Cloud LoadBalancer๊ฐ€ ๊ธฐ๋ณธ ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ๋กœ ์‚ฌ์šฉ๋ผ.
๊ธฐ์กด Ribbon์€ deprecated ๋์œผ๋‹ˆ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ ๊ผญ ํ™•์ธํ•ด!

๐Ÿ“จ ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹  - REST vs ๋ฉ”์‹œ์ง€ ํ

MSA์—์„œ ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹  ๋ฐฉ์‹์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€์•ผ.
์–ด๋–ค ๊ฑธ ์„ ํƒํ•˜๋А๋ƒ์— ๋”ฐ๋ผ ์‹œ์Šคํ…œ์˜ ์„ฑ๊ฒฉ์ด ์™„์ „ํžˆ ๋‹ฌ๋ผ์ ธ!

๐Ÿ”„ ๋™๊ธฐ ํ†ต์‹  (Synchronous) - REST / gRPC ์š”์ฒญํ•˜๊ณ  ์ฆ‰์‹œ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋ฐฉ์‹.
Spring WebClient OpenFeign RestTemplate(deprecated)

โœ… ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ 
โœ… ์ฆ‰๊ฐ์ ์ธ ์‘๋‹ต ํ™•์ธ ๊ฐ€๋Šฅ
โŒ ํ˜ธ์ถœ๋œ ์„œ๋น„์Šค๊ฐ€ ์ฃฝ์œผ๋ฉด ํ˜ธ์ถœํ•œ ์„œ๋น„์Šค๋„ ์˜ํ–ฅ๋ฐ›์Œ
โŒ ์—ฐ์‡„ ์žฅ์• (Cascading Failure) ์œ„ํ—˜
๐Ÿ“จ ๋น„๋™๊ธฐ ํ†ต์‹  (Asynchronous) - ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค ๋ฉ”์‹œ์ง€๋ฅผ ํ์— ๋„ฃ๊ณ  ๋‚˜์ค‘์— ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹.
Apache Kafka RabbitMQ AWS SQS

โœ… ์„œ๋น„์Šค ๊ฐ„ ๊ฒฐํ•ฉ๋„ ๋‚ฎ์Œ (๋А์Šจํ•œ ๊ฒฐํ•ฉ)
โœ… ํ•œ ์„œ๋น„์Šค๊ฐ€ ์ฃฝ์–ด๋„ ๋ฉ”์‹œ์ง€๋Š” ํ์— ๋ณด์กด
โœ… ๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ ์ฒ˜๋ฆฌ์— ์œ ๋ฆฌ
โŒ ์ฆ‰๊ฐ์ ์ธ ์‘๋‹ต์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—” ๋ถ€์ ํ•ฉ
โŒ ๊ตฌํ˜„ ๋ณต์žก๋„ ์ฆ๊ฐ€

Spring Boot 3.0์—์„œ OpenFeign์œผ๋กœ ๋™๊ธฐ ํ†ต์‹ ํ•˜๋Š” ์˜ˆ์‹œ๋ฅผ ๋ณด์ž:

// OrderService์—์„œ UserService ํ˜ธ์ถœํ•˜๋Š” Feign Client
@FeignClient(
    name = "USER-SERVICE",           // Eureka์— ๋“ฑ๋ก๋œ ์„œ๋น„์Šค ์ด๋ฆ„
    fallback = UserClientFallback.class  // ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค ํด๋ฐฑ
)
public interface UserServiceClient {

    @GetMapping("/users/{userId}")
    UserResponse getUserById(@PathVariable Long userId);
    
    @PostMapping("/users/{userId}/points")
    void deductPoints(
        @PathVariable Long userId,
        @RequestBody PointDeductRequest request
    );
}

// ํด๋ฐฑ ๊ตฌํ˜„ (์„œ๋น„์Šค ์žฅ์•  ์‹œ ๋Œ€์ฒด ์‘๋‹ต)
@Component
public class UserClientFallback implements UserServiceClient {

    @Override
    public UserResponse getUserById(Long userId) {
        // ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜ ๋˜๋Š” ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜
        return UserResponse.defaultUser(userId);
    }
    
    @Override
    public void deductPoints(Long userId, PointDeductRequest request) {
        // ์‹คํŒจ ์ด๋ฒคํŠธ๋ฅผ ๋ฉ”์‹œ์ง€ ํ์— ๋ฐœํ–‰ํ•ด์„œ ๋‚˜์ค‘์— ์žฌ์ฒ˜๋ฆฌ
        log.error("ํฌ์ธํŠธ ์ฐจ๊ฐ ์‹คํŒจ - userId: {}, ์žฌ์ฒ˜๋ฆฌ ํ์— ๋“ฑ๋ก", userId);
    }
}

์ด๋ฒˆ์—” Kafka๋ฅผ ์ด์šฉํ•œ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ๋ฐœํ–‰ ์˜ˆ์‹œ์•ผ:

// ์ฃผ๋ฌธ ์™„๋ฃŒ ์ด๋ฒคํŠธ ๋ฐœํ–‰ (Order Service)
@Service
@RequiredArgsConstructor
public class OrderService {

    private final KafkaTemplate<String, OrderCompletedEvent> kafkaTemplate;
    private static final String TOPIC = "order-completed";

    public OrderResponse createOrder(OrderRequest request) {
        // ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ๋กœ์ง...
        Order savedOrder = orderRepository.save(order);
        
        // ์ด๋ฒคํŠธ ๋ฐœํ–‰ - ๊ฒฐ์ œ/์•Œ๋ฆผ ์„œ๋น„์Šค๊ฐ€ ๊ตฌ๋…ํ•ด์„œ ์ฒ˜๋ฆฌ
        OrderCompletedEvent event = OrderCompletedEvent.builder()
            .orderId(savedOrder.getId())
            .userId(request.getUserId())
            .totalAmount(savedOrder.getTotalAmount())
            .items(savedOrder.getItems())
            .build();
            
        kafkaTemplate.send(TOPIC, String.valueOf(savedOrder.getId()), event);
        
        return OrderResponse.from(savedOrder);
    }
}

// ์ด๋ฒคํŠธ ๊ตฌ๋… (Payment Service)
@Service
@RequiredArgsConstructor
public class PaymentEventConsumer {

    @KafkaListener(topics = "order-completed", groupId = "payment-group")
    public void handleOrderCompleted(OrderCompletedEvent event) {
        log.info("์ฃผ๋ฌธ ์™„๋ฃŒ ์ด๋ฒคํŠธ ์ˆ˜์‹  - orderId: {}", event.getOrderId());
        // ๊ฒฐ์ œ ์ฒ˜๋ฆฌ ๋กœ์ง...
        paymentService.processPayment(event);
    }
}

๐Ÿ›ก๏ธ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค ํŒจํ„ด - ์žฅ์•  ์ „ํŒŒ๋ฅผ ๋ง‰์•„๋ผ!

MSA์—์„œ ๊ฐ€์žฅ ๋ฌด์„œ์šด ๊ฑด ์—ฐ์‡„ ์žฅ์• (Cascading Failure)์•ผ.
์„œ๋น„์Šค A โ†’ B โ†’ C โ†’ D ์ˆœ์„œ๋กœ ํ˜ธ์ถœํ•˜๋Š”๋ฐ D๊ฐ€ ์ฃฝ์œผ๋ฉด?
C๊ฐ€ D๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋‹ค ํƒ€์ž„์•„์›ƒ, B๊ฐ€ C๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋‹ค ํƒ€์ž„์•„์›ƒ, A๋„ ์ฃฝ์–ด๋ฒ„๋ ค... ๐Ÿ˜ฑ

์ด๊ฑธ ๋ง‰๋Š” ๊ฒŒ ๋ฐ”๋กœ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค(Circuit Breaker) ํŒจํ„ด์ด์•ผ!
์ „๊ธฐ ํšŒ๋กœ์˜ ์ฐจ๋‹จ๊ธฐ์ฒ˜๋Ÿผ, ์žฅ์• ๊ฐ€ ๊ฐ์ง€๋˜๋ฉด ํšŒ๋กœ๋ฅผ ๋Š์–ด๋ฒ„๋ฆฌ๋Š” ๊ฑฐ์ง€.

๐Ÿ”Œ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค 3๊ฐ€์ง€ ์ƒํƒœ CLOSED ์ •์ƒ ์šด์˜ ์š”์ฒญ ํ†ต๊ณผ โœ… OPEN ์žฅ์•  ๊ฐ์ง€ ์š”์ฒญ ์ฐจ๋‹จ โŒ HALF OPEN ๋ณต๊ตฌ ํ…Œ์ŠคํŠธ ๐Ÿ” ์‹คํŒจ์œจ ์ดˆ๊ณผ ๋Œ€๊ธฐ ์‹œ๊ฐ„ ํ›„ ์„ฑ๊ณต โ†’ CLOSED ์‹คํŒจ โ†’ OPEN

Spring Boot 3.0์—์„œ๋Š” Resilience4j๊ฐ€ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค ํ‘œ์ค€์ด์•ผ:

// application.yml - Resilience4j ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค ์„ค์ •
resilience4j:
  circuitbreaker:
    instances:
      userService:
        slidingWindowSize: 10           # ์ตœ๊ทผ 10๋ฒˆ์˜ ์š”์ฒญ ๊ธฐ์ค€
        failureRateThreshold: 50        # 50% ์‹คํŒจ ์‹œ OPEN
        waitDurationInOpenState: 10s    # 10์ดˆ ํ›„ HALF-OPEN
        permittedNumberOfCallsInHalfOpenState: 3
        
  retry:
    instances:
      userService:
        maxAttempts: 3                  # ์ตœ๋Œ€ 3๋ฒˆ ์žฌ์‹œ๋„
        waitDuration: 1s
        
  timelimiter:
    instances:
      userService:
        timeoutDuration: 3s             # 3์ดˆ ํƒ€์ž„์•„์›ƒ

// ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉ
@Service
public class OrderService {

    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    @Retry(name = "userService")
    @TimeLimiter(name = "userService")
    public CompletableFuture<UserResponse> getUser(Long userId) {
        return CompletableFuture.supplyAsync(() -> 
            userServiceClient.getUserById(userId)
        );
    }
    
    // ํด๋ฐฑ ๋ฉ”์„œ๋“œ
    public CompletableFuture<UserResponse> getUserFallback(Long userId, Exception e) {
        log.warn("User Service ์žฅ์•  - ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜, userId: {}", userId);
        return CompletableFuture.completedFuture(UserResponse.defaultUser(userId));
    }
}

๐Ÿ“Š ๋ถ„์‚ฐ ์ถ”์ ๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง - ์–ด๋””์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋Š”์ง€ ์•Œ์•„์•ผ์ง€!

MSA์—์„œ ํ•˜๋‚˜์˜ ์š”์ฒญ์ด ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๋ฅผ ๊ฑฐ์น˜๋ฉด,
๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ "์–ด๋А ์„œ๋น„์Šค์—์„œ ๋А๋ ค์ง„ ๊ฑฐ์•ผ?"๋ฅผ ์•Œ๊ธฐ๊ฐ€ ์ •๋ง ์–ด๋ ค์›Œ.
์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒŒ ๋ถ„์‚ฐ ์ถ”์ (Distributed Tracing)์ด์•ผ!

Spring Boot 3.0์—์„œ๋Š” Micrometer Tracing์ด ๊ธฐ๋ณธ ํ†ตํ•ฉ๋˜์–ด ์žˆ์–ด.
Zipkin์ด๋‚˜ Jaeger์™€ ์—ฐ๋™ํ•˜๋ฉด ์š”์ฒญ ํ๋ฆ„์„ ์‹œ๊ฐ์ ์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์–ด. ๐Ÿ‘€

// build.gradle - ๋ถ„์‚ฐ ์ถ”์  ์˜์กด์„ฑ
dependencies {
    // Micrometer Tracing (Spring Boot 3.x ํ‘œ์ค€)
    implementation 'io.micrometer:micrometer-tracing-bridge-brave'
    implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
    
    // Prometheus ๋ฉ”ํŠธ๋ฆญ
    implementation 'io.micrometer:micrometer-registry-prometheus'
    
    // Spring Boot Actuator (ํ—ฌ์Šค์ฒดํฌ, ๋ฉ”ํŠธ๋ฆญ ์—”๋“œํฌ์ธํŠธ)
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
}

// application.yml
management:
  tracing:
    sampling:
      probability: 1.0    # 100% ์ƒ˜ํ”Œ๋ง (์šด์˜์—์„  0.1 ์ •๋„๋กœ)
  zipkin:
    tracing:
      endpoint: http://zipkin:9411/api/v2/spans
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    tags:
      application: ${spring.application.name}
๐Ÿ”ญ ๋ชจ๋‹ˆํ„ฐ๋ง ์Šคํƒ ์ถ”์ฒœ ์กฐํ•ฉ Prometheus ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘
Grafana ๋Œ€์‹œ๋ณด๋“œ ์‹œ๊ฐํ™”
Zipkin / Jaeger ๋ถ„์‚ฐ ์ถ”์ 
ELK Stack ๋กœ๊ทธ ์ˆ˜์ง‘ยท๋ถ„์„
Spring Boot Actuator ํ—ฌ์Šค์ฒดํฌ ์—”๋“œํฌ์ธํŠธ

์ด ์กฐํ•ฉ์ด๋ฉด MSA ์šด์˜ ์‹œ ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ์›์ธ ํŒŒ์•…์ด ํ›จ์”ฌ ๋นจ๋ผ์ ธ!

๐Ÿณ Docker + Kubernetes๋กœ MSA ๋ฐฐํฌํ•˜๊ธฐ

MSA๋Š” ์ปจํ…Œ์ด๋„ˆ ์—†์ด๋Š” ์šด์˜ํ•˜๊ธฐ ์ •๋ง ํž˜๋“ค์–ด.
์„œ๋น„์Šค๊ฐ€ 10๊ฐœ, 20๊ฐœ๊ฐ€ ๋˜๋ฉด ๊ฐ๊ฐ ์ˆ˜๋™์œผ๋กœ ๋ฐฐํฌํ•˜๋Š” ๊ฑด ๋ถˆ๊ฐ€๋Šฅ์— ๊ฐ€๊น๊ฑฐ๋“ .
Docker๋กœ ์ปจํ…Œ์ด๋„ˆํ™”ํ•˜๊ณ , Kubernetes(K8s)๋กœ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ํ•˜๋Š” ๊ฒŒ ํ˜„์—… ํ‘œ์ค€์ด์•ผ! ๐Ÿšข

Spring Boot 3.0์˜ Buildpacks ์ง€์›์œผ๋กœ Dockerfile ์—†์ด๋„ ์ด๋ฏธ์ง€ ๋นŒ๋“œ๊ฐ€ ๊ฐ€๋Šฅํ•ด:

# Dockerfile (Spring Boot 3.x ์ตœ์ ํ™”)
FROM eclipse-temurin:17-jre-alpine AS base
WORKDIR /app

# ๋ ˆ์ด์–ด ์บ์‹ฑ ์ตœ์ ํ™” (Spring Boot ๋ ˆ์ด์–ด๋“œ JAR)
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar

RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app

COPY --from=base /app/dependencies/ ./
COPY --from=base /app/spring-boot-loader/ ./
COPY --from=base /app/snapshot-dependencies/ ./
COPY --from=base /app/application/ ./

ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
# Gradle๋กœ ์ง์ ‘ Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ (Buildpacks ์‚ฌ์šฉ)
./gradlew bootBuildImage --imageName=my-service:latest

# ๋˜๋Š” build.gradle์— ์„ค์ •
tasks.named("bootBuildImage") {
    imageName = "my-registry/order-service:${version}"
    builder = "paketobuildpacks/builder:base"
    environment = ["BP_JVM_VERSION": "17"]
}
# kubernetes/order-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  labels:
    app: order-service
spec:
  replicas: 3                    # 3๊ฐœ ์ธ์Šคํ„ด์Šค ์šด์˜
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: my-registry/order-service:1.0.0
        ports:
        - containerPort: 8082
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: EUREKA_CLIENT_SERVICEURL_DEFAULTZONE
          value: "http://eureka-server:8761/eureka/"
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:           # ํ—ฌ์Šค์ฒดํฌ
          httpGet:
            path: /actuator/health/liveness
            port: 8082
          initialDelaySeconds: 30
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8082
          initialDelaySeconds: 20
๐Ÿ’ก Spring Boot 3.0์˜ Liveness/Readiness Probe! Spring Boot 3.x๋Š” Kubernetes์˜ Liveness/Readiness Probe๋ฅผ ๊ธฐ๋ณธ ์ง€์›ํ•ด.
/actuator/health/liveness์™€ /actuator/health/readiness ์—”๋“œํฌ์ธํŠธ๊ฐ€
์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋‹ˆ๊นŒ K8s ์„ค์ •์ด ํ›จ์”ฌ ๊ฐ„ํŽธํ•ด์กŒ์–ด!

โšก GraalVM Native Image - MSA์˜ ๋ฏธ๋ž˜

Spring Boot 3.0์˜ ๊ฐ€์žฅ ํ•ซํ•œ ๊ธฐ๋Šฅ์ด ๋ฐ”๋กœ GraalVM Native Image ์ง€์›์ด์•ผ!
๊ธฐ์กด JVM ๋ฐฉ์‹๊ณผ ๋น„๊ตํ•˜๋ฉด ์ฐจ์ด๊ฐ€ ์–ด๋งˆ์–ด๋งˆํ•ด:

โšก JVM vs GraalVM Native Image ๋น„๊ต โ˜• JVM ๋ฐฉ์‹ โšก Native Image ์‹œ์ž‘ ์‹œ๊ฐ„: 3~10์ดˆ ์‹œ์ž‘ ์‹œ๊ฐ„: 50~200ms ๐Ÿš€ ๋ฉ”๋ชจ๋ฆฌ: 300~500MB ๋ฉ”๋ชจ๋ฆฌ: 50~100MB ๐Ÿ’ช ์ตœ๊ณ  ์„ฑ๋Šฅ: JIT ์ตœ์ ํ™”๋กœ ์šฐ์ˆ˜ ์ตœ๊ณ  ์„ฑ๋Šฅ: JVM๋ณด๋‹ค ๋‚ฎ์„ ์ˆ˜ ์žˆ์Œ ๋นŒ๋“œ ์‹œ๊ฐ„: ๋น ๋ฆ„ โšก ๋นŒ๋“œ ์‹œ๊ฐ„: ์ˆ˜ ๋ถ„ ์†Œ์š” โณ ์žฅ๊ธฐ ์‹คํ–‰ ์„œ๋น„์Šค์— ์ ํ•ฉ ์„œ๋ฒ„๋ฆฌ์ŠคยทMSA ์ปจํ…Œ์ด๋„ˆ์— ์ตœ์  ๐ŸŽฏ
# Native Image ๋นŒ๋“œ (GraalVM ์„ค์น˜ ํ•„์š”)
./gradlew nativeCompile

# ๋˜๋Š” Docker๋กœ Native Image ๋นŒ๋“œ
./gradlew bootBuildImage --imageName=order-service:native \
  -Pspring.aot.enabled=true

# build.gradle์— AOT ์„ค์ •
plugins {
    id 'org.graalvm.buildtools.native' version '0.9.28'
}

graalvmNative {
    binaries {
        main {
            imageName = 'order-service'
            buildArgs.add('--initialize-at-build-time')
            buildArgs.add('-H:+ReportExceptionStackTraces')
        }
    }
}
โš ๏ธ Native Image ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ โ‘  ๋ฆฌํ”Œ๋ ‰์…˜(Reflection) ์‚ฌ์šฉ ์‹œ ๋ณ„๋„ ํžŒํŠธ ํŒŒ์ผ ํ•„์š”
โ‘ก ๋™์  ํด๋ž˜์Šค ๋กœ๋”ฉ ์ œํ•œ
โ‘ข ์ผ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ Native ๋ฏธ์ง€์›
โ‘ฃ ๋นŒ๋“œ ์‹œ๊ฐ„์ด JVM ๋ฐฉ์‹๋ณด๋‹ค ํ›จ์”ฌ ๊ธธ์–ด (CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ณ ๋ ค ํ•„์š”)

Spring Boot 3.x๋Š” AOT(Ahead-of-Time) ์ฒ˜๋ฆฌ๋กœ ์ด ๋ฌธ์ œ๋“ค์„ ๋งŽ์ด ํ•ด๊ฒฐํ–ˆ์–ด!

๐Ÿ” MSA ๋ณด์•ˆ ์ „๋žต - Spring Security 6.0

Spring Boot 3.0์€ Spring Security 6.0์„ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•ด.
MSA ํ™˜๊ฒฝ์—์„œ ๋ณด์•ˆ์€ ๋” ๋ณต์žกํ•ด์ง€๋Š”๋ฐ, ์ฃผ์š” ์ „๋žต์„ ์‚ดํŽด๋ณด์ž.

๐Ÿ”‘ MSA ์ธ์ฆยท์ธ๊ฐ€ ์ „๋žต JWT (JSON Web Token) ๋ฌด์ƒํƒœ ์ธ์ฆ ํ† ํฐ
OAuth 2.0 ํ‘œ์ค€ ์ธ์ฆ ํ”„๋ ˆ์ž„์›Œํฌ
Spring Authorization Server ์ž์ฒด ์ธ์ฆ ์„œ๋ฒ„ ๊ตฌ์ถ•
Keycloak ์˜คํ”ˆ์†Œ์Šค IAM ์†”๋ฃจ์…˜

API Gateway์—์„œ JWT ๊ฒ€์ฆ ํ›„, ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ—ค๋”๋กœ ์ „๋‹ฌํ•˜๋Š” ํŒจํ„ด์ด ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ด์•ผ.
// Spring Security 6.0 ์„ค์ • (Lambda DSL ๋ฐฉ์‹)
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            // CSRF ๋น„ํ™œ์„ฑํ™” (REST API๋Š” ์„ธ์…˜ ์—†์Œ)
            .csrf(csrf -> csrf.disable())
            
            // ์„ธ์…˜ ์‚ฌ์šฉ ์•ˆ ํ•จ (JWT ์‚ฌ์šฉ)
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            
            // ์š”์ฒญ ๊ถŒํ•œ ์„ค์ •
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health/**").permitAll()
                .requestMatchers(HttpMethod.POST, "/users/register").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            
            // JWT ํ•„ํ„ฐ ์ถ”๊ฐ€
            .addFilterBefore(
                jwtAuthenticationFilter, 
                UsernamePasswordAuthenticationFilter.class
            )
            
            // ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint(customAuthenticationEntryPoint)
                .accessDeniedHandler(customAccessDeniedHandler)
            )
            .build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

๐Ÿ“ MSA ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ์ „๋žต - ๊ฐ์ž์˜ DB๋ฅผ ๊ฐ€์ ธ๋ผ!

MSA์˜ ํ•ต์‹ฌ ์›์น™ ์ค‘ ํ•˜๋‚˜๊ฐ€ "Database per Service"์•ผ.
๊ฐ ์„œ๋น„์Šค๊ฐ€ ์ž์‹ ๋งŒ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ฐ€์ง€๊ณ , ๋‹ค๋ฅธ ์„œ๋น„์Šค์˜ DB์— ์ง์ ‘ ์ ‘๊ทผํ•˜์ง€ ์•Š๋Š” ๊ฑฐ์ง€.

๊ทผ๋ฐ ์ด๋Ÿฌ๋ฉด ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒจ.
์ฃผ๋ฌธ ์„œ๋น„์Šค์—์„œ ์ฃผ๋ฌธ์„ ์ƒ์„ฑํ•˜๊ณ , ์žฌ๊ณ  ์„œ๋น„์Šค์—์„œ ์žฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ•˜๋Š” ๊ฒŒ ์›์ž์ ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•˜๋Š”๋ฐ
๊ฐ์ž ๋‹ค๋ฅธ DB๋ฅผ ์“ฐ๋ฉด ํŠธ๋žœ์žญ์…˜์ด ์•ˆ ๋˜์ž–์•„? ๐Ÿ˜ฐ

์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋Š” ํŒจํ„ด์ด ๋ฐ”๋กœ SAGA ํŒจํ„ด์ด์•ผ!

๐Ÿ”„ SAGA ํŒจํ„ด - ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜ ํ•ด๊ฒฐ์‚ฌ Choreography SAGA: ๊ฐ ์„œ๋น„์Šค๊ฐ€ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๊ณ  ๊ตฌ๋…ํ•˜๋ฉด์„œ ์ž์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
โ†’ ๊ฒฐํ•ฉ๋„ ๋‚ฎ์Œ, ๊ตฌํ˜„ ๋ณต์žก๋„ ๋†’์Œ

Orchestration SAGA: ์ค‘์•™ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ๊ฐ€ ๊ฐ ์„œ๋น„์Šค์— ๋ช…๋ น์„ ๋‚ด๋ฆฌ๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ˆ˜์ง‘
โ†’ ํ๋ฆ„ ํŒŒ์•… ์‰ฌ์›€, ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ๊ฐ€ ๋‹จ์ผ ์žฅ์• ์ ์ด ๋  ์ˆ˜ ์žˆ์Œ

์‹คํŒจ ์‹œ ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜(Compensating Transaction)์œผ๋กœ ๋กค๋ฐฑ ์ฒ˜๋ฆฌ!

๋˜ํ•œ CQRS(Command Query Responsibility Segregation) ํŒจํ„ด๋„ MSA์—์„œ ์ž์ฃผ ์“ฐ์—ฌ.
์“ฐ๊ธฐ(Command)์™€ ์ฝ๊ธฐ(Query) ๋ชจ๋ธ์„ ๋ถ„๋ฆฌํ•ด์„œ ๊ฐ๊ฐ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ์‹์ด์•ผ.
์˜ˆ๋ฅผ ๋“ค์–ด ์“ฐ๊ธฐ๋Š” MySQL, ์ฝ๊ธฐ๋Š” Elasticsearch๋‚˜ Redis๋ฅผ ์“ฐ๋Š” ์‹์œผ๋กœ!


๐ŸŽฏ ์‹ค์ „ MSA ๊ตฌํ˜„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์ž, ์ด์ œ ์‹ค์ œ๋กœ MSA ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ ์ฒดํฌํ•ด์•ผ ํ•  ๊ฒƒ๋“ค์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ.
์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ๊ฐœ๋ฐœ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๋ฐฐ์šธ ๋•Œ๋„ ์ด ์ฒดํฌ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋„์›€์ด ๋  ๊ฑฐ์•ผ! ๐Ÿ˜Š

โœ…
์„œ๋น„์Šค ๊ฒฝ๊ณ„ ์„ค๊ณ„ - ๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„(DDD)์˜ Bounded Context๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„œ๋น„์Šค ๋ถ„๋ฆฌ
โœ…
API Gateway ๊ตฌ์„ฑ - Spring Cloud Gateway๋กœ ๋ผ์šฐํŒ…, ์ธ์ฆ, ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ
โœ…
์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ - Eureka ๋˜๋Š” Consul๋กœ ๋™์  ์„œ๋น„์Šค ๋“ฑ๋ก/๋ฐœ๊ฒฌ
โœ…
Config Server - ์ค‘์•™ ์ง‘์ค‘์‹ ์„ค์ • ๊ด€๋ฆฌ (Spring Cloud Config)
โœ…
์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค - Resilience4j๋กœ ์žฅ์•  ๊ฒฉ๋ฆฌ ๋ฐ ํด๋ฐฑ ์ฒ˜๋ฆฌ
โœ…
๋ถ„์‚ฐ ์ถ”์  - Micrometer Tracing + Zipkin์œผ๋กœ ์š”์ฒญ ํ๋ฆ„ ์ถ”์ 
โœ…
๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค - Kafka/RabbitMQ๋กœ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
โœ…
์ปจํ…Œ์ด๋„ˆํ™” - Docker + Kubernetes๋กœ ๋ฐฐํฌ ์ž๋™ํ™”
โœ…
๋ชจ๋‹ˆํ„ฐ๋ง - Prometheus + Grafana + ELK Stack
โœ…
CI/CD ํŒŒ์ดํ”„๋ผ์ธ - GitHub Actions / Jenkins๋กœ ์ž๋™ ๋นŒ๋“œยท๋ฐฐํฌ
๐Ÿ’ก ์ฒ˜์Œ๋ถ€ํ„ฐ ์™„๋ฒฝํ•œ MSA๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค ํ•˜์ง€ ๋งˆ! Martin Fowler๊ฐ€ ๋งํ–ˆ๋“ฏ์ด, ์ฒ˜์Œ์—” ๋ชจ๋†€๋ฆฌ์‹์œผ๋กœ ์‹œ์ž‘ํ•ด์„œ
์„œ๋น„์Šค๊ฐ€ ์„ฑ์žฅํ•˜๋ฉด์„œ ์ ์ง„์ ์œผ๋กœ MSA๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒŒ ํ˜„๋ช…ํ•œ ์ „๋žต์ด์•ผ.
"Strangler Fig Pattern"์„ ๊ฒ€์ƒ‰ํ•ด๋ด! ๐ŸŒฟ

๐ŸŒŸ Spring Boot 3.0 MSA ์ตœ์‹  ํŠธ๋ Œ๋“œ

2024๋…„ ํ˜„์žฌ Spring Boot 3.x MSA ์ƒํƒœ๊ณ„์—์„œ ์ฃผ๋ชฉ๋ฐ›๋Š” ํŠธ๋ Œ๋“œ๋“ค์ด์•ผ:

๐Ÿ”ฅ Virtual Threads (Project Loom) Java 21์˜ ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ๋ฅผ Spring Boot 3.2+์—์„œ ์ง€์›ํ•ด.
spring.threads.virtual.enabled=true ํ•œ ์ค„๋กœ ํ™œ์„ฑํ™”!
๊ธฐ์กด ์Šค๋ ˆ๋“œ ํ’€ ๋ฐฉ์‹๋ณด๋‹ค ํ›จ์”ฌ ๋งŽ์€ ๋™์‹œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด.
WebFlux ์—†์ด๋„ ๋†’์€ ๋™์‹œ์„ฑ์„ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์–ด. ๐Ÿš€
๐ŸŒŠ Spring WebFlux + R2DBC ์™„์ „ํ•œ ๋ฆฌ์•กํ‹ฐ๋ธŒ ์Šคํƒ ๊ตฌ์„ฑ.
WebFlux(๋น„๋™๊ธฐ ์›น) + R2DBC(๋น„๋™๊ธฐ DB ์ ‘๊ทผ)๋กœ
I/O ๋ฐ”์šด๋“œ ์ž‘์—…์—์„œ ๊ทนํ•œ์˜ ์„ฑ๋Šฅ์„ ๋ฐœํœ˜ํ•ด.
๐Ÿค– Spring AI ํ†ตํ•ฉ Spring Boot 3.x ์ƒํƒœ๊ณ„์— AI ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋Š”
Spring AI ํ”„๋กœ์ ํŠธ๊ฐ€ ํ™œ๋ฐœํžˆ ๊ฐœ๋ฐœ ์ค‘์ด์•ผ.
OpenAI, Anthropic, Ollama ๋“ฑ ๋‹ค์–‘ํ•œ AI ๋ชจ๋ธ๊ณผ ์—ฐ๋™ ๊ฐ€๋Šฅ!
// Java 21 Virtual Threads ํ™œ์„ฑํ™” (Spring Boot 3.2+)
// application.yml
spring:
  threads:
    virtual:
      enabled: true

// ๋˜๋Š” ์ฝ”๋“œ๋กœ ์„ค์ •
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> {
        protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    };
}

๐Ÿ’ฌ ๋งˆ๋ฌด๋ฆฌ - MSA, ์–ธ์ œ ์จ์•ผ ํ• ๊นŒ?

์†”์งํžˆ ๋งํ• ๊ฒŒ. MSA๊ฐ€ ํ•ญ์ƒ ์ •๋‹ต์€ ์•„๋‹ˆ์•ผ.
ํŒ€์ด ์ž‘๊ณ , ์„œ๋น„์Šค๊ฐ€ ๋‹จ์ˆœํ•˜๋‹ค๋ฉด ๋ชจ๋†€๋ฆฌ์‹์ด ํ›จ์”ฌ ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ์–ด.

MSA๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•  ์‹œ์ ์€ ์ด๋Ÿด ๋•Œ์•ผ:

๐ŸŽฏ MSA ๋„์ž…์„ ๊ณ ๋ คํ•  ๋•Œ โ‘  ํŒ€์ด 10๋ช… ์ด์ƒ์œผ๋กœ ์„ฑ์žฅํ•˜๊ณ  ๋ฐฐํฌ ์ถฉ๋Œ์ด ์žฆ์•„์งˆ ๋•Œ
โ‘ก ํŠน์ • ๊ธฐ๋Šฅ๋งŒ ๋…๋ฆฝ์ ์œผ๋กœ ์Šค์ผ€์ผ์•„์›ƒ์ด ํ•„์š”ํ•  ๋•Œ
โ‘ข ์„œ๋น„์Šค์˜ ์ผ๋ถ€ ๊ธฐ๋Šฅ์ด ๋‹ค๋ฅธ ๊ธฐ์ˆ  ์Šคํƒ์ด ํ•„์š”ํ•  ๋•Œ
โ‘ฃ ์žฅ์•  ๊ฒฉ๋ฆฌ๊ฐ€ ๋น„์ฆˆ๋‹ˆ์Šค์ ์œผ๋กœ ์ค‘์š”ํ•ด์งˆ ๋•Œ
โ‘ค ๋ฐฐํฌ ์ฃผ๊ธฐ๋ฅผ ์„œ๋น„์Šค๋ณ„๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ€์ ธ๊ฐ€๊ณ  ์‹ถ์„ ๋•Œ

Spring Boot 3.0์€ ์ด ๋ชจ๋“  MSA ๊ตฌํ˜„์„ ์œ„ํ•œ ์ตœ๊ณ ์˜ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•ด.
GraalVM Native Image, Virtual Threads, ๊ฐœ์„ ๋œ Observability...
Java ์ƒํƒœ๊ณ„๊ฐ€ ์ด๋ ‡๊ฒŒ ๋น ๋ฅด๊ฒŒ ๋ฐœ์ „ํ•˜๋Š” ๊ฒŒ ์ •๋ง ์‹ ๊ธฐํ•˜๊ณ  ํฅ๋ฏธ๋กญ์ง€ ์•Š์•„? ๐Ÿ˜„

์ด ๊ธ€์—์„œ ๋‹ค๋ฃฌ ๋‚ด์šฉ๋“ค์„ ํ•˜๋‚˜์”ฉ ์‹ค์Šตํ•ด๋ณด๋ฉด์„œ ์ตํ˜€๋‚˜๊ฐ€๋ฉด ๋ผ.
์žฌ๋Šฅ๋„ท์—์„œ Spring Boot๋‚˜ MSA ๊ด€๋ จ ๋ฉ˜ํ† ๋ง์„ ์ฐพ์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ!
์‹ค๋ฌด ๊ฒฝํ—˜์ด ์žˆ๋Š” ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์ง์ ‘ ๋ฐฐ์šฐ๋ฉด ํ›จ์”ฌ ๋น ๋ฅด๊ฒŒ ์„ฑ์žฅํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐ŸŒฑ

MSA๋Š” ์ฒ˜์Œ์—” ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ์ดํ•ดํ•˜๋‹ค ๋ณด๋ฉด
์™œ ํ˜„๋Œ€ ๋Œ€๊ทœ๋ชจ ์„œ๋น„์Šค๋“ค์ด ์ด ๋ฐฉ์‹์„ ์„ ํƒํ–ˆ๋Š”์ง€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊นจ๋‹ซ๊ฒŒ ๋  ๊ฑฐ์•ผ.
ํ™”์ดํŒ…! ๐Ÿ’ช

๐Ÿš€ Spring Boot 3.0 MSA ํ•ต์‹ฌ ์š”์•ฝ โ˜• Spring Boot 3.0 Java 17+ / Jakarta EE GraalVM Native Image ๐ŸŒ API Gateway Spring Cloud Gateway ๋ผ์šฐํŒ… ยท ์ธ์ฆ ยท ํ•„ํ„ฐ๋ง ๐Ÿ” ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ Eureka / Consul ๋™์  ์„œ๋น„์Šค ๋“ฑ๋กยท๋ฐœ๊ฒฌ ๐Ÿ›ก๏ธ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค Resilience4j ์žฅ์•  ๊ฒฉ๋ฆฌ ยท ํด๋ฐฑ ์ฒ˜๋ฆฌ ๐Ÿ“จ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค Kafka / RabbitMQ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๐Ÿ“Š ๋ชจ๋‹ˆํ„ฐ๋ง Prometheus + Grafana Zipkin ๋ถ„์‚ฐ ์ถ”์  ๐Ÿณ Docker + Kubernetes = ์™„๋ฒฝํ•œ MSA ์šด์˜ ํ™˜๊ฒฝ ์ปจํ…Œ์ด๋„ˆํ™” ยท ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ยท ์ž๋™ ์Šค์ผ€์ผ๋ง
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.