Using Transaction Events To Defer Actions

We can bind handlers to transaction events to easily defer specific actions until after the transaction has been committed and our changes have persisted to the database.

Published
Jul 11, 23

Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.

Adocasts

Burlington, KY

Transactions have two events we can bind handlers to.

  1. Commit - when the transaction persists its changes to the database

  2. Rollback - when the transaction reverts any changes applied to the database

Snippet Scenario

Let's say we have a Notification model. When new notification records are created we want to emit an event to send an email to the notification recipient.

However, our notification is created inside a transaction that's in charge of persisting other records besides our notification.

In case we run into any errors between our notification creation and our transaction commit, we'll only want to emit our event that sends the email when we know our transaction completes and our records are all persisted.

// inside a controller or service

const notification = new Notification()

if (trx) {
  notification.useTransaction(trx)
}

notification.merge({
  userId: post.author.id,
  initiatorUserId: user?.id,
  notificationTypeId: NotificationTypes.COMMENT,
  table: Comment.table,
  tableId: comment.id,
  title: `${user.username} commented on your post`,
  body: UtilityService.truncate(comment.body),
  href: this.getGoPath(comment)
})

await notification.save()
await notification.trySendEmail(post.author.id, trx) // 👈 we'll dig into this

// ... other actions

await trx?.commit()
Copied!

Here we're creating our notification record, which may or may not utilize a transaction.

export default class Notification extends BaseModel {
  // ... other stuff

  public async trySendEmail(
    userId: number, 
    trx: TransactionClientContract | undefined | null = undefined
  ) {
    const user = await User.query()
      .where({ id: userId })
      .preload('profile')
      .firstOrFail()
    
    // user doesn't want notified, skip send
    if (!this.isEmailEnabled(user.profile)) {
      return
    }

    // no transaction, go ahead and send then exit
    if (!trx) {
      return Event.emit('notification:send', { 
        notification: this, 
        user 
      })
    }

    // otherwise, wait for transaction to commit, then send
    trx.on('commit', () => {
      Event.emit('notification:send', { 
        notification: this, 
        user 
      })
    })
  }
}
Copied!
  • app
  • Models
  • Notification.ts

Join The Discussion! (0 Comments)

Please sign in or sign up for free to join in on the dicussion.

robot comment bubble

Be the first to Comment!