feat(admin): stabilize blog and admin patterns
TaxBaik CI/CD / build-and-deploy (push) Has been cancelled
TaxBaik CI/CD / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# Admin Pattern Critique And WBS
|
||||
|
||||
대상은 어드민 Blog, 문의사항, 등록/수정 페이지 전반이다. 이 문서는 비판, 개선 방향, 정량 완료 기준을 한 곳에 둔다.
|
||||
|
||||
## Brutal Critique
|
||||
|
||||
| 영역 | 현재 문제 | 왜 위험한가 | 개선 기준 |
|
||||
| --- | --- | --- | --- |
|
||||
| API-first 위반 | 어드민 Razor 컴포넌트가 `BlogService`, `InquiryService`, repository를 직접 주입 | 어드민을 클라이언트 사이드 Blazor WebAssembly로 운용할 때 구조가 깨지고 API 계약 테스트가 우회된다 | 모든 어드민 화면은 BrowserClient를 통해 `/api/*` 호출 |
|
||||
| Blog 등록/수정 중복 | `BlogCreate.razor`와 `BlogEdit.razor`가 필드, JS 편집기, 저장 로직을 반복 | 한쪽만 수정되는 파편화가 생긴다 | `BlogForm.razor` + `BlogEditorJsModule` 패턴 |
|
||||
| JS 과다/전역 상태 | `window.easyMDEInstance` 단일 전역 인스턴스 사용 | 페이지 이동/다중 편집/재렌더에서 내용 섞임 위험, Blazor 책임 경계가 흐려진다 | JS 제거 우선 검토, 불가피하면 JS module + element별 instance map + dispose |
|
||||
| 문의 수정 착시 | `InquiryEdit`가 이름/전화/이메일/내용 수정 UI를 보여주지만 실제 저장은 상태/메모 중심 | 운영자가 저장 성공을 믿어도 핵심 데이터가 DB에 반영되지 않을 수 있다 | 전체 수정 API를 만들거나 해당 필드를 read-only 처리 |
|
||||
| 문자열 상태 난립 | 문의 상태, 서비스 유형이 UI 문자열/API 문자열/DB 값으로 분산 | 오타 하나가 통계와 필터를 깨뜨린다 | enum/공통코드/상태 mapper 단일화 |
|
||||
| 삭제 위험 | Blog/Inquiry 삭제가 즉시 hard delete | 운영 감사, 상담 이력, SEO URL 보존에 취약 | soft delete 또는 archive 정책 |
|
||||
| 정합성 부족 | Blog slug 생성이 전체 목록 조회 기반 | 동시 생성 충돌에 약하고 데이터가 늘면 느려진다 | DB unique index + 충돌 재시도 |
|
||||
| 템플릿 부재 | CRUD 페이지마다 버튼, 오류, 로딩, 페이징 패턴이 다름 | 바이브코딩식 흔들림이 반복된다 | List/Form/Detail/PageState 템플릿화 |
|
||||
| 배포 완료 착시 | 문서상 완료 항목과 운영 검증 항목이 섞임 | 체크박스가 실제 성공을 대체한다 | WBS는 수치, 로그, CI URL로만 완료 |
|
||||
|
||||
## Target Admin Pattern
|
||||
|
||||
```text
|
||||
Razor Page/Form
|
||||
-> BrowserClient with JWT
|
||||
-> Controller DTO
|
||||
-> Application Service
|
||||
-> Repository
|
||||
-> DB constraints/indexes
|
||||
```
|
||||
|
||||
어드민은 클라이언트 사이드 Blazor WebAssembly 기준이다. 예외는 명시해야 한다. 서버 전용 컴포넌트가 Application Service를 직접 호출해야 한다면 `ENGINEERING_HARNESS.md`의 API-first 기준에 대한 사유와 제거 예정 WBS를 남긴다.
|
||||
|
||||
## Quantitative Success Metrics
|
||||
|
||||
| 지표 | 기준값 | 측정 방법 |
|
||||
| --- | --- | --- |
|
||||
| Admin direct service injection | 0건 | `rg "@inject .*Service|@inject I.*Repository" TaxBaik.Web/Components/Admin` |
|
||||
| Blog create/edit duplicate fields | 0개 중복 폼 | `BlogForm.razor` 단일 사용 여부 |
|
||||
| Admin JavaScript surface | 필수 module만 허용 | `window.*` 전역 admin JS 0건, JS interop 사유 문서화 |
|
||||
| Inquiry visible-but-unsaved fields | 0개 | E2E로 수정 후 API 재조회 |
|
||||
| Protected admin API anonymous access | 0개 | API smoke에서 401/403 확인 |
|
||||
| CI required gates | 6/6 통과 | build, unit, publish, deploy, browser e2e, api smoke |
|
||||
| Playwright admin flows | 8개 이상 통과 | login, blog CRUD, inquiry CRUD/status, responsive, password, smoke |
|
||||
| DB integrity constraints | 핵심 테이블 100% | PK, FK, unique/check/index 리뷰 |
|
||||
| WBS evidence coverage | 100% | 각 완료 항목에 command/log/test 파일 기재 |
|
||||
|
||||
## Roadmap
|
||||
|
||||
| Phase | 목적 | 종료 조건 |
|
||||
| --- | --- | --- |
|
||||
| P0 Harness | 기준 고정과 문서 최소화 | 이 문서와 Engineering Harness가 README에서 참조됨 |
|
||||
| P1 Stabilize | Blog/Inquiry 착시와 중복 제거 | API-first 전환, 공통 폼, 정합성 테스트 통과 |
|
||||
| P2 Harden | DB 제약, 충돌 방지, 삭제 정책 | migration + 회귀 테스트 + E2E 통과 |
|
||||
| P3 Standardize | CRUD 템플릿화와 반복 패턴 제거 | 신규 CRUD 생성 시 템플릿만 사용 |
|
||||
| P4 Integrate | 더존 UX 정신 내재화 | 고밀도 화면, 표준 동선, 빠른 입력, 상태 가시성, 회귀 최소화 검증 |
|
||||
| P5 Operate | CI/CD와 운영 지표 고도화 | 배포본 기준 smoke/E2E/로그 알림 안정화 |
|
||||
|
||||
## Detailed WBS
|
||||
|
||||
| ID | 작업 | 산출물 | 정량 완료 기준 |
|
||||
| --- | --- | --- | --- |
|
||||
| P0-01 | 문서 기준점 정리 | `docs/INDEX.md`, `ENGINEERING_HARNESS.md` | canonical 문서 3개 이하, README 링크 1곳 |
|
||||
| P0-02 | 기존 장문 문서 역할 축소 | README 문서 섹션 정리 | `CLAUDE.md`를 보조자료로 표시 |
|
||||
| P1-01 | Blog API client 도입 | `IBlogBrowserClient`, `BlogBrowserClient` | Blog admin page direct service/repository injection 0건 |
|
||||
| P1-02 | Blog 공통 폼 도입 | `BlogForm.razor` | create/edit 필드 중복 0건, 저장 E2E 2개 통과 |
|
||||
| P1-03 | Markdown editor JS 최소화/격리 | Blazor 대체 또는 JS module | 전역 `window.easyMDEInstance` 사용 0건, JS interop 사유 명시 |
|
||||
| P1-04 | Inquiry 수정 계약 확정 | `UpdateInquiryRequest` 또는 read-only UI | 화면 표시 editable 필드와 저장 필드 불일치 0건 |
|
||||
| P1-05 | Inquiry API client 도입 | `IInquiryBrowserClient` 정비 | Inquiry admin direct service injection 0건 |
|
||||
| P1-06 | 상태/서비스 유형 단일화 | enum/common code/mapper | 상태 문자열 하드코딩 UI 위치 0건 또는 공통 상수 참조 |
|
||||
| P2-01 | Blog slug 충돌 방지 | unique index + retry | 동시 생성 테스트 1개 통과 |
|
||||
| P2-02 | 삭제 정책 정리 | soft delete migration 또는 archive 정책 | hard delete 운영 엔티티 0건 또는 예외 문서화 |
|
||||
| P2-03 | DB index 점검 | migration | 목록/검색/상태 필터 explain 기준 seq scan 위험 제거 |
|
||||
| P2-04 | 낙관적 충돌 방지 | `updatedAt` 조건부 update | stale update API 테스트 1개 이상 통과 |
|
||||
| P3-01 | CRUD 템플릿 작성 | page/form/client/test skeleton | 신규 admin CRUD 생성 시간 30% 감소 |
|
||||
| P3-02 | 공통 PageState/Error 처리 | reusable component/service | admin page 중복 try/catch/snackbar 패턴 50% 감소 |
|
||||
| P3-03 | 메뉴/라우팅 표준화 | route registry 또는 constants | admin route 문자열 중복 50% 감소 |
|
||||
| P4-01 | 더존 UX 패턴 캡슐화 | 고밀도 grid/form/template 규칙 | 신규 어드민 화면이 템플릿을 따르지 않는 경우 0건 |
|
||||
| P4-02 | UX 회귀 검증 | responsive, keyboard flow, density, state visibility test | 핵심 CRUD 화면 E2E 100% 통과 |
|
||||
| P5-01 | CI gate 명문화 | workflow 체크 목록 | 6개 gate 모두 required |
|
||||
| P5-02 | 배포본 API smoke 확장 | workflow curl 추가 | Blog/Inquiry create-read-update test 2xx |
|
||||
| P5-03 | 운영 회귀 대시보드 | test report/version endpoint | 배포 커밋과 E2E 결과 추적 가능 |
|
||||
|
||||
## Immediate Refactor Order
|
||||
|
||||
1. `InquiryEdit` 착시 제거: 전체 수정 API를 추가하거나 저장 안 되는 필드를 read-only로 바꾼다.
|
||||
2. `BlogForm.razor`를 만들고 create/edit 중복을 제거한다.
|
||||
3. Blog/Inquiry 어드민 페이지를 BrowserClient 경유로 바꾼다.
|
||||
4. 상태/서비스 유형 문자열을 단일 source로 모은다.
|
||||
5. DB 제약과 삭제 정책을 migration으로 고정한다.
|
||||
|
||||
## Completion Rule
|
||||
|
||||
WBS 항목은 다음 네 가지가 모두 있어야 완료다.
|
||||
|
||||
- 관련 코드 또는 문서 diff
|
||||
- 로컬 검증 명령과 결과
|
||||
- CI/CD workflow 성공
|
||||
- 배포본 기준 API 또는 Browser E2E 증거
|
||||
@@ -0,0 +1,72 @@
|
||||
# Combo Policy
|
||||
|
||||
이 문서는 TaxBaik 어드민의 콤보 정책을 정한다. 여기서 콤보는 `MudSelect`, `MudAutocomplete`, `MudChip`, 상태 필터, 코드 선택 입력을 포함한다.
|
||||
|
||||
## Policy
|
||||
|
||||
- 닫힌 집합은 `MudSelect`를 쓴다.
|
||||
- 열린 집합 또는 검색이 필요한 집합은 `MudAutocomplete`를 쓴다.
|
||||
- 상태/유형/등급처럼 값이 고정된 항목은 문자열 직접 입력을 금지한다.
|
||||
- 선택한 값은 저장 값과 표시 값을 분리한다.
|
||||
- 표시 값은 사람이 읽는 라벨, 저장 값은 코드값이어야 한다.
|
||||
- `null` 허용 여부는 UI에서 명시한다.
|
||||
- `전체`, `선택 안 함`, `기타`는 서로 다른 의미로 취급한다.
|
||||
- 다중 선택이 필요하면 단일 선택 콤보를 억지로 재사용하지 않는다.
|
||||
|
||||
## Closed Set
|
||||
|
||||
다음 경우 `MudSelect`를 기본으로 사용한다.
|
||||
|
||||
- 상태
|
||||
- 세금 유형
|
||||
- 신고 유형
|
||||
- 위험도
|
||||
- 고정 서비스 유형
|
||||
- 공지 유형
|
||||
|
||||
규칙:
|
||||
|
||||
- 값은 상수, enum, 공통코드 중 하나에서만 가져온다.
|
||||
- `MudSelectItem`의 라벨과 값은 일치하는 쌍으로 관리한다.
|
||||
- 운영자가 값의 의미를 추측해야 하는 항목은 콤보로 두지 않는다.
|
||||
|
||||
## Search Set
|
||||
|
||||
다음 경우 `MudAutocomplete`를 기본으로 사용한다.
|
||||
|
||||
- 고객 선택
|
||||
- 회사 선택
|
||||
- 데이터가 많아 스크롤 선택이 비효율적인 경우
|
||||
|
||||
규칙:
|
||||
|
||||
- 검색어 입력 후 서버 또는 클라이언트 필터 결과를 보여준다.
|
||||
- 결과가 적을 때는 `MudSelect`보다 `MudAutocomplete`를 우선하지 않는다.
|
||||
- 선택 후 보여주는 텍스트와 저장되는 id를 분리한다.
|
||||
|
||||
## Display Rules
|
||||
|
||||
- 목록에서는 상태를 칩으로 보여준다.
|
||||
- 폼에서는 텍스트보다 구조화된 값으로 저장한다.
|
||||
- 필터에서는 현재 선택값이 명확히 보이게 한다.
|
||||
- `Clearable`은 의미가 명확한 경우에만 켠다.
|
||||
|
||||
## Standard Sources
|
||||
|
||||
- 상태 값은 `InquiryStatusMapper` 또는 전용 enum을 사용한다.
|
||||
- 공지/신고/세무 정보는 각 도메인별 공통코드 소스를 둔다.
|
||||
- 고객/회사 선택은 검색형 콤보로 통일한다.
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- 같은 화면에 `MudSelect`와 자유 텍스트 입력을 섞어 같은 의미를 표현
|
||||
- 코드값과 표시값을 뒤섞어서 저장
|
||||
- 콤보 옵션을 화면마다 하드코딩
|
||||
- `기타`를 예외 처리처럼 쓰고 실제 저장 값은 제각각 두는 것
|
||||
- `전체`를 저장 값으로 사용
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- 신규 어드민 화면은 이 문서의 `Closed Set`/`Search Set` 중 하나를 명시해야 한다.
|
||||
- 상태/유형/등급 입력이 있는 화면은 콤보 정책 위반이 없어야 한다.
|
||||
- 고객/회사처럼 데이터가 많은 항목은 검색형 선택으로 통일한다.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Common Code Policy
|
||||
|
||||
이 문서는 어드민 콤보, 상태, 유형, 출처 값의 단일 기준이다. 값은 DB `common_codes`를 우선 사용하고, 화면은 표시명만 바꾼다.
|
||||
|
||||
## Canonical Rules
|
||||
|
||||
- `code_value`는 저장 키다.
|
||||
- `code_name`은 화면 표시값이다.
|
||||
- `code_value`는 공백을 넣지 않는다.
|
||||
- 새 콤보를 추가할 때는 먼저 `common_codes`에 그룹을 추가한다.
|
||||
- 화면 하드코딩 배열은 금지한다. 불가피하면 임시 폴백으로만 두고 제거 계획을 함께 적는다.
|
||||
- 같은 의미의 값이 테이블마다 다르면 저장값을 먼저 통일하고 마이그레이션으로 이관한다.
|
||||
|
||||
## Grouping Rules
|
||||
|
||||
- 상태값: `*_STATUS`
|
||||
- 유형값: `*_TYPE`
|
||||
- 출처값: `*_SOURCE`
|
||||
- 위험도/스코어: `*_LEVEL`
|
||||
|
||||
## Standard Groups
|
||||
|
||||
- `INQUIRY_SERVICE_TYPE`
|
||||
- `INQUIRY_STATUS`
|
||||
- `CLIENT_STATUS`
|
||||
- `CLIENT_SERVICE_TYPE`
|
||||
- `CLIENT_TAX_TYPE`
|
||||
- `CLIENT_SOURCE`
|
||||
- `CONTRACT_SERVICE_TYPE`
|
||||
- `REVENUE_SERVICE_TYPE`
|
||||
- `FILING_TYPE`
|
||||
- `TAX_RISK_LEVEL`
|
||||
- `BUSINESS_TYPE`
|
||||
|
||||
## Data Rules
|
||||
|
||||
- DB seed와 운영 데이터의 저장값이 다르면 UI를 먼저 맞추지 말고 저장값을 먼저 정규화한다.
|
||||
- 한글 코드값을 사용하더라도 컬럼 길이를 먼저 검토하고, 업무 테이블과 마스터 테이블을 함께 조정한다.
|
||||
- 표시용 문구가 길면 `code_name`에 둔다.
|
||||
|
||||
## UI Rules
|
||||
|
||||
- `MudSelect`는 `code_value`를 바인딩하고 `code_name`을 보여준다.
|
||||
- 검색형이면 `MudAutocomplete`를 쓰고, 선택형이면 `MudSelect`를 쓴다.
|
||||
- 자유 입력을 허용하지 않을 값은 텍스트 필드로 만들지 않는다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- 신규 콤보 추가 시 DB 마이그레이션이 먼저 존재해야 한다.
|
||||
- 화면에 하드코딩된 선택값이 없어야 한다.
|
||||
- 기존 저장값과 신규 저장값의 불일치가 없어야 한다.
|
||||
|
||||
## Audit
|
||||
|
||||
- 점검 SQL은 [docs/ops/COMMON_CODE_AUDIT.sql](./ops/COMMON_CODE_AUDIT.sql)를 사용한다.
|
||||
@@ -0,0 +1,104 @@
|
||||
# DOUZONE UX Guide
|
||||
|
||||
이 문서는 TaxBaik 어드민 UX의 기준선이다. 목표는 더존 세무회계프로그램류의 고밀도 운영 화면을 구현하되, TaxBaik의 도메인과 검증 규칙을 유지하는 것이다.
|
||||
|
||||
## UX Principles
|
||||
|
||||
- 고밀도 우선: 한 화면에서 상태, 입력, 결과, 작업을 함께 본다.
|
||||
- 표준 동선 우선: 목록 -> 상세 -> 수정 -> 저장 흐름을 기본으로 둔다.
|
||||
- 빠른 입력 우선: 마우스 최소, 키보드/단축 동선 최대, 기본값 명확화.
|
||||
- 상태 가시성 우선: 진행중/성공/실패/비활성/삭제됨을 즉시 구분 가능하게 한다.
|
||||
- 회귀 최소화 우선: 같은 화면 패턴은 같은 컴포넌트와 같은 구조를 사용한다.
|
||||
- 추측 금지: 의미가 불명확한 텍스트, 상태, 버튼, 색상은 새로 만들지 않는다.
|
||||
|
||||
## Layout Template
|
||||
|
||||
어드민 화면은 기본적으로 아래 구조를 따른다.
|
||||
|
||||
```text
|
||||
PageHeader
|
||||
FilterBar or ActionBar
|
||||
ContentSurface
|
||||
-> DenseGrid or DetailPanel
|
||||
-> EmptyState when empty
|
||||
-> Paging/Footer when needed
|
||||
```
|
||||
|
||||
권장 규칙:
|
||||
|
||||
- 페이지 제목은 1개만 둔다.
|
||||
- 보조 설명은 1줄만 둔다.
|
||||
- 주요 액션은 우측 상단 또는 헤더 우측에 둔다.
|
||||
- 목록은 `Dense`를 기본으로 한다.
|
||||
- 상세/수정은 좌우 2열 또는 상단 요약 + 하단 폼 패턴을 우선한다.
|
||||
|
||||
## Component Template
|
||||
|
||||
### Page Header
|
||||
|
||||
- 구성: `Eyebrow`, `Title`, `Subtitle`, `Primary Action`
|
||||
- 역할: 화면 맥락 고정, 다음 행동 제시
|
||||
- 금지: 동일 화면에 헤더가 2개 이상 존재
|
||||
|
||||
### Dense Grid
|
||||
|
||||
- 행 간격은 좁게 유지한다.
|
||||
- 컬럼은 우선순위 순으로 배치한다.
|
||||
- 상태는 텍스트 대신 칩/색상/아이콘으로 함께 보여준다.
|
||||
- 작업 버튼은 `보기`, `수정`, `삭제`처럼 짧고 일관되게 둔다.
|
||||
|
||||
### Form
|
||||
|
||||
- 기본값은 채워진 상태로 시작한다.
|
||||
- 저장 전 필수 검증은 화면에서 즉시 보인다.
|
||||
- 저장되지 않는 필드는 read-only로 바꾼다.
|
||||
- 입력이 많은 폼은 섹션으로 나누되, 섹션 수는 최소화한다.
|
||||
|
||||
### Empty State
|
||||
|
||||
- 데이터 없음, 필터 결과 없음, 로드 실패를 구분한다.
|
||||
- 단순 문구보다 다음 행동 버튼을 함께 둔다.
|
||||
|
||||
### Status Chip
|
||||
|
||||
- 상태는 문자열 그대로 노출하지 말고 칩으로 시각화한다.
|
||||
- 색상은 의미를 유지한다.
|
||||
- 동일 상태는 동일 색을 사용한다.
|
||||
|
||||
## Text And Labels
|
||||
|
||||
- 라벨은 짧게 쓴다.
|
||||
- 같은 개념은 같은 단어를 쓴다.
|
||||
- 약어는 화면 전체에서 통일한다.
|
||||
- 운영자가 오해할 수 있는 추상적인 표현은 금지한다.
|
||||
|
||||
## Serving Rules
|
||||
|
||||
- 공개 사이트는 SSR, 어드민은 Blazor WebAssembly 기준으로 본다.
|
||||
- 어드민 화면은 API-first 경유를 기본으로 한다.
|
||||
- JS는 불가피할 때만 사용하고, 모듈로 격리한다.
|
||||
- 상태/메뉴/라우트/버튼은 문자열 흩뿌리기를 금지하고 공통 상수 또는 템플릿으로 묶는다.
|
||||
|
||||
## Reference Rules
|
||||
|
||||
- 이 문서를 어드민 UX의 1차 기준으로 사용한다.
|
||||
- 세부 코드 규칙은 [ENGINEERING_HARNESS.md](./ENGINEERING_HARNESS.md)를 따른다.
|
||||
- 콤보/선택/검색 규칙은 [COMBO_POLICY.md](./COMBO_POLICY.md)를 따른다.
|
||||
- 공통코드/저장값 규칙은 [COMMON_CODE_POLICY.md](./COMMON_CODE_POLICY.md)를 따른다.
|
||||
- 패턴 비판과 WBS는 [ADMIN_PATTERN_CRITIQUE_WBS.md](./ADMIN_PATTERN_CRITIQUE_WBS.md)를 따른다.
|
||||
- 문서 인덱스는 [INDEX.md](./INDEX.md)를 따른다.
|
||||
|
||||
## Prohibited Patterns
|
||||
|
||||
- 목록마다 서로 다른 헤더 구조
|
||||
- 버튼 색과 의미의 중복/충돌
|
||||
- 저장 안 되는 필드를 편집 가능한 척 보여주기
|
||||
- 전역 JS 상태에 의존하는 편집기
|
||||
- 같은 CRUD 화면의 개별 구현체마다 다른 DOM/행 높이/행동 패턴
|
||||
- 불필요한 중첩 컴포넌트와 과한 추상화
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- 신규 어드민 화면은 이 문서의 레이아웃/컴포넌트 규칙 중 최소 80%를 따른다.
|
||||
- 기존 화면은 새로 건드릴 때 이 문서로 수렴한다.
|
||||
- 화면 추가 시 `PageHeader`, `EmptyState`, `DenseGrid`, `Form` 패턴 중 하나 이상을 재사용한다.
|
||||
@@ -0,0 +1,96 @@
|
||||
# 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 관습을 왜곡해 구현하지 않는다.
|
||||
@@ -0,0 +1,31 @@
|
||||
# TaxBaik Engineering Index
|
||||
|
||||
이 디렉터리의 문서만 현재 개발 기준의 기준점으로 사용한다. 기존 장문 문서는 이 문서에서 참조하지 않으면 보조 자료로만 본다.
|
||||
|
||||
## Canonical Documents
|
||||
|
||||
| 문서 | 용도 | 변경 조건 |
|
||||
| --- | --- | --- |
|
||||
| [ENGINEERING_HARNESS.md](./ENGINEERING_HARNESS.md) | 아키텍처, 코드 품질, 배포, 데이터 정합성 하네스 | 방향성 변경 또는 반복 위반 발견 |
|
||||
| [DOUZONE_UX_GUIDE.md](./DOUZONE_UX_GUIDE.md) | 더존식 어드민 UX 원칙, 템플릿, 컴포넌트, 서빙 규칙 | 화면 패턴 변경 또는 신규 템플릿 추가 |
|
||||
| [COMMON_CODE_POLICY.md](./COMMON_CODE_POLICY.md) | 공통코드, 저장값, 컬럼 길이, 하드코딩 금지 규칙 | 공통코드 또는 콤보 추가/수정 |
|
||||
| [COMBO_POLICY.md](./COMBO_POLICY.md) | 콤보/선택/검색 입력 정책과 저장값 규칙 | 상태/유형/선택 입력 정책 변경 |
|
||||
| [ADMIN_PATTERN_CRITIQUE_WBS.md](./ADMIN_PATTERN_CRITIQUE_WBS.md) | 어드민 Blog/문의 등록 패턴 비판, 개선 로드맵, 정량 WBS | WBS 상태 또는 성공 지표 변경 |
|
||||
|
||||
## Route And Serving Map
|
||||
|
||||
| 영역 | 라우트/파일 | 기준 |
|
||||
| --- | --- | --- |
|
||||
| Public Home/Blog/Contact | `/taxbaik/`, `/taxbaik/blog`, `/taxbaik/contact` | 서버 사이드 렌더링, SEO 우선, WASM 의존 금지 |
|
||||
| Admin Blog | `/taxbaik/admin/blog`, `/taxbaik/admin/blog/create`, `/taxbaik/admin/blog/{id}/edit` | 클라이언트 사이드 Blazor WebAssembly, API-first 클라이언트 경유, JS 최소화 |
|
||||
| Admin Inquiry | `/taxbaik/admin/inquiries`, `/taxbaik/admin/inquiries/create`, `/taxbaik/admin/inquiries/{id}/edit` | 클라이언트 사이드 Blazor WebAssembly, 공개 접수/관리자 등록/상태 변경 분리 |
|
||||
| Public API | `/taxbaik/api/*` | JWT 인증, ProblemDetails 오류, DTO 입출력 |
|
||||
| CI/CD | `.gitea/workflows/deploy.yml`, `.gitea/workflows/browser-e2e.yml` | 수동 배포 금지, 배포본 E2E 통과 후 완료 |
|
||||
|
||||
## Document Rules
|
||||
|
||||
- 문서는 짧게 유지한다. 새 문서를 만들기 전에 이 인덱스에 추가할 가치가 있는지 판단한다.
|
||||
- 동일한 기준을 여러 문서에 중복 작성하지 않는다.
|
||||
- WBS 완료 여부는 체크박스가 아니라 수치와 실행 로그로 판단한다.
|
||||
- 코드 변경 시 관련 WBS ID를 커밋/PR 설명 또는 작업 메모에 남긴다.
|
||||
- 공통코드 관련 규칙은 [COMMON_CODE_POLICY.md](./COMMON_CODE_POLICY.md)만 1차 기준으로 사용한다.
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Common code audit checks
|
||||
SELECT code_group, code_value
|
||||
FROM common_codes
|
||||
WHERE code_value LIKE '% %';
|
||||
|
||||
SELECT code_group, COUNT(*)
|
||||
FROM common_codes
|
||||
GROUP BY code_group
|
||||
ORDER BY code_group;
|
||||
|
||||
SELECT DISTINCT c.service_type
|
||||
FROM clients c
|
||||
LEFT JOIN common_codes cc
|
||||
ON cc.code_group = 'CLIENT_SERVICE_TYPE'
|
||||
AND cc.code_value = c.service_type
|
||||
WHERE c.service_type IS NOT NULL
|
||||
AND cc.code_value IS NULL;
|
||||
Reference in New Issue
Block a user