기술

Spring Boot(BFF: Backend For Frontend)가 도메인 데이터를 소유하면 왜 문제일까?

2026.04.208분 읽기

Spring Boot가 너무 많은 걸 하고 있었습니다

시스템 아키텍처는 3-tier로 나눠져 있거든요. Next.js(프론트엔드) → Spring Boot → AI Server(FastAPI). Spring Boot가 프론트엔드와 AI Server 사이에서 중간 역할을 하는 구조입니다.

그런데 운영하다 보니까 Spring Boot가 중간 역할을 넘어서 도메인 데이터까지 소유하고 있었어요. 문서 업로드 상태, 온톨로지 진화 이력, 업로드 버전 관리 같은 것들이 전부 Spring Boot의 MySQL에 들어있었습니다. AI Server는 Neo4j와 Qdrant를 갖고 있고요.

처음에는 자연스러웠거든요. Spring Boot가 파일을 받아서 저장하고, AI Server에 경로를 넘겨서 처리시키고, 결과를 다시 받아서 MySQL에 저장하는 흐름이었으니까요. 그런데 시스템이 커지면서 이 구조에서 문제가 터지기 시작했습니다.


세 가지가 동시에 불편해졌습니다

콜백이 꼬입니다

AI Server가 문서를 임포트하면서 미매핑 타입을 감지하면, 그 결과를 Spring Boot에 HTTP 콜백으로 보내서 MySQL에 저장시킵니다. 재임포트할 때 기존 데이터를 삭제하는 로직도 Spring Boot와 AI Server 두 곳에 나눠져 있어요.

AI Server가 "이 문서의 엔티티를 지워야 해"라고 판단하면 Neo4j에서는 직접 지우는데, MySQL의 온톨로지 진화 이력은 Spring Boot에 콜백을 보내서 지워달라고 해야 합니다. 삭제 로직이 두 서비스에 분산돼 있으니까, 타이밍이 어긋나면 한쪽만 지워지는 상황이 생기거든요.

한쪽만 재시작하면 반만 동작합니다

Spring Boot를 재시작하면 AI Server에서 보내는 콜백을 못 받습니다. AI Server는 임포트를 정상 완료했는데, 결과 저장이 안 된 거예요. 반대로 AI Server만 재시작하면 Spring Boot에서 보낸 파이프라인 요청이 실패합니다.

3-tier 아키텍처에서 각 서비스가 독립적으로 재시작 가능해야 하는데, 도메인 로직이 두 곳에 걸쳐 있으니까 한쪽을 내리면 다른 쪽도 반쪽짜리가 되는 거예요.

파일 경로가 로컬에 묶여 있습니다

Spring Boot가 파일을 로컬에 저장하고, 그 절대 경로를 AI Server에 전달합니다. 두 서비스가 같은 서버에서 같은 파일시스템을 보고 있어야만 동작하는 구조예요. 나중에 S3 같은 클라우드 스토리지로 전환하려면 이 부분을 전부 뜯어고쳐야 합니다.

1.jpeg


원칙은 단순합니다 — 도메인 데이터는 도메인 서비스가 소유합니다

빅테크에서 마이크로서비스를 설계할 때 기본 원칙이 있거든요. "Each microservice must own its domain data and logic", 그리고 "BFF is a translation layer — should not own domain data". BFF는 프론트엔드와 백엔드 사이의 번역 계층이지, 도메인 데이터를 갖는 곳이 아니라는 거예요.

이 원칙을 적용하면 역할이 명확해집니다.

Spring Boot (BFF)

AI Server (Domain Service)

역할

프론트엔드 서빙 + API 프록시 + 조회/표시

파이프라인 전체 소유

데이터

북마크, 대화 이력, 사용자 설정 (UI 도메인)

문서 상태, 온톨로지 진화, Neo4j, Qdrant, 파일

로직

프론트엔드에 맞게 API 변환/조합

임포트, 추출, 거버넌스, 검색

Spring Boot가 소유하는 건 UI 도메인 데이터뿐이에요. 채팅 대화 이력, 북마크, 사용자 설정처럼 프론트엔드와 직접 관련된 것들만요. 문서 임포트 상태나 온톨로지 진화 이력 같은 파이프라인 도메인 데이터는 전부 AI Server로 이관합니다.


Phase별로 옮깁니다

한 번에 다 옮기면 위험하니까 3단계로 나눴어요.

Phase 1: 파일 업로드 + 문서 상태 이관

가장 먼저 파일 업로드를 바꿉니다. 지금은 Spring Boot가 파일을 받아서 로컬에 저장한 뒤 경로를 전달하는데, 이걸 Spring Boot가 받은 파일을 multipart로 AI Server에 그대로 넘기는 방식으로 바꿉니다. AI Server가 직접 파일을 저장하고, 문서 상태(uploaded/processing/completed)도 자체 DB에서 관리하는 거예요.

[현재] Frontend → Spring Boot 저장 → 로컬경로 전달 → AI Server 읽기
[변경] Frontend → Spring Boot → multipart 전달 → AI Server 저장+처리

이러면 파일 관리가 AI Server 한 곳으로 모이고, 로컬 경로 의존도 사라집니다. 나중에 S3로 전환할 때도 AI Server만 수정하면 돼요.

Phase 2: 온톨로지 진화 이관

콜백으로 꼬이던 부분을 해결하는 단계입니다. AI Server가 미매핑 타입을 감지하면 직접 자체 DB에 저장하고, 재임포트 시 삭제도 AI Server 한 곳에서 처리합니다. Spring Boot의 콜백 메커니즘이 완전히 사라지거든요.

항목

현재

변경

미매핑 시그널 저장

AI Server → 콜백 → Spring Boot MySQL

AI Server 자체 DB

재임포트 삭제

Spring Boot + AI Server 분산

AI Server 한 곳

집계/조회 API

Spring Boot

AI Server 직접 제공

Phase 3: Spring Boot를 순수 BFF로

Phase 1, 2가 끝나면 Spring Boot에 남는 건 프론트엔드 서빙, API 프록시, 인증/권한, UI 도메인 데이터뿐입니다. 도메인 로직이 깔끔하게 AI Server로 모이고, Spring Boot는 번역 계층 역할만 하게 됩니다.

2.jpeg


왜 처음부터 이렇게 안 했을까

돌이켜보면 자연스러운 과정이었거든요. 프로젝트 초기에는 Spring Boot가 메인 서버였고, AI Server는 LLM 호출용 보조 서비스였습니다. 파일 업로드도 Spring Boot가 받는 게 당연했고, 상태 관리도 MySQL에 넣는 게 자연스러웠어요.

그런데 AI Server에 파이프라인이 쌓이면서 역할이 역전됐습니다. 엔티티 추출, 온톨로지 거버넌스, 커뮤니티 탐지, RAG 검색... 도메인 로직의 90%가 AI Server에 있는데, 데이터 일부가 Spring Boot에 남아있으니까 콜백으로 주고받는 구조가 된 거예요.

아키텍처 결정은 서비스가 작을 때가 아니라, 역할이 바뀌는 시점에 재검토해야 하는 것이었습니다. 처음에 맞았던 구조가 나중에 안티패턴이 되는 건 흔한 일이고, 중요한 건 그 시점을 인식하고 Phase별로 전환하는 거였습니다.

#마이크로서비스#BFF#SpringBoot#FastAPI#아키텍처#역할분리#AI전환기