LogoCésar Alberca
April 17, 2019 - 7 minutes

Use Cases and Command Pattern

The command pattern helps us encapsulate requests in order to perform certain operations, like logging, queuing and filtering.

We start with the interface:

export interface Command<T> { execute(): Promise<T> }

And then we can look at a specific command, for example the one used to retrieve this article:

import { Command } from '../../infrastructure/Command' import { Article, ArticlesRepository } from '../../domain/articles' import { Id } from '../../domain' import { Locale, Translator } from '../../domain/language' import { ArticlesFileRepository } from '../../infrastructure/articles/ArticlesFileRepository' import { FileLoader } from '../../infrastructure/FileLoader' import { TranslationService } from '../../domain/TranslationService' export class GetArticle implements Command<Article> { constructor( private readonly articlesRepository: ArticlesRepository, private readonly id: Id, private readonly locale: Locale, ) {} async execute(): Promise<Article> { return this.articlesRepository.findOneByLocale(this.id, this.locale) } static create(context: { id: Id; locale: Locale }) { return new GetArticle( new ArticlesFileRepository(FileLoader.create(), TranslationService.create(Translator.create())), context.id, context.locale, ) } }

This command is responsible for obtaining a certain article using a repository, where and how do we get this data we neither know nor care, that's responsibility of another class.

This command represents a Use Case of my application. Right now it only needs to get the article from the repository but in the feature it could handle if a user has read the article, or if the user is a PRO user and then can read all articles instead of a subset of articles or anything we'd like.

Who builds the command? Whoever uses it:

const article = await GetArticle.create({ id: 'use-cases-and-commands', locale: Locale.EN, }).execute()

I'm using inversion of control to provide the dependencies needed for the GetArticle use case to work. In this case I'm going from an abstraction (ArticlesRepository) to a concreation (ArticlesFileRepository). If tomorrow I decide to serve the articles via API I would only need to change the factory.

What is also interesting about commands is that they are easily augmented. For example we can log when a command is executed without touching any commands using the Decorator Pattern:

import { Command } from './Command' import { Logger } from './Logger' export class LoggerCommandDecorator<T> implements Command<T> { constructor( private readonly decoratedCommand: Command<T>, private readonly logger: Logger, ) {} execute(): Promise<T> { this.logger.log( (this.decoratedCommand as Object).constructor.name + ' - ' + Object.getOwnPropertyNames(this.decoratedCommand), ) return this.decoratedCommand.execute() } }

Then, using a UserCaseDecorator I specify which decorators I want for all my use cases:

import { Command } from '../../infrastructure/Command' import { LoggerCommandDecorator } from '../../infrastructure/LoggerCommandDecorator' import { Logger } from '../../infrastructure/Logger' export class UseCaseDecorator { private static readonly logger = Logger.create({ // eslint-disable-next-line stdout: { error: console.error, info: console.log, warn: console.warn }, }) static decorate<T>(command: Command<T>) { return new LoggerCommandDecorator<T>(command, UseCaseDecorator.logger) } }

And then in each use case we use the UseCaseDecorator like so:

import { Command } from '../../infrastructure/Command' import { Article, ArticlesRepository } from '../../domain/articles' import { Id } from '../../domain' import { Locale, Translator } from '../../domain/language' import { UseCaseDecorator } from './UseCaseDecorator' import { ArticlesFileRepository } from '../../infrastructure/articles/ArticlesFileRepository' import { FileLoader } from '../../infrastructure/FileLoader' import { TranslationService } from '../../domain/TranslationService' export class GetArticle implements Command<Article> { constructor( private readonly articlesRepository: ArticlesRepository, private readonly id: Id, private readonly locale: Locale, ) {} async execute(): Promise<Article> { return this.articlesRepository.findOneByLocale(this.id, this.locale) } static create(context: { id: Id; locale: Locale }) { return UseCaseDecorator.decorate( new GetArticle( new ArticlesFileRepository(FileLoader.create(), TranslationService.create(Translator.create())), context.id, context.locale, ), ) } }

And we could create as many decorators as we want and use composition to give more behaviour to our commands.