Entity factories are functions that are used to create an object conforming to a specific typescript interface. These functions are tedious to write but offer a ton of benefits. I use them for virtually every entity I need to build and on every single project.
What's the problem? #
Let's say we have an entity called User
. The user has a set of properties on
it:
1interface User {
2 id: string;
3 name: string;
4 email: string;
5 verified: boolean;
6 admin: boolean;
7}
Okay now we want to create a user entity in our project, the default way to do that would be something like:
1const user: User = {
2 id: createId(),
3 name: "user name",
4 email: "user@name.com",
5 verified: false,
6 admin: false,
7};
Doing this once seems straight-forward, but what about 10 times? 30 times? What about when you want to write some unit tests and you have to create a user entity a bunch of times?
This is where the problem lies. It seems easy to just build the entity everytime you need it but other problems arise: what happens when you need to refactor the user object to add or remove a property? Now you have to traverse the entire codebase to make the change to all of the objects.
What's the solution? #
If we take the same User
entity and create a function that builds the entity
for us, we can reuse it everywhere:
1const defaultUser = (u: Partial<User> = {}): User => {
2 return {
3 id: createId(),
4 name: "",
5 email: "",
6 verified: false,
7 admin: false,
8 ...u,
9 };
10};
Now when we want to create a new user object, we can do this:
1const user = defaultUser({
2 name: "user name",
3 email: "user@name.com",
4});
If we need to make a change to the user interface, all we have to do is change
the defaultUser
function and all objects that were created by the factory will
most likely "just work." Obviously if we removed name
, email
, or changed the
names of those properties, we will have to update them manually, but that is
usually rare.
So what are the benefits of this paradigm?
Easy to create an entity #
Creating a User
entity becomes much easier. All we have to do is import and
call defaultUser()
and it will create the entity for us. In the beginning it
seems very tedious to build these factories because it's a hand-written
function, but we only have to write it once and then we permenantly enjoy the
ease-of-use forever.
A single entry-point for creating an entity #
This is huge. There is only one entry-point for creating the User
entity
inside of your codebase. Once this entity factory is written, everywhere will
instinctively use it because it is so easy to use.
Easy to bake in sane defaults #
This was an idea that I got from working with golang. When creating a struct in
golang, it's very common to create a function like NewUser
which does the
exact same thing as the the typescript version written above.
In golang, there are zero values which provide sane defaults for all primitive types
If it's a string the zero value is ''
and if it's a number it's a 0
. I have
applied the same concept to typescript and it works incredibly well.
I bet you already do this an you don't even realize it. For example, if a
property is a an array then the zero value is an empty array []
. That way,
when we need to perform a map
or filter
on the array we don't have to check
if the value is an array. Make sense, right? Well I just apply the same concept
for all types in typescript. Basically, I try to avoid null
or undefined
values as much as possible. I elaborate on this concept in a previous blog
article:
Death by a thousand existential checks
Ability to override defaults #
Because we accpe a partial User
object as a parameter to our factory, it's
easy to override the defaults. This makes it easy to use: pass in what you want
to change and let the defaults do the rest of the entity building.
No libraries required #
The paradigm is so simple you don't really need a library to handle the factory building. I've thought about creating a library for this paradigm but honestly, part of the appeal is that it's just a simple function. It's easy to read, easy to understand, and there's no magic required. All we do is leverage typescript syntax and that's it.
Conclusion #
It might seem obvious, but entity factories are a very critical aspect to how I build maintainable code that is easy to update, extend, or modify entities over time.