My TypeScript Software Design & Architecture book just prelaunched! Check out solidbook.io.
Close

Class Invariant

Updated Apr 9th, 2019 / 1 min read

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.


2 Comments

Submit
Julien Heller
2 months ago

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.

Khalil Stemmler
2 months ago

Hey Julien,


That works. Though, I have a slight opinion on that. Check out this article on Functional Error Handling.


Julien Heller
2 months ago

Brilliant, thank you