Events and messages
Where possible, service to service communication will be asynchronous using Azure Service Bus.
Depending on the pattern being applied the term event
or message
may be more appropriate. For simplicity, for the remainder of this page, the term event
will be used to refer to both these definitions.
Use Azure Service Bus
Services should use Azure Service Bus as an event broker. This is because the FCP CI pipeline is relatively mature in terms of dynamically provisioning Azure Service Bus infrastructure to support the development cycle and the team have strong experience with it's use.
Avoid tight coupling
Services should be as loosely coupled as possible. This reduces the dependency a change in one service could have on related services.
Therefore, events should be published to Topics
as opposed to Queues
where possible.
Format
When sending an event to Azure Service Bus, it will be decorated by default broker properties. For full details on the specification, see the Microsoft documentation
Specification
CloudEvents.io is a specification for describing event data in a common way and should be considered if it is appropriate for the use case.
Mandatory custom properties
All FCP events from any service must also declare the following custom properties when publishing an event.
type
- the type of event, this should be prefixed with reverse DNS and describe the object and event occurring on that object in the format:<reverse DNS>.ffc.<object>.<event>
. For example,uk.gov.ffc.demo.claim.validated
source
- the service the event originated from, egffc-demo-web
subject (optional)
- the subject which will give consumers context, for example when the body cannot be consistently read or contains blob data
These additional properties are based on values included in the CloudEvents specification that are not included in the Azure Service Bus envelope. Refer to the Cloud Events specification for further examples of their usage.
Authentication
A Managed Identity should used to authenticate microservices with Service Bus when it is deployed to Azure. However, for local development outside of Kubernetes, a connection string can be used.
An ffc-messaging
npm package has been created to simplify this setup for teams.
Local development
Azure Service Bus does not yet have an official emulator, however one is expected to be released by the end of 2024.
In the absence of an official emulator, teams have two options.
Use LocalSandbox emulator
An unofficial emulator called LocalSandbox is available. This can be used to run a local instance of Azure Service Bus with the majority of the features available in the cloud.
Setup
Add a definition for local-sandbox
to your docker-compose.yaml
file.
services:
local-sandbox:
image: localsandbox/localsandbox
container_name: local-sandbox
Creating queues, topics and subscriptions
By default, LocalSandBox will create a queue called default
.
To create additional topics and subscriptions, you can use docker-exec
to run the az
CLI command within the LocalSandbox container.
Note: LocalSandbox alias the
az
command toazl
docker exec local-sandbox \
azl servicebus queue create \
--name test-queue \
--namespace-name default \
--resource-group default
docker exec local-sandbox \
azl servicebus topic create \
--name test-topic \
--namespace-name default \
--resource-group default
docker exec local-sandbox \
azl servicebus topic subscription create \
--name test-subscription \
--namespace-name default \
--resource-group default \
--topic-name test-topic
If using Docker Compose version 1.30.0 or later, you can use the post_start
lifecycle event to run these commands automatically when the container starts.
services:
local-sandbox:
image: localsandbox/localsandbox
container_name: local-sandbox
post_start:
command: >
azl servicebus queue create --name test-queue --namespace-name default --resource-group default
azl servicebus topic create --name test-topic --namespace-name default --resource-group default
azl servicebus topic subscription create --name test-subscription --namespace-name default --resource-group default
Connecting to LocalSandbox
LocalSandbox creates a Service Bus namespace named default.default.default.localhost
.
Using the @azure/service-bus
SDK, the following example shows how to connect.
import { ServiceBusClient } from '@azure/service-bus'
const connectionString = 'Endpoint=sb://default.default.default.localhost;SharedAccessKeyName=anything;SharedAccessKey=anything;UseDevelopmentEmulator=true'
const client = new ServiceBusClient(connectionString)
Note: The
UseDevelopmentEmulator=true
flag is required to connect to LocalSandbox.SharedAccessKeyName
andSharedAccessKey
can be any value, but must be present.
Once connected, the SDK can be used as normal as if connected to Azure Service Bus.
Using across different services
As FCP follows a multi-repository approach, it is likely that services created with different Compose files will need to communicate with each other through a single LocalSandbox instance. Equally, these services may need to run in isolation.
One way to achieve this is by declaring the LocalSandbox instance in both Compose files and using the label
property to identify if an instance is already running.
The Compose files must also declare a common network to allow the services to communicate.
Service A
services:
ffc-service-a:
image: ffc-service-a
networks:
- ffc-service
depends_on:
- local-sandbox
local-sandbox:
image: localsandbox/localsandbox
container_name: local-sandbox
labels:
com.docker.compose.service.role: ffc-service-local-sandbox
networks:
- ffc-service
networks:
ffc-service:
driver: bridge
name: ffc-service
Service B
services:
ffc-service-b:
image: ffc-service-b
networks:
- ffc-service
depends_on:
- local-sandbox
local-sandbox:
image: localsandbox/localsandbox
container_name: local-sandbox
labels:
com.docker.compose.service.role: ffc-service-local-sandbox
networks:
- ffc-service
networks:
ffc-service:
driver: bridge
name: ffc-service
Each repository can include a start
script to run docker compose up
programmatically deciding whether to start the LocalSandbox container based on the presence of the com.docker.compose.service.role
label.
if [ -n "$(docker container ls --filter label=com.docker.compose.service.role=ffc-service-local-sandbox --format={{.ID}})" ]; then
echo "LocalSandbox container already exists, skipping creation"
args="--scale local-sandbox=0"
fi
docker compose up $@ # $@ passes any arguments to the script
An example repository using LocalSandbox can be found here
Use the Azure Service Bus instance in the Sandpit environment
Local development may use a connection to the Azure Service Bus instance in the Sandpit environment.
To avoid collisions between different developers, each developer should have their own set of queues, topics and subscriptions in the Sandpit environment suffixed with their initials.
A repository has been created to support faster creation and deletion of these resources at scale.
Once created, developers can set a local environment variable with their initials.
export MESSAGE_SUFFIX=-jw
Then reference this in the Docker Compose file of the relevant service.
services:
ffc-service:
environment:
MESSAGE_TEST_QUEUE: test-queue${MESSAGE_SUFFIX}
MESSAGE_TEST_TOPIC: test-topic${MESSAGE_SUFFIX}
MESSAGE_TEST_SUBSCRIPTION: test-subscription${MESSAGE_SUFFIX}
Naming conventions
The naming convention for queues, topics and subscriptions should be as follows:
- all entities should be prefixed with
ffc-<service>
where<service>
is the name of the service, egffc-demo
- topics and queues should be named to denote the entity they are related to, eg
ffc-demo-payment
orffc-demo-claim
- topics and queues should be suffixed with the environment they are deployed to, eg
ffc-demo-payment-dev
orffc-demo-payment-prd
- subscriptions should match the name of the consuming microservice, eg
ffc-demo-payment-web
Pull Requests provisioning
Queues, topics and subscriptions can be automatically provisioned for each microservice PR by the Jenkins CI pipeline to ensure encapsulation of infrastructure.
Create a provision.azure.yaml
file in the root directory of your microservice, and populate with the Service Bus infrastructure that need provisioning:
resources:
queues:
- name: <queue_identifier>
topics:
- name: <topic_identifier>
where the <queue_identifier>
and/or <topic_identifier>
relates to the part of the queue or topic name described above. For example for the queue ffc-demo-payment-dev
, <queue_identifier>
would be replaced with payment
.
For every topic specified in provision.azure.yaml
, the CI pipeline will also create a subscription, so subscriptions do not need to be explicitly specified.
Add a name
entry in the provision.azure.yaml
for each required queue and topic/subscription.
Standards
- services should guarantee at least once delivery of messages, using the "outbox pattern" where appropriate
- consuming services should be idempotent and safely handle duplicate messages
- teams should ensure a suitable delivery count, retry mechanism, message lock and dead letter queue policy is in place for each entity
- teams should use session queues for request/response message patterns