To Web API or not to Web API - Part 1
Considerations for building an API using .Net and Lambda

I mentioned briefly in a past blog post about the AWS dotnet lambda serverless template serverless.ASPNetCoreWebAPI. This is a super easy way for a .Net developer to either migrate an existing Web API into AWS Lambda, or even to start a new API without having to dive too deeply into understanding the intricacies of AWS Lambda and API Gateway. Recently AWS have even added the new .Net annotations framework and a new template serverless.AspNetCoreMinimalAPI which makes it even easier to just start coding up your .Net Web API with reckless abandon. At first glance, these approaches are very tempting to use, and seems like the best way forward for producing APIs in lambda if you’re a .Net developer, but a detailed analysis raises a few awkward questions that deserve to be answered. There are also some really good reasons to use Web APIs in lambda.

Philosophical Objection

The guiding philosophy behind Function as a Service (FaaS) is to keep your functions (i.e. Lambdas) as small and as focussed as possible. This ethos is in direct conflict with The Web API approach on 2 main points. Firstly, even though .Net core has been stripped back quite significantly as opposed to the .Net Full Framework, the plumbing required to host a .Net Web API is not insignificant. Secondly, hosting an entire API consisting of a significant number of routes with associated logic will inevitably bloat your lambda. In part 1 of this series, I’d like to talk through some of the reasons you might want to consider one approach over the other. In part 2, I want to start putting some hard numbers on some of these implications.

Delegation of Responsibilities

When you use these templates, you are essentially using API Gateway as a proxy. While API Gateway is more than capable as a proxy, It by-passes all of the really cool routing and validation capabilities of API Gateway, and delegates them to your lambda. This means that your lambda will be called for ANY and EVERY request, and will have to respond appropriately. Sure, this totally makes sense for the happy path for routes you have defined as controllers, and want to handle, but might not be what you want for routes that you haven’t defined, or for mis-configured requests. This could be costly in certain circumstances, and leave you more vulnerable to DDOS attacks.

This can be mitigated by using API Gateways request validation in front of your Web API lambda, which would ensure that only valid routes with valid parameters ever call your lambda in the first place. So maybe this isn’t as big an issue if you are willing to do this step, except for the fact that you are essentially double handling the validation in both API Gateway and your Lambda.

Of course, unless you are going to write a unique lambda for every route/verb/parameter combination, you will probably want to group the lambdas in some way, perhaps by route? In which case, you’ll then be responsible for writing some logic yourself to parse parameters and delegate each request to the appropriate handling methods. This will of course be far simpler than hosting the ASP .Net Web API, and should result in performance improvements. It does, however, open up the possibility of bugs in your parsing/routing code.

Cold Start

A bigger lambda will have a bigger cold start. This much is true, so you’d definitely expect a Web API hosted inside a lambda to have a larger cold start time than a simple lambda function. Even here it’s not completely black and white. Due to the concurrency model of AWS Lambda, and depending on thetraffic to your API, it may be an advantage to have a single lambda with a slightly larger cold start, than a large number of lambdas each of which incurring their own cold start penalty. It’s also important to understand what I’ve described in another blog post as the “luke-warm start”, although that can be remediated using the “ReadyToRun” feature.

Dependency Injection

One of the things that comes out-of-the-box with Web API is dependency injection. The dependency injection is baked into the Web API hosting model, and is extremely useful, even if it does add overhead. If you want to go the way of a bunch simple lambdas routed to by API Gateway, then you’ll either have to forgo dependency injection, or add a dependency injection framework yourself as another dependency, which will add to the size of your lambdas.

Security Boundaries

This is a big one. If you have a Web API hosted in a single lambda, and it has multiple routes, you need to give that one lambda an execution role with access to every single AWS service that you need to access in that API. On the flip side, if you can nicely divide your API into a distinct set of lambdas, then you can be very tight on the permissions you grant each lambda. For example, if I needed to access an Aurora database for some routes, but not for others, you can deploy the lambdas requiring database access into the necessary VPC, and give them permissions to secrets manager for credentials. The other lambdas that don’t require access, don’t need to be deployed to the VPC, and don’t need to have permissions to access Secrets Manager. This definitely enhances your security posture.

Documentation

Swashbuckle is very easy to add to your .Net Web API, and allows you to produce dynamic Open API documentation based on your controllers, your C# types, and attributes you add. This is a big win, because you can ensure high quality documentation is produced by your code as you work on it. It will undoubtedly add to your lambdas size, again impacting cold starts. Alternatively, if you choose not to use .Net Web API, then you are responsible for producing the documentation yourself, and keeping it up to date as your API changes.

Middle Ground

Because of the way API Gateway handles routes, it is possible to find a middle ground here. API Gateway will use the most specific route to determine where to send a request. This means that it is entirely possible to have certain routes handled by a few dedicated lambdas, but still send the {proxy+} route to a .Net Web PI lambda that handles all the rest of the routes.

Conclusion

Unfortunately, there is no clear winner here, and I’m going to take the consultants way out and say… “it depends”. So what does it depend on?

There are many other considerations, these are just a few of the obvious ones that fall out. Tell me what you think, and if I’ve missed any pros or cons.

*****
Written by Scott Baldwin on 30 July 2022