DEV Community

Cover image for Domain Validation
Mo
Mo

Posted on

Domain Validation

In this blog post, I want to share with you how to implement domain validation by returning a result object instead of throwing exceptions. This approach has several benefits, such as improving performance, handling multiple errors, and separating concerns.

Domain validation is the process of checking whether the data and operations in our domain model are consistent with the business rules and invariants. For example, we may want to validate that an email address is valid, that a name is not empty, or that an order has at least one item.

Throw Exception!

One way to implement domain validation is to throw exceptions when a validation fails. For example, we could have something like this in our domain entity:

public class Person
{
    public string Email { get; private set; }
    public string Name { get; private set; }

    public Person(string email, string name)
    {
        if (string.IsNullOrEmpty(email))
            throw new ArgumentNullException(nameof(email));

        if (string.IsNullOrEmpty(name))
            throw new ArgumentNullException(nameof(name));

        if (!IsValidEmail(email))
            throw new ArgumentException("Invalid email address", nameof(email));

        if (name.Length > 50)
            throw new ArgumentException("Name cannot be longer than 50 characters", nameof(name));

        Email = email;
        Name = name;
    }

    private bool IsValidEmail(string email)
    {
        // some logic to check email format
    }
}
Enter fullscreen mode Exit fullscreen mode

This approach has some drawbacks, such as:

  • Throwing exceptions is expensive and can affect performance.
  • Exceptions should be used for exceptional situations, not for normal validation scenarios.
  • Exceptions are hard to handle at the application layer, especially when there are multiple errors.
  • Exceptions couple the domain layer with the infrastructure layer, violating the dependency inversion principle.

Result object!

A better way to implement domain validation is to return a result object instead of throwing exceptions. A result object is a simple class that encapsulates the outcome of an operation, including a success flag, a value, and a collection of errors. For example, we could define a generic result class like Result.cs

Then, we can use this result class in our domain entity methods to return the validation outcome. For example, we could have something like this:

public class Person
{
    public string Email { get; private set; }
    public string Name { get; private set; }

    public static Result<Person> Create(string email, string name)
    {
        var errors = new List<Error>();

        if (string.IsNullOrEmpty(email))
            errors.Add(new Error("Person.Email.Mandatory", "Email cannot be empty"));

        if (string.IsNullOrEmpty(name))
            errors.Add(new Error("Person.Name.Mandatory", "Name cannot be empty"));

        if (!IsValidEmail(email))
            errors.Add(new Error("Person.Email.Invalid", "Invalid email address"));

        if (name.Length > 50)
            errors.Add(new Error("Person.Name.MaxLength", "Name cannot be longer than 50 characters"));

        if (errors.Any())
            return Result.Failure<Person>(errors.ToArray());

        return Result.Success<Person>(new Person(email, name));
    }

    private Person(string email, string name)
    {
        Email = email;
        Name = name;
    }

    private bool IsValidEmail(string email)
    {
        // some logic to check email format
    }
}
Enter fullscreen mode Exit fullscreen mode

This approach has several benefits, such as:

  • Returning a result object is cheaper and faster than throwing exceptions.
  • Result objects can handle multiple errors in a single operation, making it easier to display them to the user or log them.
  • Result objects decouple the domain layer from the infrastructure layer, allowing us to handle them differently depending on the context.
  • Result objects make the domain model more expressive and explicit about its behavior and expectations.

To summarize, domain validation is an important aspect of domain-driven design that ensures our domain model is always in a valid state. By returning a result object instead of throwing exceptions, we can implement domain validation in a more efficient, flexible, and elegant way.

NuGet

I've created a NuGet package for Domain Validation with a result object that is accessible to use from here.

P.S:

For more information about this approach, please take a look at Domain Validation With .NET | Clean Architecture, DDD, .NET 6

Top comments (0)