Class Invariant
About this...
Invariants are a form of ensuring data integrity of an object.
By data integrity, we mean "what shape is this data allowed to take?”, “what methods can be called and at which point?", “what are the required parameters and pre-conditions in order to create this object”?
Methods need to preserve the invariants of an object. They need to constrain the state stored in the object such that it doesn't end up being invalid.
Here's an example using a Latitude class.
import { ValueObject } from '../../../core/valueObject';
import { Result, TypedResult } from '../../../core/result';
export interface ILatitude {
value: number;
}
export class Latitude extends ValueObject<ILatitude> {
value: number;
private constructor (props: ILatitude) {
super(props);
this.value = props.value
}
// Factory method
// The latitude must be a number between -90 and 90
public static create (latitude: ILatitude) : TypedResult<Latitude> {
if (latitude.value < -90 || latitude.value > 90) {
return Result.typedFail<Latitude>("Latitude must be within -90 and 90")
}
return Result.typedOk<Latitude>(new Latitude(latitude))
}
}
In this example, in order to create a Latitude object, we need to satisfy the invariant of passing in a value between -90 and 90.
Anything else is not a valid latitude.
Enjoying so far? Join 15000+ Software Essentialists getting my posts delivered straight to your inbox each week. I won't spam ya. 🖖

6 Comments
Commenting has been disabled for now. To ask questions and discuss this post, join the community.
Do you have opinions on whether failed invariant assertions should throw errors or not? I'm interested in your decision to return a 'typedFail | typedOk' type instead. In the current app I'm building, failed invariant assertions throw an 'AggregateIntegreityError' which are caught specifically.
Hey Julien,
That works. Though, I have a slight opinion on that. Check out this article on Functional Error Handling.
Brilliant, thank you
Hello. Why do we need a create method? Can't we enforce our constraints in the constructor as well??
@Amirhossein One of the reasons I see is you could use async code within body of factory method, wich you couldn't with constructor. For example, validate data in DB. The other reason you can have factory in a base class, but this probably too specific case.
And of course in this particular case if wee use constructor to validate a data then the only way of knowing that the data is invalid is throw an exception. Instead we can model logic of our application using pipes (one for valid work, other for failure) that is demonstrates in the example from the article.
Assume that we have fields in our request which are interdependent.
For example,
In here, if the type is a book, we expect a book object. There can be even internal fields which depend on other fields. In Joi we can handle this via doing conditional schema validations.
How we can achieve the same with above setup? it seems that simple addition will make app more complex