DEV Community

Cover image for Automate everything you can.
Rak
Rak

Posted on

Automate everything you can.

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.

Image description

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')
Enter fullscreen mode Exit fullscreen mode

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) => { ... };
Enter fullscreen mode Exit fullscreen mode

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,

            ...
        }
    }
});
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

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)