I worked with a customer on a project recently that involved explaining concepts of event-driven architectures to someone who mostly worked on monolithic applications, but was ready to embark on migration.
In this blog, I’ll work through the basics of going from an architectural diagram as above, into a functional project without worrying about a specific technology. Just addressing the business logic and services required to get us started with the serverless mindset.
The business requirement? Onboard a customer (store their info) and send an email notification when complete. The end user's client will interact with an HTTP method exposed through an API gateway to the outside world. When a customer is onboarded, we want to trigger a notification service that will send the customer a confirmation.
The example we’re starting with is a customer onboarding API. The Customer Create and Notification services are not directly aware of each other’s existence, however, they will need to communicate asynchronously to handle the business requirements.
Here’s the full code example which is written in typescript. We’re going to focus on parts of the code which represent the connecting lines in the diagram.
I’ll be using the Nitric Framework to handle infrastructure provisioning, this way I can start by mapping the resources in the diagram directly as declarations in my code without having to also create verbose config or deployment scripts with terraform, etc. to set up my infrastructure.
The diagram breaks down into a set of resources and functions which are primitives supported by Nitric.
Resources:
We’ll need a collections DB, an API for routing our requests, and a Topic for publishing and subscribing.
// Collections
export const custCollection = collection("customers").for("writing");
// API
export const custApi = api("public");
// Topics
export const custCreatePub = topic('created').for('publishing');
export const custCreateSub = topic('created')
The Nitric framework automatically configures these resources based on how you’re going to use them, e.g. when using custCollection, you’ll be unable to delete customers unless you add the ‘deleting’ permission.
Function & Handlers:
Customer Notify Service — This will be a Subscription handler that will trigger any time the Messaging Service picks up an Event with the Topic ‘Created’.
Create Customer — This will be a post request routed from our API gateway since we’re going to be creating a customer record with user input.
Generally, we’ll implement these handlers in separate files with the following handlers.
custCreateSub.subscribe(async (ctx) => { ... });
custApi.post(“/customers”, async (ctx) => { ... };
So what’s left? We will need to store data in our database and publish an event that we’ve created a customer record which captures some basic details in our customer create handler.
let firstname = ctx.req.json().firstname;
let lastname = ctx.req.json().lastname
let email = ctx.req.json().email;
// Create the new profile
await custCollection.doc(id).set({ firstname, lastname, email });
// Publish the event
await custCreatePub.publish({
payload: {
value: {
id: uuid(),
recipient: email,
...
}
}
});
I’ve created a simple common resource to handle setting up SES to send an email, we’ll use it from our subscribe handler.
custCreateSub.subscribe(async (ctx) => {
// Send the email notification
sendEmail(
createEmailRequest({
sender: process.env.SENDER_EMAIL as string,
recipient: [ctx.req.json().value.recipient],
body: "",
html: ctx.req.json().value.template,
subject: ctx.req.json().value.subject,
})
);
return ctx;
});
There is obviously some glue code required to manipulate the inbound and outbound data, and structure an email body with a template which you can see in the full example, but otherwise, this is all you need to implement the architecture described.
Test the full code example out with —
curl --location --request POST 'http://127.0.0.1:9001/apis/public/customers' \
--header 'Content-Type: text/plain' \
--data-raw '{
"firstname": "Test",
"lastname": "User",
"email" : "user@email.com"
}'
Top comments (0)