Skip to content

Repository files navigation

pairing

Spring Boot 3.5 + Java 17 기반 헥사고날(포트-어댑터) 아키텍처 백엔드입니다.

요구사항명세·정책 기준으로 컨트롤러 계층(API 계약)이 확정된 상태입니다. 엔드포인트 127개와 Swagger 문서가 모두 갖춰져 있고, 각 컨트롤러는 요청/응답 형태를 고정한 stub 응답을 반환합니다. // TODO: {Domain}UseCase 연결 주석 위치에 application·domain·infrastructure 계층을 붙여나가면 됩니다.

전체 엔드포인트 맵과 정책 반영 지점은 .ai/API.md에 정리되어 있습니다.

⚠️ POST /api/v1/auth/login은 자격 증명을 검증하지 않고 토큰을 발급합니다. Swagger에서 인증이 필요한 API를 호출해보기 위한 임시 동작이므로 실제 인증 로직을 붙이기 전에 배포하면 안 됩니다.

기술 스택

구분 내용
Language / Runtime Java 17
Framework Spring Boot 3.5.14
영속성 Spring Data JPA, PostgreSQL (테스트·로컬은 H2 PostgreSQL 호환 모드)
인증 Spring Security + JWT (jjwt)
캐시 / 저장소 Redis (Lettuce)
파일 저장 AWS S3
문서화 springdoc-openapi (Swagger UI)
매핑 MapStruct
기타 Lombok, AOP, Actuator

실행

1) 로컬 개발 — 인프라 없이 바로 실행

현재 컨트롤러는 모두 고정 응답(stub)이라 DB가 필요하지 않습니다. local 프로파일은 인메모리 H2를 쓰고 개발용 더미 설정을 포함하므로 아무 준비 없이 서버가 뜹니다.

./gradlew bootRun --args='--spring.profiles.active=local'
  • Swagger UI: http://localhost:8080/swagger-ui/index.html
  • 헬스체크: http://localhost:8080/actuator/health

Swagger에서 POST /api/v1/auth/login을 먼저 호출하면 토큰 쿠키가 세팅되어 나머지 API를 그대로 호출할 수 있습니다.

2) 실제 인프라로 실행

docker compose up -d

로컬에 PostgreSQL을 직접 설치해 쓰고 있으면 5432가 충돌합니다. 그 경우 Redis만 띄우세요. docker compose up -d redis

역할·데이터베이스가 아직 없다면 db/init/01-create-pairing-account.sql을 실행합니다. psql -U postgres -f db/init/01-create-pairing-account.sql

프로파일 없이 실행하면 필수 환경변수 두 개가 반드시 필요합니다. 없으면 기동 단계에서 무엇을 넣어야 하는지 안내와 함께 실패합니다.

export JWT_SECRET_KEY=$(openssl rand -base64 48)
export S3_BUCKET=your-bucket-name
./gradlew bootRun

JWT_SECRET_KEY에 실제 기본값을 두지 않은 것은 공개된 키로 토큰을 서명하는 사고를 막기 위한 의도입니다. AWS 자격증명은 주입하지 않으면 기본 자격증명 체인(~/.aws, IAM 역할)을 사용합니다.

의존 서비스 없이 컨텍스트만 확인하려면 테스트를 실행하면 됩니다 (H2 사용, Redis/S3는 지연 연결이라 기동만 확인).

./gradlew test

환경변수

application.yaml에 로컬 기본값이 들어있으므로 그대로도 뜨지만, 실제 사용 시에는 아래 값을 주입하세요. 전체 목록과 용도는 docs/ai/security-guide.md에 있습니다.

변수 설명
DB_URL, DB_USERNAME, DB_PASSWORD PostgreSQL 접속 정보 (기본값 jdbc:postgresql://localhost:5432/pairing, pairing/pairing)
REDIS_HOST, REDIS_PORT Redis 접속 정보
JWT_SECRET_KEY 필수. JWT 서명 키 (HS256이므로 32바이트 이상). 없으면 기동 실패
S3_BUCKET 필수. S3 버킷명. 없으면 기동 실패
COOKIE_DOMAIN 토큰 쿠키 도메인. 비워두면 host-only 쿠키
COOKIE_SECURE https 배포 시 true
CORS_ALLOWED_ORIGINS 허용 오리진 목록(쉼표 구분)
AWS_REGION 기본값 ap-northeast-2
S3_CDN_URL CloudFront 도메인. 비우면 버킷·리전으로 자동 조합
AWS_ACCESS_KEY, AWS_SECRET_KEY 비워두면 IAM 역할 등 기본 자격증명 체인 사용 (권장)

문서

파일 내용
AGENTS.md AI 작업 가이드 및 요청별 문서 라우팅
docs/ai/backend-convention.md 아키텍처·계층별 코드 규칙, global 재사용 목록
docs/ai/infrastructure-guide.md S3 업로드와 Redis 사용법
docs/ai/security-guide.md 시크릿, 환경변수, 인증/인가, 업로드 보안
docs/ai/testing-guide.md 검증 명령과 로컬 트러블슈팅
docs/ai/git-issue-pr-guide.md 브랜치·커밋·PR 규칙
.ai/ 진행 상태(STATE), 인수인계(HANDOFF), 작업기록(WORKLOG), API 변경노트

패키지 구조

com.pairing
├── PairingApplication.java
├── global/                        # 도메인에 종속되지 않는 전역 공통 기능
│   ├── annotation/swagger/        # @ApiErrorCodeExample — 에러 응답 예시 자동 문서화
│   ├── aop/                       # ApiLoggingAop — Controller/Service 공통 로깅
│   ├── common/api/response/       # ApiResponse, ErrorResponse, PageResponse
│   ├── config/                    # Async, Swagger, Web, Redis, S3, CdnJackson
│   ├── exception/                 # BaseErrorCode, BusinessException, CommonExceptionAdvice ...
│   ├── filter/                    # TraceIdFilter — 요청별 traceId 발급(MDC + X-Trace-Id)
│   ├── infrastructure/s3/         # S3 어댑터, 버킷 초기화, CDN URL 자동 매핑
│   ├── port/out/                  # FileStoragePort, StorageSettings (아웃바운드 포트)
│   ├── security/                  # JWT 발급/검증 필터, SecurityConfig, 401/403 핸들러
│   ├── type/                      # FileType
│   └── util/                      # RedisKeys, FileTypeDetector
├── auth/                          # 인증(회원가입·로그인·소셜·이메일 인증·계정 복구)
├── account/                       # 계정·프로필·결제수단 애그리거트
├── terms/                         # 약관 조회와 동의 이력
└── example/                       # 새 도메인을 만들 때 복사해서 쓰는 레퍼런스 도메인
    ├── application/
    │   ├── command/               # 입력 DTO (HTTP 기술에 의존하지 않음)
    │   ├── policy/                # 여러 도메인 객체에 걸친 정책
    │   ├── port/                  # 외부에 요구하는 출력 포트
    │   ├── service/               # UseCase 구현 + 트랜잭션 경계
    │   └── usecase/               # Controller가 바라보는 입력 포트
    ├── domain/
    │   ├── event/                 # 도메인 이벤트
    │   ├── model/                 # 순수 도메인 엔티티 (JPA 어노테이션 없음)
    │   └── repository/            # 저장소 포트
    ├── exception/                 # 도메인 에러코드 Enum (BaseErrorCode 구현)
    ├── infrastructure/
    │   ├── event/                 # 이벤트 발행/구독 어댑터
    │   ├── mapper/                # Domain <-> JPA Entity (MapStruct)
    │   └── persistence/           # JPA 엔티티, Spring Data, Repository 어댑터
    ├── presentation/
    │   ├── advice/                # 도메인 전용 @RestControllerAdvice
    │   └── api/                   # 컨트롤러 + request/response DTO
    └── settings/                  # ExampleStorageSettings, 도메인 전용 annotation / aop

각 폴더의 example.txt는 빈 디렉터리를 형상관리에 남기기 위한 placeholder이며, 해당 폴더의 역할이 한 줄로 적혀 있습니다.

아키텍처 규칙

  • 의존 방향은 항상 바깥 → 안쪽입니다. presentation → application → domain, infrastructure → domain.
  • domain은 Spring / JPA를 모릅니다. 순수 자바 객체로만 두고, 불변식 검증을 도메인 안에서 합니다(Example.validateName 참고).
  • applicationdomain/repository인터페이스(포트) 에만 의존하고, 구현체는 infrastructure/persistence의 어댑터가 제공합니다.
  • presentation은 Request DTO → Command 변환만 하고 비즈니스 판단을 하지 않습니다.
  • 예외는 BusinessException(도메인ErrorCode)로 던지면 CommonExceptionAdvice가 공통 형식으로 응답합니다.
  • 파일은 DB에 object key(상대경로)만 저장하고, 절대 URL은 응답 직렬화 시점에 조립합니다(CdnMappable). CDN 도메인이 바뀌어도 데이터 마이그레이션이 필요 없습니다.

파일 업로드 / Redis 요약

자세한 사용법은 docs/ai/infrastructure-guide.md에 있습니다.

업로드 — 서비스는 FileStoragePort에만 의존하고, 실제 저장 방식은 global/infrastructure/s3의 어댑터가 정합니다.

String key = fileStoragePort.uploadFile(command.image(), storageSettings.getDirectory());

응답 DTO에 implements CdnMappable을 붙이고 필드명을 ~Url로 끝내면 examples/uuid.pnghttps://my-bucket.s3.ap-northeast-2.amazonaws.com/examples/uuid.png로 자동 변환됩니다. 동작하는 예시는 POST /api/v1/examples/{exampleId}/image 입니다.

Redis — 키 접두사는 반드시 RedisKeys에 상수로 선언하고, TTL을 항상 지정합니다.

redisTemplate.opsForValue().set(RedisKeys.AUTH_CODE_PREFIX + email, code, 5, TimeUnit.MINUTES);

새 도메인 추가하기

  1. example 폴더를 복사해 도메인 이름으로 변경합니다. (예: product)
  2. 클래스 이름의 Example 접두사를 도메인 이름으로 일괄 변경합니다.
  3. XxxErrorCode의 코드 접두사(EX_001 등)를 도메인용으로 바꿉니다.
  4. XxxExceptionAdvicebasePackages를 새 도메인 경로로 수정합니다.
  5. 필요 없는 폴더의 example.txt는 삭제하지 말고 그대로 두면 구조가 유지됩니다.

프로젝트 이름 바꾸기

현재는 com.pairing / pairing 입니다. 다시 바꾸려면:

  1. settings.gradlerootProject.name
  2. build.gradlegroup, description
  3. src/main/java/com/pairing 디렉터리명과 모든 파일의 package / import 경로 (IDE의 Refactor → Rename 사용 권장)
  4. ApiLoggingAop의 Pointcut 표현식에 박혀있는 베이스 패키지 문자열
  5. application.yamlspring.application.name, logback-spring.xmlLOG_FILE_NAME
  6. PairingApplication, PairingApplicationTests 클래스명
  7. docker-compose.yml의 컨테이너명·POSTGRES_* 값, application.yamlDB_URL 기본값
  8. docs/ai/backend-convention.md의 베이스 패키지 표기

About

페어링 백엔드입니다.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages