← 전체 프로젝트

01. 한 줄로 보는 차곡노트

차곡노트

반려동물 미용 매장이 예약 · 고객 · 미용 기록을 관리하고, 보호자에게 서명된 링크로 미용 알림장을 보내는 멀티테넌트 플랫폼

기간
2026-09-20 ~ 진행 중
팀 · 역할
1명 · 1인 개발 (설계 · 백엔드 · Flutter 앱 · 웹 · 인프라)
  • Python 3.13
  • FastAPI
  • SQLAlchemy 2 · Alembic
  • PostgreSQL
  • Flutter
  • Next.js 16
  • MinIO · S3
  • Caddy
  • Docker Compose
차곡노트 대표 화면

↓ 스크롤 또는 ← → 키로 넘기기

02. 설계 원칙 - AI가 대량으로 짜도 구조가 무너지지 않게

혼자서 10일 동안 커밋 399개를 만들었고, 그중 389개를 AI 코딩 어시스턴트와 함께 썼습니다. 속도보다 경계를 먼저 고정했습니다 — 설계 문서, 구현 계획, 참조 브랜치 검증, 그리고 기계가 검사하는 계약.

문제
AI가 코드를 대량으로 생성하면 계층과 모듈 경계가 가장 먼저 무너진다 — 1인 개발이라 리뷰해 줄 사람도 없다
선택
설계 문서 → 구현 계획 → ref 브랜치에서 먼저 검증 → 적용. 헥사고날 + 허브-스포크를 import-linter 계약 6종으로 강제
대가
유스케이스마다 파일 8개(수직 슬라이스), 스포크를 넘는 외래 키도 걸 수 없다
효과
백엔드 단위 테스트 1,294 · 통합 405 (알림장 기능 완료 시점)
AI 협업 흐름과 구조 계약 (도식)
AI 협업 흐름과 구조 계약 (도식)

왜 이 선택인가 — 버린 대안

  • 허브에 레지스트리를 두는 조립
    허브가 상태를 갖게 되고 요청 세션을 넘기기 어렵다
  • dependencies 모듈을 규칙 예외로
    결합이 예외 뒤로 숨는다

AI와 일할 때 사람이 하는 일

AI는 빠르게 많이 쓰지만, 무엇이 금지인지는 모릅니다. 그래서 사람은 코드를 쓰는 대신 경계(설계 문서, 계약, 테스트)를 먼저 정하고, AI가 그 경계 안에서만 움직이게 만듭니다. 경계를 넘으면 검사가 실패합니다.

근거: backend/.importlinter — 계약 6종 (비공개 저장소) · _docs/decisions/002-hub-port-assembly.md

03. 멀티테넌시 - 매 요청마다 소속을 다시 확인

한 DB를 여러 매장이 함께 씁니다. 가드 하나만 빠뜨려도 다른 매장 데이터가 새기 때문에, 확인을 라우터 가드로 옮기고 누락은 구조 테스트가 잡게 했습니다.

문제
매장 범위 API가 늘어날수록 권한 확인을 빠뜨릴 위험 — 한 번 새면 다른 매장의 고객 정보가 노출된다
선택
모든 매장 범위 경로는 가드로 ShopContext를 받고, 저장소는 그것을 첫 인자로만 받는다. 매 요청 DB 재조회, 토큰에 역할 미포함. 인증은 별도 프로세스(RS256 개인키 격리)
대가
매장 범위 요청 하나에 조회 3번 — 캐시는 넣지 않았다
효과
라우트 의존성 트리를 훑는 테스트가 가드 누락을 자동으로 잡는다 (손으로 관리하는 목록 없음)
라우터 가드 · 구조 테스트 · 인증 분리 (도식)
라우터 가드 · 구조 테스트 · 인증 분리 (도식)

왜 이 선택인가 — 버린 대안

  • 인터랙터 첫 줄에서 권한 확인
    첫 줄을 빠뜨리면 그대로 열린다
  • 가드 대상 경로를 목록으로 관리
    새 경로를 목록에 추가하는 걸 잊어도 테스트가 통과한다

멀티테넌시란?

한 건물(DB)에 여러 가게가 세 들어 사는 구조입니다. 문 앞 경비원이 요청마다 "이 사람이 정말 이 가게 직원인가"를 명부로 확인하고, 경비원이 없는 문이 하나라도 생기면 자동 점검이 경보를 울립니다.

근거: backend/core/shop_guard.py (비공개 저장소) · backend/tests/route_inspection.py

04. 알림장 서명 링크 - 로그인 없이, 새지 않게

보호자는 앱을 깔지 않습니다. 링크 하나로 미용 기록을 보여 주되, DB가 유출되거나 접속 기록이 남거나 추측 공격을 받아도 열리지 않게 설계했습니다.

문제
로그인 없는 사용자에게 기록을 보여 줘야 한다 — DB 유출 · 접속 기록 · 추측 공격 · 내부 메모 누설을 모두 막아야 한다
선택
토큰 = public_id + HMAC 서명(43자). DB에는 번호표만, 토큰은 헤더로만 받고, 서명이 틀린 요청만 차단. 공개 응답은 칸을 하나씩 명시
대가
서명 키를 바꾸면 모든 링크가 한꺼번에 닫힌다
효과
DB만으로는 링크를 만들 수 없다. 웹 e2e 11개 통과 (6.6초)
토큰 구조와 세 가지 방어 (도식)
토큰 구조와 세 가지 방어 (도식)

왜 이 선택인가 — 버린 대안

  • 토큰을 URL 경로에
    서버 · 프록시 접속 기록에 그대로 남는다
  • 모든 요청에 실패 횟수 제한
    익명 요청 20번으로 모든 보호자를 막을 수 있다

HMAC 서명 링크란?

링크 주소 자체가 "번호표 + 위조 방지 도장"입니다. 서버는 번호표만 기억하고, 도장은 비밀 열쇠로 그때그때 다시 찍어 맞춰 봅니다. 장부(DB)를 훔쳐 가도 도장을 찍을 수 없으니 링크를 만들 수 없습니다.

근거: _docs/decisions/008-report.md (비공개 저장소) · backend/apps/visit/domain/services/report_token.py

05. 사진 · 영상은 서버를 거치지 않는다

긴 변 3000px 사진과 최대 200MB 영상을 올려야 했습니다. 서버는 짧게 유효한 업로드 출입증만 발급하고, 앱이 저장소에 직접 올리게 했습니다.

문제
대용량 미디어가 API 서버를 거치면 메모리 · 대역폭 · 타임아웃이 모두 문제가 된다
선택
presigned PUT 직접 업로드, 원본 · 표시용 · 썸네일 3종을 앱이 생성. 저장소는 포트 뒤에 두어 개발 MinIO ↔ 운영 S3 교체
대가
캐시 적중을 위해 서명 URL 만료를 하루 단위로 반올림 — 최대 48시간 유출 창을 감수
효과
짧은 영상 압축 12~115ms (앱 측정)
업로드 → 첨부 → 방문 상세
업로드 → 첨부 → 방문 상세

왜 이 선택인가 — 버린 대안

  • 서버 경유 업로드
    200MB 영상이 API 서버의 메모리와 연결을 붙잡는다
  • 방문 모듈 안에 미디어 표
    알림장 · 기록이 함께 쓰는 자원이라 별도 모듈로 분리

presigned URL이란?

짐을 사무실로 보냈다가 창고로 옮기지 않습니다. 사무실이 "이 칸에 15분 안에만 넣을 수 있는" 출입증을 발급하고, 앱이 창고에 직접 넣습니다.

근거: docs/superpowers/specs/2026-09-24-record-media-design.md (비공개 저장소)

06. 고객 검색 - 번호 뒷자리 · 이름 · 초성을 한 칸에서

미용실 사장님은 검색창 하나에 전화 뒷자리도, 보호자 이름도, 반려동물 이름도, 초성(ㄱㅁㅈ)도 칩니다. 검색용 한 줄을 따로 만들어 색인을 걸었습니다.

문제
원본 표를 조인하며 계산하면 색인을 쓸 수 없어 고객이 늘수록 느려진다
선택
customer_search_entries 한 줄에 검색 문자열을 역정규화하고 pg_trgm GIN 색인. 원본이 바뀌면 같은 트랜잭션에서 재생성. 검색은 CustomerSearchPort 뒤에
대가
원본과 검색 줄이 어긋날 수 있어 재생성 스크립트를 따로 둬야 한다
효과
검색창 하나로 전화 뒷자리 · 이름 · 초성을 모두 찾는다 (속도는 측정 전)
전화 뒷자리 검색 · 초성 검색
전화 뒷자리 검색 · 초성 검색

왜 이 선택인가 — 버린 대안

  • 전용 검색 엔진(Elasticsearch 등)
    매장 규모에서는 과하다 — 포트 뒤에 두어 커지면 교체

역정규화 검색 줄

책 뒤의 "찾아보기"를 따로 만드는 것과 같습니다. 이름, 초성, 번호 끝자리를 한 줄에 모아 두고 그 줄에만 색인을 걸면, 여러 표를 뒤지지 않고 바로 찾습니다.

근거: docs/superpowers/specs/2026-09-22-customer-pet-design.md (비공개 저장소)

07. 결과와 회고

기간 · 커밋
10일 · 399개 (1인)
AI 공동 작성 커밋
389개
백엔드 테스트
단위 1,294 · 통합 405
웹 e2e
11개 통과 (6.6초)

아쉬운 점 · 다음에 할 것

  • 아직 운영 배포 전 — 개발 compose에서는 인증 분리가 import 규율로만 지켜진다
  • pgvector 이미지를 미리 골랐지만 AI 기능(초안 해석)은 아직 규칙 기반 자리만 있다
  • 검색 · 업로드 지연을 측정하지 않았다 — 운영 전에 수치를 남겨야 한다