Type Driven Development: Branded Types

Written by

in

Part 1 – Branded Types

This is a series of posts I’m writing about using types as another tool in software development. Automated Tests & Builds are part of Continuous Delivery. As the use of LLM’s increases the amount of code, many of us are going full shields double-front to double down on best practices to handle the increased influx of code. Types aren’t often talked about as a way to help with your shift left process; e.g. speeding up automated quality checks locally. They’re also a design & refactoring tool, a communication tool, and reduce how many tests you have to write.

Parts:
  1. Part 1 – Branded Types
  2. Part 2 – Product Types
  3. Part 3 – Union & Discriminated Unions
  4. Part 4 – Non-Empty Collections
  5. Part 5 – Indexed Types
  6. Part 6 – unknown vs any
  7. Part 7 – Result
  8. Part 8 – Schema
  9. Part 9 – Total Function
  10. Part 10 – Errors as Values
  11. Part 11 – Property Tests
  12. Part 12 – Type Proofs
  13. Part 13 – Exhaustiveness Checking
  14. Part 14 – Parse, Don’t Validate
  15. Part 15 – Anti-Corruption Layer
  16. Part 16 – Opaque Types
  17. Part 17 – Maybe
  18. Part 18 – Smart Constructors
  19. Part 19 – Pipeline
  20. Part 20 – Railway Oriented Programming
  21. Part 21 – Typestate
  22. Part 22 – Capabilities
  23. Part 23 – Immutability
  24. Part 24 – Making Impossible States Impossible
  25. Part 25 – Type Driven Development: How to do it
  26. Part 26 – Final Thoughts


Let’s talk about the 2 most basic problem using types: Naming Things & Primitive Obsession.

Naming comes from Domain Driven Design. The words we use for things should be the same & reflected in the code. If the User, Product Owner, + Business Analyst all call it a “Customer Account” then we should too as should the code.

This prevents developers from using the wrong word like just “User”, then later making some bad assumptions, such as adding incorrect logic in code simply because the “meaning” of User is different than a Customer Account. e.g. Allowing “Users” to purchase orders when only “CustomerAccounts” should be allowed to do so. Clear communication is huge.

2nd, you can help the compiler know these differences. Often developers will default to primitives, e.g. a string to differentiate between a User and a Customer Account. Here is an incorrect parsing of headers:

const customerID:string = request.headers['x-user-id']
const userID:string = request.headers['x-customer-id']Code language: JavaScript (javascript)

The problem is the compiler cannot tell the difference between primitives. That means this code looks ok:

purchase(customerID)

But in fact, you’re allowing a user to purchase something when what you meant to have happen was the customer should purchase it. Even good type systems like Python, TypeScript, and even Elm and Scala will allow this to happen if the purchase function is typed like:

type Purchase = (customerID:string) => ...Code language: JavaScript (javascript)

Instead, use Branded types (or Wrapper Types) to help the compiler act in a Nominal way. Nominal meaning it knows that a CustomerID and UserID are different.

First, define the 2 types:

type UserID = string & { brand: 'UserID' }
type CustomerID = string & { brand: 'CustomerID' }Code language: JavaScript (javascript)

Then update your function to use it:

type Purchase = (customerID: CustomerID) => ...Code language: JavaScript (javascript)

Now the compiler won’t let you mix up the ID’s, nor do you need to write unhappy path tests when you pass in the wrong ID.

When parsing from unknown sources, like request headers or JSON, schema libraries like Zod support this. Instead of:

const Headers = z.object({
  'x-user-id': z.string(),
  'x-customer-id': z.string()
})Code language: JavaScript (javascript)

Instead go:

const Headers = z.object({
  'x-user-id': z.string().brand<'UserID'>(),
  'x-customer-id': z.string().brand<'CustomerID'>()
})Code language: JavaScript (javascript)

The compiler can help you distinguish between 2 things; make “thing” the same word your Users, Design, and Business use.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *