nestly 백엔드 — 이사 전 가구 배치를 미리 확인하는 인테리어 시뮬레이터의 API 서버
평면도 이미지 + 보유 가구 + 자연어 요구를 받아, AI가 배치안을 제안하고 코드가 제약(충돌·무타공·치수)을 검증해 실현 가능한 안만 남깁니다. 프론트엔드는 nestly-web(React 19).
Spring Boot 3.5 · Java 21 · PostgreSQL 16 · Flyway · Spring Security(OAuth2) · WebFlux
flowchart LR
A[평면도 이미지] -->|VLM 인식| B[방·문·창 구조]
F[보유 가구 + 자연어 요구] --> G[Generator: 배치안 N개 제안]
B --> G
G -->|후보 좌표 JSON| V[Verifier]
V -->|위반 재현 시 피드백| G
V -->|통과안만| R[사용자에게 표시]
subgraph det [결정론 코어]
V
end
- 판정은 LLM을 신뢰하지 않는다. Verifier는 LLM 응답의 "합격/불합격" 문구를 무시하고, 코드가 위반 3종(
collisions충돌 /no_drilling_violations무타공 /size_mismatches치수)을 직접 세어 상태를 확정합니다 — 모두 0이면passed, 하나라도 있으면fallback. LLM이 모순된 판정을 내도 일관성이 코드로 보장됩니다. - AI 백엔드는 교체 가능하다. VLM·Generator·Verifier·이미지 생성·SMS를 모두 인터페이스로 추상화했습니다. 평면도 인식(VLM)은 NVIDIA 호스팅 모델을 쓰고 실패 시 로컬 7B로 폴백, Verifier/Generator는 로컬 Ollama — 품질·비용에 따라 구현체만 갈아끼웁니다.
- 실패 정책을 층별로 다르게. 검증 실패 = 결과 미저장(검증의 본질이 깨졌으므로) / 가구 사진 정규화 실패 = 사진은 저장하고 치수만 비움(보조 기능이므로).
엔티티 11개 — Space(공간·평면도) · Furniture/FurnitureItem/FurnitureCatalog(가구) · Intent(요구·제약) · Layout(배치안 + 검증 로그) · Placement(편집기 배치, 공간당 1행) · GalleryLayout(공개 갤러리) · User/Favorite/Consent.
- 유연한 결과(인식 초안·검증 로그·배치 스냅샷)는 JSONB(
@JdbcTypeCode(SqlTypes.JSON)), PostgreSQL enum은PostgreSQLEnumJdbcType로 매핑. - 스키마는 Flyway 마이그레이션 24개(V1~V24) 가 단일 출처. forward-only(ADD COLUMN/CREATE TABLE), 운영은
ddl-auto: none/ 테스트는validate로 코드-스키마 정합을 강제.
판정 위치가 두 종류로 갈립니다 — 위저드 배치안 검증(passed/fallback)은 위처럼 백엔드가 확정하고, 편집기의 초록/노랑/빨강 판정은 프론트엔드가 기하로 계산합니다(백엔드는 좌표만 보관, judgment_snapshot은 표시용 선택 저장).
도메인별 REST 컨트롤러. /api/me/**와 갤러리 쓰기는 인증 필수, 그 외 permitAll.
| 영역 | 대표 엔드포인트 |
|---|---|
| 인증·계정 | POST /api/auth/{register,login}, POST /api/auth/phone/{send,verify}(SMS OTP), OAuth2(카카오·구글), GET/PATCH/DELETE /api/me |
| 공간·인식 | POST /api/spaces, POST /api/spaces/{id}/recognize(VLM), PUT /api/spaces/{id}/intent, POST/GET /api/spaces/{id}/layouts(비동기 생성·폴링) |
| 가구 | POST /api/spaces/{id}/furniture, .../furniture/photo(사진 인식), /api/me/furniture(라이브러리 CRUD), /api/furniture-catalog |
| 내 공간 | POST /api/me/spaces/{id}/claim(익명 공간 귀속), PUT .../base·.../placement·PATCH .../calibration |
| 갤러리 | GET /api/gallery(필터), POST/PATCH/DELETE /api/gallery/{id}, .../favorite(찜) |
- 테스트 445개 — 통합 38개(
@SpringBootTest+ Testcontainers PG16 + Flyway 전체 적용) · JPA 슬라이스 10개(@DataJpaTest+ 실 PG로 enum·ON DELETE CASCADE검증) · 서비스/DTO 단위. - Testcontainers singleton 패턴 — static 블록에서 컨테이너를 1회만 띄우고 JVM 수명 공유(
withReuse(true)). 클래스마다 재시작해 포트가 바뀌며 나던ConnectExceptionflaky를 근본 제거했습니다. - N+1 회귀를 테스트로 차단 — Hibernate
generate_statistics로 발행 SQL 횟수를 단언해, 연관 로딩이 N+1로 퇴화하면 테스트가 깨지게 했습니다. - 0원 운영 제약 설계 — 유료 가구 DB·알림톡 없이 운영자 큐레이션 카탈로그 + 개인 발신번호 SMS OTP(dev/test는 고정코드 Stub,
@ConditionalOnProperty)로 대체.
# 1. DB (PostgreSQL 16)
docker compose up -d nestly-db
# 2. Backend
./gradlew bootRun # → http://localhost:8081
./gradlew test # 통합·단위 (Testcontainers, macOS는 Colima)프로파일 — dev(로컬 Postgres) / prod(접속정보 환경변수) / secret(AI·OAuth 키; application-secret.yml은 .gitignore, 미설정 시 더미 폴백으로 기동).