Response Contract Namenskonventionen: Cross-Layer Lesbarkeit für Menschen und AI
Dezember 10, 2025 | Techartikel | Autor: Ahmed Saleh
Data Contracts sind gute Kandidaten dafür, chaotisch zu werden, da sie verschiedene Formen haben. Hier etablieren wir Konventionen, die dabei helfen, die meisten verschiedenen Formen abzudecken und dabei Klarheit und Konsistenz zu bewahren.
Solide Konventionen dafür bieten besseres Verständnis und helfen AI Agents dabei, das Pattern zu erkennen und ähnliche Use-Cases zu implementieren, ohne sie durcheinander zu bringen.
1. WAS MAN NICHT MACHEN SOLLTE: SCHLECHTE NAMENS-ANTI-PATTERNS
1.1 MEHRDEUTIGER ZWECK
Namen wie FacilityModel, FacilityData, FacilityOutput geben keinen Hinweis darauf, ob es sich um eine Zusammenfassung, Detailansicht oder spezialisiertes Ergebnis handelt.
// SCHLECHT - Unklar, was das darstellt
public class FacilityModel { }
// GUT - Klarer Zweck
public class SingleFacilityResponse { }
1.2 ÜBERLADEN ÜBER ENDPOINTS
Den gleichen DTO-Namen für verschiedene API-Responses verwenden (z.B. FacilityResponse für sowohl List- als auch Detail-Endpoints).
// SCHLECHT - Gleicher Name für verschiedene Zwecke
public class FacilityResponse { } // Wird für sowohl Single als auch List verwendet
// GUT - Unterschiedliche Namen für unterschiedliche Zwecke
public class SingleFacilityResponse { } // Einzelne Entity
public class FacilityListResponse { } // List Wrapper
1.3 AN REQUEST-VERBEN GEKOPPELT
Namen wie GetFacilityResponse, FetchFacilityOutput — macht es so, als wäre es an einen Endpoint gebunden, anstatt ein wiederverwendbarer Contract zu sein.
// SCHLECHT - An spezifisches HTTP Verb/Aktion gebunden
public class GetFacilityResponse { }
// GUT - Wiederverwendbarer Contract
public class SingleFacilityResponse { }
1.4 ZU IMPLEMENTATION-ORIENTIERT
Namen, die technische oder Persistence-Details offenlegen (z.B. FacilityDbRow, FacilityElasticResult).
// SCHLECHT - Legt Implementation-Details offen
public class FacilityDbRow { }
public class FacilityElasticResult { }
// GUT - Fokussiert auf Business-Zweck
public class SingleFacilityResponse { }
1.5 VERSIONIERT IM NAMEN
FacilityResponseV2 oder NewFacilityOutput — altert schlecht und erzeugt Namens-Chaos.
// SCHLECHT - Versionierung im Namen erzeugt Chaos
public class FacilityResponseV2 { }
public class NewFacilityOutput { }
// GUT - Verwende ordentliche Versionierungs-Strategien
public class SingleFacilityResponse { } // Aktuelle Version
// Handle Versions durch API-Versionierung, nicht durch Namensgebung
1.6 ABGEKÜRZT ODER UNKLAR
Akronyme oder Kurzschreibweisen wie FacResp, FcltyDto, die nicht universell offensichtlich sind.
// SCHLECHT - Unklare Abkürzungen
public class FacResp { }
public class FcltyDto { }
// GUT - Klare, lesbare Namen
public class SingleFacilityResponse { }
public class FacilityDto { }
2. NAMENS-KOMPONENTEN
Unsere Konvention verwendet diese Komponenten in verschiedenen Kombinationen:
- Context: Single, Detailed, B2C, B2B, etc.
- ModelName: Der Domain-Entity-Name (z.B. Facility, Product)
- Extension: Summary, List, PagedList, With[RelatedEntity], etc.
- Suffix: Response, Dto, ReadModel (optional)
Hinweis: Komponenten werden basierend auf dem spezifischen Response-Typ und Zweck kombiniert, nicht immer in der gleichen Reihenfolge.
3. KONVENTIONEN & BEISPIELE
3.1 EINZELNE ENTITY RESPONSES
Namens-Pattern: [Single][ModelName]Response
SingleFacilityResponse
// Verwendung: GET /facilities/{id} gibt SingleFacilityResponse zurück
public class SingleFacilityResponse
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public int TotalRooms { get; set; }
public DateTime CreatedAt { get; set; }
// andere Properties...
}
3.2 ZUSAMMENGEFASSTE ENTITY RESPONSES
Namens-Pattern: [ModelName][Summary]
FacilityResponse (Summary)
// Verwendung: GET /facilities gibt IEnumerable<FacilityResponse> zurück
public class FacilityResponse
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
// andere Properties...
}
3.3 LIST RESPONSES
Namens-Pattern: [ModelName][List]
FacilityList
// Verwendung: GET /facilities gibt FacilityList zurück
public class FacilityList
{
public IEnumerable<FacilityResponse> Data { get; set; }
}
3.4 PAGED LIST RESPONSES
Namens-Pattern: [ModelName][Paged][List]
FacilityPagedList
// Verwendung: GET /facilities?page=1&size=20 gibt FacilityPagedList zurück
public class FacilityPagedList : PaginatedList<FacilityResponse>
{
}
// Generische Basis-Klasse für Wiederverwendbarkeit
public class PaginatedList<T>
{
public IEnumerable<T> Data { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public int TotalPages { get; set; }
}
3.5 CONTEXT-AWARE EXTENDED RESPONSES
Namens-Pattern: [Single][ModelName][Context][Response]
SingleProductB2CResponse
// Verwendung: GET /products/{id}?context=b2c gibt SingleProductB2CResponse zurück
public class SingleProductB2CResponse
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal RetailPrice { get; set; }
public string CustomerFriendlyDescription { get; set; }
public List<string> CustomerBenefits { get; set; }
// andere Properties...
}
SingleProductB2BResponse
// Verwendung: GET /products/{id}?context=b2b gibt SingleProductB2BResponse zurück
public class SingleProductB2BResponse
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal WholesalePrice { get; set; }
public string TechnicalSpecifications { get; set; }
public List<string> BusinessFeatures { get; set; }
// andere Properties...
}
3.6 MANAGED AGGREGATES & REPORTS
Namens-Pattern: [ModelName][WithRelatedEntity][List] oder [ModelName][WithRelatedEntity][Report]
FacilityWithRoomsRenovationList
// Verwendung: GET /facilities/reports/rooms-renovation gibt FacilityWithRoomsRenovationList zurück
public class FacilityWithRoomsRenovationList
{
public IEnumerable<FacilityWithRoomsRenovationData> Data { get; set; }
}
public class FacilityWithRoomsRenovationData
{
public Guid FacilityId { get; set; }
public string FacilityName { get; set; }
public int RoomsRequiringRenovation { get; set; }
public decimal TotalSquareMeters { get; set; }
// andere Properties...
}
FacilityWithRoomsRenovationReport
// Verwendung: GET /facilities/reports/rooms-renovation gibt FacilityWithRoomsRenovationReport zurück
public class FacilityWithRoomsRenovationReport
{
public IEnumerable<FacilityWithRoomsRenovationData> Facilities { get; set; }
public int TotalFacilities { get; set; }
public int TotalRoomsRequiringRenovation { get; set; }
public decimal TotalSquareMeters { get; set; }
public DateTime ReportGeneratedAt { get; set; }
// andere Properties...
}
3.7 ALTERNATIVE PLURAL-FORMEN
Namens-Pattern: [ModelName|PL|][WithRelatedEntity] (wenn kein Suffix Pluralität impliziert)
FacilityWithRoomsRenovationReport
FacilitiesWithRoomsRenovation
// Verwendung: GET /facilities/reports/rooms-renovation gibt FacilitiesWithRoomsRenovation zurück
public class FacilitiesWithRoomsRenovation
{
public IEnumerable<FacilityWithRoomsRenovationData> Data { get; set; }
}
4. CLEAN ARCHITECTURE PRINZIPIEN: DTO CONTRACT TRENNUNG ÜBER LAYERS
4.1 DIE DEPENDENCY RULE FÜR DATA CONTRACTS
Clean Architecture Prinzipien folgend sollten DTOs und Data Contracts nie über architektonische Grenzen hinweg durchsickern. Jeder Layer sollte seine eigenen Data Transfer Objects definieren, die seinem spezifischen Zweck dienen, das Dependency Inversion Principle beibehalten und verhindern, dass Infrastructure-Concerns Business Logic kontaminieren.
4.2 LAYER CONTRACT VERANTWORTLICHKEITEN
Domain Layer
- Zweck: Reine Business Logic und Entities
- Contracts: Domain Entities, Value Objects, Domain Services
- Regel: Keine externen Dependencies, keine Infrastructure-Concerns
Application Layer
- Zweck: Use Cases, Application Services, Orchestration
- Contracts: Commands, Queries, Application DTOs, Response Contracts
- Regel: Hängt nur von Domain ab, definiert Input/Output Contracts
Infrastructure Layer
- Zweck: Externe Concerns (Databases, APIs, Messaging)
- Contracts: Database Entities, External API Models, Persistence Models
- Regel: Implementiert Interfaces, die vom Application Layer definiert werden
Presentation Layer
- Zweck: User Interface, API Controllers, Views
- Contracts: View Models, API Request/Response DTOs
- Regel: Hängt von Application Layer Contracts ab, nicht von Infrastructure
4.3 CONTRACT TRANSFORMATION STRATEGIE
Wenn Data zwischen Layers fließt, verwende explizite Transformation anstatt geteilte Contracts:
// Domain Entity (reine Business Logic)
public class Facility
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public Address Address { get; private set; }
public void UpdateName(string newName)
{
if (string.IsNullOrWhiteSpace(newName))
throw new ArgumentException("Name cannot be empty");
Name = newName;
}
}
// Application Response Contract
public class SingleFacilityResponse
{
public Guid Id { get; set; }
public string Name { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string State { get; set; }
}
// Infrastructure Persistence Model
public class FacilityDbEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
4.4 MAPPING-STRATEGIEN ZWISCHEN LAYER CONTRACTS
Wenn du Data zwischen Layers transformierst, verwende explizites Mapping anstatt geteilte Contracts. Hier sind häufige Ansätze:
AutoMapper/Mappster (Library-basiertes Mapping)
// Mapping Profile konfigurieren
CreateMap<FacilityDbEntity, SingleFacilityResponse>();
// Verwendung: _mapper.Map<SingleFacilityResponse>(dbEntity)
Implicit Operators (Sprach-Feature)
public static implicit operator SingleFacilityResponse(FacilityDbEntity entity)
// Verwendung: SingleFacilityResponse response = dbEntity;
Manual Assignment (Manuelles Property-Kopieren)
return new SingleFacilityResponse { Id = entity.Id, Name = entity.Name };
4.5 VORTEILE DER CONTRACT-TRENNUNG
- Wartbarkeit: Änderungen in einem Layer beeinflussen andere nicht
- Testbarkeit: Jeder Layer kann isoliert getestet werden
- Flexibilität: Infrastructure kann sich ändern, ohne Business Logic zu beeinflussen
- Klarheit: Klare Grenzen und Verantwortlichkeiten
- Evolution: Jeder Layer kann sich unabhängig entwickeln
4.6 WANN MAN DIE REGEL BRECHEN SOLLTE (PRAGMATISCHE ÜBERLEGUNGEN)
Während strikte Trennung ideal ist, gibt es pragmatische Szenarien, wo etwas Contract-Wiederverwendung akzeptabel sein könnte:
- Read-only DTOs: Wenn der Contract dem gleichen Zweck über Layers hinweg dient
- Geteilte Konstanten: Enums, Status Codes oder Konfigurations-Werte
- Cross-cutting Concerns: Logging, Metrics oder Audit-Informationen
Aber bevorzuge immer explizite Transformation über geteilte Contracts, wenn du unsicher bist.
4.7 PRAKTISCHES BEISPIEL: BEWUSSTES REGEL-BRECHEN
Manchmal ist der pragmatische Ansatz, ein geteiltes Responses-Projekt zu erstellen, das sowohl Application- als auch Presentation-Layer referenzieren können:
4.8 TIMELINE-VERGLEICH
Timeline 1: Explizite Isolation mit Mapping
Abbildung 1: Explizite Isolation: Data Flow Timeline
Timeline 2: Geteilte Responses (Pragmatischer Ansatz)
Abbildung 2: Geteilte Responses: Data Flow Timeline
Hinweis: Beachte den reduzierten Data Transformation Lane – nur ein Mapping-Schritt (DB → Shared Response) anstatt zwei separate Transformationen. Das eliminiert die Notwendigkeit, zwischen Application- und Presentation-DTOs zu mappen, reduziert Komplexität und potenzielle Mapping-Fehler.
Ihr Partner für erfolgreiche Webapplikationen
Sie planen eine Webapplikation und suchen einen erfahrenen Partner für die Umsetzung? Wir stehen Ihnen mit unserem Know-how zur Seite – von der ersten Idee bis zur finalen Implementierung. Lassen Sie uns gemeinsam Ihre Vision verwirklichen! KONTAKTIEREN SIE UNS5. Zusammenfassung
Es gibt keine Einheitslösung für alle. Jedes Team kann seine eigenen Namens-Patterns definieren, solange sie klar, konsistent, ausdrucksstark und erweiterbar bleiben. Das Ziel ist es, Contracts sowohl für Menschen als auch für AI lesbar zu halten und dabei an reale Bedürfnisse anzupassen.
Wichtige Prinzipien:
- Konsistenz: Verwende das gleiche Pattern über ähnliche Response-Typen hinweg
- Klarheit: Namen sollten selbsterklärend sein
- Erweiterbarkeit: Patterns sollten zukünftige Variationen ermöglichen
- Eindeutigkeit: Vermeide Namenskonflikte in Namespaces/Packages
Denk dran: Gute Namensgebung bedeutet klare Kommunikation. Deine Contracts sollten für sich selbst sprechen, ohne dass Entwickler in Implementation-Details oder Dokumentation graben müssen.