.NET Core: Result-Pattern

Einleitung
In dem Artikel "ASP.NET Core 8: IExceptionHandler für APIs" wurde die Fehlerbehandlung durch Exception Handling präzisiert. Unter Entwicklern ist die Methode der Fehlerbehandlung ein intensiv diskutiertes Thema.
Es gibt drei Varianten der Fehlerbehandlung. Eine davon ist die Rückgabe von Null bei einem Funktionsaufruf, was nur angewendet werden sollte, wenn der Fehler klar definiert ist, wie zum Beispiel beim Laden eines Datensatzes aus der Datenbank (vorhanden/nicht vorhanden).
public Task<object?> GetCompanyAsync(Guid id) {
return Repository.GetOrDefaultAsync(id);
}
Die zweite Methode ist das Auslösen von Ausnahmen (Exceptions). Auf vielen Plattformen wird davon abgeraten, Exceptions zu verwenden, insbesondere wenn sie dazu dienen, den Programmfluss zu steuern.
Zum Beispiel wird anstelle der Verwendung von File.Exists
versucht, die Datei zu öffnen, und im Falle einer Ausnahme wird der Pfad zur Erstellung der Datei verfolgt.
Ein Punkt, den ich noch nicht geprüft habe, ist die Leistung. Es wird behauptet, dass das Auslösen von Ausnahmen zeitaufwendig ist.
public void CancleTicket(Ticket ticket) {
if(ticket.state != 0) {
throw new Exception("Ticket muss zum stornieren geöffnet sein.");
}
}
Eine dritte Methode zur Fehlerbehandlung ist das Result-Pattern.
Result-Pattern
Das Result-Pattern gibt den Fehler als Rückgabewert weiter. Dazu sind zunächst grundlegende Klassen erforderlich.
public sealed record Error(string? Code, string Description)
{
public static Error NotFound(object id) => new Error("404", $"Entity with Id {id} not found");
}
public class Result
{
protected Result(bool isSuccess, Error? error)
{
if (isSuccess ^ error == null)
{
throw new ArgumentException("Invalid error", nameof(error));
}
IsSuccess = isSuccess;
Error = error;
}
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public Error? Error { get; }
public static Result Success() => new(true, null);
public static Result Failure(Error error) => new(false, error);
}
public class Result<TValue> : Result
{
protected Result(TValue value, bool isSuccess, Error? error) : base(isSuccess, error)
{
Value = value;
}
public TValue Value { get; }
public static Result Success(TValue result) => new Result<TValue>(result, true, null);
public static Result Failure(TValue result, Error error) => new Result<TValue>(result, false, error);
}
Anschließend können die zuvor erwähnten Klassen eingesetzt werden.
public Result CancleTicket(Ticket ticket) {
if(ticket.state != 0) {
return Result.Failure(new Error(null, "Ticket muss zum stornieren geöffnet sein."));
}
}
Für Asp.Net Core muss die genannte Implementierung angepasst werden. Bei Minimal APIs können Werte durch statische Methoden in Results
(IResult
) zurückgegeben werden, wodurch ein entsprechender HTTP-Statuscode generiert wird. Bei einer eigenen Implementierung müssen die Statuscodes manuell zugeordnet werden.
Bestehende Bibliotheken
Bei einfachen Implementierungsaufgaben, die nur wenige Stunden in Anspruch nehmen, bevorzuge ich es, diese selbst durchzuführen. Die Verwendung externer Bibliotheken kann das Risiko von Angriffen und Abhängigkeiten erhöhen. Dennoch möchte ich hier zwei Bibliotheken erwähnen.