secure by design - reducing complexity of state

Abstract

  • Entities can be designed to be partially immutable.
  • State handling is easier to test and develop when extracted to a separate object.
  • Multithreaded environments for high capacity require a careful design.
  • Database locking can put a limit on availability of entities.
  • Entity snapshots are a way to regain high availability in multithreaded environments.
  • Entity relay (when fulfillment of one entity gives rise to another) is an alternative way to model an entity that has lots of different states.

Partially immutable entities

When there are attributes that shouldn’t change, make entities partially immutable to avoid integrity-breaking mistakes.

In Java, we can use the keyword final in the object property to ensure its property is cannot be changed after the object is created. Moreover, the compiler enforces that a final field is set by the constructor.

Entity state objects

Upholding entity state

One thing that makes working with entities difficult is that not all actions are allowed in all states (e.g. marital states: unmarried and married).

Warning

Hearing something like, “It’s just an if statement,” is a sign that you’re on a dangerous slope, heading downhill toward broken entity states. Listen carefully during story-planning or solution-design meetings.

Obviously, having a state implemented as if statements in service methods or entity methods isn’t good design, but is it a security problem? Well, yes. If an entity leaves an opening for using a method that shouldn’t be open in that state, then that opening can potentially be used as the starting point of an attack that exploits the mistake.

Upholding rules in service methods becomes inconsistent as code evolves over time hard to get an overview of what rules apply.

public class Work {
  private Person boss;
  private Person employee;
  void aferwork() {
    if (!boss.isMarried()) {
      boss.date(employee);
    }
  }
}

A somewhat better version is when entity methods upload the state rules, but even this approach might lead to exploitable inconsistencies.

public class Person {
  private boolean isMarried;
  public void date(Person datee) {
    if (!isMarried) {
      dinnerAndDrinks();
    }
  }
}
public class Work {
  void afterwork() {
    boss.date(employee);
  }
}

Implementing entity state as a separate object

A state object can be used as a delegated helper object for the entity. Every call to the entity is first checked with the state object.

public class MaritalStatus {
  private boolean isMarried = false;
 
  public void date() {
    validateState(!isMarried, "Not appropriate to date when married")
  }
 
  public void marry() {
    validateState(!isMarried);
    this.isMarried = true;
  }
 
  public void divorce() {
    validateState(isMarried);
    this.isMarried = false;
  }
}
public class Person {
  private MaritalStatus maritalStatus = new MaritalStatus();
 
  public void date(Person datee) {
    this.maritalStatus.date();
    buyDrinks();
    offerCompliments();
  }
}

Extracting your state management into a separate object makes your entity code much more robust and much less prone to subtle business integrity problems like customers avoiding to pay for their orders before they’re shipped.

Tip

Use a separate state object when there are at least two states with different rules and when the transitions between them aren’t completely trivial.

However, such approach is not the silver bullet and has flaws in multi-threaded environments.

Entity snapshots

Entity snapshots isn’t represented in code through a mutable entity class, there are snapshots of the entity that are used to look at that entity and take action.

A metaphor is the photos that are shared by your friends which are snapshots of your friend as each photo is a representation of your friend at a particular point in time.

The entity snapshot is not just a dump reporting object, it contains interesting domain logic like a classical entity.

In order to change the state of the underlying entity, as the mutable entity can be represented through immutable snapshots, we can provide a domain service to which you send updates.

Entity relay

When entity state graph grows and becomes quite large and less easy to grasp, it’s better to have entity chaining, i.e. split the entity’s lifespan into phases, and let each entity represent its won phase. When a phase is over, the entity goes away, and another kind of entity takes over, like a relay race.

Consider forming an entity relay when:

  • there are too many states in an entity
  • phases where your never go back to an earlier phase
    • otherwise, the simplicity of a relay race is lost
  • simple transitions from one phase to another and few transition points (preferably only one)