“Yesterday’s the past, tomorrow’s the future, but today is a gift. That’s why it’s called the present.”
Bil Keane
It’s not that often that a design pattern really blows my mind anymore, but recently I discovered the Passage of Time Pattern by Mathias Verraes. This pattern describes how to deal with time-dependent processes in event-based systems. What makes this pattern stand out is its elegance and simplicity.
In event-based applications, state changes of a component are distributed via events so that other components can react to these state changes. This type of communication has the advantage that components only need to know about the existence of the events and do not need to know how other components work.
As an example, a billing component and an ordering component for telecommunication products should cooperate with each other. When a new order arrives, it is checked within the order component and a NewOrder
event is sent if it is processed successfully.
The invoice component receives this event and creates a new account if necessary. Sometimes, however, contracts are revoked in the first 10 working days, so the order component sends an OrderRevocation
event from time to time. The invoice component receives this event and deactivates the account that has already been created.
For legal reasons, both components cyclically discard data that is no longer active after specified periods of time. In addition, reports are required that are created weekly, monthly and quarterly.
An external scheduler is used to start corresponding processes on the components on a daily, weekly, monthly and quarterly basis.
Although the system appears to be event-based, a second dimension of processing has crept in via the scheduler. In order to understand the overall system, both dimensions, the asynchronous event communication and the synchronous clocking via the scheduler, must be considered.
The overall system also has to contend with some weaknesses resulting from this dualism. Although the asynchronous architecture can compensate for the failure of some components, the synchronous call by the scheduler requires an active component on the opposite side. If the component is not active, the necessary action that should be called expires. Re-importing events to repair or provide a test system does not produce the previous result because the scheduler’s time intervals are not adhered to.
The Passage of Time Events provides a solution to the dilemma with an ingenious trick. The scheduler no longer calls components, but generates events. For our example scenario, these are the events EndOfWorkday
, EndOfWeek
, EndOfMonth
, EndOfQuarter
.
The new events are added to the sequence of existing events and are consumed by both components. The problem of having two views of the system has now obviously been solved. However, the solution also has other pleasant consequences.
Previously, the billing system had to check for each account whether ten working days had already elapsed since the current call by the scheduler. With the new approach, the component only has to count the number of unique EndOfWorkday
events since the corresponding NewOrder
event. If the ten working days have elapsed, OrderRevocation
events are rejected. The billing system does not have to perform any other public holiday calculations for this. The scheduler checks this before sending an EndOfWorkday
event. The event is not sent on a public holiday or at the weekend.
The scheduler no longer needs to know anything about the various components in the system, as there is no synchronous call to components. The implicit knowledge about the reporting periods is also no longer present in the scheduler. The components generate their reports on receipt of an EndOfWeek
, EndOfMonth
or EndOfQuarter
event. If a weekly report is to be replaced by a bi-weekly report, the relevant component only reacts to every second EndOfWeek
call.
The events can also be used for smaller time periods, e.g. half of the day, end of the night shift, end of opening hours, an hour and a minute. The actual selection of events must be derived from the respective use cases. However, if the granularity is too fine, the event-based system will be slowed down by the sheer flood of Passage of Time events.
The problems with importing events into a test system no longer exist. An exact image of the production environment can now be created because the interventions of the scheduler are now embedded in the event stream. A correct revocation of the order can now be derived directly from the events.
If you are still coupling event-based systems with an external scheduler, you should consider switching to Passage of Time Events. The conversion of an existing system is quite simple and can be done incrementally. First, a new component is created to generate the Passage of Time events. This is linked to the scheduler and sends all the necessary events. Then the components previously linked to the scheduler can be gradually converted. For a transitional period, some components will still work with the scheduler and others will already work with the passage of time events.