Dela via


Utforma valideringar i domänmodelllagret

Tips/Råd

Det här innehållet är ett utdrag från eBook, .NET Microservices Architecture for Containerized .NET Applications, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.

Miniatyrbild av omslag för eBooken om .NET mikroservicearkitektur för containerbaserade .NET-applikationer.

I DDD kan valideringsregler betraktas som invarianter. Huvudansvaret för ett aggregat är att upprätthålla invarianter vid tillståndsändringar för alla entiteter inom detta aggregat.

Domänentiteter ska alltid vara giltiga entiteter. Det finns ett visst antal invarianter för ett objekt som alltid ska vara sant. Ett orderobjekt måste till exempel alltid ha en kvantitet som måste vara ett positivt heltal, plus ett artikelnamn och pris. Därför är tillämpning av invarians ansvaret för domänobjekten (särskilt aggregeringsroten), och ett entitetsobjekt kan inte existera utan att vara giltigt. Invarianta regler uttrycks helt enkelt som kontrakt, och undantag eller meddelanden genereras när de överträds.

Resonemanget bakom detta är att många buggar inträffar eftersom objekt är i ett tillstånd som de aldrig borde ha varit i.

Låt oss föreslå att vi nu har en "SendUserCreationEmailService" som tar en "UserProfile" ... hur kan vi motivera i den tjänsten att Namn inte är null? Ska vi kontrollera det igen? Eller mer troligt ... du bryr dig bara inte om att kontrollera och "hoppas på det bästa" — du hoppas att någon brytt sig om att validera det innan den skickas till dig. Naturligtvis är ett av de första testerna vi bör skriva när vi använder TDD att om jag skickar en kund med ett namn som är null, ska det leda till ett fel. Men när vi börjar skriva den här typen av tester om och om igen inser vi ... "Tänk om vi aldrig tillät att namnet blev null? vi skulle inte ha alla dessa tester!".

Implementera valideringar i domänmodelllagret

Valideringar implementeras vanligtvis i domänentitetskonstruktorer eller i metoder som kan uppdatera entiteten. Det finns flera sätt att implementera valideringar, till exempel att verifiera data och skapa undantag om verifieringen misslyckas. Det finns också mer avancerade mönster som att använda specifikationsmönstret för valideringar och meddelandemönstret för att returnera en samling fel i stället för att returnera ett undantag för varje validering när det sker.

Verifiera villkor och utlösa undantag

Följande kodexempel visar den enklaste metoden för validering i en domänentitet genom att skapa ett undantag. I referenstabellen i slutet av det här avsnittet kan du se länkar till mer avancerade implementeringar baserat på de mönster som vi har diskuterat tidigare.

public void SetAddress(Address address)
{
    _shippingAddress = address?? throw new ArgumentNullException(nameof(address));
}

Ett bättre exempel skulle visa behovet av att se till att antingen det interna tillståndet inte ändrades eller att alla mutationer för en metod inträffade. Följande implementering skulle till exempel lämna objektet i ett ogiltigt tillstånd:

public void SetAddress(string line1, string line2,
    string city, string state, int zip)
{
    _shippingAddress.line1 = line1 ?? throw new ...
    _shippingAddress.line2 = line2;
    _shippingAddress.city = city ?? throw new ...
    _shippingAddress.state = (IsValid(state) ? state : throw new …);
}

Om värdet för delstaten är ogiltigt har den första adressraden och staden redan ändrats. Det kan göra adressen ogiltig.

En liknande metod kan användas i entitetens konstruktor, vilket skapar ett undantag för att se till att entiteten är giltig när den har skapats.

Använda valideringsattribut i modellen baserat på dataanteckningar

Dataanteckningar, till exempel attributen Required eller MaxLength, kan användas för att konfigurera EF Core-databasfältegenskaper, som beskrivs i detalj i avsnittet Tabellmappning , men de fungerar inte längre för entitetsverifiering i EF Core (inte metoden heller IValidatableObject.Validate ), som de har gjort sedan EF 4.x i .NET Framework.

Datakommentarer och IValidatableObject gränssnittet kan fortfarande användas för modellverifiering under modellbindningen, före kontrollerns åtgärdsanrop som vanligt, men den modellen är avsedd att vara en ViewModel eller DTO och det är en MVC- eller API-angelägenhet, inte ett domänmodellproblem.

När du har gjort den konceptuella skillnaden tydlig kan du fortfarande använda dataanteckningar och IValidatableObject i entitetsklassen för validering, om dina åtgärder tar emot en entitetsklassobjektparameter, vilket inte rekommenderas. I så fall sker validering vid modellbindning, precis innan du anropar åtgärden och du kan kontrollera kontrollantens egenskap ModelState.IsValid för att kontrollera resultatet, men det händer sedan igen i kontrollanten, inte innan du bevarar entitetsobjektet i DbContext, som det hade gjort sedan EF 4.x.

Du kan fortfarande implementera anpassad validering i entitetsklassen med hjälp av dataanteckningar och IValidatableObject.Validate-metoden, genom att åsidosätta SaveChanges-metoden i DbContext.

Du kan se en exempelimplementering för validering av IValidatableObject entiteter i den här kommentaren på GitHub. Det exemplet utför inte attributbaserade valideringar, men de bör vara enkla att implementera med återspegling i samma åsidosättningsmetod.

Men från en DDD-synvinkel hålls domänmodellen bäst lean med hjälp av undantag i din entitets beteendemetoder, eller genom att implementera specifikations- och meddelandemönstren för att framtvinga verifieringsregler.

Det kan vara klokt att använda dataanteckningar på programlagret i ViewModel-klasser (i stället för domänentiteter) som accepterar indata för att tillåta modellverifiering i användargränssnittslagret. Detta bör dock inte göras med undantag för validering i domänmodellen.

Verifiera entiteter genom att implementera specifikationsmönstret och meddelandemönstret

Slutligen är en mer detaljerad metod för att implementera valideringar i domänmodellen genom att implementera specifikationsmönstret tillsammans med meddelandemönstret, som beskrivs i några av de ytterligare resurser som anges senare.

Det är värt att nämna att du också bara kan använda ett av dessa mönster, till exempel validera manuellt med kontrollinstruktioner, men använda meddelandemönstret för att stapla och returnera en lista över valideringsfel.

Använda uppskjuten validering i domänen

Det finns olika metoder för att hantera uppskjutna valideringar i domänen. I sin bok Implementing Domain-Driven Design diskuterar Vaughn Vernon dessa i avsnittet om validering.

Tvåstegsverifiering

Överväg även tvåstegsvalidering. Använd validering på fältnivå på kommandot Data Transfer Objects (DTOs) och validering på domännivå i dina entiteter. Du kan göra detta genom att returnera ett resultatobjekt i stället för undantag för att göra det enklare att hantera valideringsfelen.

Om du till exempel använder fältverifiering med dataanteckningar duplicerar du inte verifieringsdefinitionen. Körningen kan dock vara både på serversidan och på klientsidan när det gäller DTU:er (kommandon och ViewModels, till exempel).

Ytterligare resurser