Arad
Arad

Reputation: 12852

How to deal with temporal business rules in DDD+Event Sourcing?

Say we have an OTP entity that represents a one-time password that expires after e.g. 15 minutes.

Our system is event-sourced and adheres to DDD principles; for example, we have the following event:

public record OtpCreatedEvent(
    string MobilePhoneNumber,
    string Password,
    IPAddress RequesterIp,
    DateTimeOffset OccurredAt
) : IEvent;

Now, upon the creation of new OTPs (e.g. inside a CreateOtp command), we have two invariants/validations/business rules that need to be satisfied before a new OTP can be created (both of which are set-based):

And in a VerifyOtp command, we have to retrieve the list of all non-expired (i.e. active) OTPs for the provided phone number, and check the entered password against all of them.

There's a lot of time-based logic going on. I think broadly speaking, I have 2 approaches I could take:

  1. Incorporate this logic into the queries that are made to the data store when the commands above are invoked (e.g. var nonExpiredOtps = dataStore.Otps.Where(otp => otp.CreatedAt > DateTime.Now.AddMinutes(-15)) — massively simplified but you get the idea).
  2. Upon creating a new OTP, somehow "schedule" an ExpireOtp command (which will in turn emit an OtpExpiredEvent) to be invoked 15 minutes from now.

Obviously, approach #2 would require some extra plumbing infrastructure-wise. And approach #1 would result in some awkward-looking code, in some parts.

But assuming that both are doable, I'm curious which approach you'd recommend and why? And if there are any others, I'd like to know.

Upvotes: 0

Views: 122

Answers (1)

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57307

What you probably want to be doing is gathering all of the significant "decision making" into a single authority (controlled by a single "lock"), and anywhere else you are reading the decisions of that authority (ie: without holding the lock) you need to understand that you are working with information that may already be out of date, and design your model accordingly.

One thing you ought to be looking at very carefully is this 15 minute policy - is that information that should be distributed, or is the "currently active policy" something that is applied by the single authority; and if so, when do changes to the policy take effect? Do we need to track which policies apply to which OTP? etc.


Upon creating a new OTP, somehow "schedule" an ExpireOtp command (which will in turn emit an OtpExpiredEvent) to be invoked 15 minutes from now.

One answer to consider here is to simply write the expiration time into the OTPCreated event, so that "everybody" who looks at the event knows when it is no longer valid, and can act accordingly.

More broadly, in the general case you have two different decisions you are making (a) can we add another entry into the OTP collection and (b) what expiration time(s) do we assign to an entry in the collection. Those could be decisions made by different authorities, or distinct decisions made at different times by a single authority. In the easy case they are decisions made at the same time by a single authority, and you get to think about whether that means a single "event" or two distinct "events" being written while you hold the lock.


Note: the fact that you are using "event sourcing" (assuming you are using that label to approximate the current patterns of the ddd-cqrs-es community) really isn't significant to this problem.

Which is to say that the hard part here is figuring out what information needs to be locked when you are writing things down, and which parts of the processing can work with unlocked copies of data, and how to keep all of this aligned with a "real world" where time is relentlessly passing.

Writing facts down as sequences of events doesn't actually add a lot of difficulty to the problem. (That said, the "cost of being wrong" can be higher, depending on how flexible your event storage appliance is about allowing you to change your lock boundaries later.)

Upvotes: 1

Related Questions