Transactional outbox relays normally provide at-least-once delivery. Consumers must therefore reject duplicates, and per-aggregate sequence numbers can prevent later events from being applied before missing predecessors.
Design an OrderedEventConsumer class:
OrderedEventConsumer() creates an empty consumer.String apply(String aggregateId, int sequence, int delta) processes one event.int balance(String aggregateId) returns that aggregate's accumulated balance.int lastSequence(String aggregateId) returns its last applied sequence.
Every unseen aggregate starts with balance 0 and last sequence 0. For an incoming event:
- If
sequence <= lastSequence, return "DUPLICATE" and change nothing. - If
sequence > lastSequence + 1, return "GAP" and change nothing. Gapped events are not buffered. - Otherwise, add
delta to the balance, set the last sequence to sequence, and return "APPLIED".
State is independent for each aggregate. A gapped event can be delivered again after its missing predecessors have been applied.
Example 1:
Input:
Output:
Explanation: Sequence 1 changes the balance once. The repeated delivery is recognized from the stored sequence and has no effect.
Example 2:
Input:
Output:
Explanation: Sequence 2 cannot cross the gap. Once sequence 1 is applied, redelivered sequence 2 is accepted.
Constraints
1 <= aggregateId.length <= 401 <= sequence <= 10^9-10^6 <= delta <= 10^6- Balances remain within the signed 32-bit integer range.
- At most
1000 method calls are made per object.