Cloud-First TDD with .net CORE — Part 1
Everybody loves a good buzzword! These posts aim to be part walk-through, part experience report regarding my own encounters with serverless tech, and a frictionless, enjoyable pattern that I’ve arrived at for building cloud-native APIs. So without further ado!
A bit of background
As you may have gathered from my previous post I am a bit of obsessed with creating stuff. Like many others “in” software development; my github is filled with oft-started (and some finished!) forays into different toolsets, frameworks, projects and tutorials. It is unquenchable. One of the challenges that this obsession brings is that if you don’t keep it in check it can prove to be an expensive hobby — Are you still hosting that random website you spun up 3 months ago as a PoC for your mate? 😕
Thankfully things have shifted on a bit since we all started hacking about with ASP.net and the barrier to cloud-hosting has been lowered, making experiments and access to learning easier and cheaper than ever before!
Amazon Web Services is the cloud platform provided by Amazon. Other cloud providers do exist, but the focus of this article is going to be on AWS — specifically on Lambda and S3.
Putting it very simply, Lambda is a service which allows you to deploy functions to the cloud. Ever had the need to run a batch script on a daily schedule? Ever needed to respond to a text message or email being received in a very specific way? Lambda’s been around for about 4 years now, and supports a whole bunch of languages. It’s now got first-class support for .net CORE 2.1, which is completely amazing.
S3 is a fancy file store. It scales very well, allows you to secure resources, and do other fancy things like being able to host your files over HTTP.
The really great thing about these technologies is that they’re very, very cheap. So much so that you can get going with your skunkworks project or next-biggest-thing startup application without paying anything at all for the first bazillion or so requests.
But what about deployments??
Being the good citizens that we are, we care very deeply about code quality, right? We want to make sure that whatever stuff we’re building is integrated properly, has unit tests that run in a like-live environment upon check-in, and that your feedback cycles are lovely and short. But I’m guessing you don’t have a TeamCity / Jenkins build server kicking around at home right? No bother, let’s use Gitlab’s “Pipelines” to off-load all this heavy lifting. You can get 1000 free build minutes with these guys every month if you sign up for an account. Just do it already.
Show me the code!
Woah there, Nelly. First, a few pre-reqs — let’s get you dev-ready:
- Sign-up for an AWS account (make sure to keep your access and secret keys somewhere handy!)
- Sign-up for a gitlab account
- Create a new gitlab project and enable CI
- Create 2 new gitlab environment variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY and plumb in your credentials as per step 1
The walking skeleton
The first thing we’re gonna want to do is create a pipeline for our Api! This is going to build our code and deploy it out to AWS for us. But first we’ve got to have some code to physically deploy, right? Pop open your favourite shell and run in the following:
dotnet new webapi
dotnet add package Amazon.Lambda.AspNetCoreServer -v 2.0.4
dotnet add package Amazon.Lambda.Tools -v 2.2.0
This is going to create a brand new dotnet-core webapi project for you, and add some useful extensions, essentially bootstrapping your api as a lambda!
You should now have a new web api application with a simple “Values” controller that looks like this:
// GET api/values/5
public string Get(int id)
Let’s spin the app and see what we’ve got! You should be able to hit the following url: http://localhost:5000/api/values/5
The main points to takeaway at this point:
- You now have a vanilla aspnet core web api.
- You have the “standard” Program.cs which is using the Kestrel web server to bootstrap.
In order to “Lambda-fy” the app, we’re going to have to create a second entry point. Go ahead and create a new LambdaEntryPoint.cs code file which contains the following (trivial) bootstrapping code. This leverages the functionality of the APIGatewayProxyFunction base class behind the scenes:
Note that the code we’re using to bootstrap our Lambda is exactly the same as our api, right?
Packaging the lambda
Now’s the exciting part, hold your breath! If you open the App.csproj file in VS Code, you’ll see a package reference to the Amazon.Lambda.Tools nuget package. Let’s hack the .csproj such that it isn’t used as a runtime dependency, but a design-time dependency:
<PackageReference Include="Amazon.Lambda.Tools" Version="2.2.0" /><DotNetCliToolReference Include="Amazon.Lambda.Tools" Version="2.2.0" />
You’ll now be able to access lambda functionality using the dotnet toolchain! In order to deploy our code we’ll have to use the dotnet toolchain to compile the code to a binary, and subsequently package it:
dotnet lambda package -c Release -f netcoreapp2.1
This is going to create you a .zip archive containing your lambda in the bin folder of your app. Copy this file to your local folder so we can publish it to AWS:
mv ./bin/Release/netcoreapp2.1/*.zip ./
Deploying the lambda
Now we have a deployment artifact, we can take a look at the next piece of the puzzle, namely deployment!
We’re going to be using the Serverless Framework here in order to codify the process of deploying our Lambda code. For those of you unfamiliar with Serverless, it’s a free tool you can download through npm. Do it now:
npm i serverless --g
Your next step is to create a serverless.yml file which will describe our lambda, where to find it, where to put it and how to configure it. It’s actually way more simple than it sounds! Create a new file with the following content at the root of your App folder, and name it serverless.yml
Very simplistically, the above template:
- Defines a new lambda called HelloWorld, whose entry point is the inherited method FunctionHandlerAsync in the class we have defined above.
- Proxies all HTTP traffic from a new APIGateway (a second AWS service which allows you to define web APIs).
- Specifies the input as being the App.zip file we have packaged up in our previous step.
- Deploys this out to the eu-west-1 region, using the dotnetcore 2.1 runtime
Let’s tell Serverless to deploy our lambda!
serverless deploy -v --stage staging
Test you’re up and running
Once this has all been configured, you should be able to hit your Api. Follow the link indicated in your shell output:
Once you’re happy and want to tear it down, you can actually do this using Serverless, too:
serverless remove --stage staging
The final piece of the puzzle
If you’ve been able to follow along with my ramblings, so far we’ve:
- Defined a walking skeleton dotnet core API
- Bootstrapped the API as a lambda using well-known nuget packages
- Packaged the lambda up into a deployment artifact
- Used the Serverless framework to deploy it to the cloud
Part 2 of this post will explore how to actually TDD some functionality into our API, and leverage cloud-hosted pipelines to create a Continuous Integration platform!