Идемпотентность
Одно из базовых свойств, которое стоит поддерживать при асинхронной обработке команд/событий — это идемпотентность: при повторной обработке события результат не должен меняться (разве что какие-нибудь служебные поля вроде updated_at
).
Достичь exactly-once доставки (т.е. когда гарантируется, что событие будет доставлено ровно 1 раз) в распределенных системах практически невозможно, но с помощью идемпотентной обработки и at-least-once системы (т.е. могут быть дубликаты) можно к этому очень близко подойти. В хардкорной реализации каждое сообщение имеет уникальный идентификатор, каждый клиент помнит идентификаторы обработанных сообщений и выкидывает дубликаты. Однако во многих случаях можно поступить проще и немного дополнить бизнес-логику, чтобы она учитывала возможность повторной обработки, благо многие системы поддерживают upsert или его аналог.
Чтобы не было недоразумений, это поведение стоит покрывать тестами. Наверняка в тестах есть какой-то вспомогательный метод, который проверяет поведение системы после получения какого-то события:
fun onReceiving(event: Event, assertions: () -> Unit)
Вот прямо внутрь него можно и встроить проверку идемпотентности: продублировать внутренности два раза, чтобы два раза прислать сообщение и два раза проверить результат.