Vue 3 + Spring Boot 로 만드는 연습용 이커머스. Claude Code 와 함께 진행한다.
목표 — Vue 를 처음 써보면서, 실제 운영에 가까운 스택(Oracle · Redis · Nginx)을 한 번씩 다 거쳐보는 것. 주제 — 상품 카탈로그 · 장바구니 · 주문 · 리뷰 · 쿠폰 · 적립금 · 관리자 도구를 갖춘 단일 브랜드 커머스.
게시판(공지)에서 출발해 이커머스로 확장했다. 공지는 지금도
notice도메인으로 남아 있다.
문서 지도 — 이 파일은 어떻게 띄우는가만 다룬다.
설계·도메인 경계·인프라 로드맵은 ARCHITECTURE.md,
화면 규칙(토큰·컴포넌트·레퍼런스)은 DESIGN.md,
일하는 방식·사고 기록은 docs/WORKING-AGREEMENTS.md,
기능 후보는 docs/BACKLOG.md, 날짜별 작업 기록은 docs/handoffs/.
| 기술 | 버전 | 용도 |
|---|---|---|
| Vue | 3.3 | UI 프레임워크 (Composition API) |
| Vue Router | 4 | 라우팅 + 인증/권한 가드 |
| rsbuild | 1.7 | 번들러 (dev 서버 · 빌드) |
| DevExtreme | 23.1 | 관리자 화면 DataGrid |
| Tailwind CSS | 4 | 레이아웃 · 스타일 (CSS-first @theme 토큰) |
| Vitest | — | 단위 테스트 |
| pnpm | 10 | 패키지 매니저 |
상태 관리 라이브러리는 쓰지 않는다 —
reactive기반의 얇은 스토어(src/stores/)로 충분하다.
| 기술 | 버전 | 용도 |
|---|---|---|
| Spring Boot | 4.1 | 웹 · DI |
| Java | 25 (Oracle JDK) | 런타임 |
| Gradle | 9.5 (wrapper) | 빌드 |
| Spring Data JPA / Hibernate | 7.x | ORM |
| QueryDSL | 5.1 | 동적 검색 (상품 필터 등) |
| Flyway | — | 스키마 버전 관리 (V1~V33) |
| Spring Security + JJWT | 0.12 | JWT 인증 · 역할 기반 인가 |
| Spring Data Redis (Lettuce) | — | 캐시 · 카운터 · 토큰 저장 |
| springdoc-openapi | 3.0 | Swagger UI |
| uuid-creator | 6.0 | UUIDv7 PK 생성 |
| scrimage | 4.3 | 이미지 파생본(WebP 썸네일) |
| p6spy | 3.9 | dev 프로파일 SQL 로깅 |
| 기술 | 버전 | 용도 |
|---|---|---|
| Oracle Database | 19c | 주 데이터 저장소 |
| Redis | 7.4.7 | 캐시 · 조회수 카운터 · 토큰 |
| Nginx | 1.27.1 | 정적 파일 서빙 · API 리버스 프록시 · TLS 종단 |
Redis 와 Nginx 는 소스 빌드해 systemd 서비스로 등록했다. 서버 설정 원본은
infra/에 있고, 서버는 그 사본이다(scripts/check-infra-drift.sh로 대조).
.
├─ glassvue-backend/ Spring Boot (REST API)
│ └─ src/main/java/com/glassvue/
│ ├─ domain/ 업무 도메인 — MSA 분리 단위
│ │ (member · auth · catalog · cart · order · coupon · point ·
│ │ review · inquiry · notice · notification · restock ·
│ │ wishlist · image · audit)
│ └─ global/ 공통·인프라 (security · config · exception · response ·
│ messaging · policy · querydsl · storage · common)
├─ glassvue-frontend/ Vue 3 SPA
├─ docs/ 상시 문서(WORKING-AGREEMENTS · BACKLOG) + handoffs/
├─ infra/ nginx · systemd 설정 원본
└─ scripts/ 배포 · 드리프트 검사
패키지는 레이어가 아니라 도메인으로 나눈다 — domain/<도메인> 을 폴더째 들어내면 하나의 서비스가
되도록. 자세한 근거는 ARCHITECTURE.md §1.
요청 흐름:
브라우저 → Nginx(:443 TLS) ─┬─ / → 정적 파일 (Vue 빌드 결과)
└─ /api/* → Spring Boot(:8080) ─┬─ Oracle
└─ Redis
:80은:443으로 301 리다이렉트. 내부 VM 이라 인증서는 self-signed(SAN 에 IP 포함).- 개발 중에는 Nginx 를 거치지 않고 rsbuild dev 서버(
:3000)를 쓴다./api는:8080으로 프록시된다. - 알림은 SSE(
/api/notifications/stream) — nginx 에서 이 경로만 버퍼링을 끈다.
- Node.js 24.13.0 (nvm), pnpm 10
- JDK 25
- 접근 가능한 Oracle DB, 로컬 Redis(
127.0.0.1:6379)
.env 파일을 만든다. 커밋하지 않는다(.gitignore 에 포함).
키 목록과 형식은 infra/env.example 을 참고한다 — 값은 비어 있다.
DB_HOST=<oracle 호스트>
DB_PORT=<리스너 포트>
DB_SERVICE=<서비스명 또는 PDB명>
DB_USER=<계정>
DB_PASSWORD=<비밀번호>
JWT_SECRET=<256bit 이상 랜덤값 — openssl rand -base64 48>⚠ 테스트를 돌리기 전에도 반드시 소싱한다. 안 하면 DB 통합 테스트가 조용히 skip 되고
BUILD SUCCESSFUL로 끝난다(실제로 겪은 사고 —docs/WORKING-AGREEMENTS.md §3).
cd glassvue-backend
set -a; . /path/to/.env; set +a # .env를 환경변수로 주입
./gradlew bootRun # http://localhost:8080동작 확인:
curl http://localhost:8080/actuator/health
# {"status":"UP"} → Oracle · Redis 연결까지 정상API 문서: http://localhost:8080/swagger-ui.html
cd glassvue-frontend
pnpm install
pnpm dev # http://localhost:3000⚠ 운영(:8080)은 메일을 보내지 않는다 — application.yml 에 spring.mail 키가 없어
JavaMailSender 빈이 아예 없다. 메일이 나가는 건 dev 프로파일뿐이다.
# 1) 메일 캐처 확인(부팅 시 자동 기동 — 꺼져 있으면 systemctl start mailpit)
systemctl is-active mailpit && curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8025/
# 2) dev 백엔드를 운영과 겹치지 않는 포트로 (운영은 그대로 둔다)
cd glassvue-backend
set -a; . /path/to/.env; set +a
./gradlew bootRun --args="--server.port=8084 --spring.profiles.active=dev"
# 3) 프론트 dev 서버를 그 백엔드로 붙인다
cd glassvue-frontend
API_TARGET=http://127.0.0.1:8084 pnpm dev # http://localhost:3000그 뒤 브라우저에서 http://localhost:3000 으로 흐름을 밟고, 나간 메일은
http://127.0.0.1:8025(Mailpit 웹 UI)에서 본다.
⚠ 서버 밖(개발 PC)에서 볼 때는 Mailpit 이 루프백에만 바인딩돼 있어 바로 못 연다. SSH 로 끌어온다:
ssh -L 8025:127.0.0.1:8025 -L 3000:127.0.0.1:3000 -p 2222 ecstel@127.0.0.1이후 개발 PC 브라우저에서
http://localhost:3000(화면)·http://localhost:8025(메일함).⚠ 서버 IP 로 접속하지 않는다(2026-07-30) — VM 이 NAT 라 호스트에서 포트포워딩으로 들어간다 (
127.0.0.1:2222→ VM:22). 운영 화면도 같은 이유로https://localhost/(443 포워딩)다. IP 는 두 번 바뀌었고(.36→.14→ NAT), 그때마다 IP 를 적어 둔 문서가 전부 낡았다.
# 백엔드 — .env 소싱 후에 돌린다(위 경고 참고)
cd glassvue-backend && ./gradlew test
# 프론트
cd glassvue-frontend && pnpm test# 백엔드 → 실행 가능한 jar
cd glassvue-backend && ./gradlew bootJar
# 프론트 → 정적 파일 (Nginx가 서빙)
cd glassvue-frontend && pnpm buildddl-auto=validate+ Flyway — 스키마는src/main/resources/db/migration/의 버전 스크립트가 소유한다. 엔티티를 고치면 마이그레이션이 세트로 따라온다(안 쓰면 앱이 아예 안 뜬다). 절차·주의사항은 그 디렉터리의README.md.- PK 는 UUIDv7 + Oracle
RAW(16)— 앱에서 생성한다(시간순 정렬 가능). 시퀀스·IDENTITY 를 쓰지 않는다. - Redis 용도 — 목록 캐시(
products:list·notices:list), 조회수 카운터(notice:view:), JWT refresh 토큰(auth:refresh:)·블랙리스트(auth:blacklist:)·비밀번호 재설정 토큰(auth:reset:). ⚠allkeys-lru라 메모리가 차면 오래된 키부터 삭제된다 — 지워지면 안 되는 데이터는 넣지 않는다. - 인증은 JWT (세션 아님) — access 30분 / refresh 14일, 로그아웃 시 access 를 블랙리스트에 올린다.
- 비밀값 — 코드나
application.yml에 직접 쓰지 않고.env환경변수로만 주입한다.
커머스 한 바퀴가 돌아간다 — 상품(옵션·재고) → 장바구니 → 주문 → 배송 추적 → 반품.
| 갈래 | 내용 |
|---|---|
| 카탈로그 | 카테고리 · 상품 옵션(variant)별 재고 · 이미지(WebP 파생본) · 정가/할인가 · 검색·필터·정렬 |
| 주문 | 장바구니 · 주문서 · 배송지 주소록 · 배송비 정책 · 운송장 추적 · 반품(적립금 환불) |
| 혜택 | 쿠폰(정액/정률) · 적립금 · 회원 등급(누적 구매액 기준 적립률) |
| 참여 | 리뷰(별점·포토) · 상품 문의 · 위시리스트 · 재입고 알림 |
| 알림 | 인앱 알림 SSE(벨·토스트) + 타입별 on/off |
| 계정 | 가입·로그인(JWT) · 비밀번호 변경/재설정 · 이메일 · 탈퇴 |
| 관리자 | 상품·카테고리·주문·회원·쿠폰 관리, 매출 통계, 감사 로그(최상위 관리자 전용) |
권한은 USER / ADMIN / SUPER_ADMIN 3계층이다.
아직 가짜인 것: 결제(
pay는 상태 전이 플레이스홀더 — PG 미연동), 메일·SMS 발송(채널 없음), 배송 추적(목업 화면). 각각의 도입 조건은docs/BACKLOG.mdD 절에 있다.
다음에 무엇을 만들지는 docs/BACKLOG.md 가 소유한다(완료 항목도 근거와 함께 거기 남는다).
인프라·관측 도구의 도입 시점은 ARCHITECTURE.md §6.