Building common SaaS features à la serverless: Prelude
This post marks the beginning of the second half of this blog series, which will focus on showcasing frequently encountered application features built in an event-driven, serverless fashion as AWS Lambda applications.
As discussed in the epilogue of my last blog post, we'll start by introducing a re-usable framework (read: library) for abstracting away the different AWS services that can be utilized as Lambda function triggers. I want to emphasize that the code is implemented with an eye for best practices so the input is generic, meaning you can use this as is (or adopt the mental model of this library) with any underlying serverless computing platform (Azure Functions, GCP Cloud Functions or AWS Lambda Functions).
There are multiple benefits here and the following are just some of those:
- considerably reduced boilerplate code for creating new AWS Lambda functions in .NET,
- standardized, familiar-looking code and
- improved team velocity because this will allow your engineers to focus on the thing that matters most: your business (instead of infrastructure).
Much of this body of work was inspired by our conversations with the evergreen Renato Golia a few years back and his work, which he has since open-sourced, on C# project templates for AWS Lambda:
If I had to sum this up, I'd say that you don't have to give up SOLID development principles when implementing serverless code and this post aims to be a functional demo of how to go on about accomplishing that.
Which is remarkably similar to a boilerplate ASP.NET Core Startup.cs
class!
This class provides methods for addressing all the usual cross-cutting application development concerns such as logging, environment variable configuration, secrets management, dependency injection wiring etc.
Let's start from the top though; the EventFunction
class that our Lambda's Function.cs
class inherits these methods from is part of a .NET Standard 2.1 library that you could reference as a NuGet package in your project.
EventFunction deep dive
An EventFunction
, promptly named after event-driven architecture 'n all, represents a Lambda function that is not expected to produce any value, aka we won't be returning anything back to whatever AWS service triggered the invocation of our Lambda function.
To create an EventFunction
, simply change your Function.cs
class so that it inherits from EventFunction<TInput>
where TInput
is a class representing the incoming data.
TInput
can be whatever really and that's the beauty of this ♥. It can be anything ranging from a string (that represents the JSON payload of the Lambda function's trigger) you'll deserialize to a type for further processing or possibly one of the classes that ship with the various libraries of the AWS SDK for .NET in the lambda events namespace, for instance: Amazon.Lambda.SQSEvents. Those classes contain the metadata attributes and body of an event associated with a particular AWS service, in this case SQS. So it goes without saying that, if you do the latter, you should use the AWS SDK's NuGet package that matches the service you're using as a trigger for your particular Lambda function or serverless app.
Finally, an EventFunction
requires an handler that implements IEventHandler<TInput>
to be registered in ConfigureServices
(which you can see as part of the code sample above at the very end of the class). But more about this bit later on.
The end-game here is you becoming familiar enough with how this works, that you are able to either start crunching immediately by adopting such an approach in your serverless projects to streamline the handling of cross-cutting concerns or possibly even extend EventFunction
by creating new .NET libraries in case you need to add or modify specific functionality like skipping execution for certain types of events (the event type might part of the payload's metadata for certain services); think a DynamoDB event stream to a relational SQL database, you might not want to process DELETE type of events if your SQL database schema revolves around "soft" deletes.
An EventHandler interface implementation
Think of an EventHandler
interface implementation as the class where all your business logic occurs. This mental model provides a much-sought after separation of concerns.
A minimal, concrete implementation of the EventHandler
interface might look like this for a cloud service trigger with a string payload in JSON format:
Conclusions
tl;dr In event-driven, serverless applications you receive a payload per invocation the structure of which differs depending on the kind of cloud service that is being used as the function's trigger. We can abstract and streamline the work of dealing with the details of how that event looks by introducing generics into our code.
In the next post we will build, by leveraging the mental model exhibited in this one, the following common SaaS feature ala serverless: a user uploading a file from some web client to a cloud provider's blob storage service (think AWS S3) and how we can leverage an S3 PUT event as a Lambda trigger to execute business code, in an asynchronous fashion, that will process this file as well as notify the user of the status and completion of said processing.
The processing bit could be anything, ranging from fancier business use cases like image recognition ML code if the file is a picture or a .PDF to more common ones like extracting information out of an Excel file.
The important thing to remember is that this 👆 is not the important thing! Your product, your code. I'm just showing a way of laying the necessary infrastructure for writing serverless applications in a reliable, re-usable and streamlined manner.