Wrap Primitives

Create classes around primitives carrying a rich domain meaning.

String is a storage unit

Let's look at some code right away.

const emailAddress: string = "johndoe@gmail.com"

What's the problem with this code ?

The problem is between the ":" and the "=".

Yes you, string, i'm looking at you.

What the hell are you doing here ? This isn't a place for you to be.

Why ? Because a string has no meaning for us. It only has a meaning for the computer.

Thus, our email address is terribly weak. It's treated as a simple string, but it isn't. An e-mail address is a very complex structure with a lot of behaviors.

Don't believe me ? Then tell me...

  • How would you validate that e-mail address and make sure it has the proper format ?

  • How would you extract the local part (johndoe) ?

  • How would you extract the domain ?

  • How would you ensure the e-mail belong to a specific domain ?

  • How would you get the canonical address (getting rid of plus addressing) ?

See, it's more than a string, much more.

The same can be applied to many other common values represented as strings :

  • Urls

  • Passwords

  • Names

  • ISBNs

The list goes on.

In each of these cases, string is a terrible type choice because it's a storage unit, not a type, and certainly not an object.

Have you ever heard of business people talking of strings ?

I certainly haven't.

Hence, emailAddress is dying to be an object. Let's satisfy its needs.

class EmailAddress {
    constructor(private readonly value: string) {
        // validate
    }
    
    function localPart(): string;
    function canonicalAddress(): string;
    function domain(): string;
}

const emailAddress = new EmailAddress("johndoe@gmail.com");

Two patterns are at work here. This is a Value Object maintaining its validity using the Always Valid pattern.

This is elegant, satisfying and very useful. That object can be reused in any program, just as is, and be extremely useful for a wide range of use cases.

Number is a storage unit

Look at this code.

const durationInSeconds: number = 60;

See what this pesky number is forcing us to do ? We know have to use a composed name because that number has absolutely no meaning at all. We can't Abandon Composed Names in this case because otherwise, we'd have no idea what that value stands for !

Moreover, a duration is more than just a number. It has a lot of behaviors inside :

  • Arithmetic

  • Comparisons

  • Conversions

I don't want to add more useless cognitive load to my programming activity. Translating business requirements into code is already a complicated job in itself.

So I want to push that value in an object and forget about it. I want a reliable object to take the burden for me, to grab my shoulder and to tell me "don't worry buddy, I've got this".

Here's what I would do.

class Duration {
    private constructor(private readonly seconds: number) {
        // can't be < 0
    }
    
    // Constructors
    static fromSeconds(seconds: number);
    static fromMinutes(minutes: number);
    
    // Arithmetic
    add(duration: Duration);
    sub(duration: Duration);
    
    // Comparison
    eq(duration: Duration);
    gt(duration: Duration);
    
    // Conversions
    toSeconds(): number;
    toMinutes(): number;
}

const duration = Duration.fromSeconds(60);

I don't want to open the constructor, because the internal representation of the duration ought to be private. Maybe one day I'll change my mind and decide to use milliseconds instead of seconds. Should all my code break because of an internal change ? Certainly not. Hence, .

I can sleep in peace.

Last updated