0

From what I understand of DDD, my entities and value objects should be able to enforce its own invariants, meaning that its constructors will check for nulls, must contains numbers and letters, whatever else. In this way an instance of a domain object will always be valid.

And from what I heard, if my domain throws an exception, it means that there's a bug in the layer that consumes the domain (application layer in my case), as data should be validated before being sent to the domain layer. But, if this is true, I will need to make the same validation in the application layer and in the domain layer, no? How can I avoid duplicating validation rules?

Let's say I have a value object Latitude whose value must be between -90 and 90. Will I need to validate that in the constructor of the value object and also in the application layer when converting something like LatitudeDTO to Latitude?

5
  • What would be the point of performing the same check 3 times as you pass through the layers?
    – Flater
    Jan 26, 2023 at 21:11
  • @Flater why 3 times? Did you meant 2 times? Jan 26, 2023 at 21:33
  • I misread your question to include 3 layers, so for your case it would be 2, but the question stands on principle.
    – Flater
    Jan 26, 2023 at 21:41
  • "And from what I heard" - maybe you have misread or misunderstood something. How shall we asess this if you don't give any references?
    – Doc Brown
    Jan 26, 2023 at 21:47
  • @DocBrown Yeah, pretty unclear from my part. I didn't heard from it, specially since I'm not using C#, but this article explains it all: enterprisecraftsmanship.com/posts/… Jan 26, 2023 at 21:54

1 Answer 1

6

Different validations exist for different reasons, which is why several kinds of validation exist. If the purpose was exactly the same, there'd be no point to have both of these equivalent validations.

Let's use an example here, this is my model (and DTO, etc):

public class Foo
{
    public Guid CountryId { get; set; }
    public int? Amount { get; set; }
}

You can assume the same data structure on every layer discussed.

In short, these are optional (and therefore nullable) values that are being applied to a particular command. We're going to focus on 4 kinds of validation: frontend, backend (API), application and domain.

Just to be clear, I assumed a certain amount of layers. If you're working with a simpler architecture with less layers, the listed validation layers will merge based on which layer contains which responsibility.

Domain

On the domain level, you're going to want to validate your business rules, i.e. the rules that revolve around how you want your application to behave.

  • One example could be that the amount is required for certain countries and not others, or that you can only take X amount when that country has >= X amount of units available.

Application

On the application level, you're going to want to validate that your input data makes sense. It's not about the rules, it's about sanity checking for obvious bad data.

One of the common validations you see here is matching the input values against the data store to see if they match up (where relevant).

  • Does the CountryId GUID actually exist in our list of countries?

API

At its very core, your API converts the incoming web request into your application behavior. Therefore, what you want to validate here is that the API is correctly mapping the incoming request to your application's DTOs.

A clear example would be that you cannot enforce types in what is most likely a JSON format, which means that you cannot trust the validity of the data coming in.

  • Is the CountryId value you're receiving actually a GUID value?

Important!
Take care to note the difference between this question and the question we asked in the application layer validation. We're not interested in validating that the GUID refers to an existing country entity. We're only interested in validating that the incoming value correctly maps to a GUID.

Here's a list of which kind of bad data would be caught by which kind of validation:

  • CountryId
    • fe2fc0e2-cfe8-4418-a7de-bbd459112229 (exists in database)
      • API validation: passes
      • Application validation: passes
      • Domain validation: passes
    • fe2fc0e2-cfe8-4418-a7de-bbd459112229 (exists in database, but country's assets are frozen and cannot be taken)
      • API validation: passes
      • Application validation: passes
      • Domain validation: fails
    • 860fa9c6-c01c-48d4-ac3d-6b26a8085b24 (does not exist in database)
      • API validation: passes
      • Application validation: fails
      • Domain validation: N/A
    • bananabanana (garbage data)
      • API validation: fails
      • Application validation: N/A
      • Domain validation: N/A
  • Amount
    • 5 (less than units in country)
      • API validation: passes
      • Application validation: passes
      • Domain validation: passes
    • 10 (more than units in country)
      • API validation: passes
      • Application validation: passes
      • Domain validation: fails
    • -5 (clearly nonsensical data even though it's an int value)
      • API validation: passes
      • Application validation: fails
      • Domain validation: N/A
    • bananabanana (not an integer)
      • API validation: fails
      • Application validation: N/A
      • Domain validation: N/A

Frontend

Lastly, we get to frontend validation. This is a bit of an odd beast. From a technical perspective, you don't need frontend validation. The backend layers are already catching everything. When you pass through the backend validations, you already know that your input data:

  • [API] ... is of the appropriate type and maps to the application
  • [Application] ... is sensical and refers to existing items (where relevant)
  • [Domain] ... complies with all the known business rules

So why do we need frontend validation?

The main consideration here is the user experience. So far, we've only really validated to the benefit of the application owners to make sure that nothing wrong happens.

1
Have you ever gone through the effort of filling a large form, maybe even one where you have to enter complex data that you have to read off of documents you have with you, only to submit it and then being told that one of these complex values is missing a character? That's really frustrating. Now I have to find the document again and read through the complex data again.

If the system had immediately alerted me of this when I still had the document in front of me, that would've been a much nicer experience for me as a user.

2
Maybe I tried to take more items from a specific country than it had available. But if the only way for me to find this out is after I clicked the submit button, that means that I already had to prepare to actually take the items.

For example, if I wanted to buy 5 tickets for a concert, I will have spent time checking if I had 4 friends that would want to attend the concert with me. If I had known that there were only 2 tickets available, I wouldn't have needed to invite the last 3 friends, who I now have to uninvite.

Okay, maybe the screen already displayed that there were only two tickets available, but I could've missed that. When I entered a "5" in the amount box, it would've been nice if the system alerted me of my mistake.

3
I accidentally swapped the name/number of my credit card details, because I'm used to these fields being in a different order. I only noticed my mistake after submitting the purchase. It would've been nice to know that this was wrong at the time of inputting it.

I used these three examples as cases where the user experience would be significantly improved by doing frontend validation before submitting the form, because they mirror the backend validation layers. 1 is effectively the same as application validation (doesn't match a known identifier), 2 is domain validation (breaks the rules), and 3 is API validation (incompatible data that doesn't map).

Frontend validation is often duplicated validation. The reason you duplicate it is because:

  • Only doing frontend validation means that your backend is at risk of receiving doctored requests that did not come from your frontend logic.
  • Only doing backend validation leads to a lessened user experience.

Frontend validation is done for the user, not the application owner. If it is important that your customers like using your application, then the extra cost of duplicating the validation can make sense to your business model.

Your question

Let's say I have a value object Latitude whose value must be between -90 and 90. Will I need to validate that in the constructor of the value object and also in the application layer when converting something like LatitudeDTO to Latitude?

The domain model should definitely validate this.

Given that the domain model is already validating this, what added benefit is there to catching the same mistake in an earlier layer? There's cost attached to having to now maintain two validations, but what is the benefit? A cost with no benefit makes no sense.

Frontend validation, however, is a different beast. Here you might actually want a duplicated validation of the input values simply to not have to make a round trip to the backend before finding out that your input values are incorrect.


Updates from comments

That's because I'm considering domain validation as protection against bad code, not bad user input.

That's not what domain validation is about. That's something you should cover with unit tests. Before you even deploy your application you should have reasonable confidence that the code you wrote works the way you expect it to. Runtime validation happens after deployment, so it's not the right tool for the job.

Bad user input would be handled by the application layer

"Bad user input" is vague. For example, if someone tries to purchase alcohol on your web store and they correctly enter that they were born in 2009, that is not bad user input. All details are provided correctly.

However, you shouldn't be selling alcohol to minors. What has been violated here is a business rule, not the validity of the user input. That distinction matters, and it lies at the heart of what the difference between domain and application validation is all about.

which is responsible for throwing exceptions with pretty messages that are later mapped to HTTP 400

You can handle validation failures however you want, but I'd suggest not using exceptions for known potential failures.

Exceptions should be, well, exceptional. They are intended as the last resort in how to exit your code when something happened that you did not anticipate. In theory, code that consciously anticipates every possible point of failure would never need to throw an exception.

If you specifically build validation to verify the input, then you clearly expect that an incoming request might fail this validation. Therefore, validation failure is not an unexpected outcome of your logic, and therefore an exception is not the right tool for the job.

Consider using result objects. FluentResults is a good example of how you can handle failures without needing to resort to exceptions.

if the application layer starts to depend on the validations of the domain to validate user input

If the application validation depends on the domain validation, then it can just be validated by the domain. The application's validation responsibility is inherently defined as a responsibility that's not already covered by the domain.

If you're talking about the application validation depending on the domain (the domain, not the domain validations!), then that's can be contextually okay. The application layers obviously is allowed to handle domain objects so I see no concern here.

cannot always be mapped to something that points out to user's fault

You need to design your returned result in a way that it can express every result that you want to express. It seems like you're currently using a response that cannot adequately describe what you need it to describe.

So maybe I should ask you to elaborate a little more and explain, for each layer, how a failed validation result should be handled by the previous layer. I think it's generic enough to be explained as a general rule.

This is highly contextual, and cannot be put into a blanket statement. I'm not going to present you with a dogma. This requires evaluation and consideration.

For example, a domain validation failure might result in the application layer needing to exit (thus passing its own kind of failure to the API), or maybe the application layer has a fallback protocol to continue with in case the first approach doesn't work.

Consider the example I used before of a minor trying to purchase alcohol. When the age verification fails (domain validation), your application could for example try to confirm if this is an emancipated minor (in this scenario, emancipated minors can be allowed to purchase alcohol).
Following my example, if this needs to be looked up in a government database, then the application layer needs to handle the domain validation failure and divert towards communication with the infrastructure layer in order to confirm if the minor is emancipated.

application layer will need to catch that exception and make reason of it to inform to the user that he inputted a invalid value for Latitude, as the exception is generic and does not necessarily implies in user's fault (Latitude can be instantiated by a service that queries data from another system, so the exception can also be thrown on other's fault)

Even if you stick with exception-based validation (which I strongly advise against), the application layer can catch exceptions around specific parts of the code. A crude example:

public Result<LatLon> GetAntipodeanPoint(double lat, double lon)
{
    LatLon latLon;
    try
    {
        latLon = new LatLon(lat, lon);
    }
    catch(...)
    {
        return Result<LatLon>.Fail("The user is at fault");
    }

    LatLon antipodeanPoint;
    try
    {
        antipodeanPoint = someOtherService.GetAntipodeanPoint(latLon);
    }
    catch(...)
    {
        return Result<LatLon>.Fail("The service is at fault");
    }

    return Result.Ok(antipodeanPoint);
}

In general, this kind of issue is precisely why I don't like constructor validation, as it disallows the ability to handle validation failures gracefully.

At the very least, I would then expose the validation in some way that it is publicly accessible. If you really want to, you can still have the constructor rely on that validation as a last line of defense, but at the same time your consumer can call the validation before attempting to create the instance, which makes it possible to receive a non-exception result object to explain whether the object can/can't be instantiated.
If your consumer is reckless, they'll get the exception thrown at them. But if the consumer validates before creating, they can avoid the exception and work with a result object instead.

14
  • That's very good. I'm glad I got an answer from who seems to be an expert in the subject. Now, you asked why I would validate in the application layer if it is already validated in the domain layer. I have being mapping exception thrown by the domain to HTTP 500. That's because I'm considering domain validation as protection against bad code, not bad user input. Bad user input would be handled by the application layer, which is responsible for throwing exceptions with pretty messages that are later mapped to HTTP 400. Jan 26, 2023 at 23:25
  • 1
    @DouglasMonteiro: You've got the right spirit but you're off track in the details. I've updated the answer with redirections to your comments.
    – Flater
    Jan 27, 2023 at 0:13
  • 2
    @DouglasMonteiro: Whether you allow a model to be instantiated in an invalid state and then validate it, ensure to validate before instantiation, or outright refuse for an invalid instance of a model to ever exist (by validating in the constructor) is a bit of a holy war between developers, there is no universally agreed upon answer. However, putting that aside, one last update was added to the answer to address your last comment.
    – Flater
    Jan 27, 2023 at 3:59
  • 1
    @DouglasMonteiro: Whether you call it twice or deal with the unnecessary overhead of exception collection, it's always more effort. Personally, I do neither, and I rely on the assumption that I'll validate my inputs at the appropriate time, which I would be writing tests for anyway so the odds of me missing something here are tiny. And if the cost of missing something is insurmountably huge (let's say it's life-critical software), then the added overhead cost of double validation is justifiable.
    – Flater
    Jan 27, 2023 at 4:16
  • 1
    @DouglasMonteiro I understand the allure of never having an invalid object. My problem with it is that there is just no way to graciously handle willful rejection of an invalid instantiation unless you use exceptions, which forces you to use the flow by exception anti-pattern which I really don't like. I'm also not sure how the language could fix it in a way that you get the benefits but not the drawbacks.
    – Flater
    Jan 28, 2023 at 22:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.