Design Tip – Avoid Enum Types in Domain Layer

Problem

An enum is a special value type that lets you specify a group of named numeric constants. They can help make code more readable, as opposed to using int to represent constants or flags. However, using them to represent domain abstractions, within the domain layer, could lead to:

  • Poor encapsulation of domain concepts
  • Duplication of business rules
  • Difficult to incorporate changes
  • Exceptions due to invalid state

Let’s look at an example, imagine we’re developing a payroll application that calculates National Insurance (NI) for employees. Every employee has NI Letter that represent the percentage of their contribution (deduction). We could represent the set of NI Letters as an enum:

Then within our application we’ll use Employee’s NI Letter to decide:

  • Whether an employee can pay NI
  • Percentage of contribution
  • Can the employee defer NI
  • Will employee be classified as apprentice for NI purposes

Note: this is not a definite list of business rules related to National Insurance Letters, they are too many to list here. I am keeping the rules and sample code simple to keep our focus on enums and their use within domain logic.

To implement these business rules we’ll use the NationalInsuranceLetters enum in our application, for example:

By default underlying value of enum is of type int, hence in order to avoid exceptions thrown in case of invalid value passed to this function, we’ll need to add a guard clause:

This seems quite harmless and is probably OK for demo or simple applications. However, as the complexity grows, this little piece of logic (i.e. to check for valid value and if employee is exempt from NI) would be duplicated throughout your application. Several other business rules related to NI Letters would be spread throughout your code in a similar way.

You could solve the duplication issue by creating methods in common library/service/utility class:

Now the calling method could use this service:

Although this is an improvement, I am still not happy with NationalInsuranceService:

  • Method signature for IsExcept() method is not honest, it’s promising to return Boolean but could throw exception too.
  • Details of the service must be understood by programmer consuming it e.g. in order understand why the exception was thrown, this leads to poor encapsulation.
  • It is only at runtime that an invalid value for NI Letter is caught, the design is not defensive i.e. allows a programmer to write code that could potentially fail.
  • This is not an object-oriented design, instead a procedural design implemented using object-oriented constructs.

How can we improve on this? I’ll provide one solution that I prefer – domain abstractions.

Solution

When we think in terms of enum, int or string etc, we’re thinking in terms of programming abstractions. There is nothing wrong with that, when writing code.

However, when designing application or with a designer hat on during coding, we need to think in terms of domain abstractions. That is, thinking in terms of concepts present in business domain for which we’re writing the application e.g. NI Letter, Gender, Marital Status are not just enumeration lists, they are concepts within the domain of payroll processing.

Thus to represent the abstraction of NI Letter, you could create a class NationalInsuranceLetter to encapsulate business rules:

This could be used by the caller like:

Note that this class can’t be instantiated, its constructor is private:

This means that you’ve made it impossible for anyone using this class to instantiate in an invalid state. Thanks to the factory methods (implemented as static properties), the usage of this would be similar to enums:

Now that you have an abstraction, you could add related behaviour to it, encapsulating domain logic, increasing re-use and making changes easy. For instance, some of the other rules could be implemented like:

Encapsulation is a fundamental tenant of good software design. Wrapping an enum into an object and providing cohesive behaviour to it would lead you down the path of well encapsulated and designed applications.

Note: this is not the only way to design your code, even if you don’t agree with my solution, hopefully it will add a technique to your repertoire.

Source Code

GitHub: https://github.com/TahirNaushad/Fiver.Design.EnumToObject

Leave a Reply