UnityBase Messaging
Overview
UnityBase messaging subsystem consists of the following components:
- Notifications: send messages from server to currently active users
- Exchange: server to server messages exchange
Both components uses RabbitMQ as a transport, and there is set of services developed (out of scope of this package) to facilitate this. This particular package shall be part of solution as a UnityBase model. It contain both client and server APIs.
Notification
Notification of UnityBase users:
- Uses JWT technology for managing permissions of UnityBase clients to AMQP resources and topics
- Allows to send messages to:
- administrative subjects: users / roles / groups
- organizational units: staff unit, organization, etc, when use
@unitybase/org
model - customize what group of users to send to
- broadcast messages, send to all users
- Receive messages from users
Object- vs Subject- centric messages
There different cases, when sever needs notify client about something:
- Notify all clients - broadcast
- Notify some clients - subject-centric messages
- Notify clients, who cares about some events - object-centric messages
These different types of notifications are organized into a topic system, where top-level topic determine notification type:
inbox.300010203
broadcast
entity.pln
Entity mixin
In order to add notification
server method to an entity, add a mixin to the entity:
{
"mixins": {
"notification": null
}
}
Make sure to configure ELS accordingly, to allow users to send messages using this method.
As for receiving messages, any user who has ELS to select
method, will be able to receive messages.
SECURITY NOTE!!! RLS is not applied to messages, so users will be able to receive messages even for instances, then do not have RLS permissions to. So, unlike inbox notifications, these notification messages are not as secure, do NOT send in those messages any sensitive information, use it purely for signaling purposes.
Messages will be sent to topic equal to entity model name. But this is customizable:
{
"mixins": {
"notification": {
"topic": "custom_topic"
}
}
}
Message deduplication
There might be scenarios, when server sends messages for multiple roles, and if the current user is a member of more than one of them, it will receive the same message more than once. In order to prevent that, this class has message deduplication code based on unique message ID assigned by server.
Message exchange
Interaction with external services:
- Allows to send messages to external services
- Allows to receive messages from external services
- API for registering
onMessage
handler for message type
- API for registering
Getting Started
Install package and add model to ubConfig.json
Install npm packages:
npm install @unitybase/messaging
Add @unitybase/messaging
as a UnityBase model into ubConfig.json
:
{
"application": {
"domain": {
"models": [
{"path": "./node_modules/@unitybase/messaging"}
]
}
}
}
After that, make sure to execute ub-migrate
at least once, so that roles would be created and give access to
appropriate users on UnityBase HTTP endpoint provided by this model.
Add some server-to-client notification code
Add the following code somewhere to your application:
const {notifyUser} = require('@unitybase/messaging/notification')
// Send a message to a user, who's profile was updated
uba_user.on('update:before', ctx => {
notifyUser(
ctx.mParams.execParams.ID,
{
userID: ctx.mParams.execParams.ID,
operation: 'update'
},
'profile.updated'
)
})
This shall send a notification message from server to user, every time, user's profile updated. Message will be actually delivered only if the user currently logged in. It won't be stored. So, server to client notifications are not to reliably deliver messages to user, but to make interaction between server and currently logged in user.
Make sure the code above is located in a javascript file, which is loaded on server start,
require
this file in index.js
, if needed.
Add some reaction on message on a client
Add the following code somewhere in your client-side code, make sure the file is actually run.
For example, you may put this code in initModel.js
to guarantee that.
UB.messaging.ClientNotifier.on('message', m => alert(JSON.stringify(m)))
This will alert with any incoming message from server.
Configure interaction with rabbitMQ in ubConfig.json
Before the whole example work, we need to set up additional services. Apart from RabbitMQ itself, the following services needed:
- JWT issuer, to make request for JWT issuing
- AMQP to UB Gateway, to make requests for sending messages:
@unitybase/messaging
does not rely on AMQP client compiled into UnityBase binary code, but it uses an external service to send messages.
URLs to both services MUST be set up in customSettings
section of the configuration file, for this "Getting Started"
guide, services are configured to localhost:
{
"uiSettings": {
"adminUI": {
"amqpNotificationUrl": "ws://localhost:15674/ws"
}
},
"application": {
"customSettings": {
"messaging": {
"transport": "rabbitMQ:router-service"
},
"rabbitMQ": {
"vhost": "/",
"routerUrl": "http://localhost:8081",
"jwtIssuerUrl": "http://localhost:8082",
"notifyExchange": "user-notify"
}
}
}
}
uiSettings.adminUI.amqpNotificationUrl
is needed so that client knows where to connect to.
Now, UnityBase application is ready to run, start it.
Run a docker composition
This package contains an example of how to configure services for a developer environment using Docker Composition. Make sure you have installed Docker for your operating system.
Copy content of https://gitlab.intecracy.com/unitybase/bpm/tree/master/packages/messaging/doc/examples/messaging_dev_services into your project, and in context of that directory run the following command:
docker-compose up --build
It assumes your already have up and running UnityBase on local port 8881, so if you have not started it yet, start it.
Testing it
Log into the test application.
Try to update the current user profile, for example, the admin user. If everything was done right, you will see a notification alert message.
If you experience any problems with it, the package contains a working example with this example: https://gitlab.intecracy.com/unitybase/bpm/tree/master/packages/messaging/doc/examples/ub-app
Using UnityBase Messaging
RabbitMQ configuration
RabbitMQ MUST be configured to allow UnityBase issued JWT as a user password.
See documentation for rabbitmq-jwt-auth project
on how to do that.
Make sure that shared secret for rabbitmq-jwt-auth
service matches the secret used for jwt-issuer
service, as well
as other parameters: issuer
, audience
, subject
.
Create an exchange for notifications:
- Exchange name: default is
user-notify
- Exchange type:
topic
Notify users
Notify users: server
Send a message to a particular user or group or role or staff unit:
const {notifyUser} = require('@unitybase/messaging/notification')
// Some userID...
let userID = 10
// Send a message to a user about new task assigned
notifyUser(userID, 'bpm.task.assigned', {
taskID,
dueDate: new Date()
})
// Some orgID...
let orgID = 123456
// Send a message to all organization users about new CEO
notifyUser(orgID, 'message', {
text: 'Our organization has a new CEO!'
})
Send message to every user:
const {broadcast} = require('@unitybase/messaging/notification')
// ...
// Broadcast message to all users about maintenance
broadcast('maintenance', {
severity: 'high',
message: 'Application will be updated to a new version next weekend'
})
Notify users: client
To listen to client-side events, there is message
event.
UB.messaging.ClientNotifier.on(
'message',
function(payload) {
// ...
}
)
To unsubscribe, there is corresponding method un
.
To listen to object-centric events, use pair of methods onEntityEvent
and unEntityEvent
to subscribe and unsubscribe
events, for example, when using with Vue components:
function onOpen(message) {
const senderUser = Repository('uba_user')
.attrs('fullName', 'name')
.selectById(message.senderID)
.then(
function (senderUser) {
uiServices.$notify({
message: `User ${senderUser.fullName || senderUser.name} opened Document ${store.state.docNumber} for editing`
})
}
)
}
new Vue({
el,
render: h => h(Root),
store,
created() {
UB.messaging.ClientNotifier.onEntityEvent('doc_Document', instanceID, 'clientAction', 'open', onOpen)
},
destroyed() {
UB.messaging.ClientNotifier.unEntityEvent('doc_Document', instanceID, 'clientAction', 'open', onOpen)
}
})
It is crucial to always unsubscribe from event, when listening is is not needed anymore, to avoid memory leaks and misbehavior of application, like triggering event handlers when they are not supposed to, or to avoid double triggering the same handler, when re-subscribe to the same event.
Publish event from client
Entity-centric events could be published from clients using UB.messaging.ClientNotifier.publishEntityClientAction
function. There shall be ELS configured for notification
method and there MUST be notification
plugin set for
the entity.
UB.messaging.ClientNotifier.publishEntityClientAction('doc_Document', instanceID, 'open')
Exchange messages
Exchange messages: receive
To receive messages, call subscribeToMessageType
function on server start.
It receives a callback called on message receiving:
const {subscribeToMessageType} = require('@unitybase/messaging/exchange')
subscribeToMessageType('doc_Document.published', documentPublished)
subscribeToMessageType('doc_Document.unpublished', documentUnpublished)
function documentPublished(payload) {
// ...
}
function documentUnpublished(payload) {
// ...
}
Router service will create a permanent queue per subscription, messages won't be lost, even when UnityBase server is not available.
Exchange messages: send
const {publishMessage} = require('@unitybase/messaging/exchange')
function sendToExternalSystem(messageType, messageBody) {
console.log('Network: sending message: "%s"', messageType)
publishMessage(
EXCHANGE_NAME,
`main_topic.${messageType}`,
messageBody,
true
)
}
Advanced server-side scenarios
Support additional subject IDs in token
Subscribe to messaging:notifications:jwt_subject_ids
event to push additional IDs to subject IDs of the user, so that when
notification is sent using that ID, the current user will be notified.
const {Session} = require('@unitybase/ub')
Session.on('messaging:notifications:jwt_subject_ids', (/** @type {number[]} */ admSubjIDs) => {
/** @type {number} */
let myID = 1234
admSubjIDs.push(myID)
})
Adjust rules in JWT payload
Subscribe to customize rules, which will be put into JWT used to RabbitMQ user authorization:
const {Session} = require('@unitybase/ub')
Session.on('messaging:notifications:jwt_payload', /** @type {JwtPayload} */ data => {
data.rules.push('read:vhost2/queue1234')
})
See documentation for rabbitmq-jwt-auth project
on format of data
object.
Customize client-side configuration
Client-side utility gets information about what to listen from server. It is possible to customize that info from server side:
Session.on('messaging:notifications:client_config', clientConfig => {
/** @type {string} */
let myTopic = '/exchange1/topic1'
clientConfig.topics.push(myTopic)
})
Q&A
What is the difference between @unitybase/messaging
and @unitybase/amqp
models
@unitybase/amqp*
packages rely on built-in AMQP UnityBase client. But its implementation is not complete and its
completion is complication, because of conflict of UnityBase synchronous model and AMQP asynchronous nature.
It is very hard to implement receiving messages, and therefore, even such things as getting error messages from
server.
Used AMQP client to send messages
@unitybase/messaging
: External service, usually deployed as a docker container.@unitybase/amqp*
: Built-in AMQP client, the@unitybase/amqp
packet is the binding for it.
Ability to receive messages:
@unitybase/messaging
: Yes, using external service.@unitybase/messaging
: No, need to do custom development.
RabbitMQ authentication and authorization:
@unitybase/messaging
: Yes, using JWT, secure.@unitybase/amqp*
: Partially, hardcoded logic, non-secure login/password.
RabbitMQ authentication and authorization:
@unitybase/messaging
: Yes, using JWT, secure.@unitybase/amqp*
: Partially, hardcoded logic, non-secure login/password.
RabbitMQ authentication and authorization:
@unitybase/messaging
: Yes, using JWT, secure.@unitybase/amqp*
: Partially, hardcoded logic, non-secure login/password.
Ability to send message to different subjects:
@unitybase/messaging
: Yes, send to role, group, department, etc.@unitybase/amqp*
: No, send to a user or broadcast.
Exchange name:
@unitybase/messaging
:user-notify
@unitybase/amqp*
:ub-amqp-notify
Broadcast topic name:
@unitybase/messaging
:broadcast.<event>
@unitybase/amqp*
:<name>.bcst.bcst
User/subject topic name:
@unitybase/messaging
:<subjectID>.<event>
@unitybase/amqp*
:<name>.U<user>.0
Migration from @unitybase/amqp
:
- Change used npm modules:
npm uninstall @unitybase/amqp @unitybase/amqp-notify @unitybase/amqp-notify-pub npm install @unitybase/messaging
- Reconfigure exchange topic name from
ub-amqp-notify
touser-notify
.
Submodules
Members
notification: notificationstatic #
Description