Skip to content

first-ticket/common

Repository files navigation

com.first-ticket.common

공통 예외 처리, 응답 형식, Feign/Web/JSON 설정을 제공하는 공통 모듈입니다.


🚀 배포 방법

⚠️ 배포 전 .env 파일에 GitHub 인증 정보가 설정되어 있어야 합니다. .env.example을 복사하여 .env 파일을 생성하고 값을 채워주세요.

cp .env.example .env
# .env
GITHUB_USER=깃허브유저명
GITHUB_TOKEN=ghp_xxxxxxxxxxxx

GITHUB_TOKEN은 GitHub → Settings → Developer settings → Personal access tokens에서 write:packages 권한으로 발급합니다.

1. build.gradle에서 버전 수정

// 기능 추가 시 버전 올리기
version = '0.0.1-SNAPSHOT'
version = '0.0.2-SNAPSHOT'

2. 배포

./gradlew publish

📝 버전

버전 변경 내용
0.0.1-SNAPSHOT • 공통 응답 구조 추가 (ApiResponse, ErrorCode, SuccessCode)
• 공통 예외 처리 추가 (BusinessException, GlobalExceptionHandler)
• JPA 기본 엔티티 및 설정 추가 (BaseEntity, BaseUserEntity, JpaConfig)
• 자동 설정 등록 (CommonAutoConfiguration)
0.0.2-SNAPSHOT • Feign 설정 추가 (FeignConfig, FeignErrorDecoder)
• PageableResolver 추가 (CustomPageableResolver, WebConfig)
JsonUtil 추가 (JSON 직렬화/역직렬화 정적 유틸)
ObjectMapper 빈 등록 (JavaTimeModule, FAIL_ON_UNKNOWN_PROPERTIES 설정)
0.0.3-SNAPSHOT • JPA 관련 코드 제거 (BaseEntity, BaseUserEntity, JpaConfig) → common-jpa로 분리
0.0.4-SNAPSHOT AuthContext 추가 (Gateway 주입 헤더에서 인증 사용자 정보를 정적 메서드로 추출)
UserRole enum 추가 (공통 역할 타입 — role별 분기 처리 시 사용)
AuthContext.getRole() 반환 타입 String → UserRole 변경

📦 의존성 추가

GitHub Packages는 public 패키지도 인증이 필요합니다. 팀원 각자 본인 GitHub 계정으로 토큰을 발급하여 .env에 넣어주세요. 토큰 발급 — GitHub → Settings → Developer settings → Personal access tokens → read:packages 권한

def env = [:]
def envFile = rootProject.file(".env")
if (envFile.exists()) {
    envFile.eachLine { line ->
        if (line && !line.startsWith("#") && line.contains("=")) {
            def (key, value) = line.split("=", 2)
            env[key.trim()] = value.trim()
        }
    }
}

repositories {
    mavenCentral()
    maven {
        url = 'https://maven.pkg.github.com/first-ticket/common'
        credentials {
            username = env['GITHUB_USER']
            password = env['GITHUB_TOKEN']
        }
    }
}

dependencies {
    implementation 'com.first-ticket:common:0.0.3-SNAPSHOT'
}

서비스 루트에 .env 파일이 있어야 합니다.

# .env
GITHUB_USER=깃허브유저명
GITHUB_TOKEN=ghp_xxxxxxxxxxxx

🗂️ 패키지 구조

com.firstticket.common
├── exception
│   ├── BusinessException.java       ← 베이스 예외
│   └── GlobalExceptionHandler.java  ← 전역 예외 처리
├── response
│   ├── ApiResponse.java             ← 공통 응답 래퍼
│   ├── ErrorCode.java               ← 에러 코드 interface
│   ├── SuccessCode.java             ← 성공 코드 interface
│   ├── CommonErrorCode.java         ← 공통 에러 코드
│   └── CommonSuccessCode.java       ← 공통 성공 코드
├── feign
│   ├── FeignConfig.java             ← Feign 설정, 헤더 전파
│   └── FeignErrorDecoder.java       ← Feign 에러 처리
├── web
│   ├── WebConfig.java                ← CustomPageableResolver 등록
│   ├── CustomPageableResolver.java   ← 페이지 크기 강제
│   ├── AuthContext.java              ← Gateway 주입 헤더 → 정적 메서드로 추출
│   └── UserRole.java                 ← 공통 사용자 역할 enum (CUSTOMER / HOST / ADMIN)
└── json
    ├── JsonConfig.java              ← ObjectMapper 빈 등록
    └── JsonUtil.java                ← JSON 직렬화/역직렬화 정적 유틸

🚀 사용 방법

1. 서비스 전용 예외 클래스 생성

BusinessException을 상속받아 서비스 전용 예외 클래스를 생성합니다.

// domain/exception/SampleException.java
public class SampleException extends BusinessException {

    public SampleException(SampleErrorCode errorCode) {
        super(errorCode);
    }
}

2. 서비스 전용 에러 코드 생성

ErrorCode interface를 구현한 enum을 생성합니다.

// domain/exception/SampleErrorCode.java
@Getter
@RequiredArgsConstructor
public enum SampleErrorCode implements ErrorCode {

    SAMPLE_NOT_FOUND(HttpStatus.NOT_FOUND, "샘플을 찾을 수 없습니다"),
    ALREADY_CANCELLED(HttpStatus.BAD_REQUEST, "이미 취소된 샘플입니다"),
    INVALID_AMOUNT(HttpStatus.BAD_REQUEST, "유효하지 않은 금액입니다");

    private final HttpStatus status;
    private final String message;
}

3. 서비스 전용 성공 코드 생성

SuccessCode interface를 구현한 enum을 생성합니다.

// presentation/SampleSuccessCode.java
@Getter
@RequiredArgsConstructor
public enum SampleSuccessCode implements SuccessCode {

    SAMPLE_CREATED(HttpStatus.CREATED, "샘플이 생성되었습니다"),
    SAMPLE_UPDATED(HttpStatus.OK, "샘플이 수정되었습니다"),
    SAMPLE_DELETED(HttpStatus.OK, "샘플이 삭제되었습니다"),
    SAMPLE_FOUND(HttpStatus.OK, "샘플을 조회했습니다");

    private final HttpStatus status;
    private final String message;
}

4. 예외 발생

throw new SampleException(SampleErrorCode.ALREADY_CANCELLED);

5. 컨트롤러에서 응답 반환

// presentation/SampleController.java
@RestController
@RequiredArgsConstructor
@RequestMapping("/samples")
public class SampleController {

    private final SampleService sampleService;

    @PostMapping
    public ResponseEntity<ApiResponse<SampleResponse>> create(
            @RequestBody SampleCreateRequest request) {
        SampleResult result = sampleService.create(request.toCommand());
        return ApiResponse.success(SampleSuccessCode.SAMPLE_CREATED,
                SampleResponse.from(result));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
        sampleService.delete(id);
        return ApiResponse.success(SampleSuccessCode.SAMPLE_DELETED);
    }
}

📋 응답 형식

✅ 성공 응답

{
    "success": true,
    "code": "SAMPLE_CREATED",
    "message": "샘플이 생성되었습니다",
    "timestamp": "2024-01-01T00:00:00",
    "data": {
        "id": 1,
        "name": "샘플"
    }
}

❌ 에러 응답

{
    "success": false,
    "code": "ALREADY_CANCELLED",
    "message": "이미 취소된 샘플입니다",
    "timestamp": "2024-01-01T00:00:00"
}

🔗 Feign

서비스 간 HTTP 통신을 위한 Feign 설정이 자동으로 적용됩니다.

FeignClient 사용

// infrastructure/client/UserClient.java
@FeignClient(name = "user-service")
public interface UserClient {

    @GetMapping("/users/{id}")
    UserResponse getUser(@PathVariable UUID id);
}

헤더 자동 전파

모든 Feign 요청에 아래 헤더가 자동으로 전파됩니다.

헤더 설명
X-User-Id 현재 요청한 유저 ID
X-User-Role 현재 요청한 유저 권한

Feign 에러 코드

Feign 호출 실패 시 아래 에러 코드로 변환됩니다.

코드 HTTP 상태 메시지
FEIGN_BAD_REQUEST 400 외부 서비스 요청이 올바르지 않습니다
FEIGN_UNAUTHORIZED 401 외부 서비스 인증에 실패했습니다
FEIGN_FORBIDDEN 403 외부 서비스 접근 권한이 없습니다
FEIGN_NOT_FOUND 404 외부 서비스 리소스를 찾을 수 없습니다
FEIGN_SERVER_ERROR 500 외부 서비스 오류가 발생했습니다

📄 CustomPageableResolver

허용된 페이지 크기 외 요청 시 기본값으로 강제 적용됩니다.

항목
허용 페이지 크기 10, 30, 50
기본 페이지 크기 10

⚠️ CustomPageableResolver가 적용되려면 컨트롤러 파라미터에 org.springframework.data.domain.Pageable을 사용해야 합니다.

import org.springframework.data.domain.Pageable;

@GetMapping
public ResponseEntity<ApiResponse<SampleListResponse>> getList(Pageable pageable) {
    ...
}

요청 방법

GET /samples?page=0&size=30&sort=createdAt

허용되지 않은 사이즈 요청 시 기본값 10으로 처리됩니다.

GET /samples?size=100  →  size=10 으로 강제 적용

🔧 JsonUtil

JSON 직렬화/역직렬화를 정적 메서드로 제공합니다.
ObjectMapper 빈이 자동으로 주입되므로 별도 설정 없이 바로 사용할 수 있습니다.

// 직렬화
String json = JsonUtil.toJson(sampleDto);

// 역직렬화
SampleDto dto = JsonUtil.fromJson(json, SampleDto.class);

// 제네릭 타입 역직렬화
List<SampleDto> list = JsonUtil.fromJson(json, new TypeReference<List<SampleDto>>() {});

직렬화/역직렬화 실패 시 예외를 던지지 않고 null을 반환합니다.
호출하는 쪽에서 null 체크가 필요합니다.

ObjectMapper 설정

설정 설명
JavaTimeModule 등록 LocalDateTime, Instant 등 Java 8 날짜/시간 타입 지원
WRITE_DATES_AS_TIMESTAMPS false 날짜를 ISO-8601 문자열로 직렬화
FAIL_ON_UNKNOWN_PROPERTIES false 알 수 없는 필드 무시 (Feign, Kafka 통신 시 필요한 필드만 선택적으로 받을 수 있음)

🔐 AuthContext

Gateway가 주입한 X-User-Id, X-User-Role 헤더를 정적 메서드로 추출합니다.
컨트롤러 파라미터 선언 없이 필요한 시점에 어디서든 호출 가능합니다.

사용 방법

UUID     userId = AuthContext.getUserId(); // X-User-Id  → UUID
UserRole role   = AuthContext.getRole();   // X-User-Role → UserRole

동작 규칙

헤더 필수 여부 누락 시
X-User-Id 필수 UNAUTHORIZED (401)
X-User-Role 필수 UNAUTHORIZED (401)

주의 사항

  • HTTP 요청 컨텍스트 안에서만 동작합니다.
  • @Async, Kafka 컨슈머, 스케줄러 등 요청 범위 밖에서는 사용할 수 없습니다.
  • X-User-Role 헤더값이 UserRole에 정의되지 않은 값이면 UNAUTHORIZED (401) 발생합니다.

👤 UserRole

Gateway가 전파하는 X-User-Role 헤더값에 대응하는 공통 역할 enum입니다.
동일 API에서 role별 동작이 달라지는 경우 타입 안전하게 분기 처리할 수 있습니다.

역할 목록

역할 설명
CUSTOMER 일반 사용자
HOST 주최자
ADMIN 관리자

사용 예시

UserRole role = AuthContext.getRole();

switch (role) {
    case ADMIN    -> // 관리자 전용 로직
    case HOST     -> // 호스트 전용 로직
    case CUSTOMER -> // 일반 사용자 로직
}

🔴 공통 에러 코드

코드 HTTP 상태 메시지
INVALID_INPUT 400 입력값이 올바르지 않습니다
UNAUTHORIZED 401 인증에 실패했습니다
FORBIDDEN 403 접근 권한이 없습니다
NOT_FOUND 404 요청한 리소스를 찾을 수 없습니다
METHOD_NOT_ALLOWED 405 허용되지 않는 HTTP 메서드입니다
CONFLICT 409 이미 존재하는 리소스입니다
INTERNAL_SERVER_ERROR 500 서버 내부 오류가 발생했습니다

🟢 공통 성공 코드

코드 HTTP 상태 메시지
OK 200 요청이 성공했습니다
CREATED 201 리소스가 생성되었습니다
DELETED 200 리소스가 삭제되었습니다

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages