DEV Community

Cover image for Domain Modeling Made Functional: A jump into the functional world
Cesar Aguirre
Cesar Aguirre

Posted on • Originally published at

Domain Modeling Made Functional: A jump into the functional world

I originally published an extended version of this post on my blog.

Are you intrigued by functional programming? But you find it difficult because of all definitions and slang? Monads, monoids, endofunctors…This is a good starting point to jump into the functional world.

"Domain modeling made functional" teaches to capture requirements, constraints, and business rules in a system using types.

All the code samples are in F#. But, you can port the concepts from this book to your Object-Oriented language using built-in features or third-party libraries.

These are the two main lessons I learned from reading this book.

1. Express Domain Restrictions with Types

Express restrictions in your design and enforce constraints with new types.

In an order processing system, don't use integers to represent unit quantities in orders. A unit is a concept in the business domain. It should be in a separate class like UnitQuantity.

To restrict unit quantities between 1 and 1000, create a private constructor in the UnitQuantity type and only expose a factory method with the validation.

To enforce that an order should have at least one line item, create a NonEmptyList instead of a possibly empty List.

To represent optional values, don't use null values. Use an optional type, like Option<T>, similar to nullable primitive types in C#.

Café customers in monochrome

Speaking of processing orders...Photo by Alex Jones on Unsplash

2. Make errors part of your domain

Follow the Signature Method Honesty principle by documenting all possible outputs of a method in its signature.

For example, a Divide function shouldn't throw an exception. Instead write, int Divide(int a, NonZeroInt b);

Stay away from exceptions. Instead, use a Result type to wrap failed and successful values.

For example, a method to validate addresses should return Result<CheckedAddress, AddressValidationError>. It means either a valid address or a validation error.

public Result<CheckedAddress, AddressValidationError>
    CheckAddressExists(UnvalidatedAddress address)

public class AddressValidationError {}
public class InvalidFormat : AddressValidationError {}
public class AddressNotFound : AddressValidationError {}
Enter fullscreen mode Exit fullscreen mode

To work with exceptions in third-party code, wrap that code in a function that catches exceptions and returns a Result. Don't catch all exceptions, only those relevant to the domain. Like, timeouts or failed logins.

Voilà! These are the two main lessons I learned from this book. You can still take advantage of the concepts of this book in your everyday programming. Even if you don't work with functional language.

To find all other lessons and takeaways from this book, check Domain Modeling Made Functional: Takeaways.

For more takeaways, check Clean Coder and The Art of Unit Testing. If you're interested in unit testing, check my series of posts Unit Testing 101.

Hey! I'm Cesar, a software engineer, and lifelong learner. I help teams to grow high-quality code. Visit my blog to learn more about my work.

Happy coding!

Discussion (0)