This document includes some specific use cases related to plugins.

Using NotificationQueue from plugins

There are times when a plugin needs to queue some events for later/future processing. Such situations could be to handle certain bus events at a later time, or enqueue specific operations that have failed so they can be retried, …​

In those situations, the plugin implementor has the choice to use its own mechanism, use open source solutions,…​ and this all depends on the objectives (e.g persistent queue?) and the familiarity with certain frameworks.

One possibility is to use the NotificationQueue abstraction that is already provided by Kill Bill framework.

One needs to keep in mind that the use of such notification is rather internal to Kill Bill core but in some situations it is acceptable to use it from plugins with the following understanding:

  • This should not be used for high throughput/low latency kind of situations, as the mechanism fundamentally stores events into the database and allows to be dispatched to a specific handler

  • Improper use of such queue can have bad consequences for the rest of the system

  • It forces the plugin to know low level details (e.g accountRecordId)

  • Only a small number of queues should be created (1 or max 2 for a plugin) as they create some load on the system (each queue will have its own pool of thread for dispatching event notifications).

The goal of this documentation is to provide some guidelines on how to make it work.

Queue Creation

Services can create a new queue and later use that queue to send (event) notifications and process such notifications.

The creation of the queue will require the following parameters:

  • svcName : A string that identifies the service (a good convention is the plugin name)

  • queueName: The name of the queue (a good convention is to use a string that describes its function)

  • handler : The handler that will be called back each time a notification is dispatched. The handler should be idempotent as the system ensures at least one delivery

The handler takes the following form:

interface NotificationQueueHandler {

    /**
     * Called for each notification ready
     *
     * @param eventJson  the notification key associated to that notification entry
     * @param userToken  user token associated with that notification entry
     * @param searchKey1 the searchKey1 associated with that notification entry
     * @param searchKey2 the searchKey2 associated with that notification entry
     */
    void handleReadyNotification(NotificationEvent eventJson, DateTime eventDateTime, UUID userToken, Long searchKey1, Long searchKey2);
}
  • The NotificationEvent interface is a marker interface; the class implementation must be serializable using json. An example can be found here.

  • userToken is a UUID of your choice that is passed when publishing events.

  • searchKey1 must be the accountRecordId attached to the account or null

  • searchKey2 must be the tenantRecordId attached to the tenant or null (if operations is cross tenant, but very unlikely)

Note that accountRecordId and tenantRecordId are usually not visible from plugins (instead the plugin will see the accountId and the tenantId which are UUID. However there is 1-1 mapping between those two ID (one being internal and the other being user visible) and info can be retrieved using the following APIs:

final CallContext callContext = ...
accountRecordId = osgiKillbillAPI.getRecordIdApi().getRecordId(killbillEvent.getAccountId(), ObjectType.ACCOUNT, callContext);
tenantRecordId = osgiKillbillAPI.getRecordIdApi().getRecordId(killbillEvent.getTenantId(), ObjectType.TENANT, callContext);

Queue Configuration

Each queue that is created at runtime by the system (whether a plugin or Kill Bill core), needs to have its own set of tables in the database. By convention, we usually name such tables in the following way:

  • {tablename}_notifications

  • {tablename}_notifications_history

Where tablename is the name of the plugin or something related that will be unique and easily understood to be related to the given plugin. This value comes from the configuration associated with that queue.

A good example that shows how to wire things can be found in the start method of the analytics plugin. We will see the following:

  1. Create the NotificationQueueConfig by replacing the instanceName with the desired value (e.g plugin name)

  2. Create the DefaultNotificationQueueService specifying the config for that queue

  3. Create the handler

  4. Create the queue

Lifecycle

Before this can happen the queue needs to be properly started and also stopped when the plugin stops.

Starting the queue will start the pool of thread that is attached to the queue. The system will print a trace showing that such threads were started. Such threads should be visible by running a jstack command and looking for the following name: config.getTableName() + "-th".

Of course such lifecycle operations should also match the lifecycle of the plugin, the start and stop function defined in the activator. Note that failure to start the queue will have the effect to see such events in the IN_PROCESSING state but handler will never be called.

For more information on configuring the queue, please refer to this document.