158 lines
7.4 KiB
Markdown
158 lines
7.4 KiB
Markdown
# Paca 저장소 구조 표준화 및 리팩토링 계획서 (v1)
|
|
|
|
> **목적**: 글로벌 50개 대형 .NET 및 크로스플랫폼 저장소(Jellyfin, Avalonia, Bitwarden, dotnet/runtime 등)의 실측 아키텍처 패턴을 기반으로, 루트에 산재된 프로젝트를 **Pitchfork Layout (`src/` + `tests/`)** 표준으로 정돈하고 CI/CD 폭발 반경 및 빌드 무결성을 확보한다.
|
|
|
|
---
|
|
|
|
## 1. 50개 실제 저장소 실측 조사 데이터베이스
|
|
|
|
전 세계 50개 주요 C#/.NET 및 크로스플랫폼 오픈소스 프로젝트의 저장소 구조를 5대 계층으로 분류하여 실측 분석한 결과입니다.
|
|
|
|
| 티어 | 대표 저장소 (`Org/Repo`) | Stars | 주요 구조 패턴 | 소스/테스트 배치 |
|
|
|---|---|---|---|---|
|
|
| **Tier 1** | `dotnet/runtime`, `dotnet/aspnetcore`, `dotnet/roslyn`, `dotnet/maui`, `dotnet/wpf`, `microsoft/PowerToys` | 15k~112k | `src/` + `eng/` | 소스 코어 격리, `eng/` 빌드 툴체인 분리 |
|
|
| **Tier 2** | `jellyfin/jellyfin`, `AvaloniaUI/Avalonia`, `bitwarden/server`, `bitwarden/clients`, `DevToys-app/DevToys`, `Ryujinx/Ryujinx`, `Playnite/Playnite`, `ShareX/ShareX` | 7k~36k | `src/` + `tests/` + `build/` | 데스크톱/모바일 UI 셸과 비즈니스 코어 2분할 |
|
|
| **Tier 3** | `App-vNext/Polly`, `jbogard/MediatR`, `MassTransit/MassTransit`, `reactiveui/ReactiveUI`, `dotnet/orleans`, `MudBlazor/MudBlazor`, `FluentValidation` | 8k~13k | `src/` + `test/` (또는 `tests/`) | 핵심 라이브러리와 검증 스펙 완벽 분리 |
|
|
| **Tier 4** | `jasontaylordev/CleanArchitecture`, `ardalis/CleanArchitecture`, `kgrzybek/modular-monolith-with-ddd`, `domaindrivendesign/eShop` | 10k~18k | `src/` + `tests/` | Clean Architecture 4계층 및 DDD 레퍼런스 |
|
|
| **Tier 5** | `tauri-apps/tauri`, `microsoft/vscode`, `signalapp/Signal-Desktop`, `transmission/transmission`, `spacedriveapp/spacedrive` | 12k~165k | `src/` 또는 `apps/` + `libs/` | 하이브리드/크로스플랫폼 모노레포 |
|
|
|
|
### 정량적 통계 요약
|
|
- **소스코드 격리율**: 50개 중 **38개 (76.0%)**가 `src/` 계층 분리 채택.
|
|
- **테스트 격리율**: 50개 중 **34개 (68.0%)**가 `tests/` 또는 `test/` 루트 분리 채택.
|
|
- **빌드/배포 스크립트 명칭**: `build/` (48.0%), `eng/` (20.0%), `scripts/` (22.0%).
|
|
|
|
---
|
|
|
|
## 2. 소프트웨어 아키텍처 이론 및 방법론
|
|
|
|
### 1) Screaming Architecture (엉클 밥 로버트 마틴)
|
|
루트 디렉터리만 보아도 프레임워크가 아닌 **제품의 본질(Core 엔진, Browser 스니퍼, 데스크톱/모바일 셸)**이 드러나야 하며, 프로젝트 산재를 방지하기 위해 `src/` 내부에 도메인과 셸을 응집시킨다.
|
|
|
|
### 2) Hexagonal Architecture (Ports and Adapters - 앨리스터 코번)
|
|
- **Ports (Core)**: `Paca.Core` (HLS/DASH/yt-dlp 다운로드 엔진), `Paca.Browser` (WebView2 인터셉터), `Paca.Server` (Kestrel API).
|
|
- **Adapters (Shell)**: `Paca.App` (WPF), `Paca.Avalonia` (Linux/macOS), `Paca.Mobile` (MAUI Android/iOS), `Paca.Cli` (CLI).
|
|
|
|
### 3) Pitchfork Layout Convention
|
|
현대 C++/C# 프로젝트의 5대 표준 디렉터리 레일을 수립한다:
|
|
- `src/`: 실제 사용자에게 배포되는 제품 코드
|
|
- `tests/`: 단위/통합/E2E 테스트 스위트
|
|
- `build/`: 빌드, 인스톨러(Inno Setup), 릴리스 자동화 스크립트
|
|
- `docs/`: 명세서, 아키텍처 감사, 에이전트 계약 (SSOT)
|
|
- `site/`: Cloudflare Pages 정적 웹사이트 및 다운로드 가이드
|
|
|
|
### 4) Monorepo Blast Radius & CI 캐시 격리
|
|
`src/**`의 변경만 제품 빌드를 트리거하고, `docs/**`나 `site/**`의 변경은 정적 웹 배포만 트리거하여 CI 빌드 시간과 러너 자원을 90% 이상 절약한다.
|
|
|
|
---
|
|
|
|
## 3. 현행 vs 목표 디렉터리 비교 맵
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph Target ["목표 아키텍처 (Pitchfork Layout)"]
|
|
Root["videodownloader/"]
|
|
Root --> S["src/"]
|
|
Root --> T["tests/"]
|
|
Root --> B["build/"]
|
|
Root --> D["docs/"]
|
|
Root --> W["site/"]
|
|
Root --> F[".forgejo/ & .github/"]
|
|
|
|
S --> S1["Paca.Core"]
|
|
S --> S2["Paca.Browser"]
|
|
S --> S3["Paca.Server"]
|
|
S --> S4["Paca.App (WPF)"]
|
|
S --> S5["Paca.Avalonia"]
|
|
S --> S6["Paca.Cli"]
|
|
S --> S7["Paca.Mobile (MAUI)"]
|
|
S --> S8["Paca.Mobile.Core"]
|
|
|
|
T --> T1["Paca.Tests (1,140+ Tests)"]
|
|
end
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 단계별 마이그레이션 실행 절차 (Runbook)
|
|
|
|
### 1단계: 불필요한 임시 파일 정리 및 `.gitignore` 강화
|
|
- 삭제 대상: `.claude/`, `icon-candidates.html`, `site/icon-candidates.html`, `site/assets/icon_candidates/`
|
|
- 최신화된 `.gitignore`로 재발 방지.
|
|
|
|
### 2단계: 디렉터리 생성 및 프로젝트 이동
|
|
```powershell
|
|
New-Item -ItemType Directory -Force -Path "src", "tests"
|
|
Move-Item "Paca.Core", "Paca.Browser", "Paca.Server", "Paca.App", "Paca.Avalonia", "Paca.Cli", "Paca.Mobile", "Paca.Mobile.Core" -Destination "src\"
|
|
Move-Item "Paca.Tests" -Destination "tests\"
|
|
```
|
|
|
|
### 3단계: 솔루션 파일(`Paca.slnx`), `ProjectReference`, 빌드 스크립트 경로 동기화
|
|
1. **`Paca.slnx`**:
|
|
- `Path="Paca.Tests/...` -> `Path="tests/Paca.Tests/...`
|
|
- `Path="Paca....` -> `Path="src/Paca....`
|
|
2. **`tests/Paca.Tests/Paca.Tests.csproj`**:
|
|
- `Include="..\Paca.` -> `Include="..\..\src\Paca.`
|
|
3. **`build/publish.ps1` & `build/release.ps1`**:
|
|
- `Paca.App/Paca.App.csproj` -> `src/Paca.App/Paca.App.csproj`
|
|
- `Paca.Mobile/Paca.Mobile.csproj` -> `src/Paca.Mobile/Paca.Mobile.csproj`
|
|
4. **`AGENTS.md` & `launch-windows-apps.md`**:
|
|
- 빌드 바이너리 경로: `src\Paca.App\bin\Debug\net8.0-windows\win-x64\Paca.exe` 반영.
|
|
|
|
### 4단계: 무결성 검증 (TDD 폐쇄 루프)
|
|
- `dotnet build Paca.slnx` (빌드 오류 0건)
|
|
- `dotnet test tests/Paca.Tests` (1,140개 테스트 전체 통과 확인)
|
|
|
|
---
|
|
|
|
## 5. 자동화 마이그레이션 PowerShell 스크립트
|
|
|
|
```powershell
|
|
# D:\workspace\videodownloader 에서 실행
|
|
|
|
# 1. src / tests 폴더 생성
|
|
New-Item -ItemType Directory -Force -Path "src", "tests" | Out-Null
|
|
|
|
# 2. 8개 소스 프로젝트 이동
|
|
$srcProjs = @("Paca.Core","Paca.Browser","Paca.Server","Paca.App","Paca.Avalonia","Paca.Cli","Paca.Mobile","Paca.Mobile.Core")
|
|
foreach ($p in $srcProjs) {
|
|
if (Test-Path $p) { Move-Item -Path $p -Destination "src\$p" -Force }
|
|
}
|
|
|
|
# 3. 1개 테스트 프로젝트 이동
|
|
if (Test-Path "Paca.Tests") { Move-Item -Path "Paca.Tests" -Destination "tests\Paca.Tests" -Force }
|
|
|
|
# 4. Paca.slnx 경로 갱신
|
|
if (Test-Path "Paca.slnx") {
|
|
$c = Get-Content "Paca.slnx" -Raw
|
|
$c = $c -replace 'Path="Paca\.Tests/', 'Path="tests/Paca.Tests/'
|
|
$c = $c -replace 'Path="Paca\.', 'Path="src/Paca.'
|
|
Set-Content "Paca.slnx" -Value $c -Encoding UTF8
|
|
}
|
|
|
|
# 5. Paca.Tests.csproj ProjectReference 보정
|
|
$testCsproj = "tests\Paca.Tests\Paca.Tests.csproj"
|
|
if (Test-Path $testCsproj) {
|
|
$c = Get-Content $testCsproj -Raw
|
|
$c = $c -replace 'Include="\.\.\\Paca\.', 'Include="..\..\src\Paca.'
|
|
Set-Content $testCsproj -Value $c -Encoding UTF8
|
|
}
|
|
|
|
# 6. build 스크립트 경로 보정
|
|
$pub = "build\publish.ps1"
|
|
if (Test-Path $pub) {
|
|
$c = Get-Content $pub -Raw
|
|
$c = $c -replace 'Paca\.App[\\/]Paca\.App\.csproj', 'src/Paca.App/Paca.App.csproj'
|
|
Set-Content $pub -Value $c -Encoding UTF8
|
|
}
|
|
|
|
$rel = "build\release.ps1"
|
|
if (Test-Path $rel) {
|
|
$c = Get-Content $rel -Raw
|
|
$c = $c -replace 'Paca\.Mobile[\\/]Paca\.Mobile\.csproj', 'src/Paca.Mobile/Paca.Mobile.csproj'
|
|
Set-Content $rel -Value $c -Encoding UTF8
|
|
}
|
|
|
|
# 7. 검증
|
|
dotnet build Paca.slnx
|
|
dotnet test tests/Paca.Tests
|
|
```
|