Modeling superstate and substate combinations in DDD

I am designing an analytics application using domain driven design and test driven development. I am having difficulty modeling the following requirement: The application shows a workitem's state. States are modeled as a composition of a superstate and a substate. Superstate can be any of the following: "A", "B", "C", "D" Substate can be any of the following: "X", "Y", "Y1", "Y2", "Z" Each superstate has a set of valid substates: Valid substates of "A" are "Y", "Z". Valid substates of "C" are "X", "Y1", "Y2", "Z". All other superstates have the following valid substates: "X", "Y", "Z" I devised a model which includes the following aggregate: Workitem is the aggregate root and Workitem State is a value object. A check at the instantiation of Workitem will ensure that the state has a valid combination of superstate and substate. Since I'm following a TDD approach, the implementation of the state validation logic should be preceded by the definition of a corresponding test(psedudo-code): #test module func test_state_validity(): # tests all combinations of superstate and substate for sup in SuperState: for sub in SubState: try: s = State(sup, sub) assert sub in get_allowed_substates(sup) except InvalidStateException: assert sub not in get_allowed_substates(sup) Then i wrote the following implementation(psedudo-code): # states module enum SuperState: A, B, C, D enum SubState: X, Y, Y1, Y2, Z class WorkitemState: superstate: SuperState substate: SubState def create_state(superstate, substate): if substate in get_allowed_substates(superstate): return WorkitemState(superstate, substate) else: raise InvalidStateException func get_allowed_substates(superstate): switch superstate: case SuperState.A: return (SubState.Y, SubState.Z) case SuperState.C: return (SubState.X, SubState.Y1, SubState.Y2, SubState.Z) case _: return (SubState.X, SubState.Y, SubState.Z) In compliance with DDD, the invariant has been enforced within the Domain Layer (in the aggregate root). In compliance with TDD, a corresponding test has been defined as well. This made me realize there is significant overlap between the validation logic within the domain objects and unit tests (in my case I tried to keep it DRY by having both refer to the function get_allowed_substates). My questions are: is writing tests for the domain model redundant, and thus be avoided? (in general and in this specific case) is it a sensible choice to test all possible combinations of superstate and substate, or should I rather restrict the scope of the test? e.g. all valid combinations plus a few edge cases such as {("A","X"), ("C","Y"), ("B", "Y1")} note: differently from the example, my actual use case has ~100 possible state combinations. This number is unlikely to change in the future, though.

Jun 20, 2025 - 15:20
 0
Modeling superstate and substate combinations in DDD

I am designing an analytics application using domain driven design and test driven development. I am having difficulty modeling the following requirement:

The application shows a workitem's state. States are modeled as a composition of a superstate and a substate.

  • Superstate can be any of the following: "A", "B", "C", "D"
  • Substate can be any of the following: "X", "Y", "Y1", "Y2", "Z"

Each superstate has a set of valid substates:

  • Valid substates of "A" are "Y", "Z".
  • Valid substates of "C" are "X", "Y1", "Y2", "Z".
  • All other superstates have the following valid substates: "X", "Y", "Z"

I devised a model which includes the following aggregate: UML class diagram showing a "Workitem" class referencing a "Workitem State" class, in turn referencing two enum classes: "Superstate" and "Substate". The enums value are those described earlier

Workitem is the aggregate root and Workitem State is a value object. A check at the instantiation of Workitem will ensure that the state has a valid combination of superstate and substate.

Since I'm following a TDD approach, the implementation of the state validation logic should be preceded by the definition of a corresponding test(psedudo-code):

#test module

func test_state_validity():
   # tests all combinations of superstate and substate
   
   for sup in SuperState:
      for sub in SubState:
         try:
            s = State(sup, sub)
            assert sub in get_allowed_substates(sup)
         except InvalidStateException:
            assert sub not in get_allowed_substates(sup)

Then i wrote the following implementation(psedudo-code):

# states module

enum SuperState:
   A,
   B,
   C,
   D

enum SubState:
   X,
   Y,
   Y1,
   Y2,
   Z

class WorkitemState:
   superstate: SuperState
   substate: SubState

   def create_state(superstate, substate):
      if substate in get_allowed_substates(superstate):
         return WorkitemState(superstate, substate)
      else:
         raise InvalidStateException


func get_allowed_substates(superstate):

   switch superstate:
      case SuperState.A:
            return (SubState.Y, SubState.Z)

      case SuperState.C:
            return (SubState.X, SubState.Y1, SubState.Y2, SubState.Z)

      case _:
            return (SubState.X, SubState.Y, SubState.Z)

In compliance with DDD, the invariant has been enforced within the Domain Layer (in the aggregate root). In compliance with TDD, a corresponding test has been defined as well.

This made me realize there is significant overlap between the validation logic within the domain objects and unit tests (in my case I tried to keep it DRY by having both refer to the function get_allowed_substates).

My questions are:

  • is writing tests for the domain model redundant, and thus be avoided? (in general and in this specific case)
  • is it a sensible choice to test all possible combinations of superstate and substate, or should I rather restrict the scope of the test? e.g. all valid combinations plus a few edge cases such as {("A","X"), ("C","Y"), ("B", "Y1")}

note: differently from the example, my actual use case has ~100 possible state combinations. This number is unlikely to change in the future, though.