Sobes.tech
Senior

How to implement the reverse side — Inbox pattern on the notification service side? How to ensure idempotency of event processing?

sobes.tech AI

Answer from AI

The Inbox pattern in the notification service is used to ensure idempotent processing of incoming events. The idea is to store unique identifiers of processed events (for example, eventId) in a special table (Inbox) so that when the same event is received again, it is not processed a second time.

Implementation:

  1. When receiving an event, check if eventId exists in the Inbox table.
  2. If not, process the event and save eventId in the Inbox.
  3. If it exists, skip processing.

To ensure idempotency, it is important that the recording of eventId and event processing are atomic. In Go, using a database, this can be done within a transaction.

Example in Go (simplified):

func ProcessEvent(db *sql.DB, event Event) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()

    var exists bool
    err = tx.QueryRow("SELECT EXISTS(SELECT 1 FROM inbox WHERE event_id = ?)", event.ID).Scan(&exists)
    if err != nil {
        return err
    }
    if exists {
        // Already processed
        return nil
    }

    // Process the event
    err = handleEvent(tx, event)
    if err != nil {
        return err
    }

    // Record eventId in Inbox
    _, err = tx.Exec("INSERT INTO inbox(event_id) VALUES(?)", event.ID)
    if err != nil {
        return err
    }

    return tx.Commit()
}

Thus, even if the event is delivered multiple times, it will only be processed once.

It is also possible to use unique constraints on the event_id column to prevent duplication during parallel processing.