Placement of business logic as validation of data in architecture

52 Views Asked by At

I have a budget entity in my domain which can be closed by the user if the budget is open. This means if the budget opens today and closes in seven days, in between this time I can close it, not before (I'd have to delete it), not after (can't do any action).

This time constraint raised a doubt: should I add a validation in my domain code for that, like the one below, or should the validation be in the controller that will access a service which will access the domain?

class Budget {
  ...

  public close(): void {
    if (this.isOnPeriod()) {
      this.closed = true
    }
  }

  private isOnPeriod() {
    if (new Date() < this.closing_date) {
      throw new BudgetIsClosedError()
    }
    if (this.opening_date > new Date()) {
      throw new BudgetIsNotYetOpen()
    }

    return true
  }

  ...
}

I don't know how to explain it, but it this seem like this is business logic, although it also seems like this case (of not being on the period) will never even arrive to the domain because I am going to have validation for the data that arrives in the controller (but have in mind I don't know all the business rules and requirements yet).

2

There are 2 best solutions below

0
VoiceOfUnreason On BEST ANSWER

This time constraint raised a doubt: should I add a validation in my domain code for that, like the one below, or should the validation be in the controller that will access a service which will access the domain?

What you want most of the time is for all of the business policies to be expressed within the domain model, rather than scattered all over your code.

The stuff outside of the domain model is not responsible for business policy, but is responsible to providing information to the domain model.

So your example might look something like this:

  public close(Date today): void {
    if (this.isOnPeriod(today)) {
      this.closed = true
    }

And somewhere else you would have code that looks like:

Date today = new Date();

Budget budget = someRepository.get(/* whatever */);
budget.close(today);

One additional advantage to this design: it is really easy to test that Budget::close handles all of its edge cases correctly because in the test you can pass whatever date you want.

0
R.Abbasi On

This surely goes in the domain. Either via a ValueObject or the entity itself, this validation will be enforced. It's a business invariant and must be on the domain. However, are all the validations the same? There is a debate about where the validation should happen. This is because of fail-fast practice. I've got this rule of mine that there are two types of validation:

  1. Validation can be done without fetching the entity.
  2. Entity must be fetched to validate the action.

The first one says that you can do it in the presentation layer (in fact application layer is a better option all presentation models should convert to commands and property validation happens in this phase) before fetching the aggregate to respond faster if anything primitive goes wrong. This is an input validation that you do without any knowledge of the existing aggregate.

The second one says you have to fetch the aggregate before validation. Some rules need the state of your domain. Anything that puts your domain in a bad state should be prevented in the domain itself. So these validations must be placed on the domain.

Should I have these two kinds of validation in my domain layer? These two types of validations are crucial and must be done. So the domain would not enter a wrong state, right? But not so fast, it depends. You have two options to handle the situation:

  1. Trust your application layer that it provides valid input to the domain. This way you don't need to validate the input again in the domain.
  2. You don't trust the application layer and validate all inputs as well. This way you have duplicated validation to be sure nothing goes wrong but at the cost of duplication.

I think if your team develops both the application and domain layers and they trust each other, the first option would be better. Otherwise choose the second option.