Valid vs NOT valid objects?
By: Laerti Papa
Tags: Ruby on Rails, Clean Code, Software Development, Ruby, Clean Architecture

Valid vs NOT valid objects?

When to check when business rules apply?

The question is quite common in software development. Given an instance of a class, you want to know when to check if a class is valid or not. Do you skip validations before executing an action or do you first check if the instance is valid before executing an action?

Let’s see a simple example. Image an entity called FlightReservation and some validation or better business invariant rules that require a flight reservation to be valid when it contains no more than max_passenger passengers.

class FlightReservation < AggregateRoot

MaxPassengersReachedError = Class.new(StandardError) attr_reader :passengers

attr_reader :max_passengers def initialize(max_passenger)

@max_passengers = max_passengers

@passengers = Set.new

end



add_passenger(passenger)

if (passengers.size > max_passengers)

raise MaxPassengersReachedError passengers.add passenger

end

end

In the above example, we initialize a flight reservation with its total amount of passengers indicating how many passengers are allowed to be added. We also have a method called add_passenger and make sure that the maximum passengers’ size is not exceeded. If it is we throw an exception since the invariant of the entity is violated. This way we do not allow the entity to be in an invalid state.

Let’s see another example where we try to solve the problem with a different implementation. In the example below we allow to have a reservation in an invalid state:

class FlightReservation < AggregateRoot

MaxPassengersReachedError = Class.new(StandardError) attr_reader :passengers

attr_reader :max_passengers def initialize(max_passenger)

@max_passenger = max_passengers

@passengers = Set.new

end



add_passenger(passenger)

passengers.add passenger

end def valid?

passengers.size < max_passengers

end

end

We allow adding any number of passengers in the reservation without raising an exception.

Both approaches are correct but what is the difference between them?

When we chose the first approach (the entity is always valid), we do not have to worry about the inconsistent or invalid state of an object. When we chose the second approach (The entity is NOT always valid) it allows us to gather all the validations of the entity in one place which simplifies the validation logic.

But which approach to choose?

Although the not always valid approach provides better benefits I tend to like the is always a valid approach. When releasing new features, I tend to think and consider different use cases that may something go wrong so I always raise an exception trying to keep the entity in a valid state. That exception may not be raised at all but it’s hard to remember one time that I haven’t seen an exception being raised. It’s always one use case that is missing. Moreover, you keep protecting your entity based on business rules.

This is because of the requirements not being communicated correctly (or not completely understandable/predictable) or because you don’t have control on external dependencies and you cannot guarantee the input that will be given in your domain services. Other than that, there may be use cases where you or the business owner have not anticipated. Unhappy paths or weird use cases that it's hard to predict.

I like to think every business process like parsing an input request from a user. You don’t trust it. You can never know it’s validity or what the input will be. So you have validation logic and authorization logic. The same happens with business processes as well. Why should you process an invalid request? No need to do so.

So I prefer having entities in my domain that will be always valid. No matter what, the invariants should be fulfilled and maintained during its lifecycle.

Except the above consider also the following:

Introducing an is_valid? method would force the client to always call it in order to check what to do next.

method would force the client to always call it in order to check what to do next. Checking that the invariants are maintained with the is always a valid approach promotes DRY principle and eliminates duplication. Imagine during a business process checking if an entity is valid all the time?

It’s better to follow business rules telling that an entity cannot be in state A rather than having it in a state B and search around why, how and when it went there.

A violation of the invariants should signalize a bug and lead to a failure in order to protect the business rules.

Last but not least it follows DDD principles.

Promotes fail fast principle.

The last question would be where to check for business validations since all our domain objects have a valid state. For example when or where whould we check if we can add a passenger to the reservation? That is an easy question to answer. This kind of validation logic should be added to the application services. That would be our boundaries on top of our domain layer in clean architecture or onion architecture. An example is shown below:

# ... some application service result = flight_reservation.check_validity

result.then { |data|

flight_reservation.book

}.or_else { |err| handle_error(err)}

Summarize

Domain entities should always respect their constraints and invariants. They should always be in a valid state during their lifetime. If we need to check for their state we should do so in the application services layer before performing an action. That way we ensure that our domain entities will be in a valid state before executing an action.

References

[1] https://www.pluralsight.com/courses/domain-driven-design-in-practice?gclid=Cj0KCQjwt_nmBRD0ARIsAJYs6o0eubu0-DAky3nxtM6ZLaaXd9zzjyObWMVMMLvqsKE0lsEdydbUimcaArxiEALw_wcB&ef_id=Cj0KCQjwt_nmBRD0ARIsAJYs6o0eubu0-DAky3nxtM6ZLaaXd9zzjyObWMVMMLvqsKE0lsEdydbUimcaArxiEALw_wcB:G:s&s_kwcid=AL!5668!3!339565055182!b!!g!!&aid=7010c000002SNseAAG&promo=&oid=&utm_source=non_branded&utm_medium=digital_paid_search_google&utm_campaign=EMEA_DE_Dynamic&utm_content=

[2] https://en.wikipedia.org/wiki/Domain-driven_design

[3] https://www.infoq.com/articles/ddd-in-practice