Mastering commercetools integration: A developer's guide to real-time listening with Deno"

Listening to messages from commercetools with Deno

Willem Haring
Willem Haring
Sales Engineer, commercetools
Published 18 March 2024
Estimated reading time minutes

When an entity is changed in commercetools, you can subscribe to this event by registering an external queue on Google Cloud Platform (GCP), AWS, Azure or Confluent cloud running Kafka. This is done through the subscription API. In production this is a rock-solid mechanism, allowing for guaranteed message delivery and processing.

Mastering commercetools integration: A developer's guide to real-time listening with Deno"

The process of registering a subscription and using it is well described in the commercetools documentation tutorials on using subscriptions. However, in a proof of concept (POC) scenario, it can be a bit challenging to manage all of these subscriptions and make sure that all the client actions are doing what they are supposed to do. Sometimes it is more than enough to have a client running on your laptop that responds to changes in commercetools so you can perform some actions. In this article, I will describe one possible approach to enable rapid development around events that happen in commercetools.

Messages

While the explanation is somewhat hidden away in the documentation, commercetools also provides an API endpoint to pull event messages. This allows you to retrieve a list of messages that happened in the project. By default, the messages are not stored in commercetools, and need to be enabled thru the project API endpoint, by performing an update action on the project configuration:

import {sdk} from "https://deno.land/x/commercetools_demo_sdk/clientsdk.ts";

const handle = sdk.init()
const project = await handle
  .root()
  .get()
  .execute()
if (project.statusCode && project.statusCode < 400) {
  const update = await handle
    .root()
    .post({body: {actions: [{
      action: "changeMessagesConfiguration",
      messagesConfiguration: {
        enabled: true,
        deleteDaysAfterCreation: 1
      }}],
      version: project.body.version
    }})
    .execute()
}

When the messages configuration is enabled, you can retrieve a list of messages through the messages endpoint. Here is a small example using the Deno SDK:

import {sdk} from "https://deno.land/x/commercetools_demo_sdk/clientsdk.ts";

const handle = sdk.init()
const result = await handle
  .root()
  .messages()
  .get()
  .execute()

console.log(result.body)

This will elicit the following response:

{ limit: 20, offset: 0, count: 0, total: 0, results: [] }

This is logical if no updates have happened to an entity. However, let’s explore what happens when we create a customer:

import {sdk} from "https://deno.land/x/commercetools_demo_sdk/clientsdk.ts";

const handle = sdk.init()
const customer = await handle
  .root()
  .customers()
  .post({body: {
    email: "johh@doe.nl",
    password: "password"
  }})
  .execute()

And let’s retrieve the messages again:

{
 limit: 20,
 offset: 0,
 count: 1,
 total: 1,
 results: [
   {
      id: "d8479ee3-0a56-4903-94b6-8183acbcd70f",
      version: 1,
      versionModifiedAt: "2023-12-22T12:33:33.026Z",
      sequenceNumber: 1,
      resource: {
        typeId: "customer",
        id: "a5fb995d-b28d-4926-a3ef-14e18019c632"
      },
      resourceVersion: 1,
      type: "CustomerCreated",
    customer: {
        id: "a5fb995d-b28d-4926-a3ef-14e18019c632",
        version: 1,
        versionModifiedAt: "2023-12-22T12:33:33.026Z",
        lastMessageSequenceNumber: 1,
        createdAt: "2023-12-22T12:33:33.026Z",
        lastModifiedAt: "2023-12-22T12:33:33.026Z",
        lastModifiedBy: {
          clientId: "GwUAkwU2WTk_6PfO1FhpVHNa",
          isPlatformClient: false
        },
        createdBy: {
          clientId: "GwUAkwU2WTk_6PfO1FhpVHNa",
          isPlatformClient: false
        },
        email: "johh@doe.nl",
        password: "****/uE=",
        addresses: [],
        shippingAddressIds: [],
        billingAddressIds: [],
        isEmailVerified: false,
        stores: [],
        authenticationMode: "Password"
      },
      createdAt: "2023-12-22T12:33:33.026Z",
      lastModifiedAt: "2023-12-22T12:33:33.026Z",
      lastModifiedBy: { clientId: "GwUAkwU2WTk_6PfO1FhpVHNa", isPlatformClient: false },
      createdBy: { clientId: "GwUAkwU2WTk_6PfO1FhpVHNa", isPlatformClient: false }
     }
   ]
}

This shows that we have a new message, with an ID, indicating that we have a new or changed resource of type customer. We can also see that there is a new customer created and we have the entire customer object.

So with this information, we can start pulling messages from commercetools at an interval. Every time a new message arrives, we can act upon it. Something like this:

import {sdk} from "https://deno.land/x/commercetools_demo_sdk/clientsdk.ts";

function delay(ms: number) {
  return new Promise( resolve => setTimeout(resolve, ms) );
}

const handle = sdk.init()
const peek = await handle.root().messages().get().execute()
let msgCount = peek.body.total!
while (true) {
  const status = await handle.root().messages().get().execute() // get the list of messages
  const current = status.body.total!
  if (current > msgCount) {
    const newmessages = await handle.root().messages().get({
      queryArgs: {
        limit: current - msgCount,
        offset: msgCount
      }}).execute()
    console.log(newmessages)
    msgCount = current
  }
  await (delay(1000));
}


Every time a resource has been changed, it will display the updated resource, until the program is stopped.

Turning messages into events

To make working with messages easier, I created a small helper that allows me to treat messages like incoming subscriptions. First, I created a simple registry where I can register to what resource I would like to listen. 

await new listener(["customer", "cart", "order"]).pull(1000)

This allows me to only listen to changes on the customer, cart and order resources. 

If you look at the commercetools documentation, you can also see that there are many more details on the messages that I am also interested in. For instance, the customer entity can detect the following changes: Customer Created, Customer Deleted, Customer Address Changed, etc. Therefore it will raise an event in Deno when such a message is received. This allows me to register an event handler, that will only be called when a specific message type is coming in:

addEventListener(EventTypes.Customer, (msg: EventMessage) => {
  console.log(`CustomerEvent::${msg.detail?.type}`)
});

This will log a message when a change has been made to the customer entity, while the following handler will only log a message when the customer company name is set.

addEventListener(EventTypes.CustomerCompanyNameSet, (msg: EventMessage) => {
  console.log(`CustomerEvent::${msg.detail?.type}`)
});

You can test this by using the following snippet, just make sure you have an .env file with the client details in the same folder as where you are running the script from:

listen.ts

import { listener, EventTypes, EventMessage, Message } from
"https://deno.land/x/commercetools_demo_sdk/messages.ts";

addEventListener(EventTypes.Customer, (msg: EventMessage) => {
  console.log(`CustomerEvent::${msg.detail?.type}`)
});

await new listener(["customer"]).pull(1000)

Run this like: deno run -A listen.ts

When making a change to a resource, you should see the following on your terminal screen:

listening to customer messages in project: wh-******
Enabling messages for project wh-******
CustomerEvent::CustomerTitleSet
CustomerEvent::CustomerLastNameSet
Disabling messages for project wh-******

In an upcoming blog, I will create a module that will listen to an order and send an order confirmation by email using this same simple listener!


To learn more about how to use commercetools API extensions with Deno and run them on your laptop for demo or POC purposes, read my blog, A guide to enhancing commercetools API extensions with Deno.

Willem Haring
Willem Haring
Sales Engineer, commercetools

Willem has been working in retail technology since the beginning of 2000. He has worked with several technology companies and with clients on point of sales, stores infrastructure, reporting and dashboarding. He has been a participating member on the ARTS committee for the data warehouse chapter under ARTS XML. At commercetools, Willem works with clients, prospects and customers to translate complicated use cases to workable eCommerce solutions based on commercetools.

Related Blog Posts