DEV Community

Gealber Morales
Gealber Morales

Posted on • Originally published at

Transactional outbox. Part I.

This pattern also known as application events, try to solves the following problem.


How to reliably/atomically update the database and send messages/events?


  1. 2PC is not an option. 2PC stands for Two phase commit...Google :).
  2. If the database transaction commits messages must be sent. Conversely, if the database rolls back, the messages must not be sent
  3. Messages must be sent to the message broker in the order they were sent by the service. This ordering must be preserved across multiple service instances that update the same aggregate.

Application description

In order to be more explicit with my examples, I'll describe you a silly application which business model revolves around cats, ok? So of course we have a
table in our DB called "cats", which store basic information about the cats registered in our platform. The problem that we want to resolve it's that
on every update that we made to a cat in our DB, for example address changed, name changed, starving state changed, etc... We want to let know this to other
services, or consumers in this case, that might use this information. All clear right, now reading at the Forces section, you must notice that those requirements are not that crazy. Now let's go to the solution, propose for this pattern.


The solution that this pattern propose is as follow. Apart from the table "cats", with all the information about cats, we will have another table that will be the outbox("buzón de salida", in my beloved language), let's name this table "events". In this table we will store messages/events, only when the operation in the cats DB succeeded. As both operations, the one that write into the "cats" table and the one that write into "events" table, will happen in the
same transaction this assure us that we will only commit the transaction when both operations succeeded.
Now, after this write in "events" table, we will have a process that will read from this table, and publish into the message broker.

The flow it's better described in the following image.

Pattern diagram


  1. Messages are guaranteed to be sent if and only if the database transaction commits
  2. Messages are sent to the message broker in the order they were sent by the application


  1. Developer might forget to publish the message/event after updating the database.
  2. If the Message Relay crash, you might end up sending more than one time the same message. This implies that message consumers should be idempotent, so the processing of a same message more than one time, doesn't has an effect the second time it's processed with the same input. Read more about idempotent in this Stackoverflow question.

Implementation example

Until now we've been basically copying and pasting from Pattern: Transactional outbox, shameless copy and paste, that's right. As almost all my articles, the purpose is to actually get my hands dirty and really learn about something I don't have
a clue. In order to grasp this pattern well, we need to code it, otherwise I'll forget it. Let's do it. I'll be using Golang for this.

Project structure

├── config
│   └── config.go
├── controllers
│   ├── cat
│   │   └── controller.go
│   └── model
│       ├── cat_converter.go
│       └── cat.go
├── database
│   ├── database.go
│   └── migrations
│       ├── 20221005203113_events_table.sql
│       └── 20221005203128_cat_table.sql
├── docker-compose.yaml
├── go.mod
├── go.sum
├── main.go
├── Makefile
├── middlewares
│   └── cors.go
├── msgrelay
│   └── msgrelay.go
├── repositories
│   ├── cat
│   │   └── repository.go
│   ├── event
│   │   └── repository.go
│   └── model
│       ├── cat.go
│       └── event.go
└── routes
    └── routes.go
Enter fullscreen mode Exit fullscreen mode


Let's declare our models in golang.


Here are some characteristics from cats that we want to reflect in our platform.

  • Name
  • Color
  • Weight
  • Intelligence (in a scale from 1-5, because only dogs are in a scale from 1-10, they are way more smart)
  • Laziness (in a scale from 1-10)
  • Curiosity (in a scale from 1-10)
  • Sociability (in a scale from 1-10)
  • Egoism (in a scale from 5-10)
  • Miau Power (how loud he can miau, from a scale from 1-10)
  • Attack power (in a scale from 1-10)

According to these requirements we have the following structs:

file: repository/model/cat.go

package model

type Cat struct {
    ID           string  `gorm:"primaryKey"`
    Name         string  `gorm:"column:name;size:100"`
    Color        string  `gorm:"column:color;size:100"`
    Weight       float64 `gorm:"column:weight"`
    Intelligence int     `gorm:"column:intelligence"`
    Laziness     int     `gorm:"column:laziness"`
    Curiosity    int     `gorm:"column:curiosity"`
    Sociability  int     `gorm:"column:sociability"`
    Egoism       int     `gorm:"column:egoism"`
    MiauPower    int     `gorm:"column:miau_power"`
    Attack       int     `gorm:"column:attack"`

Enter fullscreen mode Exit fullscreen mode

Just remember that this is our main resource, the want the we want to perform an update operation wit.


In this table we will store the message/event information we want to send to the other services, it's up to you what you want here, but you should at
least reference which resource was changed. In our case we just want to store, the id of the resource, the type of resource, and the type of event that was triggered.

file: repository/model/event.go

package model

type Event struct {
    ID           string `gorm:"primaryKey"`
    Type         string `gorm:"column:type"`
    ResourceID   string `gorm:"column:resource_id"`
    ResourceType string `gorm:"column:resource_type"`
    Published    bool   `gorm:"column:published"`

Enter fullscreen mode Exit fullscreen mode

Basic Create and Update operations

For our purpose the simplest scenario it's as follows, I create a cat, then update the cat, and this update operation should trigger the event. So we will
only be creating these two operations, feel free to create the other part of the CRUD, I'm lazy.

file: repository/cat/repository.go

package cat

import (


type repository struct {
    db *gorm.DB

func New(db *gorm.DB) *repository {
    return &repository{db: db}

// Create creates a new cat :).
func (r *repository) Create(ctx context.Context, cat model.Cat) (*model.Cat, error) {
    if err := r.db.Create(&cat).Error; err != nil {
        return nil, err

    return &cat, nil

// Update perform update operation to specified cat :).
func (r *repository) Update(ctx context.Context, id string, cat model.Cat) (*model.Cat, error) {
    err := r.db.Transaction(func(tx *gorm.DB) error {
        // update record in cats table.
        result := tx.Model(&model.Cat{}).Clauses(clause.Returning{}).
            Where("id = ?", id).

        if result.RowsAffected < 1 {
            return gorm.ErrRecordNotFound

        // create event to store.
        event := model.Event{
            Type:         "update",
            ResourceType: "cat",
            ResourceID:   id,

        // write event in events table.
        if err := tx.Model(&model.Event{}).Create(&event).Error; err != nil {
            return err

        return nil

    if err != nil {
        return nil, err

    return &cat, nil

Enter fullscreen mode Exit fullscreen mode

To be continued...

Now, one of the parts in this pattern, and an important one, is the Message Relay. Let's discuss that in other article, I'm tired of typing :), lazy like a cat.


Part of the code can be found in this repository, I say part because the message relay use the Polling publisher pattern, which is not scalable. I'll discuss that other time.


  1. Transactional outbox

Top comments (0)