현재 진행 상황:
- ✅ Phase 1: Docker 환경 및 MSA 기본 구조
- ✅ Phase 2: Post Service CRUD API 구현
- ✅ Phase 3: CDC 기반 검색 시스템 (Debezium + Kafka + Elasticsearch)
Spring Boot 기반 Microservices Architecture 학습 프로젝트입니다. Gateway를 통한 단일 진입점, Database per Service 패턴, Flyway 스키마 관리, jOOQ 타입 안전 쿼리, TDD 100% 커버리지, Elasticsearch 하이브리드 검색(키워드 + 벡터) 등 실전 MSA 패턴을 적용합니다.
현재 구현 완료:
- ✅ Docker Compose 기반 PostgreSQL 18 환경
- ✅ Spring Cloud Gateway (단일 진입점, 라우팅)
- ✅ Post Service CRUD API (게시글 생성/조회/수정/삭제)
- ✅ Flyway DB 마이그레이션
- ✅ jOOQ 타입 안전 SQL 쿼리
- ✅ 80% 테스트 커버리지 (Repository, Service, Controller)
- PostgreSQL 18: 최신 안정 버전으로 JSON, Full-Text Search, Vector 검색 등 고급 기능 활용 가능
- Docker Compose: 로컬 개발 환경 일관성 보장, 팀원 간 환경 차이 최소화
- Kotlin: Java와의 호환성 + Null Safety + 간결한 문법으로 생산성 향상
- Spring Boot 3.x: 최신 LTS 버전, Native Image 지원 및 성능 최적화
- Spring Cloud Gateway: Netflix Zuul 대비 성능 우수, Reactive 기반 비동기 처리
- JDK 21 LTS: Virtual Threads, Pattern Matching 등 최신 기능 활용
- Flyway: 스키마 버전 관리를 Git으로 추적, 팀원 간 DB 동기화 자동화
- jOOQ: MyBatis 대비 컴파일 타임 타입 체크로 SQL 오류 조기 발견, IDE 자동완성 지원
- Gradle 8.x: Groovy 대비 Kotlin DSL의 타입 안전성, 빌드 캐싱으로 속도 개선
┌─────────────┐
│ Client │
└──────┬──────┘
│
▼
┌─────────────────────┐
│ Gateway (8080) │ ← 단일 진입점
└──────┬──────────────┘
│
├─→ /api/posts/** ─→ ┌──────────────────┐
│ │ Post Service │
│ │ (8081) │
│ └────────┬─────────┘
│ │ CRUD
│ ▼
│ ┌──────────────────┐
│ │ PostgreSQL 18 │
│ │ (15432) │
│ │ Schema: posts │
│ │ WAL: logical │
│ └────────┬─────────┘
│ │ CDC
│ ▼
│ ┌──────────────────┐
│ │ Debezium │ ← pgoutput plugin
│ │ Connect │
│ │ (8083) │
│ └────────┬─────────┘
│ │ JSON events
│ ▼
│ ┌──────────────────┐
│ │ Kafka │ ← posts.posts.posts topic
│ │ (9092/19092) │
│ └────────┬─────────┘
│ │ consume
│ ▼
├─→ /api/search/** ─→ ┌──────────────────┐
│ │ Search Service │ ← @KafkaListener
│ │ (8082) │
│ └────────┬─────────┘
│ │ index
│ ▼
│ ┌──────────────────┐
│ │ Elasticsearch │ ← Nori analyzer
│ │ (9200) │
│ │ Index: posts │
│ └──────────────────┘
설계 원칙:
- Gateway 우선: 모든 요청은 Gateway를 거쳐 서비스로 라우팅
- Database per Service: Post Service는
posts스키마만 접근 (데이터 독립성) - CDC 기반 동기화: PostgreSQL 변경사항을 Debezium이 자동 감지 → Kafka → Search Service
- Eventual Consistency: PostgreSQL 변경 후 ~5초 내 Elasticsearch 동기화 (검증 완료)
| 도구 | 버전 | 확인 명령 |
|---|---|---|
| Docker Desktop | 최신 | docker --version |
| Docker Compose | 최신 | docker-compose --version |
| JDK | 21 LTS | java -version |
| Gradle | 8.x (Wrapper 사용 가능) | gradle --version |
시스템 요구사항:
- 메모리: Docker Desktop에 최소 4GB 할당
- 디스크: 최소 2GB 여유 공간
- 포트: 8080, 8081, 15432 사용 가능 확인 (
lsof -i :8080)
cd moa-space
docker-compose up -d postgres
# 컨테이너 상태 확인
docker-compose ps
# Expected: moa-space-postgres (healthy)cd gateway
./gradlew bootRun
# 다른 터미널에서 health check
curl http://localhost:8080/actuator/health
# Expected: {"status":"UP"}cd post-service
./gradlew bootRun
# Flyway 마이그레이션 자동 실행 확인 (로그)
# "Migrating schema `posts` to version 1"
# "Successfully applied 3 migrations"
# Health check
curl http://localhost:8081/actuator/health
# Expected: {"status":"UP","components":{"db":{"status":"UP"}}}cd post-service
./gradlew generateJooq
# 생성된 파일 확인
ls -la build/generated-src/jooq/main/me/muheun/moaspace/post/jooq/generated/tables/
# Expected: Posts.java, Comments.java# Gateway를 통한 게시글 생성
curl -X POST http://localhost:8080/api/posts \
-H "Content-Type: application/json" \
-d '{"title":"테스트 게시글","content":"내용입니다","authorId":1}'
# Expected: HTTP 201, {"id":1,"title":"테스트 게시글",...}
# 게시글 조회
curl http://localhost:8080/api/posts/1
# Expected: HTTP 200, 게시글 상세 정보
# 게시글 목록
curl "http://localhost:8080/api/posts?page=0&size=10"
# Expected: HTTP 200, 게시글 배열
# 게시글 수정
curl -X PUT http://localhost:8080/api/posts/1 \
-H "Content-Type: application/json" \
-d '{"title":"수정된 제목","content":"수정된 내용"}'
# Expected: HTTP 200, 수정된 게시글
# 게시글 삭제
curl -X DELETE http://localhost:8080/api/posts/1
# Expected: HTTP 204
# 테스트 실행
cd post-service
./gradlew test jacocoTestReport
# Expected: BUILD SUCCESSFUL, 80% coverage# PostgreSQL 접속
docker exec -it moa-space-postgres psql -U moauser -d moaspace
# psql에서
moaspace=# \dn -- 스키마 목록
moaspace=# SET search_path TO posts;
moaspace=# \dt -- 테이블 목록 (posts, comments 확인)
moaspace=# SELECT * FROM posts; -- 게시글 데이터 확인
moaspace=# SELECT * FROM flyway_schema_history; -- 마이그레이션 이력
moaspace=# \qmoa-space/
├── docker-compose.yml # PostgreSQL, Kafka, Debezium, Elasticsearch
├── .env # Credentials (gitignored)
├── init/ # PostgreSQL 초기화 스크립트
├── data/ # PostgreSQL 데이터 (gitignored)
│
├── gateway/ # Spring Cloud Gateway (port 8080)
│ ├── build.gradle.kts
│ └── src/main/
│ ├── kotlin/me/muheun/moaspace/gateway/
│ └── resources/application.yml
│
├── post-service/ # Post Service (port 8081)
│ ├── build.gradle.kts
│ ├── src/main/
│ │ ├── kotlin/me/muheun/moaspace/post/
│ │ │ ├── PostServiceApplication.kt
│ │ │ ├── controller/ # REST API
│ │ │ ├── service/ # 비즈니스 로직
│ │ │ └── repository/ # jOOQ 데이터 접근
│ │ └── resources/
│ │ ├── application.yml
│ │ └── db/migration/ # Flyway
│ └── src/test/
│
└── search-service/ # Search Service (port 8082) ⭐ NEW
├── build.gradle.kts
├── src/main/
│ ├── kotlin/me/muheun/moaspace/search/
│ │ ├── SearchApplication.kt
│ │ ├── config/
│ │ │ └── ElasticsearchConfig.kt # 인덱스 초기화
│ │ ├── model/
│ │ │ └── PostDocument.kt # @Document 엔티티
│ │ └── consumer/
│ │ ├── CdcEventListener.kt # 공용 @KafkaListener
│ │ ├── CdcEventHandler.kt # 엔티티별 핸들러 인터페이스
│ │ └── PostDocumentCdcEventHandler.kt # 게시글 인덱싱 담당
│ └── resources/
│ ├── application.yml # Kafka, Elasticsearch 설정
│ └── elasticsearch/
│ ├── settings/posts-settings.json # Nori 분석기
│ └── mappings/posts-mapping.json
└── src/test/
# 로그 확인
docker-compose logs postgres
# 흔한 원인 1: data/ 디렉토리 권한 문제
sudo chown -R 999:999 data/
# 흔한 원인 2: 포트 충돌
lsof -i :15432
# .env에서 PORT=15433으로 변경 후 재시작# 원인 1: PostgreSQL 아직 준비 안 됨
docker-compose ps # postgres healthy 확인 후 Post Service 재시작
# 원인 2: flyway_schema_history 테이블 손상
docker exec -it moa-space-postgres psql -U moauser -d moaspace
moaspace=# DROP TABLE IF EXISTS posts.flyway_schema_history CASCADE;
# Post Service 재시작# 원인 1: Flyway 마이그레이션 미실행
# → Post Service 먼저 실행하여 Flyway 완료 확인
# 원인 2: posts 스키마 없음
docker exec -it moa-space-postgres psql -U moauser -d moaspace -c "\dn"
# posts 스키마 존재 확인CREATE 시나리오:
1. Post Service → PostgreSQL INSERT
2. Debezium이 WAL 감지 (pgoutput plugin)
3. Kafka에 JSON 이벤트 발행 (posts.posts.posts topic)
4. Search Service @KafkaListener 소비
5. Elasticsearch에 문서 인덱싱 (Nori 분석기 적용)
UPDATE 시나리오:
PostgreSQL UPDATE → Debezium CDC → Kafka → Search Service
→ Elasticsearch 문서 업데이트 (동일 ID)
DELETE 시나리오:
PostgreSQL DELETE → Debezium (tombstone event)
→ Kafka (__deleted: "true") → Search Service
→ Elasticsearch 문서 삭제
| 항목 | 결과 | 상태 |
|---|---|---|
| 동기화 속도 | 100 posts in ≤10s | ✅ PASS |
| 데이터 정합성 | 0% 손실 (100% 일치) | ✅ PASS |
| 레이턴시 | PostgreSQL → Elasticsearch 약 5초 | ✅ PASS |
# 1. PostgreSQL에 데이터 생성
docker exec -i moa-space-postgres psql -U moauser -d moaspace -c \
"INSERT INTO posts.posts (title, content, author_id) VALUES ('테스트', '내용', 1);"
# 2. 5초 대기
sleep 5
# 3. Elasticsearch 확인
curl -s http://localhost:9200/posts/_search?size=1 | jq '.hits.hits[]._source'예상 결과: PostgreSQL 데이터와 동일한 문서가 Elasticsearch에 존재
| 컴포넌트 | 역할 | 포트 |
|---|---|---|
| Debezium Connect | PostgreSQL CDC 캡처 | 8083 |
| Kafka | 이벤트 스트리밍 | 9092 |
| Search Service | Kafka 소비 + Elasticsearch 인덱싱 | 8082 |
| Elasticsearch | 검색 엔진 (Nori 한국어 분석기) | 9200 |
- Phase 4: 한국어 키워드 검색 API 구현 (Search Service REST API)
- Phase 5: Gateway 통합 및 서비스 간 통신 패턴
- Phase 6: Docker Compose 통합 환경 자동화
# Docker
docker-compose up -d # 전체 서비스 시작
docker-compose down # 전체 서비스 중지
docker-compose down -v # 데이터까지 완전 삭제
docker-compose ps # 서비스 상태 확인
# Gradle
./gradlew bootRun # 애플리케이션 실행
./gradlew build # 빌드 (테스트 포함)
./gradlew test # 테스트 실행
./gradlew generateJooq # jOOQ 코드 생성 (Post Service)
# PostgreSQL
docker exec -it moa-space-postgres psql -U moauser -d moaspace # psql 접속
docker exec -i moa-space-postgres psql -U moauser -d moaspace -c \
"SELECT COUNT(*) FROM posts.posts;" # 게시글 수 확인
# Elasticsearch
curl http://localhost:9200/_cluster/health # 클러스터 상태
curl http://localhost:9200/posts/_count # 인덱싱된 문서 수
curl http://localhost:9200/posts/_doc/1 # 특정 문서 조회
# Kafka
docker exec -it moa-space-kafka kafka-topics --bootstrap-server localhost:9092 --list
docker exec -it moa-space-kafka kafka-console-consumer \
--bootstrap-server localhost:9092 --topic posts.posts.posts --from-beginning
# Debezium Connector
curl http://localhost:8083/connectors # 커넥터 목록
curl http://localhost:8083/connectors/moa-space-posts-connector/status # 상태 확인
# 커넥터 설정은 debezium/connectors/*.json 에서 관리
# 새 엔터티를 추가/수정했다면 JSON 작성 후 `docker compose up -d debezium-init`으로 초기화 컨테이너만 재실행하면 자동 반영됩니다
# CDC 파이프라인 검증 (PostgreSQL → Elasticsearch)
docker exec -i moa-space-postgres psql -U moauser -d moaspace -c \
"INSERT INTO posts.posts (title, content, author_id) VALUES ('테스트', '내용', 1);" && \
sleep 5 && \
curl -s http://localhost:9200/posts/_search?size=1 | jq '.hits.hits[]._source'MIT License