Looking for Senior AWS Serverless Architects & Engineers?
Let's TalkMultiples patterns exists, multiple patterns are possible. Theses patterns aren’t mutually exclusive, they are often used together in a same stack.
Common Event Patterns
When building an Event Driven Application, we generally think of asynchronous tasks. This doesn't need to be the case. You can have a request/response model using events that behaves synchronously. This is generally achieved by polling a resource to read the result, or by the use of websockets listing to the response.
Synchronous Request Response Model (Blocking Model)
This model follows a Send → Wait → Receive sequence. The sender initiates the request and has to wait for the process to end to be able to free the connection, hence it is often referred to as a blocking model.
This kind of model is often used in web application with a server side processing component, like PHP or Ruby on Rails.
At first glance, there doesn't seem to be any event in this scenario. Let drill into the details of a form submission from a web application towards an API Gateway integrated with Lambda and DynamoDB:
- A User fills out a form on a website
- The User submits the form
- The Browser initiates a POST request towards our HTTP endpoint
- API Gateway notifies the Lambda Service of a new request. This is an event containing the payload and the Lambda Function to execute.
- The Lambda Service executes the Lambda function
- The Lambda function calls the DynamoDB endpoint to store the payload. This is an event inside the NodeJS application.
- The DynamoDB Service will use events to ensure that the data is successfully stored in multiple AZs
- Our code awaits the DynamoDB response
- Our function returns the response to the Lambda Service
- The Lambda Service returns the response to API Gateway
- API Gateway returns the response to the Browser
With this approach, the Browser has to wait for the full process to finish. API Gateway needs to wait for Lambda to reply. Lambda needs to wait for DynamoDB to reply. During this waiting time, for the User submitting the form, nothing seems to happen. His browser will display a blank page until the response is returned.
In modern web applications, the waiting process is hidden to the User with the use of Javascript and well thought spinners and/or modals.
When to use this model?
When the client needs a response directly and on short executions. The example of a database insert fits this model. The Browser needs to know if the insert was successful, to be able to inform the User. This execution is short lived unless complex verification are in place.
When not to use this model?
When the execution time is long. Expanding on the form submission example, let’s add an external validation:
- The Lambda function calls an external API
- On Success, the data is stored in the database
- On Error, an error is returned to the client
A more complex synchronous form submission: The backend process needs to call a 3rd party service before storing to the database
Asynchronous Point to Point Model (Queue)
This model follows a Send → Queue → Consumer model. The sender receives a response that his request will be honored and the request is sent to a queue for asynchronous processing.
This model is an extension on the previous model. The Browser doesn’t need to wait for the 3rd party call to be finished. The backend application acknowledges the reception of the payload and will process it asynchronously, terminating the initial call from the Browser.
With this solution, we are not only able to control the request rate made to the 3rd party endpoint, but we can also retry failed requests to the 3rd party in case of transient failures.
A new endpoint needs to be added to allow the browser to inquire and retrieve the result of the task.
When to use this model?
- Need to throttle requests to a downstream service
- Need to retry failed requests to a downstream service
When not to use this model?
- A synchronous response is need from the downstream service
Asynchronous Point to Point Model (Router)
With this model, the sender maintains the routing logic. The sender determines which message is intended for a specific consumer. The message are formatted for a specific consumer.
This model is well suited for communication inside a stack, where the number of consumers is small and the payload format is well known.
This model can be implemented with the use of Amazon SNS. Every event produced is pushed to SNS. All subscribed consumers will receive the message as formatted by the sender.
An example of such an integrations is a webhook integration with an alarm service.
When to use this model?
- When there is a small amount of well known consumers
- When all consumers use the same format
- Events are processed only once and no throttling is needed
When not to use this model?
- When consumers are not identifiable or not a discrete and manageable amount
- When multiple consumers need different payloads
- When retry and/or throttling is needed
Asynchronous Point to Point Model (Bus)
This model follows a Sender → Bus model. Multiple receivers can subscribe to the bus and receive messages matching a specific filter.
This model is well suited to communicate between stacks, services, departments or even enterprises. It is generally the model that is thought of when speaking of Event Driven Applications.
The sender doesn’t need to know who is subscribed. The events are not formatted for a specific receiver, but rather from the sender’s point of view.
In the AWS ecosystem, Amazon EventBridge or Amazon Managed Kafka (MSK) are the services of choice.
When to use this model?
- Multiple services need to act on something that happened in the sender’s service
- The sender service doesn’t need to be concerned with what happens downstream
When not to use this model?
- The sender needs to know the result of a downstream service
- This could be overcome with events sent back from the downstream service
- The receiver process needs to be invoke synchronously
Some implementations in the world
Applications notifications
Any mobile or web application will use events to notify their users. Be it a messenger style app, news app or even games.
You post a new message on Facebook, Facebook triggers an event that a new message from userA has been posted. This event is then processed asynchronously, the process fetches the list of friends of userA. Which creates a new event for userA’s friends: userB and userC.
Theses events are picked up by the notification process that will send the message to userB and userC.
By using events, the notion of “posting a new message” becomes independent from “sending a notification”. UserA doesn’t need to know who is subscribed to his feed.
At the same time, userB can receive notifications from other feeds he is subscribed to: userA, userD, userE without adding a specific integration.
Chatbots
The user sends a message or request to and endpoint. This endpoint will invoke through an event a chatbot service built on an LLM stack. The LLM’s response will be returned to the user.
In this case, we have a synchronous (blocking) event flow. The initial user’s request needs to wait for the response. Internally, the services used (gateway, validation, LLM) are invoked by events.
Webhooks
Webhooks as the name implies are http based transfers and are used to transfer events between two independent systems. An event on systemA triggers a webhook call to systemB. Upon reception in systemB a new event is created and systemB can act upon it.
As an example, we can look at Github: you can define a webhook for all “push” events. When a push event is happening in your repository, an http request to the webhook endpoint is made.
Your endpoint will parse the request and either create an event or directly process it. Generally we want to create a new event to free quickly the incoming request.
Siri, Alexa, Google Home
Theses devices transform a voice command into an event. The event is being processed by the concerned application on the device, which makes a request to the relevant service. The response is creates a new event, that is in turn processed by the device and transformed back into voice.
As an example, when telling Alexa “ask OurGrocieries to add milk”, the keyword “ask” tells Alexa the the event is intended to a 3rd party app, “OurGroceries” being this application.
Alexa processed this event and sends it to the webhook provided by the integration. The integration knows what to do with the payload “add Milk”.
In this example, Alexa stops the processing as soon as the webhook is called. It doesn’t need to wait for a response.
Internet of Things (IoT)
This is very similar to the “applications notifications” use case. But instead of displaying a notification, the IoT device will execute a process in reaction to the event.
Let’s look at a simple case of a connected light bulb. The light bulb’s address is known by a controller unit. This controller unit is linked to Alexa.
A user initate the command “Alexa, turn on livingroom”. This action is already an event, Alexa received the event “turn on” with the payload “livingroom”. The light integration listens to all events “turn on”, it will process this event, and ignore events like “play”.
“Livingroom” is probably a collection of light bulbs, we configured them as such in Alexa. The process acting on the event, will list all light bulbs in this group and send a notification via a webhook to the controller for each light bulb. Either in batch (turn on: lb1, lb2, lb3), or individually.
The controller will create an event for each light bulb and send via the Zigbee protocol the command to each bulb. Upon receiving the event, the bulb turns on.
In this flow, events are used to decouple multiple technologies. First we have a speech to text recognition, then an HTTP transport and finally a Zigbee transport. Alexa doesn’t need to implement Zigbee. The bulb doesn’t need to implement http.
By using events, we are able to have more than one source to control our lights. You can control them via an app, via a movement detector or another smart home device.
You can also control them via a physical remote, in this case the remote directly talks to the bulb. In return the bulb will send an event to the controller to inform it of its new state.
Database triggers
In this scenario, an action is taken when items in the database are modified. This can be synchronous or asynchronous.
As an example, in an SQL database, a value is updated in another table before each insert. This a blocking event, since the event is happening “before insert”.
CREATE OR REPLACE TRIGGER Update_Total
BEFORE INSERT ON Sales_tab
FOR EACH ROW
WHEN (new.amount > 0)
BEGIN
UPDATE Total_tab SET total = total + new.amount WHERE category = new.catagory
END;
An event happening after insert, can be asynchronous. This is the case with Amazon DynamoDB. Using DynamoDB Streams, all actions on a table are sent to a stream. The stream is the source for events on which a new action can be executed.
In theses cases, events are used to decouple the user action with database actions. The user or application interacting with the database doesn’t need to know what is happening at the database level. For asynchronous tasks, the caller isn’t impacted by the latency that additional operations are creating.
Change Data Capture (CDC)
In this scenario, we want to publish any data change in the database to be consumed by any client. As you probably guessed it, this is an extension on the “Database Trigger” model. Instead of updating a table, we can simply publish the event (or DynamoDB stream) to EventBridge.