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.

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 serviceresult = 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

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store