๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ”ฌ์•„ํ‚คํ…์ฒ˜/- distributed system

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ ์„œ๋น„์Šค๊ฐ„ ํ†ต์‹ ์„ ์œ„ํ•œ 2๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋น„๊ต (1) [OpenFeign vs Rest Template] - ์„œ๋น„์Šค ๊ตฌํ˜„

by Wonit 2021. 4. 29.

์ด ๊ธ€์€ 2๊ฐœ์˜ ๊ธ€๋กœ ๋‚˜๋ˆ„์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ ์„œ๋น„์Šค๊ฐ„ ํ†ต์‹ ์„ ์œ„ํ•œ 2๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋น„๊ต [OpenFeign vs Rest Template] - ์„œ๋น„์Šค ๊ตฌํ˜„
  2. ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ ์„œ๋น„์Šค๊ฐ„ ํ†ต์‹ ์„ ์œ„ํ•œ 2๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋น„๊ต [OpenFeign vs Rest Template] - ๊ฐ๊ฐ์˜ ๋น„๊ต

ํ•ด๋‹น ๊ธ€์—์„œ ๋‚˜์˜ค๋Š” ์‹ค์Šต ๋‚ด์šฉ์€ Spring Cloud๋ฅผ ์ด์šฉํ•œ MSA ๊ตฌ์„ฑ์˜ ์ „๋ฐ˜์ ์ธ ์ดํ•ด๊ฐ€ ํ•„์š”ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.
์‹ค์Šต์€ Eureka + Gateway + Microservices (2)๋ฅผ ์ด์šฉํ•œ ํ™˜๊ฒฝ์œผ๋กœ ํ•ด๋‹น ๊ธ€์—์„œ๋Š” Eureka์™€ Gateway ์„ค์ •์— ๋Œ€ํ•ด์„œ๋Š” ์–ธ๊ธ‰ํ•˜์ง€ ์ง€๋งŒ ๋งŒ์•ฝ Eureka๋‚˜ Gateway์˜ ์ดํ•ด๊ฐ€ ๋ถ€์กฑํ•˜์‹  ๋ถ„๋“ค์ด๋‚˜ ๋” ์•Œ์•„๋ณด๊ณ  ์‹ค์Šต ํ™˜๊ฒฝ์„ ๋”ฐ๋ผ ํ•ด๋ณด๊ณ ์‹ถ์€ ์‚ฌ๋žŒ์€ ์•„๋ž˜์˜ ์‹ค์Šต ๊ณผ์ •์— ์กด์žฌํ•˜๋Š” URL์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชฉ์ฐจ

์ด๋ฒˆ ์‹œ๊ฐ„

  • ์„œ๋น„์Šค๊ฐ„ ํ†ต์‹ 
    • ์„œ๋น„์Šค ๊ตฌ์กฐ
    • ํ†ต์‹  ๊ณผ์ •
  • ์„œ๋น„์Šค ๊ตฌํ˜„
    • ์„œ๋น„์Šค ๊ตฌ์„ฑ
      • Eureka Service
      • Gateway Service
      • User Service
      • Team Service

๋‹ค์Œ ์‹œ๊ฐ„

  • Rest Template ์œผ๋กœ ํ†ต์‹ ํ•˜๊ธฐ
  • Spring Cloud OpenFeign ์œผ๋กœ ํ†ต์‹ ํ•˜๊ธฐ
  • ๋‘ ํ†ต์‹  ๋ฐฉ๋ฒ•์˜ ์ฐจ์ด
    • ์„ ์–ธ ๋ฐฉ์‹๊ณผ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ
    • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
    • ์œ ์ง€๋ณด์ˆ˜
    • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
  • ์ข…ํ•ฉ ์ •๋ฆฌ ๋ฐ ๊ฒฐ๋ก 
    • ํ‘œ๋กœ ์ •๋ฆฌ
    • rest template deprecated

Microservices ์—์„œ ์„œ๋น„์Šค๊ฐ„์˜ ํ†ต์‹ 

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ๋Š” ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ฐ๊ฐ์˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค๋“ค์ด ์œ ๊ธฐ์ ์œผ๋กœ ์ƒํ˜ธ์ž‘์šฉ์„ ํ•ด์„œ ์ ์ ˆํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋‚ด๋ ค์ฃผ๋Š” ๋ฐฉ์‹์„ ์ทจํ•œ๋‹ค.

 

๋ณดํ†ต์˜ MSA ์—์„œ ๊ฐ๊ฐ์˜ ์„œ๋น„์Šค๋Š” RESTful ํ•œ API๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ, ์ด ๋•Œ ๊ฐ๊ฐ์˜ ์„œ๋น„์Šค๋Š” ํŠน์ • ์„œ๋น„์Šค๊ฐ€ ๋…ธ์ถœํ•˜๋Š” Endpoint ์— API ํ˜ธ์ถœ์„ ํ†ตํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•œ๋‹ค.

 

๋Œ€ํ‘œ์ ์œผ๋กœ MSA ์—์„œ ์‚ฌ์šฉํ•˜๋Š” 2๊ฐ€์ง€ ํ†ต์‹  ๋ฐฉ์‹์ด ์กด์žฌํ•œ๋‹ค.

 

  1. Rest Template
  2. Spring Cloud OpenFeign

์˜ค๋Š˜์€ ์ง์ ‘ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค๋ฉด์„œ ๊ฐ๊ฐ์˜ ๋ฐฉ์‹์˜ ์ฐจ์ด์ ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๊ณ  ์–ด๋–ค ์„œ๋น„์Šค๊ฐ€ ์–ด๋””์— ํšจ๊ณผ์ ์ธ์ง€ ์ •๋ฆฌ๋ฅผ ํ•ด๋ณด๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์ ธ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

์ด๋ฒˆ ๊ธ€์€ ์‹ค์Šต์ด ํ•ต์‹ฌ์ธ ๋‚ด์šฉ์ด๋ฏ€๋กœ ์„œ๋น„์Šค ๊ตฌ์„ฑ์— ์กฐ๊ธˆ ์‹ ๊ฒฝ์„ ์จ๋ณด์ž.

 

์„œ๋น„์Šค ๊ตฌํ˜„

์šฐ๋ฆฌ๋Š” ์ถ•๊ตฌ ํŒ€ ๊ด€๋ฆฌ ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉ์ž id ๋ฅผ ๋ฐ›์œผ๋ฉด ํŒ€์— ์ €์žฅํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ ๊ฒƒ์ด๋‹ค.

 

 

๋ฌด์—‡์„ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€?

  • ์ถ•๊ตฌ ํŒ€ ๊ด€๋ฆฌ ์„œ๋น„์Šค์˜ ํŒ€ ์ €์žฅ ์„œ๋น„์Šค

์–ด๋–ป๊ฒŒ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€?

  • 2๊ฐœ์˜ Service Mesh์™€ 2 ๊ฐœ์˜ Microservices
    • Service Mesh
      • Discovery Service
      • Gateway Service
    • Microservices
      • User Service : ์‚ฌ์šฉ์ž ์„œ๋น„์Šค
      • Team Service : ํŒ€ ์„œ๋น„์Šค

์–ด๋– ํ•œ End Point๋ฅผ ๋…ธ์ถœํ•  ๊ฒƒ์ธ๊ฐ€

  • Discovery Service
    • http://localhost:8761/eureka
  • Gateway Service
    • http://localhost:8000/
  • User Service
    • ์œ ์ € ์ƒ์„ฑ : http://localhost:50010/users POST
    • ์œ ์ € ์กฐํšŒ : http://localhost:50010/users/{userId} GET
  • Team Service
    • ํŒ€ ์ƒ์„ฑ : http://localhost:60010/teams POST
    • ํŒ€์› ์ถ”๊ฐ€ : http://localhost:60010/{userId}/teams POST
    • ์‚ฌ์šฉ์ž ๋ฒˆํ˜ธ๋กœ ํŒ€ ๊ฒ€์ƒ‰ : http://localhost:60010/{userId}/teams GET

 

์‹ค์งˆ์ ์ธ ์„œ๋ฒ„์˜ ํฌํŠธ๋Š” 50010๊ณผ 60010๋ฒˆ์ด์ง€๋งŒ ์šฐ๋ฆฌ๋Š” Gateway๋ฅผ ์ด์šฉํ•ด์„œ 8000 ๋ฒˆ์œผ๋กœ Endpoint๋ฅผ ํ•ฉ์ณ๋ณด์ž

 

๊ฐ Endpoint๋Š” ์–ด๋– ํ•œ API ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์ธ๊ฐ€

// ์œ ์ € ์ƒ์„ฑ : http://localhost:50010/users POST

REQUEST
{
  "username": "์žฅ์›์ต",
}

RESPONSE
{
  "userId": "1",
  "username": "์žฅ์›์ต",
}


// ์œ ์ € ์กฐํšŒ : http://localhost:50010/users/{userId} GET

RESPONSE
{
  "userId": "1",
  "username": "์žฅ์›์ต",
  "name": [null, "๋ฆฌ๋ฒ„ํ’€"]
}


// ํŒ€ ์ƒ์„ฑ : http://localhost:60010/teams POST

REQUEST
{
  "name": "๋ฆฌ๋ฒ„ํ’€",
  "address": "์˜๊ตญ ๋จธ์ง€์‚ฌ์ด๋“œ"
}

RESPONSE
{
  "teamId": "1",
  "name": "๋ฆฌ๋ฒ„ํ’€",
  "address": "์˜๊ตญ ๋จธ์ง€์‚ฌ์ด๋“œ"
}


// ํŒ€์› ์ถ”๊ฐ€ : http://localhost:60010/{userId}/teams POST
REQUEST
{
  "teamname": "๋ฆฌ๋ฒ„ํ’€"
}

RESPONSE : 204 No Content

// ์‚ฌ์šฉ์ž ๋กœ ํŒ€ ๊ฒ€์ƒ‰ : http://localhost:60010/{userId}/teams GET

RESPONSE
{
  "teamId": "1",
  "name": "๋ฆฌ๋ฒ„ํ’€",
  "address": "์˜๊ตญ ๋จธ์ง€์‚ฌ์ด๋“œ"
}

๊ตฌํ˜„ ์ˆœ์„œ

  1. Eureka Server ๊ธฐ๋™
  2. Gateway Server ๊ธฐ๋™
  3. User Service ๊ตฌํ˜„ (API ํ˜ธ์ถœ ์ œ์™ธ)
  4. Order Service ๊ตฌํ˜„
  5. User Service ์—์„œ Order Service๋กœ API ํ˜ธ์ถœ (Rest Template vs Feign)

Eureka Server

์œ ๋ ˆ์นด ์„œ๋ฒ„๋Š” Service Discovery๋ฅผ ์œ„ํ•ด์„œ ์‚ฌ์šฉ๋œ๋‹ค.


๊ธฐ๋ณธ ์„ค์ •๋งŒ์œผ๋กœ๋„ ์šฐ๋ฆฌ๊ฐ€ ํ•˜๋ ค๋Š” ๊ฒ€์ฆ์„ ์œ„ํ•œ ์ค€๋น„๊ฐ€ ์ถฉ๋ถ„ํ•˜๋‹ˆ ๋‹ค์Œ๊ณผ ๊ฐ™์ด Application.java.์™€ application.yml ๋งŒ ์ˆ˜์ •ํ•ด๋„๋ก ํ•˜์ž.


์ž์„ธํ•œ ์˜์กด์„ฑ๊ณผ ์„ค์ •์€ Service Discover Server๋กœ Netflix Eureka ์ด์šฉํ•˜๊ธฐ ์—์„œ ํ™•์ธํ•  ์ˆ˜์žˆ๋‹ค.

 

EurekaServerApplication.java

@SpringBootApplication
@EnableEurekaServer
public class EurekaServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

}

 

yml ์—๋Š” ์ž๊ธฐ ์Šค์Šค๋กœ๋ฅผ eureka instance ๋กœ ๋“ฑ๋กํ•˜์ง€ ๋ง๋ผ๋Š” 2๊ฐœ์˜ ์„ค์ •์„ false ๋กœ ์ง€์ •ํ•œ๋‹ค.

 

server:
  port: 8761

spring:
  application:
    name: discovery-service
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

Gateway Server

Gateway ์„œ๋ฒ„๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋“ค์˜ Endpoint๋ฅผ ํ•˜๋‚˜๋กœ ๋ชจ์•„์ฃผ๋Š” ์šฉ๋„ ํ˜„์žฌ ์‚ฌ์šฉ๋œ๋‹ค.


API Gateway ํŒจํ„ด์—์„œ ์‚ฌ์šฉ๋˜๋Š” Gateway๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ (๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ, ์ธ์ฆ, ๋กœ๊น…)์ด ์žˆ์ง€๋งŒ ํ˜„์žฌ๋กœ์จ๋Š” Endpoint๋ฅผ ๋‹จ์ผํ™” ํ•˜๋Š” ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.


๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Spring Cloud Gateway ๋Š” ๋‹ค์Œ ๋งํฌ๋“ค์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

GatewayServiceApplication.java

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayServiceApplication.class, args);
    }

}

 

Gateway ์— ๋Œ€ํ•œ ์„ค์ • ํŒŒ์ผ์—์„œ ๊ณ ๋ คํ•ด์•ผํ•  ์‚ฌํ•ญ์€ 2๊ฐ€์ง€์ด๋‹ค.

 

  1. Eureka Instance ๋“ฑ๋ก
  2. Gateway Route ์ •๋ณด ์ˆ˜์ •

Route ์ •๋ณด ์ˆ˜์ •์—์„œ๋Š” /user ๋กœ ํ˜ธ์ถœ๋˜๋Š” ๋ชจ๋“  url ์„ USER-SERVICE ๋กœ ๋ฐ”์ธ๋”ฉ ์‹œํ‚ค๊ณ  /team ์œผ๋กœ ํ˜ธ์ถœ๋˜๋Š” url ์„ TEAM-SERVICE ๋กœ ๋ฐ”์ธ๋”ฉ ์‹œ์ผœ์ฃผ์ž

 

application.yml

server:
  port: 8000

eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://localhost:8761/eureka

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE
          predicates:
            - Path=/user/**
          filters:
            - RewritePath=/user/?(?<segment>.*), /$\{segment}

        - id: team-service
          uri: lb://TEAM-SERVICE
          predicates:
            - Path=/team/**
          filters:
            - RewritePath=/team/?(?<segment>.*), /$\{segment}

User Service ๊ตฌํ˜„ (API ํ˜ธ์ถœ ์ œ์™ธ)

User Service ์—์„œ๋Š” API ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์œ ์ € ์กฐํšŒ ๋งŒ ๋นผ๊ณ  ์ž‘์„ฑํ•ด๋ณด์ž.

 

๊ฐ„๋‹จํ•˜๊ฒŒ ๋‹ค์Œ๊ณผ ๊ฐ™์€ Layered Architecture ๋กœ ๊ตฌ์„ฑ์„ ํ•˜๋ ค ํ•œ๋‹ค.

 

  • ์˜์กด์„ฑ ์ถ”๊ฐ€ & application.yml
  • Controller
    • UserController
  • Domain
    • Entity
      • User
    • Repository
      • UserRepository
    • dto
      • UserRegisterRequestData
      • UserResponseData
  • Service
    • UserService

์˜์กด์„ฑ ์ถ”๊ฐ€ & application.yml

build.gradle ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ฃผ๋„๋ก ํ•˜์ž.

 

์ด ์„ค์ •์€ Team Service ๋‚˜ User Service ๋‚˜ ๋™์ผํ•˜๋ฏ€๋กœ Team Service ์—์„œ๋Š” ์ƒ๋žตํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค. port ๋งŒ ๋‹ค๋ฅด๊ฒŒ ์ง€์ •ํ•˜์ž

ext {
    set('springCloudVersion', "2020.0.2")
}

dependencies {

     // web
    implementation 'org.springframework.boot:spring-boot-starter-web'

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

    // h2 database
    runtimeOnly 'org.h2database:h2'

    // eureka
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'

    // lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

 

๊ทธ๋ฆฌ๊ณ  ์ด์ œ ํ•ด๋‹น ์„œ๋น„์Šค๋ฅผ Eureka ํด๋ผ์ด์–ธํŠธ๋กœ ๋“ฑ๋ก์‹œํ‚ค๊ธฐ ์œ„ํ•œ ์ž‘์—…๊ณผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ ์ž‘์—…์„ ์œ„ํ•ด์„œ applicaton.yml ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•œ๋‹ค.

 

server:
  port: 50010

spring:
  application:
    name: user-service
  datasource:
    url: jdbc:h2:mem:userdb
    username: sa
    password:
  h2:
    console:
      enabled: true
      settings:
        web-allow-others: true
      path: /h2-console

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

 

๋˜ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ h2๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์ธ๋ฉ”๋ชจ๋ฆฌ์—์„œ๋งŒ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •๋„ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค.

 

Domain ์ž‘์„ฑํ•˜๊ธฐ

Domain ์—๋Š” 3๊ฐœ์˜ ํด๋ž˜์Šค๊ฐ€ ์œ„์น˜ํ•  ๊ฒƒ์ด๋‹ค.

  1. ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๋“ฑ๋ก์‹œํ‚ฌ Entity ๊ฐ์ฒด
  2. Data Jpa ์—์„œ ์ œ๊ณตํ•˜๋Š” JpaRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›๋Š” ์ปค์Šคํ…€ Repository
  3. ๋ฐ์ดํ„ฐ ์ „์†ก์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๊ฐ์ฒด

๊ฐ๊ฐ์˜ ํด๋ž˜์Šค๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„ํ•ด์ฃผ์ž.

 

// lombok ์–ด๋…ธํ…Œ์ด์…˜ ์ƒ๋žต
public class UserResponseData {
    private Long userId;
    private String username;
    private TeamResponseData team;
}

// lombok ์–ด๋…ธํ…Œ์ด์…˜ ์ƒ๋žต
public class UserCreateData {
    private String username;
}

// lombok ์–ด๋…ธํ…Œ์ด์…˜ ์ƒ๋žต
public class TeamResponseData {
    private Long teamId;
    private String name;
    private String address;
}

@Entity(name = "users")
// lombok ์–ด๋…ธํ…Œ์ด์…˜ ์ƒ๋žต
public class User {

    @Id @GeneratedValue
    private Long id;
    private String username;
}

public interface UserRepository extends JpaRepository<User, Long> {
}

UserService.class

User-Service ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•  ํด๋ž˜์Šค์ด๋‹ค.


ํ•ด๋‹น ๋ถ€๋ถ„์— Rest Template๋‚˜ Feign Client ๊ฐ€ ํฌํ•จ๋  ์˜ˆ์ •์ด๋‹ค.

 

@Service
@Transcational
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * ์‚ฌ์šฉ์ž๋ฅผ ์ €์žฅํ•œ๋‹ค
     *
     * @param userCreateData ์ €์žฅํ•˜๋ ค๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„
     * @return ์ €์žฅ๋œ ์‚ฌ์šฉ์ž
     */
    public UserResponseData save(UserCreateData userCreateData) {

        User user = User.builder()
                .username(userCreateData.getUsername())
                .build();

        user = userRepository.save(user);

        // Rest Template or Feign Client ์ž‘์—… ์ฒ˜๋ฆฌ

        return  UserResponseData.builder()
                .userId(user.getId())
                .username(user.getUsername())
                .team(null) // Rest Template or Feign Client
                .build();
    }

    /**
     * ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•œ๋‹ค.
     *
     * @param id ์‚ฌ์šฉ์ž id
     * @return ์ €์žฅ๋œ ์‚ฌ์šฉ์ž์™€ ํŒ€ ์ •๋ณด
     * @throws RuntimeException
     */
    public UserResponseData getUserById(Long id) {
        User userOptional = userRepository.findById(id)
                .orElseThrow(RuntimeException::new);

        // Rest Template or Feign Client ์ž‘์—… ์ฒ˜๋ฆฌ

        return UserResponseData.builder()
                .userId(userOptional.getId())
                .username(userOptional.getUsername())
                .team(null)
                .build();
    }
}

Service ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ํ•จ๊ผ Transaction Boundary๋ฅผ ๊ฑธ์–ด์ค€๋‹ค.

 

์œ„์— ์กด์žฌํ•˜๋Š” .team(null) ์— ์šฐ๋ฆฌ๊ฐ€ ๋‹ค๋ฅธ ์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ค„ ๊ฒƒ์ด๋‹ค.

 

UserController.class

์™ธ๋ถ€๋กœ ๋…ธ์ถœ์‹œํ‚ฌ User-Service ์˜ API Endpoint๋ฅผ ์ž‘์„ฑํ•ด์ค€๋‹ค.

 

@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/users")
    public UserResponseData createUser(@RequestBody UserCreateData userCreateData) {
        return userService.save(userCreateData);
    }

    @GetMapping("/users/{userId}")
    public UserResponseData getUser(@PathVariable("userId") Long id) {
        return userService.getUserById(id);
    }
}

 

์ด์ œ Eureka, Gateway ์™€ ํ•จ๊ป˜ User-Service ๋„ ์‹คํ–‰์‹œ์ผœ์„œ ๊ฐ๊ฐ์˜ Endpoint ๊ฐ€ ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ๋ธŒ๋ผ์šฐ์ €๋‚˜ Api Tester ๋กœ ํ…Œ์ŠคํŠธํ•ด๋ณด์ž.

 

Team Service ๊ตฌํ˜„

Team Service ๋„ ์—ญ์‹œ Layered ๋กœ ๊ตฌ์„ฑํ•˜๊ณ  ํŒ€์„ ์ƒ์„ฑ, ํŒ€์— ์‚ฌ์šฉ์ž ์ถ”๊ฐ€, ํŒ€ ์กฐํšŒ ๋น„์ฆˆ๋‹ˆ์Šค๋งŒ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

  • Controller
    • TeamController
  • Domain
    • Entity
      • Team
    • Repository
      • TeamRepository
    • dto
      • TeamCreateData
  • Service
    • TeamService

Domain ์ž‘์„ฑํ•˜๊ธฐ

DTO classes

public class TeamCreateData {
  private String name;
  private String address;
}

public class TeamMemberAddData {
  private String name;
}

public class TeamResponseData {
  private Long teamId;
  private String name;
  private String address;
}

Entity & Jpa Repository class and interface

public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private String address;
}

public class TeamMember {
    @Id @GeneratedValue
    private Long id;
    private Long userId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "teamId")
    private Team team;
}

public interface TeamRepository extends JpaRepository<Team, Long> {
    Team findByName(String name);
}

public interface TeamMemberRepository extends JpaRepository<TeamMember, Long> {
    TeamMember findByUserId(Long userId);
}

TeamService.class

Team ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•  ํด๋ž˜์Šค์ด๋‹ค.
User-Service ๊ฐ€ API ํ˜ธ์ถœ์„ ํ•  ์—”๋“œํฌ์ธํŠธ๋Š” getTeamByUserId ์ด๋‹ค.

@Service
@Transactional
public class TeamService {

    private final TeamRepository teamRepository;
    private final TeamMemberRepository teamMemberRepository;

    public TeamService(TeamRepository teamRepository, TeamMemberRepository teamMemberRepository) {
        this.teamRepository = teamRepository;
        this.teamMemberRepository = teamMemberRepository;
    }

    /**
     * ํŒ€์„ ์ƒ์„ฑํ•œ๋‹ค.
     *
     * @param teamCreateData ํŒ€ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด ๊ฐ์ฒด
     * @return TeamResponseData (teamId, name, address)
     */
    public TeamResponseData save(TeamCreateData teamCreateData) {
        Team team = Team.builder()
                .name(teamCreateData.getName())
                .address(teamCreateData.getAddress())
                .build();

        team = teamRepository.save(team);

        return TeamResponseData.builder()
                .teamId(team.getId())
                .name(team.getName())
                .address(team.getAddress())
                .build();
    }

    /**
     * ํŒ€ ์ด๋ฆ„๊ณผ ์‚ฌ์šฉ์ž Id๋ฅผ ๋ฐ›์•„ ํŒ€์— ์„ ์ˆ˜๋กœ ๋“ฑ๋กํ•œ๋‹ค.
     *
     * @param userId ์‚ฌ์šฉ์ž Id
     * @param teamName ํŒ€ ์ด๋ฆ„
     */
    public void addTeamMember(Long userId, String teamName) {
        Team selectedTeam = teamRepository.findByName(teamName);

        TeamMember teamMember = TeamMember.builder()
                .team(selectedTeam)
                .userId(userId)
                .build();

        teamMemberRepository.save(teamMember);
    }

    /**
     * ์‚ฌ์šฉ์ž ๋ฒˆํ˜ธ๋กœ ํŒ€์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
     *
     * @param userId ์กฐํšŒํ•˜๋ ค๋Š” ์‚ฌ์šฉ์ž ๋ฒˆํ˜ธ
     * @return ๋ฐ˜ํ™˜ํ•  ํŒ€ ๊ฐ์ฒด
     */
    public TeamResponseData getTeamByUserId(Long userId) {
        TeamMember teamMember = teamMemberRepository.findByUserId(userId);

        Team team = teamMember.getTeam();

        return TeamResponseData.builder()
                .teamId(team.getId())
                .name(team.getName())
                .address(team.getAddress())
                .build();
    }
}

TeamController.class

@RestController
public class TeamController {

    private final TeamService teamService;

    public TeamController(TeamService teamService) {
        this.teamService = teamService;
    }

    @PostMapping("/teams")
    public TeamResponseData create(@RequestBody TeamCreateData teamCreateData) {
        return teamService.save(teamCreateData);
    }

    @PostMapping("/{userId}/teams")
    public ResponseEntity addTeamMember(@PathVariable("userId") Long id,
                                        @RequestBody TeamMemberAddData requestData) {
        teamService.addTeamMember(id, requestData.getName());
        return ResponseEntity.noContent().build();
    }

    @GetMapping("/{userId}/teams")
    public TeamResponseData getTeamByUserId(@PathVariable("userId") Long userId) {
        return teamService.getTeamByUserId(userId);
    }

}

 

์ด์ œ Team-Service์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ๋‹ค ๊ตฌํ˜„์ด ๋˜์—ˆ์œผ๋‹ˆ API Tester๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์ž.

 

์ •์ƒ์ ์œผ๋กœ ์ž˜ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


์ด์ œ ๋‹ค์Œ ์‹œ๊ฐ„๋ถ€ํ„ฐ ๋ณธ๊ฒฉ์ ์œผ๋กœ Feign ๊ณผ RestTemplate ์„ ๋น„๊ตํ•˜์ž

๋Œ“๊ธ€