I'm developing the internal microservice that handles operation confirmations using one-time passwords. I've decided to create good domain model use event sourcing approach.
I have Confirmation Session for Operation of a certain Operation Type. User can add ask Session for a Confirmation Attempt and specify the Channel by which he wants to recieve Challenge (one-time password). Channel is like SMS, E-Mail, Push, etc.
Here is code excerpt from command handler
var session = _sessionFactory.CreateSession(operationType, userId);
var challengeCreationResult = await _challengeProvider.CreateAsync(
recipient, channelType, session, addOnFields);
if (!challengeCreationResult.IsSuccess)
{
return OperationResult<AttemptCreationResult>.Error(
challengeCreationResult.ErrorCode, challengeCreationResult.ErrorMessage);
}
session.AddAttempt(challengeCreationResult.Data);
After creating the Confirmation Attempt and receiving Challenge, User is able to submit his response for validation.
var session = await _sessionStore.GetAsync(sessionId);
var result = await session.ApplyResponseAsync(response, _responseValidator, actingUserId, operationType);
Both AddAttempt
and ApplyResponseAsync
result in appropriate events being fired and return operation result to return to User.
I was satisfied with this implementation until I've tried to add business rules that require knowledge of more than one Confirmation Session. These are:
How to implement these rules while keeping aggregate root/services/event handlers small and focused?
I think that UserConfirmation
looks OK as an aggregate, but not the idea of UserConfirmationHistory
, as it is not recommended to have an aggregate with a lot of children, which would be the case here.
This is a case where the business rule does not naturally fit the aggregate and should be moved up to a domain service, which can operate on a list of UserConfirmation
instances, supplied by an Application Service.
I mean, I don't like having any infrastructure concerns at all in my domain classes (be they aggregates or services), and fetching a list of UserConfirmation
entries would require a Query or Repository implementation, which are infrastructure concerns, even if you do abstract them with interfaces.
IUserConfirmationRepository
and fetches a list of UserConfirmation
entries, passing them to a...UserConfirmation
entries and validate logic spanning multiple aggregatesThis way, your business rules are still protected by a domain class.
But proceed with caution:
UserConfirmation
entity without going through this Domain Service, then they will be able to bypass this invariant;internal
in the UserConfirmation
aggregate, and make it so that the only possible path to create this aggregate is by going through the Domain Service.PS. you mention that things were easier when you just had to do simple INSERTS, but: