Zwergenland.CleanDomainValidation 1.0.0-alpha-hf-01

This is a prerelease version of Zwergenland.CleanDomainValidation.
dotnet add package Zwergenland.CleanDomainValidation --version 1.0.0-alpha-hf-01
NuGet\Install-Package Zwergenland.CleanDomainValidation -Version 1.0.0-alpha-hf-01
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Zwergenland.CleanDomainValidation" Version="1.0.0-alpha-hf-01" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Zwergenland.CleanDomainValidation --version 1.0.0-alpha-hf-01
#r "nuget: Zwergenland.CleanDomainValidation, 1.0.0-alpha-hf-01"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Zwergenland.CleanDomainValidation as a Cake Addin
#addin nuget:?package=Zwergenland.CleanDomainValidation&version=1.0.0-alpha-hf-01&prerelease

// Install Zwergenland.CleanDomainValidation as a Cake Tool
#tool nuget:?package=Zwergenland.CleanDomainValidation&version=1.0.0-alpha-hf-01&prerelease

CleanDomainValidation

With this package you write your validation logic once in your domain layer and use it anywhere by just calling one method. By returning custom error objects and not throwing exceptions you can achieve a better information flow without try catch blocks all over your code! This package also brings methods for better validation and mapping of API Requests to commands or queries using an validator optimized for error handling. The CanFail part of the package is inspired by the ErrorOr Package

How Domain validation works

Any Entity parameter that needs to be validated must be implemented as a new class. This is called a "value object" and you will get many more benefits with this pattern. Instead of using a public constructor, make it private and create a static factory method for creating an object instance. Put your validation logic in this method and make it return CanFail<T>.

Example

If you are creating a user entity with an Email Field, create a valueobject for the email type and use it instead of string.

public class Email
{
  private Email(string value)
  {
    Value = value;
  }

  public string Value {get; private set;}

  public CanFail<Email> Create(string value)
  {
    if(String.IsNullOrEmpty(value)
    {
      return Error.Validation("Email.Empty", "The email cannot be empty");
    }

    if(!value.Contains('@'))
    {
      return Error.Validation("Email.Invalid", "The email is in an invalid format");
    }

    return new Email(value);
  }
}

You can create the Email with the following code:

public CanFail CreateUser(string email)
{
  //Call the factory method
  var emailCreationResult = Email.Create(email);

  //check if email creation failed
  if(emailCreationResult.HasFailed)
  {
    //return errors
    return CanFail.FromFailure(emailCreationResult);
  }
  
  //create user with the result value
  User user = new User(emailCreationResult.Value);

  //return success
  return CanFail.Success();
}

But feel free of using the Error class and CanFail for other methods that have to handle exceptions as well.

The more important part: validating API requests

The most annoying part of implementing a REST API with clean architecture and DDD is redefining validation behaviour in the Application Layer and not receiving the validated valueobjects. Since we already created the factory method which supports error handling, there is no more need for custom validation configurations.

If you are familiar with the CQRS pattern and MediatR, you are used to the following behaviour:

  • The json request is automatically mapped to an object containing the needed parameters
  • A manually configured validator will validate the incoming object paramers and reject the request if the validation fails
  • A request object needs to be created
  • The request object will be sent to the request handler
  • Use case will be executed The bad information first: You will need all of this steps for this approach as well BUT:
  • The request object contains already the value objects (so they can be used much easier in the handler)
  • The factory methods of the value objects can be used for validation, so no custom validation logic needs to be implemented
  • If something fails, CanFail will be used to return the errors so they can be handled in the controller without Exception handling

Lets think about a REST API where you can create a user. You have an endpoint that takes a user json of the following format:

{
    "email": "meow"
}

The json will be converted to

public class UserParameter : IParameter
{
    public string? Email {get; set;}
}

ℹī¸ All parameters will be defined as nullable as we want to handle null values manually as well. Our command will be looking like this:

public record CreateUserCommand(Email Email) : ICommand

With those two clases we can create the validator for the command:

public class CreateCommandValidator : CommandValidator<UserParameter, CreateUserCommand>>
{
    protected override void Configure(UserParameter parameters)
    {
        //Configure the email as an required parameter
        //so the given Error wil be returned when it is null. 
        var validatedEmail = RequiredAttribute(
            parameters.Email, Error.Validation("User.Email.Missing", "The email attribute is missing",
            value => Email.Create(value));

        //Configure the method that will be called to create the command
        CreateInstance(() => new CreateUserCommand(validatedEmail));
    }
}

In the API Controller, the command can be created the following way:

public void ControllerMethod(UserParameter parameters)
{
    var createUserCommand = CommandBuilder<CreateUserCommand>
        .AddParameter(parameters)
        .ValiateByUsing<CreateCommandValidator>();

    //Handle command creation failure
    if(createUserCommand.HasFailed)
    {
        ...
        return;
    }
    
    //You can access the valid command here using createUserCommand.Value
}

To Be Continued...

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0-alpha-hf-01 54 4/2/2024
1.0.0-alpha 63 4/1/2024
0.2.0 93 2/5/2024
0.1.0 85 1/16/2024
0.0.0 81 1/15/2024