I am modeling an aggregate root, which has several actions that perform operations against other entities, as you'd expect. The aggregate, however, has a state, and several of these operations can only be performed when the aggregate is in a particular state.
I created an implementation of the state pattern, so that the aggregate would simply delegate the action to the state concrete object. However, now that I have implemented it, I found meself with the following concerns:
- There are operations that can be invoked in more than one state, thus I ended up repeating implementations.
- There are operations that generate domain events, so I had to pass the root's event collection so they can add the events properly.
- Some operations require access to the private members of the aggregate root, so I ended up either declaring them as internal (C#) or creating internal methods that modify the private members.
So now I'm wondering whether the implementation was worth it, or if the state object should only have CanPerformOperation1 properties, and let the aggregate root check this property and if false, throw an InvalidOperationException.
The following code is a summary of what I'm trying to attempt.
interface IState {
void Register(DomainName domain, CustomerCode code);
void Activate(ActivationManifest manifest);
void Lock();
void Unlock();
void EnsureConsistency();
}
class NewState : IState {
// can only call Register method, transitions to RegisteredState
}
class RegisteredState : IState {
// can only call Activate method, transitions to ActiveState
}
class ActiveState : IState {
// can call Lock or EnsureConsistency
// Lock transitions to locked state
// EnsureConsistency can transition to RestrictedState or ActiveState
}
class LockedState : IState {
// can only call Unlock, transitions to ActiveState
}
class RestrictedState : IState {
// can only call EnsureConsistency, which can transition
// to ActiveState or RestrictedState
}
class Tenant {
private IState _state = new NewState(this);
private readonly UserAccountCollection _accounts;
private readonly LicenseCollection _licenses;
private readonly ApplicationCollection _applications;
// had to make these internal accesors to be used by
// EnsureConsistency in ActiveState and RestrictedState
internal UserAccountCollection _accounts => _accounts;
internal Application RegisterApplication(AppKey key, UserAccount admin){
// this method is called by the RegisteredState.Activate method
// so what's the point of delegating?
}
internal License RegisterLicense(LicenseKey key) {
// this method is also called by the RegisteredState.Activate
// method, just like the one above.
}
// etc
}
Now, this will only increase in complexity, as the customer requires me to add more methods that depend on state. So I was just wondering whether I should just add properties like CanRegisterApplication, CanRegisterLicense, etc., and then the states will only be acting as a flag switch.
What would be a proper way to implement what I'm trying to achieve? Or maybe I'm getting the state pattern wrong?