# Engineering Harness 이 문서는 TaxBaik 코드가 매번 흔들리지 않도록 막는 최소 하네스다. 여기에 없는 내용은 추측하지 않고 코드, 테스트, 운영 로그, DB 스키마 중 하나로 확인한다. ## Non-Negotiables | 항목 | 기준 | 실패 판정 | | --- | --- | --- | | Runtime | ASP.NET Core `net10.0` 기준 유지 | 프로젝트별 TargetFramework 불일치 | | Public UI | 홈페이지/공개 페이지는 서버 사이드 렌더링 기준 | 공개 페이지가 불필요하게 WASM 번들에 의존 | | Admin UI | 어드민은 클라이언트 사이드 Blazor WebAssembly + MudBlazor + API-first | 어드민 컴포넌트가 Application/Repository를 직접 주입 | | API | 모든 운영 기능은 `/api/*` DTO 경유 | UI 전용 서비스 호출만 존재 | | Auth | JWT 인증, 관리자 API는 `[Authorize]` | 익명으로 관리자 데이터 접근 가능 | | Deploy | Gitea Actions CI/CD만 배포 경로 | 수동 SSH/복사로 운영 반영 | | Evidence | 빌드, 테스트, E2E, API smoke 로그 | "확인함", "될 것" 같은 진술 | ## Architecture Guardrails - Domain은 엔티티, enum, repository interface만 가진다. - Application은 use case와 검증 규칙을 가진다. HTTP, JS, MudBlazor, DB 연결 세부를 모른다. - Infrastructure는 Dapper SQL과 외부 시스템 구현을 가진다. - Web은 Controller, 공개 Razor Pages SSR, Blazor host, 인증/서빙 설정을 가진다. - Web.Client/Admin UI는 클라이언트 사이드 Blazor WebAssembly로 본다. 서버 DI 서비스에 의존하지 않고 API client만 호출한다. - JavaScript는 최소화한다. 브라우저 API, 인증 토큰 저장, 서드파티 편집기처럼 Blazor/MudBlazor만으로 해결하기 부적절한 경우에만 JS module로 격리한다. - 상속은 프레임워크 요구 또는 명확한 다형성 모델에만 사용한다. 폼/테이블/CRUD 재사용은 기본적으로 컴포넌트 합성과 작은 service/client로 처리한다. ## Code Quality Harness | 원칙 | 적용 방식 | | --- | --- | | SOLID | 페이지는 orchestration만, 검증은 Application, 저장은 Repository, HTTP 계약은 DTO | | 유지보수 | Blog/Inquiry 같은 CRUD는 `List`, `Form`, `Client`, `Dto`, `Validator` 패턴으로 고정 | | 리팩토링 | 동작 보존 테스트를 먼저 추가하고 작은 단위로 이동 | | 일관성 | 오류 응답은 ProblemDetails, 페이징은 `{ data, total, page, pageSize }` | | 파편화 방지 | 같은 필드/상태/서비스유형 문자열은 enum/상수/공통 코드 중 하나로 단일화 | | 과유불급 | 추상화는 2개 이상 실제 사용처가 생긴 뒤 도입 | | 정규화 | 고객, 문의, 상담, 계약, 세금신고는 원천 테이블을 분리 | | 역정규화 | 대시보드/검색/운영 요약용 스냅샷만 허용하고 원천 id와 갱신 시점을 저장 | | 충돌방지 | 수정 API는 가능하면 `updatedAt` 또는 row version 기반 충돌 감지를 둔다 | | 더존 UX 정신 | 더존 세무회계프로그램처럼 고밀도, 표준 동선, 빠른 입력, 상태 가시성, 회귀 최소화를 기본 UX 원칙으로 삼는다 | | 추측금지 | 세법, 세율, 더존 필드, 운영 계정, 배포 결과는 공식 자료/코드/DB/로그 없이는 단정하지 않는다 | | JS 최소화 | Blazor/MudBlazor 우선, 불가피한 JS는 module + dispose + 테스트 가능한 얇은 wrapper | | 공통코드 | 상태/유형/출처/위험도는 `common_codes`를 우선 소스로 사용하고 화면 하드코딩을 금지 | ## Data Integrity Harness - DB 제약 조건이 1차 방어선이다: NOT NULL, UNIQUE, FK, CHECK, index. - Application validation은 사용자 메시지와 use case 규칙을 담당한다. - UI validation은 빠른 피드백일 뿐이며 유일한 검증으로 보지 않는다. - 관리자 수정 화면에 노출한 필드는 실제 저장되어야 한다. 저장하지 않는 필드는 read-only로 표시한다. - 상태 전이는 허용 목록을 둔다. 임의 문자열 저장을 금지한다. - 삭제는 운영 데이터 손실 위험이 있으면 soft delete 또는 archive를 우선 검토한다. - 콤보 값은 [COMMON_CODE_POLICY.md](./COMMON_CODE_POLICY.md)를 1차 기준으로 삼는다. ## API-First Admin Pattern 새 어드민 기능은 클라이언트 사이드 Blazor WebAssembly를 기준으로 아래 구조를 기본 템플릿으로 따른다. | Layer | Naming | 책임 | | --- | --- | --- | | DTO | `CreateXRequest`, `UpdateXRequest`, `XResponse` | HTTP 계약 | | Controller | `XController` | 인증, 라우팅, status code, ProblemDetails | | Client | `IXBrowserClient`, `XBrowserClient` | JWT 포함 HTTP 호출 | | Page | `XList.razor`, `XCreate.razor`, `XEdit.razor` | 화면 상태와 navigation | | Form | `XForm.razor` | 입력 컴포넌트와 UI validation | | Tests | unit + Playwright/API smoke | 회귀 방지 | ## Rendering Boundary | 영역 | 렌더링 | 데이터 접근 | | --- | --- | --- | | Public Home/Blog/Contact | 서버 사이드 렌더링 | 서버 Application Service 직접 사용 가능 | | Admin | 클라이언트 사이드 Blazor WebAssembly | JWT 포함 HTTP API만 사용 | | Shared DTO | 서버/클라이언트 공유 가능 | UI 전용 상태와 DB 엔티티를 섞지 않음 | 공개 페이지의 SEO와 초기 로딩은 SSR로 최적화한다. 어드민은 앱처럼 동작해야 하므로 WebAssembly와 API 계약을 기준으로 설계한다. ## CI Harness 완료는 로컬 성공이 아니라 CI와 배포본 성공이다. | Gate | Command/Check | Target | | --- | --- | --- | | Build | `dotnet build TaxBaik.sln -c Release --no-restore` | error 0 | | Unit | `dotnet test TaxBaik.sln -c Release --no-build` | failed 0 | | Browser | `npx playwright test --project="Desktop Chrome"` | failed 0 | | API Smoke | login + protected admin API curl | HTTP 2xx | | Deploy | `.gitea/workflows/deploy.yml` | success | | Post Deploy | `.gitea/workflows/browser-e2e.yml` | success | ## Stop Conditions - 동일 개념이 3곳 이상 다른 이름/계약으로 구현되면 기능 추가를 중단하고 정리한다. - UI가 저장한다고 보이는 필드를 API/Application이 저장하지 않으면 릴리스하지 않는다. - 운영 배포 검증이 CI 밖에서만 가능하면 완료로 보지 않는다. - 데이터 모델을 추측해서 세무 규칙이나 더존 UX 관습을 왜곡해 구현하지 않는다.