코드 저장소.

일정관리 프로젝트 본문

포폴/일정관리앱

일정관리 프로젝트

slown 2025. 10. 26. 16:06

목차

1.프로젝트 목적

2.사용 기술 스택

3.아키텍처

4.ERD

5.주요기능

6.전체 개발 과정 정리

7.소감

 

1.프로젝트 목적

이 프로젝트는 단순 CRUD 일정 관리에서 출발했습니다. 하지만 실제 서비스를 만들다 보면 일정 생성 이후에 훨씬 많은 일이 발생했습니다.

  • 알림 생성
  • 반복 일정 전파
  • 파일 업로드 처리
  • 추천 기능을 위한 외부 API 연동
  • Kafka 기반 비동기 이벤트 처리

이 중 하나라도 실패하면 전체 기능이 연쇄적으로 무너질 수 있었습니다. 그래서 이번 프로젝트의 핵심 목표는 비동기 영역에서 발생하는 장애를 어떻게 격리하고, 어떻게 자동 복구할 수 있을까?였습니다. 이를 검증하기 위해 Kafka Outbox + DLQ + 멱등성(EOS) 구조를 직접 설계하고, Prometheus·Loki·Grafana 기반 모니터링으로 실제 서비스에서 어떤 패턴으로 장애가 흘러가는지 수집해 보았습니다. 결과적으로 이 프로젝트는 일정 관리 앱이라기보다 운영 환경에서의 안정성을 실험하기 위한 실전형 백엔드 아키텍처 설계 프로젝트라고 할 수 있습니다.

2.사용 기술 스택

Backend

  • Java 17, Spring Boot 3.x
    최신 LTS 기반, 비동기 아키텍처 및 WebClient 성능 개선에 적합
  • JPA + QueryDSL
    반복 일정·충돌 감지 등 복잡한 일정 조회 로직을 안정적으로 처리
  • Kafka
    비동기 이벤트 전파 및 DLQ 기반 장애 격리
  • Redis
    세션 관리, 캐싱, 일정 중복 처리 등 고속 데이터 처리가 필요한 기능에 사용
  • WebClient (기존 OpenFeign 전환)
    일정 추천 API 연동, Timeout·Retry·CircuitBreaker 기반 회복력 구성
  • AWS S3 (Presigned URL)
    대용량 파일 업로드 처리, 고아 객체 방지를 위한 파일 이동 구조 적용
  • Flyway
    스키마 버전 관리 및 운영환경 간 이슈 방지

Frontend

  • Next.js 14 + TypeScript
    일정 조회·관리 UI 구현, 서버 컴포넌트 기반 렌더링 적용
  • Tailwind CSS
    빠른 스타일링 및 유지보수 가능한 UI 구성
  • FullCalendar
    월간/주간 일정 시각화 및 사용자 인터랙션 처리

Infra / DevOps

  • AWS Lightsail / RDS / S3
    서비스 배포, DB 운영 및 파일 저장소 구성
  • Docker & Docker Compose
    API 서버·Kafka·Redis·모니터링 스택 컨테이너 기반 통합 운영
  • GitHub Actions
    Build → Test → Docker 이미지 생성 → Lightsail 배포까지 자동화
  • Prometheus + Loki + Promtail + Grafana
    메트릭·로그 수집 및 시각화, DLQ/GC/API 처리량 모니터링

3.아키텍처

3-1. 전체 아키텍처

 

이 프로젝트는 단순 CRUD 기반 웹 서비스가 아니라,운영 환경을 모사한 구조를 직접 구축하고 검증하는 것을 목표로 설계했습니다.

  • 서비스 서버 → Kafka / Redis / Nginx / Spring Boot / Promtail
  • 모니터링 서버 → Prometheus / Loki / Grafana

이렇게 나누면 다음 장점이 생깁니다.

  • 장애가 나도 모니터링 환경이 영향을 받지 않음
  • 부하 테스트 시 로그와 메트릭이 더 정확함
  • 운영 환경 시뮬레이션이 쉬움

카프카 이벤트는 Outbox에서 발행되고, 컨슈머에서 실패하면 DLQ에 저장되며, 스케줄러가 이를 재처리한다. 전체적으로 장애가 나더라도 플랫폼이 멈추지 않도록 설계된 구조다.

 

3-2. 핵심 이벤트 흐름 아키텍처

1. 도메인 트랜잭션 → Outbox 저장

일정 생성/수정 같은 핵심 기능은 Kafka로 직접 전송하지 않고 DB 트랜잭션 안에서 Outbox 테이블에 이벤트를 먼저 저장합니다.

  • 비동기 장애가 본 트랜잭션으로 전파되지 않음
  • 트랜잭션 + 메시징 일관성 확보
  • 전송 실패 시 Outbox 기반 재발행 가능

2. Outbox Publisher → Kafka 전송

ShedLock 스케줄러가 Outbox 데이터를 읽어 Kafka로 발행한다.

  • eventId 기반 멱등 처리 준비
  • 카프카 전송 실패 시 재시도 가능
  • 서비스 로직과 비동기 로직 완전 분리

3. Kafka Consumer 처리 (EOS 멱등 + 알림 전송)

컨슈머는 이벤트를 처리할 때 eventId 중복 처리 여부 확인 (ProcessedEvent) 알림 설정 확인 알림 DB 저장  WebSocket/Push 전송 eventId 덕분에 중복 메시지도 안전하게 차단(EOS) 된다.

 

4. 컨슈머 처리 실패 → DLQ로 격리

예외 발생 시 Kafka가 자동 재시도하면 메시지 흐름이 막힌다.
그래서 DefaultErrorHandler로 DLQ 토픽에 별도 분리한다.

  • 장애 메시지가 메인 토픽을 막지 않음
  • 실패 메시지를 안전하게 저장
  • 나중에 재처리 가능

5. DLQ 재처리 스케줄러 → RetryTopic

FailMessage 테이블에 저장된 메시지를 주기적으로 재전송한다.

  • 5초 → 30초 → 2분 등 단계적 백오프
  • 정상 처리 시 FailMessage 제거
  • 반복 실패 시 최종 Fail 처리

일시적 장애는 자동 복구되는 구조다.

 

6. Grafana로 전체 흐름 모니터링

Prometheus + Loki 기반으로 다음을 추적한다.

  • Outbox 발행량
  • Kafka 처리량
  • DLQ 적재량
  • 재처리 성공률
  • 알림 처리 시간
  • JVM 메모리/GC 패턴

장애가 어디서 발생하고 어떻게 복구되는지를 실시간으로 확인할 수 있다.

 

 

3-3. 헥사고날 아키텍처 구조 

 

이 프로젝트의 백엔드 구조는 헥사고날 아키텍처(Hexagonal Architecture, Ports & Adapters) 를 기반으로 설계했습니다.
일정 관리 서비스는 API 요청, Kafka 기반 비동기 이벤트, 파일 업로드, 외부 AI API 호출 등 다양한 입출력 채널을 가집니다. 이러한 복잡한 흐름을 일관성 있게 처리하기 위해 도메인 로직을 외부 기술 요소(JPA, Kafka, WebClient, S3 등)와 분리한 구조가 필요했습니다.

 

헥사고날 아키텍처를 적용함으로써

  • 도메인은 어떤 기술에도 의존하지 않는 순수한 비즈니스 로직으로 유지된다.
  • API 요청, Kafka 이벤트 등 서로 다른 입력 채널이 동일한 도메인 서비스로 수렴한다.
  • JPA, Kafka Producer, WebClient, S3 등의 외부 기술은 모두 Outbound Adapter로 추상화되어 교체 가능하다.
  • 테스트 시 외부 기술을 Mocking하여 Core 레이어를 독립적으로 검증할 수 있다.

3-4.CI/CD 흐름도

 

 

4.ERD

 

이 프로젝트에서 ERD를 설계할 때 가장 먼저 고려한 기준은 운영 안정성이었습니다. 일정 관리 서비스는 Kafka Outbox, DLQ, 재처리 같은 비동기 흐름이 많고, 장애가 발생하면 메시지가 여러 번 재전송되거나, 특정 이벤트가 다시 처리되는 상황이 흔하게 발생합니다. 이런 환경에서 도메인 간 연관관계가 강하면 재처리 하나 때문에 연쇄적인 조회가 발생하고, 의도하지 않은 부작용이 생길 우려가 있습니다. 그래서 엔티티들은 서로를 참조하지 않고, ID(Long) 값만 들고 있는 약결합 구조로 설계했습니다.

 

데이터 무결성은 JPA에 맡기지 않고 DB의 FK와 제약조건으로 강제함으로써, 도메인 수준에서는 복잡한 의존을 제거하고 운영 단계에서는 데이터 정합성을 보장하는 형태입니다. Kafka 기반 아키텍처에서도 같은 원칙을 적용했습니다.

 

Outbox, FailMessage, ProcessedEvent 같은 이벤트 처리 테이블은 모두 독립된 구조로 설계하여, 특정 기능이 실패하더라도 나머지 시스템에 영향을 주지 않도록 격리했습니다. 덕분에 DLQ 재처리나 EOS 멱등 처리 상황에서도 부작용 없이 안정적으로 복구가 가능했습니다.

 

이런 모델링 방식은

  • N+1 문제를 원천 차단하고
  • 재처리 흐름에서의 사이드 이펙트를 줄이며
  • 운영 환경에서 예측 가능한 동작을 보장합니다.

5.주요기능

  • 일정 CRUD + 반복 일정 
  • 일정 충돌 감지
  • 알림(Kafka 기반) 실시간 전송 + WebSocket 표시
  • 웹푸시 기능 
  • 파일 업로드(Presigned URL + 썸네일 처리 + 고아 객체 제거)
  • 일정 추천(AI API + 캐싱 + 회복력 적용)

6.전체 개발 과정 정리

이 프로젝트는 처음부터 복원력 있는 아키텍처를 목표로 시작한 것이 아니라, 단순한 일정 관리 CRUD 구현에서 출발해 실제 문제를 겪으며 구조를 점진적으로 확장해 나간 과정이었다. 다만 프로젝트 초기부터 헥사고날 아키텍처를 적용해 도메인과 외부 기술을 분리해 두었기 때문에, 기능과 요구사항이 변화하더라도 기존 구조를 크게 훼손하지 않고 확장할 수 있었다.

아래는 실제 개발 순서와 각 단계에서의 문제 인식, 그리고 그에 따른 설계 선택을 정리한 내용이다.

 

6-1. 헥사고날 아키텍처 기반 CRUD 구현

프로젝트 초기에는 헥사고날 아키텍처(Hexagonal Architecture)를 기반으로 도메인 중심의 일정 CRUD 기능을 구현했다.

  • API 요청은 Inbound Adapter
  • 도메인 로직은 Core
  • JPA, 외부 API 호출은 Outbound Adapter로 분리

이 단계의 구조는 아직 단순한 동기식 처리였지만, 도메인 로직이 JPA나 외부 기술에 직접 의존하지 않도록 설계함으로써
이후 구조 변경과 확장을 염두에 둔 기본 틀을 마련했다.

6-2. 기능 확장과 이벤트 처리 필요성 인식

일정 관리 기능이 안정화되면서 다음과 같은 부가 기능이 추가되었다.

  • 일정 알림 생성
  • 파일 업로드 처리
  • 일정 추천을 위한 외부 API 연동

이 기능들이 기존 CRUD 트랜잭션 안에 결합되면서 문제가 발생했다.

  • 알림 실패 → 일정 생성 전체 롤백
  • 외부 API 지연 → 사용자 요청 타임아웃
  • 부가 기능 장애가 핵심 기능까지 전파

이를 해결하기 위해 Spring Domain Event를 도입해 알림과 부가 로직을 핵심 트랜잭션 이후로 분리했다. 트랜잭션 결합 문제는 완화되었지만, 이벤트 실패 시 재처리나 상태 추적이 불가능하다는 한계가 남았다.

6-3. Kafka 도입과 비동기 처리 본격화

비동기 이벤트를 보다 안정적으로 전파하기 위해 Kafka를 도입했다.

  • 알림, 파일 처리, 추천 이벤트를 Kafka 기반으로 분리
  • 비동기 처리와 확장성 확보

그러나 Kafka 도입 이후 새로운 문제가 드러났다.

  • at-least-once 특성으로 인한 중복 처리
  • 컨슈머 재시도 시 데이터 정합성 붕괴
  • DB 트랜잭션과 Kafka 전송 간 불일치

메시지는 전달되었지만, 메시지와 데이터의 일관성은 보장되지 않는 상태였다.

 

6-4. Outbox 패턴 도입

문제의 원인을 Kafka 자체가 아니라 DB 트랜잭션과 메시징이 분리된 구조로 판단했다.

이에 따라 Outbox 패턴을 도입했다.

  • 핵심 도메인 트랜잭션 내 Outbox 테이블에 이벤트 저장
  • 별도 Publisher가 Kafka로 발행
  • 전송 실패 시에도 DB 상태는 보존

이를 통해 트랜잭션과 메시징 간 일관성을 확보하고, 메시지 전송 실패 시 재발행이 가능한 구조를 만들었다.

 

6-5. DLQ 및 재처리 구조 설계

Outbox 도입 이후에도 운영 환경에서는 컨슈머 장애나 네트워크 지연 같은 문제가 발생할 수 있었다.

Kafka의 기본 재시도만으로는 실패 메시지가 메인 토픽을 막거나 무한 재시도가 발생할 수 있었기 때문에, 다음과 같은 DLQ 및 재처리 구조를 설계했다.

  • DefaultErrorHandler 기반 DLQ 토픽 분리
  • 실패 메시지를 FailMessage 테이블에 저장
  • 단계적 Backoff 기반 재처리 스케줄러 구성

이 구조를 통해 실패는 격리되고, 일시적 장애는 자동으로 복구되는 운영 기준을 확립했다.

6-6. Testcontainers 기반 장애 재현 테스트

DLQ와 재처리 구조는 테스트로 검증되지 않으면 운영에서 신뢰할 수 없다고 판단했다.

Kafka, Redis, MySQL을 Testcontainers로 구성해 운영과 동일한 메시징 환경을 테스트 단계에서 재현했다.

  • Outbox → Kafka → Consumer → DLQ → Retry 전 구간 통합 테스트
  • 강제 예외 주입으로 장애 시나리오 재현
  • Awaitility 기반 비동기 흐름 검증

이 단계에서 프로젝트는 단순 기능 구현에서 운영 시나리오를 검증하는 단계로 전환되었다.

6-7. EOS(멱등 처리) 도입 및 지표 기반 검증

마지막으로 남은 문제는 중복 처리였다. Kafka의 exactly-once 옵션 대신,

  • at-least-once 전제를 유지
  • eventId + ProcessedEvent 테이블 기반 멱등 처리 적용

JMeter 부하 테스트와 Grafana 지표를 통해 다음을 검증했다.

  • 메시지 유실 0건
  • 중복 처리율 0%
  • DLQ 재처리 후 최종 정상 처리율 100%

이를 통해 비동기 이벤트 처리 흐름이 구조적으로 안전함을 수치로 검증할 수 있었다.

7.소감

처음 이 프로젝트를 시작할 때는 단순히 내 일정을 정리할 수 있는 웹 서비스를 만들어보고 싶다는 생각뿐이었다. CRUD 중심의 간단한 기능을 구현하면 끝날 줄 알았고, 그 이상을 깊게 고민하지도 않았다. 그러나 기능을 실제로 만들기 시작하면서, 일정 관리라는 영역이 의외로 복잡한 도메인이라는 점을 체감했다. 반복 일정, 충돌 감지, 수정 전파 같은 기능을 어떻게 구조화하느냐에 따라 코드가 유지될 수도, 망가질 수도 있었다. 이 경험이 계기가 되어 객체지향적으로 도메인을 분리하고, 헥사고날 아키텍처 기반으로 프로젝트 구조를 재정비했다.

 

하지만 프로젝트의 진짜 전환점은 비동기 기능이 들어오면서부터였다. 알림 발송, 파일 업로드, 추천 기능처럼 외부 의존성이 많아지고 처리 시간이 예측 불가한 기능들은 생각보다 쉽게 장애를 만들었고, 그 장애는 전체 트랜잭션을 무너뜨렸다. 하나의 작은 실패가 전체 요청을 롤백시키는 상황을 반복해서 겪으면서, 기능보다 “안정성”과 “복원력”을 먼저 설계해야 한다는 사실을 몸으로 배웠다. 이를 해결하기 위해 Outbox 패턴을 도입하며 트랜잭션과 메시징을 분리했고, 장애를 흡수하기 위한 DLQ·Retry 구조, 중복 처리를 막기 위한 멱등 처리(EOS), 시스템 상태를 확인하기 위한 모니터링 환경까지 직접 구축했다.

 

이 과정에서 “왜 장애가 발생하는가”라는 질문을 넘어 “장애를 어떻게 감지하고, 어떻게 격리하며, 어떻게 자동 복구시킬 것인가” 로 사고가 확장되었다. 이 프로젝트는 일정 관리 기능을 만드는 과정이 아니라, 서비스를 운영할 수 있는 백엔드 시스템의 구조적 기준이 무엇인지 스스로 발견해가는 과정이었다. 단순한 CRUD에서 출발한 개인 프로젝트가 Outbox, Kafka, DLQ, EOS, 모니터링까지 포함하는 복원력 중심의 구조로 성장한 이유도 같다. 실제 문제를 겪어보고 해결하는 과정 속에서 “아키텍처는 기능보다 오래 남는다”는 사실을 깨달았다.

 

 이번 개발을 통해 기능 구현보다 더 어려운 것이 장애를 고려하고, 예상하고, 설계하는 역량이라는 것을 배웠다. 그리고 이것이 백엔드 개발자가 실무에서 반드시 갖춰야 하는 핵심이라고 느꼈다. 앞으로도 더 다양한 도메인과 트래픽 환경에서 이런 구조적 안정성을 스스로 설계할 수 있는 개발자로 성장하는 것이 다음 목표다.