From time to time something new and amazing comes along but one’s initial reaction is dismissive. Electric cars were like that for me. Surely if you take a motor out of your hairdryer, or even your lawnmower, and put it in a car it’s never going to be very exciting. (Wrong!)
And it happened again when I heard about Lambda and function-as-a-service. Who would want to put a single function, all on its own, into AWS?
Serverless is possible
But of course there was more to learn. You can easily put a whole web server into a Lambda function:
- the ‘function’ can be as large and complex as you like, as long as it doesn’t take too long to execute
- if you front it with API Gateway you can serve all endpoints of an API with a single Lambda function. API Gateway can simply forward the http request through to your web app in Lambda, which then internally routes it to a controller and responds as usual.
So Lambda is actually a viable hosting option for an API service.
Serverless works
We started with an application server for useMango that worked well. We’d honed our clean architecture. We’d separated our MVC concerns. So, could we simply upload it to Lambda and expect it to work? Well, actually, to a large extent we could. Very little of the MVC code needed touching.
In our case, the majority of pre-requisite work was in preparing our server to work correctly on a horizontally-scaled platform, i.e.
- removing all state from the deployed instance, and
- authenticating every incoming request rather than maintaining user sessions.
These requirements are not specific to Lambda: for the architecture we were aiming for, the same work would be needed if we were going to deploy to auto-scaling EC2 instances or to auto-scaling containers.
In preparing our service for deployment to Lambda, we took the opportunity to make two further changes:
- outsource user identity provision to Cognito. This made sense for us when moving from the former instance per customer architecture to the new shared architecture. With Cognito we could adopt a whole range of the security practices recommended by Amazon and ensure that each incoming request was tightly bound to a single account.
- separate out all web pages from the application API. We now deliver web pages statically from S3 and leave the application service to focus purely on API.
Once the pre-requisites were done, actually hooking up an incoming event from API Gateway to the .NET Core MVC framework was very straightforward with the Amazon.Lambda.AspNetCoreServer nuget package.
Then it’s simply a matter of mopping up:- redirecting log output to CloudWatch, preparing CloudFormation templates, reworking the continuous deployment pipelines, etc.
And at the end of all of this… it works.
Serverless is good!
Now that the dust is settling, I’m finding a lot to like with Lambda. There is some residual work in managing start-up latency and refining instrumentation. But the big picture is that we can now provision accounts for new customers instantaneously and with true cloud-like scalability. Lambda won’t suit everybody or every type of load, but it really is a neat service that promises to be a solid and efficient host for an ASP.NET Core application.
Points of interest to watch out for
In case you’re planning your own migration, here are a few things we encountered that you may need to plan for:
- Responding to events from other sourcesAlmost all events to our API Lambda function come from API Gateway. But we needed to listen to some events from other Lambda functions too. Since these don’t pass through the usual authenticated API Gateway we had to recognise and adapt the events that arrived from other services within AWS.
- Accepting unauthenticated CORS requestsSeparating web pages from the API meant that we needed to enable CORS. This tripped us up in a number of ways, one of which was when we found that browsers don’t add authentication headers to pre-flight CORS requests so these had to be catered for separately in the API Gateway configuration. (Another problem, incidentally, was that capitalisation of header names varies from browser to browser.)
- Combine third-party authentication with ASP.NET Core authorizationThe Amazon recommendation of authenticating requests at the API Gateway, before they reach the Lambda function, works well until you find that you still want to use ASP.NET Core’s authorization features for each endpoint. We handled this by implementing our own authentication schemes within the ASP.NET framework so that our established authorization policies would continue to work.
- Enable support engineers to see user account dataThis is not specifically a Lambda issue, but having established a secure relationship between a user identity, an account and each API request, we then had to devise a way for support engineers to work with users in the account of the user.