Spring 개발일지

[팀프로젝트] MSA 기반 티켓팅 프로그램 - 프로젝트 구조 설계 & 개발 계획

김둘리 2026. 4. 28. 09:19

개발 환경 세팅이 끝나고 본격적인 구현 전에 프로젝트 구조를 잡았다. 패키지 구조, 공통 모듈 연동 방식, 개발 계획까지 정리해보려 한다.


1. 패키지 구조

팀 공통 패키지 구조 템플릿을 기반으로 결제 도메인에 맞게 구성했다.

com/firstticket/payment/
│
├── domain/
│   ├── Payment.java                        ← JPA Entity + Aggregate Root
│   ├── PaymentHistory.java                 ← JPA Entity
│   ├── PaymentStatus.java                  ← Enum
│   ├── PaymentRepository.java              ← interface
│   │
│   ├── service/
│   │   └── dto/
│   │       ├── TossConfirmResult.java      ← 토스 승인 결과 VO
│   │       └── TossCancelResult.java       ← 토스 취소 결과 VO
│   │
│   ├── event/
│   │   ├── PaymentEvents.java              ← interface
│   │   ├── PaymentCompletedEvent.java      ← DomainEvent
│   │   └── PaymentRequestedEvent.java      ← DomainEvent
│   │
│   ├── exception/
│   │   └── PaymentErrorCode.java          ← ErrorCode 구현 enum
│   │
│   └── query/
│       ├── PaymentSearchSpec.java
│       ├── PaymentSummaryData.java
│       └── PaymentQueryRepository.java
│
├── application/
│   ├── PaymentCommandService.java          ← 결제 생성, 승인, 환불
│   ├── PaymentQueryService.java            ← 결제 조회
│   │
│   └── dto/
│       ├── command/
│       │   ├── CreatePaymentCommand.java
│       │   ├── ConfirmPaymentCommand.java
│       │   └── RefundPaymentCommand.java
│       ├── query/
│       │   └── PaymentSearchQuery.java
│       └── result/
│           ├── PaymentResult.java
│           └── PaymentSummaryResult.java
│
├── infrastructure/
│   ├── persistence/
│   │   ├── PaymentJpaRepository.java
│   │   ├── PaymentRepositoryImpl.java
│   │   ├── PaymentHistoryJpaRepository.java
│   │   └── PaymentQueryRepositoryImpl.java
│   │
│   ├── event/
│   │   └── PaymentEventsImpl.java
│   │
│   ├── messaging/
│   │   └── PaymentKafkaConsumer.java
│   │
│   ├── external/
│   │   ├── TossPaymentsAdapter.java        ← ACL 구현체
│   │   └── dto/
│   │       ├── TossConfirmRequest.java
│   │       ├── TossConfirmResponse.java
│   │       ├── TossCancelRequest.java
│   │       └── TossCancelResponse.java
│   │
│   └── outbox/
│       ├── OutboxEvent.java
│       ├── OutboxJpaRepository.java
│       └── OutboxEventPublisher.java       ← 스케줄러
│
└── presentation/
    ├── PaymentController.java
    ├── PaymentInternalController.java      ← 내부 API (/internal)
    ├── PaymentSuccessCode.java
    │
    └── dto/
        ├── request/
        │   ├── PaymentCreateRequest.java
        │   ├── PaymentConfirmRequest.java
        │   └── PaymentRefundRequest.java
        └── response/
            ├── PaymentResponse.java
            └── PaymentSummaryResponse.java

2. 각 레이어 역할

domain 순수한 비즈니스 규칙이 있는 레이어다. 외부 프레임워크에 의존하지 않는다. Aggregate Root, Entity, VO, Enum, Repository 인터페이스가 여기 있다.

application 비즈니스 로직을 처리하는 레이어다. Service가 도메인 객체를 조합해서 유스케이스를 실행한다. Command/Query를 분리해서 읽기와 쓰기 책임을 나눴다.

infrastructure 외부 기술과의 연동을 담당하는 레이어다. JPA 구현체, Kafka 발행/구독, 토스 ACL, 아웃박스 패턴이 여기 있다.

presentation 클라이언트 요청을 받는 레이어다. PaymentInternalController는 Booking 서비스에서만 호출하는 내부 API 전용 컨트롤러로 /internal/** 경로로 분리해서 외부 접근을 막는다.


3. DTO와 VO는 record로

Java 16+에서 추가된 record 키워드를 DTO랑 VO에 적용한다.

기존 방식

java
public class PaymentCreateRequest {
    private final UUID bookingId;
    private final Integer finalAmount;

    public PaymentCreateRequest(UUID bookingId, Integer finalAmount) {
        this.bookingId = bookingId;
        this.finalAmount = finalAmount;
    }

    public UUID getBookingId() { return bookingId; }
    public Integer getFinalAmount() { return finalAmount; }
    // equals, hashCode, toString 직접 작성...
}

record 적용 후

java
public record PaymentCreateRequest(
    UUID bookingId,
    Integer finalAmount
) {}

생성자, getter, equals, hashCode, toString이 자동으로 생성된다. DTO랑 VO는 데이터를 담아서 전달하는 역할이라 불변이어야 하고 setter가 필요 없어서 record가 딱 맞다.

단, JPA Entity는 record를 쓰면 안 된다. JPA는 기본 생성자랑 setter가 필요한데 record는 불변이라 맞지 않는다.


4. 공통 모듈 연동

공통 모듈은 별도 레포에서 관리하고 외부 Maven 저장소에 배포해서 각 서비스가 dependency로 가져다 쓰는 방식이다.

공통 모듈에서 제공하는 것들

  • 기본 예외처리
  • BaseEntity (JPA Auditing)
  • 기본 응답형식 (ApiResponse)
  • Kafka 이벤트 설계
  • Docker Compose

build.gradle에 추가

 
 
groovy
repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation 'com.github.first-ticket:common-module:버전태그'
}

공통 모듈 배포 전까지는 각 서비스에서 임시로 구현하다가 배포되면 교체한다. 설정값(yml)만 교체하는 거라 공수가 크지 않다.


5. 개발 계획 (4/22 ~ 5/4)

MVP 개발 기간은 4/22부터 5/4까지고 주말은 작업이 어렵다. 평일 기준 10일이다.

PR브랜치포함 이슈일정
도메인 엔티티 구현 feat/1-domain-setup Payment, PaymentHistory, PaymentStatus, PaymentRepository 4/22~4/23
결제 생성 API feat/5-payment-create Mock 환경 세팅, 결제 생성 API 4/24
토스 ACL + 결제 승인 API feat/7-payment-confirm 토스 ACL, 결제 승인 API 4/25
아웃박스 패턴 feat/9-outbox 아웃박스 패턴 구현 4/28
Kafka 연동 feat/10-kafka Kafka 발행/구독 연동 4/29
환불 + 조회 API feat/11-refund-query 환불 API, 결제 조회 API 4/30
나머지 API + 예외처리 feat/13-api-exception 어드민 API, 예외처리 5/1
통합 테스트 + Booking 연동 feat/15-integration 통합 테스트, Booking 연동 5/2
버퍼 + 마무리 - 미완성 기능, 리팩토링 5/4

Mock 전략

Booking 서비스가 준비되기 전까지 Mock으로 단독 개발한다. Booking 서비스 준비되면 Mock → 실제 연동으로 교체한다.

java
// 개발 중 테스트용
POST /internal/v1/payments
{
  "bookingId": "UUID",
  "userId": "UUID",
  "finalAmount": 50000
}

 

패키지 구조를 미리 다 만들어놓지 않고 기능 구현하면서 필요한 패키지만 그때그때 만드는 방식으로 진행하기로 했다. 미리 다 만들어놓으면 빈 폴더만 잔뜩 생기고 나중에 안 쓰는 폴더 정리하는 게 더 번거롭기 때문이다.

다음 편부터는 실제 코드 구현을 다룰 예정이다.