Tiny Types in TypeScript

Giving domain meaning to primitive types

Jan Molak
Jan Molak

--

Said Jerome K. Jerome to Ford Madox Ford,
“There’s something, old boy, that I’ve always abhorred:
When people address me and call me, ‘Jerome’,
Are they being standoffish, or too much at home?”
Said Ford, “I agree; it’s the same thing with me.”

- William Cole

It’s not that uncommon to get the parameters wrong or out of order if the signature of a method relies on primitive types. Not to mention that trying to refactor a JavaScript app to represent a domain concept with a different type is typically a lost cause (ever tried to ‘Find usages’ on a string?)

Consider the arguments of the below constructor function:

const Jerome = new Author('Jerome', 'Jerome', -3492374400);

string, string, and a number… great, but what is what? Which one’s the first name, which one the last name, and what is that number doing there anyway?

In this article I’ll introduce you to a mini-pattern called Tiny Types and my implementation of the pattern in TypeScript.

The Pattern

The Tiny Types pattern as described by Darren Hobbs, Mark Needham and introduced to me by Andy Palmer is in essence the Rule #3 of Jeff Bay’s Object Calisthenics (published in The ThoughtWorks Anthology).

The mini-pattern boils down to wrapping all primitives and strings to enrich them with domain context:

An int on its own is just a scalar with no meaning. When a method takes an int as a parameter, the method name needs to do all the work of expressing the intent. If the same method takes an hour as a parameter, it’s much easier to see what’s happening.

Small objects like this can make programs more maintainable, since it isn’t possible to pass a year to a method that takes an hour parameter. With a primitive variable, the compiler can’t help you write semantically correct programs.

With an object, even a small one, you are giving both the compiler and the programmer additional information about what the value is and why it is being used.

- Jeff Bay, “Object Calisthenics

Practical Application

To see see how we can apply this pattern in practice let’s assume the following signature of the Author class constructor:

class Author {
constructor(public readonly firstName: string,
public readonly lastName: string,
public readonly dob: number,
) {}
}

If we wanted to replace the primitives with domain-specific Tiny Types, this signature would change as follows:

class Author {
constructor(public readonly firstName: FirstName,
public readonly lastName: LastName,
public readonly dob: DateOfBirth,
) {}
}

And its invocation would now become:

const Jerome = new Author(
new FirstName('Jerome'),
new LastName('Jerome'),
new DateOfBirth(-3492374400),
);

I can already hear you scream: “So you want me to write a class for every single primitive type?? How much code would that be?”

Well, not every single primitive type. Just the ones in your public API signatures. Trust me, this will make refactoring much easier going forward.

And regarding the amount of code — yes, that might be considered a problem in Java, but in TypeScript or JavaScript it’s literally one line of code per type.

How is that possible? Well, let me show you.

Implementation in TypeScript (and JavaScript)

I’ve recently released a tiny library (bad pun intended) to make working with Tiny Types a little bit easier. You can get it from the npm:

npm install --save tiny-types

With the library in place, we can introduce types to represent the first name and the last name:

import { TinyType, TinyTypeOf } from 'tiny-types';class FirstName extends TinyTypeOf<string>() {}
class LastName extends TinyTypeOf<string>() {}

The date of birth is currently represented by a number, so let’s create a Tiny Type representing that concept:

class DateOfBirth extends TinyTypeOf<number>() {}

That’s it!

Every Tiny Type extending the TinyTypeOf<T>() has a single property value, which you can use to access the wrapped primitive type:

const firstName = new FirstName('Jerome');firstName.value === 'Jerome';
typeof firstName.value === 'string';

Easier Refactoring

One of the benefits of this approach, other than being able to capture the domain-specific meaning of the primitive types, is much easier refactoring.

Say that we wanted to represent the DateOfBirth using a Date object, rather than a number:

class DateOfBirth extends TinyTypeOf<Date>() {}

We could very easily use the refactoring tools in our IDE to find the usages of DateOfBirth and change the call signature.

Other Features

The TinyType class provides other useful features. For example, you can check if two types are equal:

class Id extends TinyTypeOf<string>() {}const id = new Id(`f7e02832-505a-42bf-ad85-da2fdde9d270`)id.equals(id) === true

You can also serialise them to JSON, which comes in handy if you want to use TinyTypes as Data Transfer Objects:

import { TinyType, TinyTypeOf } from 'tiny-types';

class FirstName extends TinyTypeOf<string>() {}
class LastName extends TinyTypeOf<string>() {}
class DateOfBirth extends TinyTypeOf<number>() {}
class Author extends TinyType {
constructor(public readonly firstName: FirstName,
public readonly lastName: LastName,
public readonly dob: DateOfBirth,
) {
super();
}
}

and then:

const Jerome = new Author(
new FirstName('Jerome'),
new LastName('Jerome'),
new DateOfBirth(-3492374400),
);

Jerome.toJSON() === {
firstName: 'Jerome',
lastName: 'Jerome',
dob: -3492374400,
}

More examples available on Github.

I’d love to hear your thoughts! Did you find the pattern useful? Would you like to use the tiny-types npm module on your project? Let me know in the comments!

--

--

Consulting software engineer and trainer specialising in enhancing team collaboration and optimising software development processes for global organisations.