Какой механизм используется для отправки событий?
Что проверяют
Различаете ли вы события внутри одного процесса и события между сервисами, и можете ли назвать для каждого случая механизм с его гарантиями. Следом почти всегда спрашивают: что будет, если запись в базу прошла, а отправка в брокер упала.
Как отвечать
Разделите ответ на два уровня. Внутри процесса базовый механизм это каналы:
- небуферизованный канал: отправка
ch <- evблокируется, пока получатель не заберёт значение, это синхронная передача; - буферизованный канал:
make(chan Event, 100)сглаживает всплески, но при заполненном буфере отправитель снова блокируется; selectсctx.Done(): отправка с возможностью отмены, иначе горутина зависнет, если получатель ушёл;- fan-out: по каналу на каждого подписчика, или шаблон «наблюдатель» с колбэками, если асинхронность не нужна.
Правило владения: закрывает канал только отправитель. Запись в закрытый канал вызывает панику, чтение из закрытого сразу возвращает нулевое значение.
Между сервисами события уходят в брокер: Kafka (клиенты franz-go, segmentio/kafka-go, IBM/sarama), NATS JetStream, RabbitMQ через amqp091-go. Реалистичная гарантия здесь at-least-once, поэтому потребитель должен быть идемпотентным, например помнить ключи уже обработанных событий.
Проблему двойной записи решает transactional outbox: событие пишется в таблицу outbox в той же транзакции, что и бизнес-данные, а отдельный процесс читает её и публикует в брокер. Альтернатива: CDC по журналу базы через Debezium.
Где спотыкаются
Закрывают канал со стороны получателя или из нескольких отправителей. Итог: паника send on closed channel под нагрузкой, которую трудно воспроизвести.
Запускают горутину-отправителя без выхода по контексту. Если читатель завершился, она висит на ch <- ev вечно, это классическая утечка.
Публикуют в Kafka сразу после COMMIT без outbox и уверены, что событие не потеряется. Любой сбой между двумя операциями даёт рассинхрон, а обещание exactly-once «из коробки» звучит как незнание темы.