Factories

Factories enable efficient and simple production of (usually) complex objects. Let's use them to simplify our implementation interfaces.

TL;DR

The Factory pattern is a classic creational pattern. Some important reasons to use the pattern include that it appropriately encapsulates creation logic, as well as provides a structured way of creating objects in a deterministic manner.

Factories encapsulate the creation of, primarily, complex objects such as those in the domain layer. The pattern itself has nothing to do with DDD (instead, please see Design Patterns: Elements of Reusable Object-Oriented Software). In the context of DDD, we gain even better enforcement of encapsulation, which is especially meaningful when we need to construct an Entity or Aggregate.

Factories help us to hide implementation and construction logic and always return valid invariants of the class ("product") that we have created. However, invariant logic and validation should as far as possible be deferred to the product being created itself, which makes perfect sense if we are using Factories to create complex objects like Entities and Aggregates that already have such logic baked in.

You can probably imagine a case where the setup of an Aggregate will require pulling lots of parameters, checking validity, and other such stuffโ€”this is a perfect case of hiding that with a Factory. I've used Factories several times when I need to create an object that requires complicated asynchronous setups. By using the Factories we can avoid leaking out any of that complexity onto the user.

The way Factories are used in the example is very basic. There is nothing blocking you from applying the Factory pattern to creational methods on Aggregates or Services themselves (see for example Vernon 2013, p.391/397).

Examples of the pattern

To be fair, there are no good uses of "proper" and complex factories in Get-A-Room.

Often you will find factories in an object-oriented class shape, but here we will use a more TypeScript-idiomatic way of using functions.

Several factories have been used to remove some of the ugly new SomeClass() calls. I'll happily use it whenever I want to avoid letting a user directly access a class, like this:

code/Analytics/SlotAnalytics/src/infrastructure/repositories/DynamoDbRepository.ts
/**
 * @description Factory function to create a DynamoDB repository.
 */
export function createNewDynamoDbRepository(): DynamoDbRepository {
  return new DynamoDbRepository();
}

This also works well in creating concrete instances of services that need some values for setting them up:

code/Reservation/Reservation/src/application/services/VerificationCodeService.ts
export function createVerificationCodeService(securityApiEndpoint: string) {
  return new ConcreteVerificationCodeService(securityApiEndpoint);
}

More on the VerificationCodeService later.

We can also use it to package some important checks or validations we may have, like with the EventBridge emitter:

code/Reservation/SlotReservation/src/infrastructure/emitters/EventBridgeEmitter.ts
/**
 * @description Factory function to return freshly minted EventBridge instance.
 */
export const makeNewEventBridgeEmitter = (region: string) => {
  if (!region)
    throw new MissingEnvVarsError(
      JSON.stringify([{ key: "REGION", value: region }])
    );

  return new EventBridgeEmitter(region);
};

While very basic, all of these (especially the two last ones) get the point across; A Factory can hide some of the ugly details involved in creating important objects.

For an excellent and more in-depth article on factories, see https://www.culttt.com/2014/12/24/factories-domain-driven-design or https://refactoring.guru/design-patterns/factory-method/typescript/example.

Overall, I highly recommend checking out the creational patterns at https://refactoring.guru/design-patterns/creational-patterns.

Last updated