Domain design

Developing a Ubiquitous Language is one of the most important challenges of domain driven design.

Once we land on a name to express our domain. We follow a few simple conventions that help drive consistently thoughout our service.


Domain name

⚠️ Suggestion

Domain names are generally pluralised.

Good users, projects, roles, auth

Bad user, project, role, authentications

At a minimum in any defined domain Enso expects an interfaces.ts file to be defined.

This helps developers easily compose how a domain has been modelled. As your domain grows in complexity, it makes sense to progressivly adopt the requests.ts and transforms.ts patterns to help streamline development.

An example of a common users domain is illustrated below.

── domain
   └── users
       ├── UserRepository.ts
       ├── UserService.ts
       ├── UsersController.ts
       ├── interfaces.ts
       ├── requests.ts
       └── transforms.ts

interfaces.ts

This file should contain all the relevant interfaces for your domain that are used when designing your Factories, Repositories and other Services.

/**
 * Requests
 */
export interface ICreateUserRequest {
  firstName: string
  lastName: string
  email: string
}

/**
 * Repositories
 */
export interface IUserRepository {
  repo: Repository<User>
  browseByUuid (uuid: string, relations?: string[]): Promise<User>
}

/**
 * Repositories
 */
export interface IUserSerialised {
  uuid: string
  name: string
  email: string
}

requests.ts

If your domain accepts requests from external services or users, it’s usually a good idea to have them validated prior to passing them on to its relevant handler.

import { IsNotEmpty } from 'class-validator'
import { ICreateUserRequest } from './interfaces'

export class RegisterUserRequest implements ICreateUserRequest {
  @IsNotEmpty()
  firstName: string

  @IsNotEmpty()
  lastName: string

  @IsNotEmpty()
  email: string
}

This file should contain all the validators that are used verifying inbound data.

Learn how we implement Validation in Enso.

transforms.ts

If you are sending a response back to service makes sense to have that response explicitlly serialised so you do not mistakenly send back unwanted data.

Generally we found that prefixing the Entity we want with serialise works well in the majority of cases.

import { User } from '../../entities'
import { IUserSerialised } from './interfaces'

export const serialiseUser = function(user: User): IUserSerialised {
  return {
    uuid: user.uuid,
    name: `${user.firstName} ${user.lastName}`,
    email: user.email
  }
}

TIP

By typing your responses it makes it easier for other services to implement your domain

Next