Domain Modeling

with

TypeScript


by Carl Vuorinen / @cvuorinen

Carl Vuorinen

w3.fi/rekry

 P.S. we're hiring
@cvuorinen
cvuorinen
cvuorinen.net

Domain Model


Is a representation of real-world concepts,
not just software components

Domain Model

is not just an UML diagram



... but sure it's helpful to draw it out

Domain-Driven Design


An approach to software development
for complex needs by connecting the
implementation to an evolving model

Domain-Driven Design


  • Originates from OOP languages
  • Useful for large and complex projects
  • CRUD better fit for simple projects

Ubiquitous Language


A language used to describe the domain
both in conversation and in code

Benefits

Helps design systems for:

  • maintainability
  • testability
  • incremental development

Quick intro to

TypeScript


TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. typescriptlang.org

Installation & Usage



npm install typescript --save

tsc src/*.ts
                    

Library Type Definitions


npm install --save @types/lodash
                    
* since TypeScript 2.0 and above

Older versions need to download definitions separately,
with Typings (or manually)


npm install typings --global

typings install lodash --save
                    

Type Definitions


function add(a: number, b: number): number {
    return a + b;
}

add(2, 3);   // 5
add(2, '4'); // Error: Type 'string' is not assignable to type 'number'

let color: string = 'red';

color = add(4, 2); // Error: Type 'number' is not assignable to type 'string'
                    

Interfaces


interface Item {
    id: number;
    name: string;
    categories?: string[];
}

const item: Item = { id: 1, name: 'MyItem' }; // OK
const item2: Item = { id: 2, name: 12345 };   // Error
    // Type '{ id: number; name: number; }' is not assignable to type 'Item'.
    //   Types of property 'name' are incompatible.
    //     Type 'number' is not assignable to type 'string'.
                    
Optional property
Same as Array<string>

Classes


class Group {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    public greet(): string {
        return `Hello ${this.name}`;
    }
}

const ngFinland = new Group('Angular Finland');

ngFinland.greet(); // Hello Angular Finland
                    

Modeling with Types


Using a rich type system to create
readable code and easily understandable APIs

Type Aliases


define custom types
for any valid type definition

Type Aliases


type AccountNumber = string;
type Money = number;
type Uuid = string;
type TransactionId = Uuid;
                    

interface Transaction {
    create: (amount: Money, from: AccountNumber, to: AccountNumber) => TransactionId;
}
                    
 Function input arguments
Output type

Literal Types


type Position = 'static';
type Enabled = 1;
type Disabled = 0;
                    
Only string literals
supported in 1.x

Union Types


type Something = string | number | boolean;

type Status = Enabled | Disabled;

type Currency = 'EUR' | 'USD' | 'BTC';
                        
Any valid type definitions
separated by "pipes"

Tuples


type Money = [number, Currency];
                    

const price: Money = [25, 'EUR'];
const price2: Money = [74.8, 'USD'];
                        


price[0]; // 25
price[1]; // 'EUR'

const [amount, currency] = price;
                        
Use array syntax to access values
 Can also use array destructuring

Tuples


function add(a: Money, b: Money): Money {
    if (a[1] !== b[1]) {
        throw 'Cannot add different currencies';
    }

    return [
        a[0] + b[0],
        a[1]
    ];
}
                    

add(price, price2); // Error 'Cannot add two different currencies'

add(price, [17.5, 'EUR']); // [ 42.5, 'EUR' ]
                        

Index signature


type Translation = string;
type TranslationList = { [id: string]: Translation };
                    
Can only be string or number


const finnishTranslations: TranslationList = {
    'Hello':   'Moi',
    'Angular': 'Kulmikas',
    'Finland': 'Suomi'
};
                        

Type Aliases recap


import Observable from "rx";

type Translation = string;
type Language = "en" | "fi" | "se";
type TranslationList = { [id: string]: Translation };
type Translations = [Language, TranslationList];
type TranslationStream = Observable<Translation>;

interface Translator {
    addTranslations: (Translations) => void;
    selectLanguage: (Language) => void;
    translate: (key: string) => Translation;
    translateAsync: (key: string) => TranslationStream;
}
                    
RxJS Observable
is generic, this one
emits a Translation

Enums


enum Direction { North, East, South, West };

enum Status { Disabled = 0, Enabled = 1 };
                    

interface Item {
    id: number;
    status: Status;
    // ...
}

const items: Item[] = [
    { id: 1, status: Status.Enabled },
    { id: 2, status: Status.Disabled }
];

const enabledItems = items.filter(item => item.status === Status.Enabled);

const item1 = items[0];
Status[item1.status]; // 'Enabled'
                        
Access values using dot notation
 Can also map value back to the label

enum Suit { Club, Diamond, Spade, Heart };

enum Rank { Two = 2, Three, Four, Five, Six, Seven, Eight,
            Nine, Ten, Jack, Queen, King, Ace };
                    

type Card = [Suit, Rank];

type Hand = Card[];

type Deck = Card[];

type Deal = (Deck) => [Card, Deck];

type PickupCard = (Hand, Card) => Hand;
                        
Models real domain concepts
and behaviours

Just because something is syntactically valid
doesn't mean that it's semantically correct

Value Objects

class Uuid {
    constructor(private value: string) {
        if (!/^[-a-fA-F\d]{36}$/.test(value)) {
            throw `Invalid UUID: "${value}"`;
        }
    }

    public toString(): string {
        return this.value;
    }

    public static create(): Uuid {
        return new Uuid(
           // generate UUID
        );
    }
} 
Copy paste implementation
from Stack Overflow
const id = new Uuid('bbd0ed2d-98e9-11e6-a49f-1633bb132a9c');

const newId = Uuid.create(); // generated id e.g. '110ec58a-a0f2-4ac4-8393-c866d813b8d1'

const invalid = new Uuid('foo'); // Error: Invalid UUID: "foo"

Compiler

When you got a rich type system coupled with a compiler,
it gives you confidence


You need less unit tests

You may not always trust your coworkers
(...or yourself few months from now)

But you can trust the compiler




Mental Model

vs.

Code Model

Domain Modeling with Types:


Making the Implicit Explicit

Dynamic typing: the belief that you can’t explain to a computer why your code works but you can keep track of it all in your head. Robert C.Martin

Effective Domain Modeling is
both an art and a science



but you can get started by making things
more explicit a little bit at a time

THE END


Questions?




@cvuorinen