docs: 통합 Web 앱 아키텍처로 CLAUDE.md 업데이트
TaxBaik CI/CD / build-and-deploy (push) Failing after 47s

- 포트 배치: 5001로 통합 (5002 제거)
- 배포 절차: 단일 Web 앱 빌드로 단순화
- 서비스: taxbaik만 관리 (taxbaik-admin 제거)
- Nginx: /taxbaik 블록 하나로 통합
- 파일 구조: Web/Components/Admin으로 명시
- 인증: JWT + localStorage 패턴 문서화

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 22:39:31 +09:00
parent 57269e281d
commit 713fbde5e4
+103 -75
View File
@@ -11,42 +11,60 @@
## 2. 아키텍처 ## 2. 아키텍처
### 2.1 프로젝트 구조 ### 2.1 프로젝트 구조 (통합)
**단일 앱 구조** (소규모 프로젝트 최적화):
``` ```
TaxBaik.Domain 클래스 라이브러리 (엔티티, 인터페이스, enum — 외부 의존성 없음) TaxBaik.Domain 클래스 라이브러리 (엔티티, 인터페이스, enum)
TaxBaik.Infrastructure 클래스 라이브러리 (Dapper repository, DB 마이그레이션) TaxBaik.Infrastructure 클래스 라이브러리 (Dapper repository, DB 마이그레이션)
TaxBaik.Application 클래스 라이브러리 (서비스, DTO, 비즈니스 로직) TaxBaik.Application 클래스 라이브러리 (서비스, DTO, 비즈니스 로직)
TaxBaik.Web ASP.NET Core 앱 (Razor Pages SSR, port 5001) TaxBaik.Web ASP.NET Core 앱 (포트 5001)
TaxBaik.Admin ASP.NET Core 앱 (Blazor Server, port 5002) ├─ Pages/ Razor Pages (공개 홈페이지, 블로그, 문의폼)
├─ Components/
│ ├─ (Web pages)
│ └─ Admin/ Blazor Server (관리자 백오피스)
│ ├─ Pages/
│ ├─ Layout/
│ └─ App.razor
└─ Services/ 인증, 블로그, 문의 등
``` ```
**경로:**
- 홈페이지: `/taxbaik` (Razor Pages)
- 관리자: `/taxbaik/admin` (Blazor)
- 로그인: `/taxbaik/admin/login`
### 2.2 계층 책임 ### 2.2 계층 책임
- **Domain**: 비즈니스 규칙, 엔티티 정의 - **Domain**: 비즈니스 규칙, 엔티티 정의
- **Infrastructure**: DB 접근, Dapper 구현체, 마이그레이션 실행 - **Infrastructure**: DB 접근, Dapper 구현체, 마이그레이션 실행
- **Application**: 서비스, DTO 매핑, 비즈니스 워크플로우 - **Application**: 서비스, DTO 매핑, 비즈니스 워크플로우
- **Web**: 공개 사이트 (SEO 최적화, Razor Pages SSR) - **Web (Pages/)**: 공개 홈페이지 (SEO 최적화, Razor Pages SSR)
- **Admin**: 관리자 백오피스 (실시간 UI, Blazor Server) - **Web (Components/Admin)**: 관리자 백오피스 (실시간 UI, Blazor Server)
- **Web (Services/)**: 인증(JWT), 블로그, 문의 관리 등
### 2.3 기술 결정 이유 ### 2.3 기술 결정 이유
**왜 Razor Pages (Web)인가?** **왜 Razor Pages (공개 사이트)인가?**
- Razor Pages는 서버에서 HTML을 렌더링 → Google, Naver가 즉시 크롤 가능 - 서버에서 HTML을 렌더링 → Google, Naver가 즉시 크롤 가능
- Blazor Server는 초기 응답이 shell HTML → SEO 불리 (블로그는 구글 검색이 핵심) - Blazor는 초기 응답이 shell HTML → SEO 불리 (블로그는 검색 유입이 핵심)
**왜 Blazor Server (Admin)인가?** **왜 Blazor Server (관리자)인가?**
- 관리자는 SEO 불필요 → WebSocket으로 실시간 UI 업데이트 가능 - 관리자는 SEO 불필요 → WebSocket으로 실시간 UI 업데이트 가능
- 기존 QuantEngine 패턴과 일치 - 복잡한 관리 UI를 쉽게 구현
**왜 단일 앱 (통합 Web)인가?**
- 소규모 프로젝트 → 분리의 이점 < 개발 복잡도
- **개발**: 터미널 1개, 포트 1개 (5001)
- **배포**: 앱 1개, DB 마이그레이션 1회
- **유지보수**: 모든 비즈니스 로직 한 곳 (Application)
- **장점**: 기존 분리 구조의 모든 기능 + 간단한 개발 경험
**왜 Dapper인가?** **왜 Dapper인가?**
- QuantEngine에서 이미 사용 중 (팀 지식) - 팀 기존 지식 (QuantEngine에서 사용)
- 복잡한 조인, 페이징, 성능 제어 용이 - 복잡한 조인, 페이징, 성능 제어 용이
- EF Core 대비 SQL 완전 제어 가능 - EF Core 대비 SQL 완전 제어 가능
**왜 두 개의 ASP.NET Core 앱인가?**
- Web: 정적 콘텐츠 + Razor Pages
- Admin: 동적 Blazor + WebSocket
- 미들웨어 파이프라인 다름 → 독립 배포로 한쪽 문제가 다른 쪽에 영향 안 함
--- ---
## 3. 로컬 개발 환경 설정 ## 3. 로컬 개발 환경 설정
@@ -88,24 +106,27 @@ psql -h localhost -U taxbaik -d taxbaikdb -c "\dt"
dotnet run -p TaxBaik.Web dotnet run -p TaxBaik.Web
``` ```
#### 단계 3: 개발 워크플로우 #### 단계 3: 개발 워크플로우 (단일 앱 통합)
```bash ```bash
# 터미널 1: SSH 터널 유지 # 터미널 1: SSH 터널 유지
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7 ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
# 터미널 2: Web 사이트 (Razor Pages) # 터미널 2: 통합 Web (Razor Pages + Blazor)
cd TaxBaik.Web cd TaxBaik.Web
dotnet run dotnet run
# 접속: http://localhost:5001/taxbaik # 접속:
# - 홈페이지: http://localhost:5001/taxbaik
# 터미널 3: Admin 앱 (Blazor) # - 관리자: http://localhost:5001/taxbaik/admin/login
cd TaxBaik.Admin # - 로그인: admin / admin123
dotnet run
# 접속: https://localhost:5002
# 로그인: admin / admin123
``` ```
**장점**:
- ✅ 한 개의 포트 (5001)
- ✅ 한 개의 터미널에서 실행
- ✅ 한 번의 DB 마이그레이션
- ✅ 모든 기능 유지 (JWT 인증, Blazor UI, Razor Pages SEO)
### 3.2 appsettings.json (로컬) ### 3.2 appsettings.json (로컬)
```json ```json
@@ -173,8 +194,7 @@ ssh kjh2064@178.104.200.7
3000 : Gitea Web (localhost만, proxy via /를 통해) 3000 : Gitea Web (localhost만, proxy via /를 통해)
2222 : Gitea SSH (공개) 2222 : Gitea SSH (공개)
5000 : QuantEngine Blazor (localhost, proxy via /quant/) 5000 : QuantEngine Blazor (localhost, proxy via /quant/)
5001 : TaxBaik.Web (localhost, proxy via /taxbaik) 5001 : TaxBaik.Web (공개 사이트 + 관리자 통합, localhost, proxy via /taxbaik)
5002 : TaxBaik.Admin (localhost, proxy via /taxbaik/admin)
5432 : PostgreSQL (localhost 바인드) 5432 : PostgreSQL (localhost 바인드)
``` ```
@@ -182,11 +202,10 @@ ssh kjh2064@178.104.200.7
**핵심 전략**: .NET Core shadow copy로 배포 중 무중단 실행 **핵심 전략**: .NET Core shadow copy로 배포 중 무중단 실행
1. **로컬 빌드**: 1. **로컬 빌드** (단일 앱 통합):
```bash ```bash
dotnet clean TaxBaik.sln dotnet clean TaxBaik.sln
dotnet publish TaxBaik.Web -c Release -o ./publish/web dotnet publish TaxBaik.Web -c Release -o ./publish
dotnet publish TaxBaik.Admin -c Release -o ./publish/admin
``` ```
2. **CI/CD 배포** (Gitea Actions): 2. **CI/CD 배포** (Gitea Actions):
@@ -213,7 +232,6 @@ ssh kjh2064@178.104.200.7
```bash ```bash
# 최근 5개 배포만 유지 # 최근 5개 배포만 유지
ls -dt ~/deployments/taxbaik_* | tail -n +6 | xargs -r rm -rf ls -dt ~/deployments/taxbaik_* | tail -n +6 | xargs -r rm -rf
ls -dt ~/deployments/taxbaik_admin_* | tail -n +6 | xargs -r rm -rf
``` ```
**systemd 서비스 graceful shutdown 설정**: **systemd 서비스 graceful shutdown 설정**:
@@ -225,19 +243,16 @@ KillMode=mixed # SIGTERM → 30초 대기 → SIGKILL
### 3.4 서비스 파일 위치 ### 3.4 서비스 파일 위치
``` ```
/etc/systemd/system/taxbaik.service ← Web /etc/systemd/system/taxbaik.service ← 통합 Web 앱 (공개 사이트 + 관리자)
/etc/systemd/system/taxbaik-admin.service ← Admin
``` ```
### 5.5 배포 디렉토리 구조 (서버) ### 5.5 배포 디렉토리 구조 (서버)
``` ```
/home/kjh2064/ /home/kjh2064/
├── taxbaik_active → ./deployments/taxbaik_20260626_150000/ ├── taxbaik_active → ./deployments/taxbaik_20260626_150000/
├── taxbaik_admin_active → ./deployments/taxbaik_admin_20260626_150000/
└── deployments/ └── deployments/
├── taxbaik_20260626_150000/ (Web publish 출력) ├── taxbaik_20260626_150000/ (통합 Web publish 출력)
├── taxbaik_20260626_140000/ (이전 버전) ├── taxbaik_20260626_140000/ (이전 버전)
├── taxbaik_admin_20260626_150000/ (Admin publish 출력)
└── ... └── ...
``` ```
@@ -253,27 +268,17 @@ KillMode=mixed # SIGTERM → 30초 대기 → SIGKILL
location /taxbaik { location /taxbaik {
proxy_pass http://127.0.0.1:5001; proxy_pass http://127.0.0.1:5001;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
location /taxbaik/admin {
proxy_pass http://127.0.0.1:5002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade"; proxy_set_header Connection "Upgrade";
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
} }
``` ```
**더 구체적인 경로가 우선**: `/taxbaik` 블록이 `/` Gitea 블록보다 먼저 매칭됨. **참고**: 단일 `/taxbaik` 블록이 공개 사이트와 관리자 (Blazor WebSocket)를 모두 처리합니다.
--- ---
@@ -341,7 +346,7 @@ public async Task<BlogPost?> GetBySlugAsync(string slug, CancellationToken ct)
- 비동기 메서드: **Async** 접미사 (GetBySlugAsync) - 비동기 메서드: **Async** 접미사 (GetBySlugAsync)
- 비공개 메서드: **Async 접미사 생략 가능** - 비공개 메서드: **Async 접미사 생략 가능**
### 6.2 파일 구조 ### 6.2 파일 구조 (통합 Web 앱)
``` ```
Domain/ Domain/
Entities/BlogPost.cs Entities/BlogPost.cs
@@ -359,12 +364,17 @@ Application/
Web/ Web/
Pages/Blog/Index.cshtml Pages/Blog/Index.cshtml
Pages/Blog/Index.cshtml.cs ← PageModel Pages/Blog/Index.cshtml.cs ← PageModel (공개 사이트)
Components/
Admin/
Pages/Blog/BlogList.razor ← Blazor 관리자 페이지
Layout/MainLayout.razor
App.razor
Services/
AuthService.cs ← JWT 인증
CustomAuthenticationStateProvider.cs
LocalStorageService.cs
wwwroot/css/site.css wwwroot/css/site.css
Admin/
Components/Pages/Blog/BlogList.razor
Services/AdminAuthStateProvider.cs
``` ```
### 6.3 모든 UI는 한국어 ### 6.3 모든 UI는 한국어
@@ -437,41 +447,60 @@ catch
--- ---
## 8. Blazor Admin 패턴 ## 8. Blazor Admin 패턴 (통합 Web 앱)
### 8.1 PathBase ### 8.1 PathBase
Admin은 `/taxbaik/admin/` 경로에서 실행: 전체 앱은 `/taxbaik/` 경로에서 실행:
```csharp ```csharp
// Program.cs // Program.cs
app.UsePathBase("/taxbaik/admin"); app.UsePathBase("/taxbaik");
``` ```
`@page` 지시문의 경로는 이 기본값에 상대적. 예: `@page` 지시문의 경로는 이 기본값에 상대적. 예:
```razor ```razor
@page "/login" ← 실제 URL: /taxbaik/admin/login @page "/admin/login" ← 실제 URL: /taxbaik/admin/login
@page "/blog" ← 실제 URL: /taxbaik/admin/blog @page "/admin/blog" ← 실제 URL: /taxbaik/admin/blog
@page "/blog" ← 실제 URL: /taxbaik/blog (Razor Pages)
``` ```
### 8.2 Cookie 인증 ### 8.2 JWT 인증 (LocalStorage + Bearer Token)
```csharp ```csharp
// Program.cs // Program.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) builder.Services.AddScoped<AuthService>();
.AddCookie(opts => opts.LoginPath = "/login"); builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(
app.UseAuthentication(); sp => sp.GetRequiredService<CustomAuthenticationStateProvider>());
app.UseAuthorization(); builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthorizationCore();
``` ```
### 8.3 모든 페이지에 [Authorize] 추가 토큰은 localStorage에 저장되며, `CustomAuthenticationStateProvider`가 자동으로 복원:
```csharp
// CustomAuthenticationStateProvider.cs
public async Task LoginAsync(string token)
{
await _localStorage.SetItemAsStringAsync("authToken", token);
StateHasChanged(); // Blazor 상태 갱신
}
public async Task LogoutAsync()
{
await _localStorage.RemoveItemAsync("authToken");
StateHasChanged();
}
```
### 8.3 모든 Admin 페이지에 [Authorize] 추가
```razor ```razor
@* _Imports.razor *@ @* Components/Admin/_Imports.razor *@
@attribute [Authorize] @attribute [Authorize]
``` ```
Login 페이지만 [AllowAnonymous]: Admin 로그인 페이지만 [AllowAnonymous]:
```razor ```razor
@page "/login" @page "/admin/login"
@attribute [AllowAnonymous] @attribute [AllowAnonymous]
``` ```
@@ -606,16 +635,15 @@ ssh kjh2064@178.104.200.7
# DB 확인 # DB 확인
psql -U kjh2064 -d taxbaikdb -c "\dt" psql -U kjh2064 -d taxbaikdb -c "\dt"
# 서비스 상태 # 서비스 상태 (통합 Web 앱만)
systemctl status taxbaik taxbaik-admin systemctl status taxbaik
# 엔드포인트 확인 # 엔드포인트 확인
curl http://127.0.0.1:5001/health curl http://127.0.0.1:5001/taxbaik
curl http://127.0.0.1:5002/health
# Nginx 라우팅 확인 # Nginx 라우팅 확인
curl http://127.0.0.1/taxbaik curl http://127.0.0.1/taxbaik
curl http://127.0.0.1/taxbaik/admin curl http://127.0.0.1/taxbaik/admin/login
``` ```
### E2E 테스트 ### E2E 테스트