Skip to content

89-49/user-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

52 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ›ก๏ธ PGSG User Service (Authentication & Member Management)

๋ณธ ํ”„๋กœ์ ํŠธ๋Š” MSA(Microservice Architecture) ํ™˜๊ฒฝ์—์„œ ๋ณด์•ˆ๊ณผ ํ™•์žฅ์„ ์ฑ…์ž„์ง€๋Š” JWT ๊ธฐ๋ฐ˜ ํ†ตํ•ฉ ์ธ์ฆ ๋ฐ ํšŒ์› ๊ด€๋ฆฌ ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค.
๊ณ ์„ฑ๋Šฅ ํšŒ์› ์กฐํšŒ, ์•ˆ์ „ํ•œ ์ธ์ฆ ํ๋ฆ„, ๊ทธ๋ฆฌ๊ณ  ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ์ˆ ์  ์žฅ์น˜๋“ค์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ—๏ธ ํŒจํ‚ค์ง€ ๊ตฌ์กฐ ๋ฐ ๋ ˆ์ด์–ด๋ง

src/main/java/org/pgsg/user_service/
โ”œโ”€โ”€ auth/                            # ์ธ์ฆ ๋„๋ฉ”์ธ
โ”‚   โ”œโ”€โ”€ application/                 # ์„œ๋น„์Šค(AuthService) ๋ฐ ํ† ํฐ ๋ฐœํ–‰ ๋กœ์ง
โ”‚   โ”œโ”€โ”€ domain/                      # ์ธ์ฆ ์ •์ฑ… ์ธํ„ฐํŽ˜์ด์Šค ๋ฐ ๊ฐ’ ๊ฐ์ฒด
โ”‚   โ”œโ”€โ”€ infrastructure/              # RedisTokenRepository, JwtTokenProvider, Security ํ•„ํ„ฐ
โ”‚   โ””โ”€โ”€ presentation/                # AuthController (๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž… API)
โ”‚
โ”œโ”€โ”€ user/                            # ํšŒ์› ๋„๋ฉ”์ธ
โ”‚   โ”œโ”€โ”€ application/                 # Facade(์กฐ์œจ) ๋ฐ ๋„๋ฉ”์ธ ์„œ๋น„์Šค
โ”‚   โ”œโ”€โ”€ domain/                      # User ์—”ํ‹ฐํ‹ฐ, UserRole, UserRepository ์ธํ„ฐํŽ˜์ด์Šค
โ”‚   โ”œโ”€โ”€ infrastructure/              # JpaUserRepository, UserQueryRepository(QueryDSL)
โ”‚   โ””โ”€โ”€ presentation/                # UserController (ํšŒ์› ์ •๋ณด ๊ด€๋ฆฌ API)
โ”‚
โ””โ”€โ”€ UserServiceApplication.java      # Application Bootstrapping

๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack)

๊ตฌ๋ถ„ ๊ธฐ์ˆ  ์ƒ์„ธ
Framework Spring Boot 3.5.13 Java 21 LTS ๊ธฐ๋ฐ˜
Common Lib org.pgsg:common ์ „์‚ฌ ๊ณตํ†ต ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐ ์œ ํ‹ธ๋ฆฌํ‹ฐ
Database PostgreSQL 17 UUID ๋ฐ JSONB ์ง€์›
Cache Redis 7.2 RefreshToken ์ €์žฅ ๋ฐ ํ† ํฐ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ๊ด€๋ฆฌ
Persistence Spring Data JPA / QueryDSL Type-safeํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ ๋ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™”
Security Spring Security / JWT Stateless ์ธ์ฆ ๋ฐ ์ธ๊ฐ€
Observability Micrometer / Zipkin ๋ถ„์‚ฐ ํŠธ๋ ˆ์ด์‹ฑ ๋ฐ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘
Infrastructure Spring Cloud Eureka(Discovery), Config(Centralized Config)

โš™๏ธ ์„ค์ • ๊ด€๋ฆฌ ๋ฐ ์šด์˜ ์ „๋žต

1. ๋กœ์ปฌ ์„ค์ • ์ตœ์ ํ™” ๋ฐ ์šฐ์„ ์ˆœ์œ„ (application.yml)

์ค‘์•™ ์„ค์ • ์„œ๋ฒ„(Config Server)๋ฅผ ํ†ตํ•ด ๊ณตํ†ต ์ •์ฑ…์„ ๊ด€๋ฆฌํ•˜๋˜, ๋กœ์ปฌ ๊ฐœ๋ฐœ ๋ฐ Docker ํ™˜๊ฒฝ์˜ ์œ ์—ฐ์„ฑ์„ ์œ„ํ•ด ๋กœ์ปฌ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์›๊ฒฉ ์„ค์ •์„ ๋ฎ์–ด์“ฐ๋„๋ก(Override) ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿ“„ application.yml ๋กœ์ปฌ ์„ค์ • ์ „๋ฌธ (ํŽผ์น˜๊ธฐ)
server:
  port: ${SERVER_PORT:8081}

spring:
  application:
    name: user-service

  profiles:
    active: dev, kafka
    include: error, user-error

  config:
    import:
      - "optional:file:.env[.properties]"
      - "optional:configserver:"

  cloud:
    config:
      allow-override: true
      override-none: true
      override-system-properties: false
      discovery:
        service-id: config-server
        enabled: true

  main:
    allow-bean-definition-overriding: true

jwt:
  secret: ${JWT_SECRET}
  access-token-expiration: ${JWT_ACCESS_EXPIRATION:1800000}
  refresh-token-expiration: ${JWT_REFRESH_EXPIRATION:604800000}

eureka:
  instance:
    prefer-ip-address: true
    ip-address: ${HOSTNAME:localhost}
    hostname: ${HOSTNAME:localhost}
    instance-id: "${HOSTNAME:${spring.application.name}}:${spring.application.name}:${server.port}"
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: ${EUREKA_SERVER_URL:http://localhost:8761/eureka/}

management:
  tracing:
    sampling:
      probability: 0.1
  zipkin:
    tracing:
      endpoint: ${ZIPKIN_ENDPOINT:http://localhost:9411/api/v2/spans}

2. Kafka SSL ๋ณผ๋ฅจ ๋งˆ์šดํŠธ (Troubleshooting)

Java ๋ณด์•ˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ œ์•ฝ์œผ๋กœ ์ธํ•ด SSL ํŠธ๋Ÿฌ์ŠคํŠธ์Šคํ† ์–ด(jks)๋Š” JAR ๋‚ด๋ถ€๊ฐ€ ์•„๋‹Œ ๋ฌผ๋ฆฌ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ํ•ด๊ฒฐ์ฑ…: docker-compose.yml ๋ณผ๋ฅจ ์„ค์ •์„ ํ†ตํ•ด ํ˜ธ์ŠคํŠธ์˜ ./ssl ํด๋”๋ฅผ ์ปจํ…Œ์ด๋„ˆ /app/ssl๋กœ ๋งˆ์šดํŠธํ•˜์—ฌ ๊ธฐ๋™ ์‹œ ์ฆ‰์‹œ ๋กœ๋“œ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

3. ์ธํ”„๋ผ ๊ตฌ์„ฑ ์ •๋ณด (Docker Configuration)

๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๋ณด์•ˆ์„ ์œ„ํ•ด ๋ฏผ๊ฐ ์ •๋ณด(๋น„๋ฐ€๋ฒˆํ˜ธ, ํ† ํฐ ๋“ฑ)๋ฅผ ํŒŒ์ผ์— ์ง์ ‘ ๊ธฐ๋กํ•˜์ง€ ์•Š๊ณ , Docker Secrets ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿณ Dockerfile (๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ)
# ๋นŒ๋“œ ์Šคํ…Œ์ด์ง€: GitHub ํŒจํ‚ค์ง€ ์ ‘๊ทผ์„ ์œ„ํ•ด ์‹œํฌ๋ฆฟ ๋งˆ์šดํŠธ ์‚ฌ์šฉ
FROM gradle:8.7-jdk21 AS build
WORKDIR /app
COPY . .
RUN --mount=type=secret,id=GPR_USER \
    --mount=type=secret,id=GPR_TOKEN \
    export GPR_USER=$(cat /run/secrets/GPR_USER) && \
    export GPR_TOKEN=$(cat /run/secrets/GPR_TOKEN) && \
    gradle bootJar --no-daemon

# ์‹คํ–‰ ์Šคํ…Œ์ด์ง€: ๊ฒฝ๋Ÿ‰ํ™”๋œ JRE ํ™˜๊ฒฝ
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
๐Ÿ™ docker-compose.yml (์„œ๋น„์Šค ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜)
services:
  user-service:
    container_name: user-service
    build:
      context: .
      secrets:
        - GPR_USER
        - GPR_TOKEN
    environment:
      - "SERVER_PORT=${SERVER_PORT:-8081}"
    ports:
      - "${SERVER_PORT:-8081}:${SERVER_PORT:-8081}"
    env_file:
      - .env
    volumes:
      - ./src/main/resources/ssl:/app/ssl
    depends_on:
      db: { condition: service_healthy }
      redis: { condition: service_healthy }
    networks:
      - pgsg-network

  db:
    image: postgres:17
    environment:
      POSTGRES_DB: ${DB_NAME:-userdb}
      POSTGRES_USER: ${DB_USERNAME:-postgres}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-postgres}"]
    networks:
      - pgsg-network

  redis:
    image: redis:7.2
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
    networks:
      - pgsg-network

networks:
  pgsg-network:
    name: pgsg-network
    external: true

volumes:
  postgres-data:

secrets:
  GPR_USER: { environment: GPR_USER }
  GPR_TOKEN: { environment: GPR_TOKEN }

๐Ÿšฆ ์‹œ์ž‘ํ•˜๊ธฐ (Getting Started)

1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ตฌ์„ฑ (.env)

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— .env ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ , ์ตœ์‹ ํ™”๋œ .env.example ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์‹ค์ œ ํ™˜๊ฒฝ์— ๋งž๋Š” ๊ฐ’์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

# [Database] PostgreSQL ์„ค์ •
DB_USERNAME=postgres
DB_PASSWORD=your_secure_password
DB_URL=jdbc:postgresql://db:5432/userdb

# [Redis] ํ† ํฐ ์ €์žฅ์†Œ ์„ค์ •
REDIS_HOST=redis
REDIS_PORT=6379

# [JWT] ์ธ์ฆ ๋ณด์•ˆ ์„ค์ •
JWT_SECRET=YOUR_SECURE_BASE64_ENCODED_SECRET
JWT_ACCESS_EXPIRATION=1800000
JWT_REFRESH_EXPIRATION=604800000

# [MSA Infrastructure] ์ค‘์•™ ์„ค์ • ๋ฐ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ
CONFIG_SERVER=http://your-config-server-ip:13100
EUREKA_SERVER_URL=http://eureka-server:8761/eureka/
ZIPKIN_ENDPOINT=http://zipkin:9411/api/v2/spans

# [Application] ์„œ๋ฒ„ ๊ตฌ๋™ ์„ค์ •
SERVER_PORT=8081
HOSTNAME=user-service

# [GitHub Packages] ๊ณตํ†ต ๋ชจ๋“ˆ ๋นŒ๋“œ ์ž๊ฒฉ ์ฆ๋ช… (PAT)
GPR_USER=your_github_id
GPR_TOKEN=your_personal_access_token

2. Docker๋ฅผ ์ด์šฉํ•œ ์‹คํ–‰

# ์™ธ๋ถ€ ํ†ต์‹ ์„ ์œ„ํ•œ ๋„คํŠธ์›Œํฌ ์ƒ์„ฑ
docker network create pgsg-network

# ๋นŒ๋“œ ๋ฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์‹คํ–‰
docker compose up -d --build

๐Ÿ“ฎ ์ฃผ์š” API ๊ฐ€์ด๋“œ

1. ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ด€๋ จ (Auth API)

๊ธฐ๋Šฅ ๋ฉ”์„œ๋“œ ๊ฒฝ๋กœ ๊ถŒํ•œ ์„ค๋ช…
๋กœ๊ทธ์ธ POST /api/v1/auth/login ๋ˆ„๊ตฌ๋‚˜ ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ํ† ํฐ ์Œ(Access/Refresh) ๋ฐœ๊ธ‰
ํšŒ์›๊ฐ€์ž… POST /api/v1/auth/signup ๋ˆ„๊ตฌ๋‚˜ ์‹ ๊ทœ ์‚ฌ์šฉ์ž ๋“ฑ๋ก
๋กœ๊ทธ์•„์›ƒ POST /api/v1/auth/logout ์ธ์ฆ ์‚ฌ์šฉ์ž Access Token ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์ฒ˜๋ฆฌ ๋ฐ ๋กœ๊ทธ์•„์›ƒ
ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ POST /api/v1/auth/reissue ๋ˆ„๊ตฌ๋‚˜ Refresh Token์„ ์ด์šฉํ•œ Access Token ๊ฐฑ์‹ 

2. ํšŒ์› ์ •๋ณด ๊ด€๋ฆฌ (User API)

๊ธฐ๋Šฅ ๋ฉ”์„œ๋“œ ๊ฒฝ๋กœ ๊ถŒํ•œ ์„ค๋ช…
๋‚ด ์ •๋ณด ์กฐํšŒ GET /api/v1/users/me ์ธ์ฆ ์‚ฌ์šฉ์ž ํ˜„์žฌ ๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž์˜ ์ƒ์„ธ ํ”„๋กœํ•„ ์กฐํšŒ
ํšŒ์› ์ƒ์„ธ ์กฐํšŒ GET /api/v1/users/{userId} ์ธ์ฆ ์‚ฌ์šฉ์ž ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์ƒ์„ธ ์ •๋ณด ์กฐํšŒ (๊ถŒํ•œ ํ™•์ธ ํฌํ•จ)
ํšŒ์› ๋ชฉ๋ก ๊ฒ€์ƒ‰ GET /api/v1/users ๊ด€๋ฆฌ์ž QueryDSL ๊ธฐ๋ฐ˜ ๋™์  ๊ฒ€์ƒ‰ ๋ฐ ํŽ˜์ด์ง• ์กฐํšŒ
๋‚ด ํ”„๋กœํ•„ ์ˆ˜์ • PATCH /api/v1/users/me ์ธ์ฆ ์‚ฌ์šฉ์ž ๋ณธ์ธ์˜ ๋‹‰๋„ค์ž„, ์ด๋ฆ„ ๋“ฑ ํ”„๋กœํ•„ ์ •๋ณด ์ˆ˜์ •
ํšŒ์› ์ •๋ณด ์ˆ˜์ • PATCH /api/v1/users/{userId} ๊ด€๋ฆฌ์ž ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์œผ๋กœ ํŠน์ • ํšŒ์› ์ •๋ณด(์—ญํ•  ๋“ฑ) ์ˆ˜์ •
ํšŒ์› ํƒˆํ‡ด/์‚ญ์ œ DELETE /api/v1/users/{userId} ์ธ์ฆ/๊ด€๋ฆฌ์ž ํšŒ์› ๊ณ„์ • ์‚ญ์ œ (Soft Delete)

3. ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์šฉ (Internal API)

๊ธฐ๋Šฅ ๋ฉ”์„œ๋“œ ๊ฒฝ๋กœ ์„ค๋ช…
ํ† ํฐ ๊ฒ€์ฆ POST /internal/v1/auth/verify ๊ฒŒ์ดํŠธ์›จ์ด ๋“ฑ์—์„œ ํ† ํฐ ์œ ํšจ์„ฑ ๋ฐ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์—ฌ๋ถ€ ํ™•์ธ
์ธ์ฆ์šฉ ์ •๋ณด ์กฐํšŒ GET /internal/v1/users username์œผ๋กœ ์ธ์ฆ์— ํ•„์š”ํ•œ ์ตœ์†Œ ์ •๋ณด ์กฐํšŒ
ํšŒ์› ์ƒ์„ธ ์—ฐ๋™ GET /internal/v1/users/{userId} ํƒ€ ์„œ๋น„์Šค์—์„œ UUID๋กœ ํšŒ์› ๊ธฐ๋ณธ ์ •๋ณด ์—ฐ๋™ ์‹œ ์‚ฌ์šฉ
์ฑ„ํŒ… ๊ฐ€๋Šฅ ์‹œ๊ฐ„ ์กฐํšŒ GET /internal/v1/users/{userId}/chat-availability ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์š”์ผ/์‹œ๊ฐ„๋ณ„ ์ฑ„ํŒ… ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ

๐Ÿ“ˆ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๊ด€๋ฆฌ

  • Actuator: /actuator/health, /actuator/info, /actuator/prometheus๋ฅผ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„ ์ƒํƒœ ๋ฐ ๋ฉ”ํŠธ๋ฆญ ํ™•์ธ ๊ฐ€๋Šฅ.
  • Tracing: Micrometer๋ฅผ ํ†ตํ•ด ํŠธ๋ ˆ์ด์‹ฑ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  Zipkin๊ณผ ์—ฐ๋™ํ•˜์—ฌ ์„œ๋น„์Šค ๊ฐ„ ์š”์ฒญ ํ๋ฆ„์„ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค.
  • Log ์ˆ˜์ง‘ (Loki/Promtail): ์šด์˜ ์„œ๋ฒ„์˜ ๋กœ๊ทธ๋ฅผ Promtail์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ˆ˜์ง‘ํ•˜์—ฌ Loki ์„œ๋ฒ„๋กœ ์ „์†กํ•˜๋ฉฐ, Grafana๋ฅผ ํ†ตํ•ด ํ†ตํ•ฉ ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ๋ถ„์‚ฐ ๋กœ๊ทธ ๊ด€๋ฆฌ: MDC ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜์—ฌ ๋กœ๊ทธ์— Trace ID๋ฅผ ํฌํ•จํ•จ์œผ๋กœ์จ ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ์˜ ๋””๋ฒ„๊น…๊ณผ ์ƒ๊ด€๊ด€๊ณ„ ๋ถ„์„์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors