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.
A somewhat better version is when entity methods upload the state rules, but even this approach might lead to exploitable inconsistencies.
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.
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)