docs
This commit is contained in:
@@ -91,6 +91,10 @@
|
|||||||
{
|
{
|
||||||
"selector": "ExportDefaultDeclaration",
|
"selector": "ExportDefaultDeclaration",
|
||||||
"message": "Default exports are forbidden. Use named exports instead."
|
"message": "Default exports are forbidden. Use named exports instead."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "TSInterfaceDeclaration[id.name=/^I[A-Z]/]",
|
||||||
|
"message": "Interface names should not start with 'I'. Use descriptive names without the 'I' prefix (e.g., 'LiverCompositor' instead of 'ILiveryCompositor')."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,207 +1,442 @@
|
|||||||
Frontend & Backend Output Shapes – Clean Architecture (Strict, Final)
|
Clean Architecture – Application Services, Use Cases, Ports, and Data Flow (Strict, Final)
|
||||||
|
|
||||||
This document defines the exact responsibilities, naming, and placement of all data shapes involved in delivering data from Core → API → Frontend UI.
|
This document defines the final, non-ambiguous Clean Architecture setup for the project.
|
||||||
|
|
||||||
It resolves all ambiguity around Presenters, View Models, DTOs, and Output Ports.
|
It explicitly covers:
|
||||||
There is no overlap of terminology across layers.
|
• Use Cases vs Application Services
|
||||||
|
• Input & Output Ports (and what does not exist)
|
||||||
|
• API responsibilities
|
||||||
|
• Frontend responsibilities
|
||||||
|
• Naming, placement, and dependency rules
|
||||||
|
• End-to-end flow with concrete paths and code examples
|
||||||
|
|
||||||
|
There are no hybrid concepts, no overloaded terms, and no optional interpretations.
|
||||||
|
|
||||||
⸻
|
⸻
|
||||||
|
|
||||||
1. Core Layer (Application / Use Cases)
|
1. Architectural Layers (Final)
|
||||||
|
|
||||||
Core Output Ports (formerly “Presenters”)
|
Domain → Business truth
|
||||||
|
Application → Use Cases + Application Services
|
||||||
|
Adapters → API, Persistence, External Systems
|
||||||
|
Frontend → UI, View Models, UX logic
|
||||||
|
|
||||||
In the Core, a Presenter is not a UI concept.
|
Only dependency-inward is allowed.
|
||||||
|
|
||||||
It is an Output Port that defines how a Use Case emits its result.
|
⸻
|
||||||
|
|
||||||
|
2. Domain Layer (Core / Domain)
|
||||||
|
|
||||||
|
What lives here
|
||||||
|
• Entities (classes)
|
||||||
|
• Value Objects (classes)
|
||||||
|
• Domain Services (stateless business logic)
|
||||||
|
• Domain Events
|
||||||
|
• Domain Errors / Invariants
|
||||||
|
|
||||||
|
What NEVER lives here
|
||||||
|
• DTOs
|
||||||
|
• Models
|
||||||
|
• Ports
|
||||||
|
• Use Cases
|
||||||
|
• Application Services
|
||||||
|
• Framework code
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
3. Application Layer (Core / Application)
|
||||||
|
|
||||||
|
The Application Layer has two distinct responsibilities:
|
||||||
|
1. Use Cases – business decisions
|
||||||
|
2. Application Services – orchestration of multiple use cases
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
4. Use Cases (Application / Use Cases)
|
||||||
|
|
||||||
|
Definition
|
||||||
|
|
||||||
|
A Use Case represents one business intent.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
• CreateLeague
|
||||||
|
• ApproveSponsorship
|
||||||
|
• CompleteDriverOnboarding
|
||||||
|
|
||||||
Rules
|
Rules
|
||||||
• Core Output Ports:
|
• A Use Case:
|
||||||
• define what data is emitted
|
• contains business logic
|
||||||
• do not store state
|
• enforces invariants
|
||||||
• do not expose getters
|
• operates on domain entities
|
||||||
• do not reference DTOs or View Models
|
• communicates ONLY via ports
|
||||||
• Core never pulls data back from an output port
|
• A Use Case:
|
||||||
• Core calls present() and stops
|
• does NOT orchestrate multiple workflows
|
||||||
|
• does NOT know HTTP, UI, DB, queues
|
||||||
|
|
||||||
Naming
|
Structure
|
||||||
• *OutputPort
|
|
||||||
• *Result (pure application result)
|
core/racing/application/use-cases/
|
||||||
|
└─ CreateLeagueUseCase.ts
|
||||||
|
|
||||||
Example
|
Example
|
||||||
|
|
||||||
export interface CompleteDriverOnboardingResult {
|
export class CreateLeagueUseCase {
|
||||||
readonly success: boolean;
|
constructor(
|
||||||
readonly driverId?: string;
|
private readonly leagueRepository: LeagueRepositoryPort,
|
||||||
readonly error?: string;
|
private readonly output: CreateLeagueOutputPort
|
||||||
|
) {}
|
||||||
|
|
||||||
|
execute(input: CreateLeagueInputPort): void {
|
||||||
|
// business rules & invariants
|
||||||
|
|
||||||
|
const league = League.create(input.name, input.maxMembers);
|
||||||
|
this.leagueRepository.save(league);
|
||||||
|
|
||||||
|
this.output.presentSuccess(league.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompleteDriverOnboardingOutputPort {
|
|
||||||
present(result: CompleteDriverOnboardingResult): void;
|
⸻
|
||||||
|
|
||||||
|
5. Ports (Application / Ports)
|
||||||
|
|
||||||
|
The Only Two Kinds of Ports
|
||||||
|
|
||||||
|
Everything crossing the Application boundary is a Port.
|
||||||
|
|
||||||
|
Input Ports
|
||||||
|
|
||||||
|
Input Ports describe what a use case needs.
|
||||||
|
|
||||||
|
export interface CreateLeagueInputPort {
|
||||||
|
readonly name: string;
|
||||||
|
readonly maxMembers: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
The Core does not know or care what happens after present() is called.
|
Rules:
|
||||||
|
• Interfaces only
|
||||||
|
• No behavior
|
||||||
|
• No validation logic
|
||||||
|
|
||||||
⸻
|
⸻
|
||||||
|
|
||||||
2. API Layer (Delivery / Adapter)
|
Output Ports
|
||||||
|
|
||||||
API Presenters (Response Mappers)
|
Output Ports describe how a use case emits outcomes.
|
||||||
|
|
||||||
API Presenters are Adapters.
|
export interface CreateLeagueOutputPort {
|
||||||
|
presentSuccess(leagueId: string): void;
|
||||||
|
presentFailure(reason: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
They:
|
Rules:
|
||||||
• implement Core Output Ports
|
• No return values
|
||||||
• translate Core Results into API Response DTOs
|
• No getters
|
||||||
• store response state temporarily for the controller
|
• No state
|
||||||
|
• Use methods, not result objects
|
||||||
|
|
||||||
They are not View Models.
|
⸻
|
||||||
|
|
||||||
|
6. Application Services (Application / Services)
|
||||||
|
|
||||||
|
Definition
|
||||||
|
|
||||||
|
An Application Service orchestrates multiple Use Cases.
|
||||||
|
|
||||||
|
It exists because:
|
||||||
|
• No single Use Case should know the whole workflow
|
||||||
|
• Orchestration is not business logic
|
||||||
|
|
||||||
Rules
|
Rules
|
||||||
• API Presenters:
|
• Application Services:
|
||||||
• implement a Core Output Port
|
• call multiple Use Cases
|
||||||
• map Core Results → API Responses
|
• define execution order
|
||||||
• may store state internally
|
• handle partial failure & compensation
|
||||||
• API Presenters must not:
|
• Application Services:
|
||||||
• contain business logic
|
• do NOT contain business rules
|
||||||
• reference frontend View Models
|
• do NOT modify entities directly
|
||||||
|
|
||||||
Naming
|
Structure
|
||||||
• *Presenter or *ResponseMapper
|
|
||||||
• Output types end with Response or ApiResponse
|
core/racing/application/services/
|
||||||
|
└─ LeagueSetupService.ts
|
||||||
|
|
||||||
|
Example (with Edge Cases)
|
||||||
|
|
||||||
|
export class LeagueSetupService {
|
||||||
|
constructor(
|
||||||
|
private readonly createLeague: CreateLeagueUseCase,
|
||||||
|
private readonly createSeason: CreateSeasonUseCase,
|
||||||
|
private readonly assignOwner: AssignLeagueOwnerUseCase,
|
||||||
|
private readonly notify: SendLeagueWelcomeNotificationUseCase
|
||||||
|
) {}
|
||||||
|
|
||||||
|
execute(input: LeagueSetupInputPort): void {
|
||||||
|
const leagueId = this.createLeague.execute(input);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.createSeason.execute({ leagueId });
|
||||||
|
this.assignOwner.execute({ leagueId, ownerId: input.ownerId });
|
||||||
|
this.notify.execute({ leagueId, ownerId: input.ownerId });
|
||||||
|
} catch (error) {
|
||||||
|
// compensation / rollback logic
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Edge cases that belong ONLY here:
|
||||||
|
• Partial failure handling
|
||||||
|
• Workflow order
|
||||||
|
• Optional steps
|
||||||
|
• Retry / idempotency logic
|
||||||
|
|
||||||
⸻
|
⸻
|
||||||
|
|
||||||
3. Frontend Layer (apps/website)
|
7. API Layer (apps/api)
|
||||||
|
|
||||||
View Models (UI-Owned, Final Form)
|
Responsibilities
|
||||||
|
• Transport (HTTP)
|
||||||
|
• Validation (request shape)
|
||||||
|
• Mapping to Input Ports
|
||||||
|
• Calling Application Services
|
||||||
|
• Adapting Output Ports
|
||||||
|
|
||||||
A View Model represents fully prepared UI state.
|
Structure
|
||||||
|
|
||||||
Only the frontend has Views — therefore only the frontend has View Models.
|
apps/api/leagues/
|
||||||
|
├─ LeagueController.ts
|
||||||
|
├─ presenters/
|
||||||
|
│ └─ CreateLeaguePresenter.ts
|
||||||
|
└─ dto/
|
||||||
|
├─ CreateLeagueRequestDto.ts
|
||||||
|
└─ CreateLeagueResponseDto.ts
|
||||||
|
|
||||||
Rules
|
|
||||||
• View Models:
|
|
||||||
• live only in apps/website
|
|
||||||
• accept API Response DTOs as input
|
|
||||||
• expose UI-ready data and helpers
|
|
||||||
• View Models must not:
|
|
||||||
• contain domain logic
|
|
||||||
• validate business rules
|
|
||||||
• perform side effects
|
|
||||||
• be sent back to the server
|
|
||||||
|
|
||||||
Naming
|
|
||||||
• *ViewModel
|
|
||||||
|
|
||||||
⸻
|
⸻
|
||||||
|
|
||||||
4. Website Presenters (DTO → ViewModel)
|
API Presenter (Adapter)
|
||||||
|
|
||||||
|
export class CreateLeaguePresenter implements CreateLeagueOutputPort {
|
||||||
|
private response!: CreateLeagueResponseDto;
|
||||||
|
|
||||||
|
presentSuccess(leagueId: string): void {
|
||||||
|
this.response = { success: true, leagueId };
|
||||||
|
}
|
||||||
|
|
||||||
|
presentFailure(reason: string): void {
|
||||||
|
this.response = { success: false, errorMessage: reason };
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponse(): CreateLeagueResponseDto {
|
||||||
|
return this.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
8. Frontend Layer (apps/website)
|
||||||
|
|
||||||
|
The frontend layer contains UI-specific data shapes. None of these cross into the Core.
|
||||||
|
|
||||||
|
There are three distinct frontend data concepts:
|
||||||
|
1. API DTOs (transport)
|
||||||
|
2. Command Models (user input / form state)
|
||||||
|
3. View Models (UI display state)
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
8.1 API DTOs (Transport Contracts)
|
||||||
|
|
||||||
|
API DTOs represent exact HTTP contracts exposed by the backend.
|
||||||
|
They are usually generated from OpenAPI or manually mirrored.
|
||||||
|
|
||||||
|
apps/website/lib/dtos/
|
||||||
|
└─ CreateLeagueResponseDto.ts
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
• Exact mirror of backend response
|
||||||
|
• No UI logic
|
||||||
|
• No derived values
|
||||||
|
• Never used directly by components
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
8.2 Command Models (User Input / Form State)
|
||||||
|
|
||||||
|
Command Models represent user intent before submission.
|
||||||
|
They are frontend-only and exist to manage:
|
||||||
|
• form state
|
||||||
|
• validation feedback
|
||||||
|
• step-based wizards
|
||||||
|
|
||||||
|
They are NOT:
|
||||||
|
• domain objects
|
||||||
|
• API DTOs
|
||||||
|
• View Models
|
||||||
|
|
||||||
|
apps/website/lib/commands/
|
||||||
|
└─ CreateLeagueCommandModel.ts
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
• Classes (stateful)
|
||||||
|
• May contain client-side validation
|
||||||
|
• May contain UX-specific helpers (step validation, dirty flags)
|
||||||
|
• Must expose a method to convert to an API Request DTO
|
||||||
|
|
||||||
|
Example responsibility:
|
||||||
|
• hold incomplete or invalid user input
|
||||||
|
• guide the user through multi-step flows
|
||||||
|
• prepare data for submission
|
||||||
|
|
||||||
|
Command Models:
|
||||||
|
• are consumed by components
|
||||||
|
• are passed into services
|
||||||
|
• are never sent directly over HTTP
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
8.3 View Models (UI Display State)
|
||||||
|
|
||||||
|
View Models represent fully prepared UI state after data is loaded.
|
||||||
|
|
||||||
|
apps/website/lib/view-models/
|
||||||
|
└─ CreateLeagueViewModel.ts
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
• Classes only
|
||||||
|
• UI logic allowed (formatting, labels, derived flags)
|
||||||
|
• No domain logic
|
||||||
|
• No mutation after construction
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
8.4 Website Presenters (DTO → ViewModel)
|
||||||
|
|
||||||
Website Presenters are pure mappers.
|
Website Presenters are pure mappers.
|
||||||
|
|
||||||
They:
|
export class CreateLeaguePresenter {
|
||||||
• convert API Response DTOs into View Models
|
present(dto: CreateLeagueResponseDto): CreateLeagueViewModel {
|
||||||
• perform formatting and reshaping
|
return new CreateLeagueViewModel(dto);
|
||||||
• are deterministic and side-effect free
|
}
|
||||||
|
}
|
||||||
|
|
||||||
They are not Core Presenters.
|
Rules:
|
||||||
|
|
||||||
Rules
|
|
||||||
• Input: API DTOs
|
• Input: API DTOs
|
||||||
• Output: View Models
|
• Output: View Models
|
||||||
• Must not:
|
• No side effects
|
||||||
• call APIs
|
• No API calls
|
||||||
• read storage
|
|
||||||
• perform decisions
|
|
||||||
|
|
||||||
⸻
|
⸻
|
||||||
|
|
||||||
5. API Client (Frontend)
|
8.5 Website Services (Orchestration)
|
||||||
|
|
||||||
The API Client is a thin HTTP layer.
|
|
||||||
|
|
||||||
Rules
|
|
||||||
• Sends HTTP requests
|
|
||||||
• Returns API DTOs only
|
|
||||||
• Must not:
|
|
||||||
• return View Models
|
|
||||||
• contain business logic
|
|
||||||
• format data for UI
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6. Website Services (Orchestration)
|
|
||||||
|
|
||||||
Website Services orchestrate:
|
Website Services orchestrate:
|
||||||
|
• Command Models
|
||||||
• API Client calls
|
• API Client calls
|
||||||
• Website Presenter mappings
|
• Presenter mappings
|
||||||
|
|
||||||
They are the only layer allowed to touch both.
|
export class LeagueService {
|
||||||
|
async createLeague(command: CreateLeagueCommandModel): Promise<CreateLeagueViewModel> {
|
||||||
|
const dto = await this.api.createLeague(command.toRequestDto());
|
||||||
|
return this.presenter.present(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rules
|
Rules:
|
||||||
• Services:
|
• Services accept Command Models
|
||||||
• call API Client
|
• Services return View Models
|
||||||
• call Website Presenters
|
• Components never call API clients directly
|
||||||
• return View Models only
|
|
||||||
• Components never touch API Client or DTOs
|
|
||||||
|
|
||||||
⸻
|
⸻
|
||||||
|
|
||||||
7. Final Data Flow (Unambiguous)
|
View Models (UI State)
|
||||||
|
|
||||||
Core Use Case
|
apps/website/lib/view-models/
|
||||||
→ OutputPort.present(Result)
|
└─ CreateLeagueViewModel.ts
|
||||||
|
|
||||||
API Presenter (Adapter)
|
export class CreateLeagueViewModel {
|
||||||
→ maps Result → ApiResponse
|
constructor(private readonly dto: CreateLeagueResponseDto) {}
|
||||||
|
|
||||||
API Controller
|
get message(): string {
|
||||||
→ returns ApiResponse (JSON)
|
return this.dto.success
|
||||||
|
? 'League created successfully'
|
||||||
|
: this.dto.errorMessage ?? 'Creation failed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Frontend API Client
|
Rules:
|
||||||
→ returns ApiResponse DTO
|
• Classes only
|
||||||
|
• UI logic allowed
|
||||||
|
• No domain logic
|
||||||
|
|
||||||
Website Presenter
|
⸻
|
||||||
→ maps DTO → ViewModel
|
|
||||||
|
Website Presenter (DTO → ViewModel)
|
||||||
|
|
||||||
|
export class CreateLeaguePresenter {
|
||||||
|
present(dto: CreateLeagueResponseDto): CreateLeagueViewModel {
|
||||||
|
return new CreateLeagueViewModel(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
Website Service (Orchestration)
|
||||||
|
|
||||||
|
export class LeagueService {
|
||||||
|
constructor(
|
||||||
|
private readonly api: LeaguesApiClient,
|
||||||
|
private readonly presenter: CreateLeaguePresenter
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createLeague(input: unknown): Promise<CreateLeagueViewModel> {
|
||||||
|
const dto = await this.api.createLeague(input);
|
||||||
|
return this.presenter.present(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
9. Full End-to-End Flow (Final)
|
||||||
|
|
||||||
UI Component
|
UI Component
|
||||||
→ consumes ViewModel
|
→ Website Service
|
||||||
|
→ API Client
|
||||||
|
→ HTTP Request DTO
|
||||||
|
→ API Controller
|
||||||
|
→ Application Service
|
||||||
|
→ Use Case(s)
|
||||||
|
→ Domain
|
||||||
|
→ Output Port
|
||||||
|
→ API Presenter
|
||||||
|
→ HTTP Response DTO
|
||||||
|
→ Website Presenter
|
||||||
|
→ View Model
|
||||||
|
→ UI
|
||||||
|
|
||||||
|
|
||||||
⸻
|
⸻
|
||||||
|
|
||||||
8. Terminology Rules (Strict)
|
10. Final Non-Negotiable Rules
|
||||||
|
• Core knows ONLY Ports + Domain
|
||||||
Term Layer Meaning
|
• Core has NO Models, DTOs, or ViewModels
|
||||||
OutputPort Core Use case output contract
|
• API talks ONLY to Application Services
|
||||||
Result Core Pure application result
|
• Controllers NEVER call Use Cases directly
|
||||||
Presenter (API) apps/api Maps Result → API Response
|
• Frontend Components see ONLY View Models
|
||||||
Response / ApiResponse apps/api HTTP transport shape
|
• DTOs never cross into UI components
|
||||||
Presenter (Website) apps/website Maps DTO → ViewModel
|
|
||||||
ViewModel apps/website UI-ready state
|
|
||||||
|
|
||||||
No term is reused with a different meaning.
|
|
||||||
|
|
||||||
⸻
|
⸻
|
||||||
|
|
||||||
9. Non-Negotiable Rules
|
11. Final Merksatz
|
||||||
• Core has no DTOs
|
|
||||||
• Core has no View Models
|
|
||||||
• API has no View Models
|
|
||||||
• Frontend has no Core Results
|
|
||||||
• View Models exist only in the frontend
|
|
||||||
• Presenters mean different things per layer, but:
|
|
||||||
• Core = Output Port
|
|
||||||
• API = Adapter
|
|
||||||
• Website = Mapper
|
|
||||||
|
|
||||||
⸻
|
Use Cases decide.
|
||||||
|
Application Services orchestrate.
|
||||||
|
Adapters translate.
|
||||||
|
UI presents.
|
||||||
|
|
||||||
10. Final Merksatz
|
If a class violates more than one of these roles, it is incorrectly placed.
|
||||||
|
|
||||||
The Core emits results.
|
|
||||||
The API transports them.
|
|
||||||
The Frontend interprets them.
|
|
||||||
|
|
||||||
If a type tries to do more than one of these — it is incorrectly placed.
|
|
||||||
115
docs/architecture/FILE_STRUCTURE.md
Normal file
115
docs/architecture/FILE_STRUCTURE.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# File Structure
|
||||||
|
|
||||||
|
## Core
|
||||||
|
|
||||||
|
```
|
||||||
|
core/ # * Business- & Anwendungslogik (framework-frei)
|
||||||
|
├── shared/ # * Gemeinsame Core-Bausteine
|
||||||
|
│ ├── domain/ # * Domain-Basistypen
|
||||||
|
│ │ ├── Entity.ts # * Basisklasse für Entities
|
||||||
|
│ │ ├── ValueObject.ts # * Basisklasse für Value Objects
|
||||||
|
│ │ └── DomainError.ts # * Domain-spezifische Fehler
|
||||||
|
│ └── application/
|
||||||
|
│ └── ApplicationError.ts # * Use-Case-/Application-Fehler
|
||||||
|
│
|
||||||
|
├── racing/ # * Beispiel-Domain (Bounded Context)
|
||||||
|
│ ├── domain/ # * Fachliche Wahrheit
|
||||||
|
│ │ ├── entities/ # * Aggregate Roots & Entities
|
||||||
|
│ │ │ ├── League.ts # * Aggregate Root
|
||||||
|
│ │ │ └── Race.ts # * Entity
|
||||||
|
│ │ ├── value-objects/ # * Unveränderliche Fachwerte
|
||||||
|
│ │ │ └── LeagueName.ts # * Beispiel VO
|
||||||
|
│ │ ├── services/ # * Domain Services (Regeln, kein Ablauf)
|
||||||
|
│ │ │ └── ChampionshipScoringService.ts # * Regel über mehrere Entities
|
||||||
|
│ │ └── errors/ # * Domain-Invariantenfehler
|
||||||
|
│ │ └── RacingDomainError.ts
|
||||||
|
│ │
|
||||||
|
│ └── application/ # * Anwendungslogik
|
||||||
|
│ ├── ports/ # * EINZIGE Schnittstellen des Cores
|
||||||
|
│ │ ├── input/ # * Input Ports (Use-Case-Grenzen)
|
||||||
|
│ │ │ └── CreateLeagueInputPort.ts
|
||||||
|
│ │ └── output/ # * Output Ports (Use-Case-Ergebnisse)
|
||||||
|
│ │ └── CreateLeagueOutputPort.ts
|
||||||
|
│ │
|
||||||
|
│ ├── use-cases/ # * Einzelne Business-Intents
|
||||||
|
│ │ └── CreateLeagueUseCase.ts
|
||||||
|
│ │
|
||||||
|
│ └── services/ # * Application Services (Orchestrierung)
|
||||||
|
│ └── LeagueSetupService.ts # * Koordiniert mehrere Use Cases
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adapters
|
||||||
|
|
||||||
|
```
|
||||||
|
adapters/ # * Alle äußeren Implementierungen
|
||||||
|
├── persistence/ # * Datenhaltung
|
||||||
|
│ ├── typeorm/ # Konkrete DB-Technologie
|
||||||
|
│ │ ├── entities/ # ORM-Entities (nicht Domain!)
|
||||||
|
│ │ └── repositories/ # * Implementieren Core-Ports
|
||||||
|
│ │ └── LeagueRepository.ts
|
||||||
|
│ └── inmemory/ # Test-/Dev-Implementierungen
|
||||||
|
│ └── LeagueRepository.ts
|
||||||
|
│
|
||||||
|
├── notifications/ # Externe Systeme
|
||||||
|
│ └── EmailNotificationAdapter.ts # Implementiert Notification-Port
|
||||||
|
│
|
||||||
|
├── logging/ # Logging / Telemetrie
|
||||||
|
│ └── ConsoleLoggerAdapter.ts # Adapter für Logger-Port
|
||||||
|
│
|
||||||
|
└── bootstrap/ # Initialisierung / Seeding
|
||||||
|
└── EnsureInitialData.ts # App-Start-Logik
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```
|
||||||
|
apps/api/ # * Delivery Layer (HTTP)
|
||||||
|
├── app.module.ts # * Framework-Zusammenbau
|
||||||
|
│
|
||||||
|
├── leagues/ # * Feature-Modul
|
||||||
|
│ ├── LeagueController.ts # * HTTP → Application Service
|
||||||
|
│ │
|
||||||
|
│ ├── dto/ # * Transport-DTOs (HTTP)
|
||||||
|
│ │ ├── CreateLeagueRequestDto.ts # * Request-Shape
|
||||||
|
│ │ └── CreateLeagueResponseDto.ts # * Response-Shape
|
||||||
|
│ │
|
||||||
|
│ └── presenters/ # * Output-Port-Adapter
|
||||||
|
│ └── CreateLeaguePresenter.ts # * Core Output → HTTP Response
|
||||||
|
│
|
||||||
|
└── shared/ # API-spezifisch
|
||||||
|
└── filters/ # Exception-Handling
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
```
|
||||||
|
apps/website/ # * Frontend (UI)
|
||||||
|
├── app/ # * Next.js Routen
|
||||||
|
│ └── leagues/ # * Page-Level
|
||||||
|
│ └── page.tsx
|
||||||
|
│
|
||||||
|
├── components/ # * Reine UI-Komponenten
|
||||||
|
│ └── LeagueForm.tsx
|
||||||
|
│
|
||||||
|
├── lib/
|
||||||
|
│ ├── api/ # * HTTP-Client
|
||||||
|
│ │ └── LeaguesApiClient.ts # * Gibt NUR API DTOs zurück
|
||||||
|
│ │
|
||||||
|
│ ├── dtos/ # * API-Vertrags-Typen
|
||||||
|
│ │ └── CreateLeagueResponseDto.ts
|
||||||
|
│ │
|
||||||
|
│ ├── commands/ # * Command Models (Form State)
|
||||||
|
│ │ └── CreateLeagueCommandModel.ts
|
||||||
|
│ │
|
||||||
|
│ ├── presenters/ # * DTO → ViewModel Mapper
|
||||||
|
│ │ └── CreateLeaguePresenter.ts
|
||||||
|
│ │
|
||||||
|
│ ├── view-models/ # * UI-State
|
||||||
|
│ │ └── CreateLeagueViewModel.ts
|
||||||
|
│ │
|
||||||
|
│ ├── services/ # * Frontend-Orchestrierung
|
||||||
|
│ │ └── LeagueService.ts
|
||||||
|
│ │
|
||||||
|
│ └── blockers/ # UX-Schutz (Throttle, Submit)
|
||||||
|
│ ├── SubmitBlocker.ts
|
||||||
|
│ └── ThrottleBlocker.ts
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user