*See my [blog article](https://serverlessfirst.com/functionless-integration-trade-offs/) which includes and expands on the content of this note.* A functionless (a.k.a. Lambda-less) integration pattern, in the context of an AWS [[Serverless MOC|Serverless]] architecture, is one where a direct cloud-enabled integration between two services is preferred over the use of an intermediate [[AWS Lambda|Lambda]] function. Examples of functionless designs include: - [[AWS API Gateway|API Gateway]] [service integrations ](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services.html) — From API Gateway to services such as [[DynamoDB]] and [[AWS EventBridge|EventBridge]] - [[AWS Step Functions|Step Functions]] [SDK integrations](https://aws.amazon.com/about-aws/whats-new/2021/09/aws-step-functions-200-aws-sdk-integration/) — From Step Functions to any of the 200+ AWS services which can be called from the AWS SDK. - [[AppSync VTL resolver]] — From AppSync to services such as DynamoDB (the most common), [[AWS EventBridge|EventBridge]] or HTTP endpoints. ## Benefits of Functionless designs - **Lower Latency**. Lambda can introduce some latency, through occasional cold starts and also simply by being an extra network hop. By going directly from one AWS service to another, you avoid this. - **No [[Code rot]]**. _You_ own the code for a Lambda function and its dependencies and so it's on you to update your dependencies when new versions are available. With direct service integrations, AWS takes care of this for you. - **Cheaper/Free**. You have to pay for Lambda invocations whereas most, if not all, Lambda-less direct integrations are free (you just pay for the service usage at either end of the integration). - **Even higher scalability**. Lambda functions are very scalable, but they are subject to an account-wide concurrent executions soft limit of 1,000. Once this limit is hit, functions get throttled. So if you have a very high throughput integration, a functionless design won't hit this limit (though the service which is the target of the integration will likely have its own limits). - **Less IaC code** is needed if you don't have to provision and wire-up the Lambda function, e.g. creating a dedicated IAM role for your function. This benefit is arguable based on how much IaC the functionless component requires to set up. ## Drawbacks of Functionless designs - **Learning curve for functionless "code" format**. While they are considered "low-code", some code is still required to define the mappings for these integrations and some mapping languages are quite esoteric and limited in features. - **Poor tooling**. The tooling for these mapping languages (e.g. VTL) is generally poor relative to full-blown programming languages (e.g. no linting or typechecking), resulting in a slower [[The inner and outer loops of software development workflow|inner loop of development]]. - **No composability**. The mapping languages don't support features such as import/include or ability to define library functions, and so make re-use difficult or impossible. - **Risk to data integrity**. This lack of composability can make direct integrations to databases such as [[DynamoDB]] problematic. [[DynamoDB single-table design|Single-table design]] approach in particular requires several index attributes (e.g. PK, SK) to be dynamically calculated before writing an item. With direct service integrations, this business logic gets spread across the entire codebase rather than centralised within a single module. The end effect is that data integrity may be compromised if, say, a wrong format is used for setting an index attribute. - **Difficult/impossible to test (in isolation)**. [[Testing Step Functions]] is already hard in and of itself, so Step Functions which integrate directly to DynamoDB can be even harder to test. If you instead invoked a Lambda function from your state machine, you could easily test the Lambda function in isolation. - **Variable debuggability**. Whereas Lambda functions can be monitored, traced and logged in a standard fashion, debugging functionless integrations is always specific to the service integration being used, and is sometimes not as optimal as Lambda. For example, AppSync resolver logging is almost a must-have if you're using VTLs, but the automatic logging to CloudWatch is very verbose and coarse-grained, and so potentially significantly expensive to enable in production, requiring [code-heavy workarounds](https://theburningmonk.com/2020/09/how-to-sample-appsync-resolver-logs/). --- ## References - [Discussion at Stedi this morning on functionless vs "just" serverless architecture – i.e. when/why to eliminate Lambdas in favor of direct managed service integrations, even when a Lambda works just fine; in this case, API Gateway-Lambda-EventBridge vs APIG-Step Functions-EB](https://twitter.com/zackkanter/status/1399739095161749507?s=20) - Thread arguing in favour of functionless by [[@Zack Kanter]], CEO of Stedi - [Twitter discussion on Lambda-less architectures](https://twitter.com/alexbdebrie/status/1473341672956305413) by [[@Alex DeBrie]] - Thread responses generally argue *against* Lambda-less - [Some code is more equal than others](https://serverlessfirst.com/some-code-more-equal/) by [[@Paul Swail]] - [Don’t wait for Functionless. Write less Functions instead](https://medium.com/lego-engineering/dont-wait-for-functionless-write-less-functions-instead-8f2c331cd651) by [[@Sheen Brisals]]