01
사주팔자 계산 엔진 — 명리학을 결정론적 코드로 옮기기
PROBLEM사주는 표준 라이브러리가 없는 도메인이다. 천간지지 변환, 오행 상생상극, 천간합, 육합/육충, 십성 — 서로 얽힌 규칙 체계를 정확하고 테스트 가능한 형태로 구현해야 했다.
SOLUTION명리학 규칙을 데이터 테이블(천간 10 × 지지 12 매핑)과 순수 함수로 분리해 결정론적 엔진으로 구현. 호환성은 오행 조화 40% + 천간합 20% + 지지 관계 15% + 십성 10% 가중 합산으로 점수화하고, 근거(breakdown)를 함께 반환한다.
export function calculateFourPillarsCompatibility(p1: FourPillars, p2: FourPillars) {
const elementHarmony = calculateElementHarmony(p1.elementDistribution, p2.elementDistribution); // 오행 상생상극
const dayStem = checkHeavenlyStemCombination(p1.dayStem, p2.dayStem); // 천간합 (갑기합토 등)
const branch = analyzeBranchRelations(p1.day.branch, p2.day.branch); // 배우자궁 육합/육충
return { totalScore: elementHarmony * 0.4 + dayStem * 0.2 + branch * 0.15 + /* 십성 0.1 ⋯ */, breakdown };
}
02
이질적인 5개 체계를 하나의 점수로 — 정규화와 가중치 설계
PROBLEMBig Five는 연속 점수, MBTI는 16개 이산 유형, 사주는 규칙 기반 판정, 별자리·혈액형은 범주형 — 스케일과 분포가 전혀 다른 체계를 합산하면 특정 요소가 점수를 지배하는 왜곡이 생긴다.
SOLUTION각 체계를 독립 모듈로 두고 0~100으로 정규화한 뒤 가중 합산(30/30/25/10/5). 검증된 심리학에 65%를 싣고 전통 요소는 보조로 제한해, 재미와 신뢰의 균형점을 가중치로 명문화했다. 모듈 분리 덕에 가중치 실험·요소 추가가 한 줄 변경으로 끝난다.
const totalScore =
bigFiveScore * 0.30 + // 5차원 성격 프로필 (유사성·보완성 혼합 전략)
mbtiScore * 0.30 + // 차원별 보완성
fourPillarsScore * 0.25 + // 오행·천간합·십성
zodiacScore * 0.10 + // 4원소 조화
bloodTypeScore * 0.05; // 성격론 기반
03
766줄 대시보드 — 도메인 단위 분해 리팩토링
PROBLEMdashboard.tsx 한 파일이 766줄 — 통계·성향 분석·좋아요 프리뷰·빠른 액션·다이얼로그가 한 컴포넌트에 엉켜 API 로직과 UI가 분리되지 않았고, 작은 수정에도 전체 파일을 읽어야 했다.
SOLUTION화면을 도메인 단위 7개 모듈(quick-stats, personality-analysis, likes-preview 등)로 분해하고, API 호출은 use-dashboard 커스텀 훅으로 추출. 본체는 165줄(-78%)로 줄고, 각 섹션이 독립적으로 수정·테스트 가능해졌다.
// Before: dashboard.tsx 766줄 (UI + API + 상태가 한 파일)
// After: dashboard.tsx 165줄 + 도메인 모듈 7개
components/dashboard/
├─ quick-stats.tsx // 퀵 스탯
├─ personality-analysis.tsx // 성향 분석 섹션
├─ likes-preview.tsx // 좋아요 프리뷰
└─ survey-results-dialog.tsx // 결과 다이얼로그
hooks/use-dashboard.ts // API 로직 분리 — UI와 데이터 경계 확정
04
WebSocket 채팅 — 연결 상태와 메시지 정합성
PROBLEM실시간 채팅은 연결이 끊기는 순간이 본질이다. 끊김 중 도착한 메시지 유실, 재연결 시 중복 수신, 읽음 상태 불일치가 사용자 신뢰를 직접 깎는다.
SOLUTION메시지는 DB 영속화를 단일 진실로 삼고 WebSocket은 전달 채널로만 사용. 재연결 시 마지막 수신 이후를 REST로 동기화하고, 연결 상태를 UI에 명시해 사용자가 현재 상태를 항상 알 수 있게 했다. 읽지 않은 배지·페이지네이션도 서버 기준으로 계산한다.
05
운영 환경 — Docker · Nginx · SSL을 VPS 한 대에서
PROBLEM관리형 PaaS 없이 Hostinger VPS 한 대에서 프론트·백엔드·DB·SSL을 모두 직접 운영해야 했다. 환경 차이로 인한 "로컬에선 되는데" 문제와 인증서 만료 사고를 막아야 한다.
SOLUTIONdev/local/prod를 docker-compose 파일로 분리해 환경 차이를 코드화. Nginx 리버스 프록시에서 SSL 종료·WebSocket 업그레이드를 처리하고, Let's Encrypt 자동 갱신 + GitHub Actions push 배포로 수동 운영 작업을 제거했다.