Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Tiny Types in TypeScript

DZone's Guide to

Tiny Types in TypeScript

In this article, we take a look at the Tiny Types 'mini-pattern' and how to implement it using TypeScript. Read on for the details!

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

It’s not that uncommon to get the parameters wrong or out of order if the signature of a method relies on primitive types. In fact, it's quite easy for this to happen.

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 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?”

So... not every single primitive type. Just the ones in your public API signatures. Trust me, this will make refactoring much easier going forward (ever tried to ‘Find Usages’ on a string?).

And regarding the amount of code — yes, that might be considered a problem in Java, but in TypeScript, 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 { 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 serialize 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.

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
data types ,typescript ,web dev ,tiny types pattern

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}