

# Serverless SaaS
<a name="serverless-saas"></a>

 The move to a SaaS delivery model is accompanied by a desire to maximize cost and operational efficiency. This can be especially challenging in a multi-tenant environment where the activity of tenants can be difficult to predict. Finding a mix of scaling strategies that align tenant activity with the actual consumption of resources can be elusive. The strategy that works today might not work tomorrow. 

 These attributes make SaaS a compelling fit for a serverless model. By removing the notion of servers from your SaaS architecture, organizations are able to rely on managed services to scale and deliver the precise amount of resources your application consumes. This simplifies the architecture and operational footprint of your application, removing the need to continually chase and manage scaling policies. This also reduces the operational overhead and complexity, pushing more of operational responsibility to managed services. 

 AWS offers a range of services that can be used to implement a serverless SaaS solution. The diagram in Figure 1 provides an example of a serverless architecture. 

![\[Serverless SaaS architecture\]](http://docs.aws.amazon.com/wellarchitected/latest/saas-lens/images/image2.png)


* Figure 1: Serverless SaaS architecture *

 Here you’ll see that the moving parts of a Serverless SaaS architecture aren’t all that different than a classic serverless web application architecture. On the left of this diagram, you see that we have our web application hosted and served from an Amazon S3 bucket (presumably using one of the modern client frameworks like React, Angular, etc.). 

 In this architecture, the application is leveraging Amazon Cognito as our SaaS identity provider. The authentication experience here yields a token that includes our SaaS context that is conveyed via a JSON Web Token (JWT). This token is then injected into our interactions with all downstream application services. 

 The interaction with our serverless application microservices is orchestrated by the Amazon API Gateway. The gateway plays multiple roles here. It validates incoming tenant tokens (via a Lambda authorizer), it maps each tenant’s requests to microservices, and it can be used to manage the SLAs of different tenant tiers (via usage plans). 

 You’ll also see that we’ve represented a series of microservices here that are placeholders for the various services that would implement the multi-tenant IP of your SaaS application. Each microservice here is composed from one or more Lambda functions that implement the contract of your microservice. In alignment with microservices best practices, these services also encapsulate the data that they manage. These services rely on the incoming JWT token to acquire and apply tenant context wherever it is needed. 

 We’ve also shown storage here for each microservice. To conform to microservice best practices, each microservice owns the resources that it manages. A database, for example, cannot be shared by two microservices. SaaS also adds a wrinkle here, since the multi-tenant representation of data can change on a service-by-service basis. One service may have separate databases for each tenant (silo) while another might comingle the data in the same table (pool). The storage choices you make here are meant to be driven by compliance, noisy neighbor, isolation, and performance considerations. 

 Finally, on the right-hand side of this diagram, you’ll see a series of shared services. These services deliver all the functionality that is shared by all of the tenants that are running in the left-hand side of the diagram. These services represent common services that are typically built as separate microservices that are needed to onboard, manage, and operate tenants. 

 More information on general serverless well-architected best practices can be found in the [Serverless Applications Lens whitepaper](https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/welcome.html). 

# Preventing cross tenant access
<a name="preventing-cross-tenant-access"></a>

 Each SaaS architecture must also consider how it will prevent tenants from accessing the resources of another tenant. Some wonder about the need to isolate tenants with AWS Lambda since, by design, only one tenant can ever be running a Lambda function at a given moment in time. While this is true, our isolation must also consider ensuring that a tenant running a function is not accessing other resources that might belong to another tenant. 

 For SaaS providers, there are two basic approaches to implementing isolation in a serverless SaaS environment. This first option follows the silo pattern, deploying a separate set of Lambda functions for each tenant. With this model, you’ll define an execution role for each tenant and deploy separate functions for each tenant with their execution role. This execution role will define which resources are accessible to a given tenant. You might, for example, deploy a collection of premium tier tenants in this model. However, this can be difficult to manage and may come up against account limits (depending on how many tenants your system supports). 

 The other option here aligns more with the pool model. Here, functions are deployed with an execution role that has a scope that’s broad enough to accepts calls from all tenants. In this mode, you must apply isolation scoping at runtime in the implementation of your multi-tenant functions. The diagram in Figure 2 provides an example of how this would be addressed.

![\[Multi-tenant architecture diagram showing isolation context, AWS Lambda, and IAM integration.\]](http://docs.aws.amazon.com/wellarchitected/latest/saas-lens/images/image3.png)


* Figure 2: Isolation in a serverless environment *

 In this example, you’ll see that we have three tenants accessing a set of Lambda functions. Because these functions are being shared, they are deployed with an execution role that covers all tenants. Within the implementation of these functions, our code will use the context of the current tenant (supplied via a JWT) to acquire a new set of tenant-scoped credentials from AWS Security Token Service (AWS STS). Once we have these credentials, they can be used to access resources with tenant context. In this example, we’re using these scoped credentials to access storage. 

 It’s important to note that this model does push part of the isolation model into the code of your Lambda functions. There are techniques (function wrappers, for example) that can introduce this concept outside the view of developers. The details of acquiring these credentials can also be moved to a Lambda layer to make this a more seamless, centrally managed construct. 

# Layers hide tenant details
<a name="layers-hide-tenant-details"></a>

 One of our goals with any SaaS architecture is to limit developer awareness of tenant details. In serverless SaaS environments, you can use Lambda layers as a way to create shared code that can implement multi-tenant policies outside the view of developers. 

 The diagram in Figure 3 provides an example of how Lambda layers can be used to address these multi-tenant concepts. Here you’ll see that we have two separate microservices (Product and Order) that have a need to publish log and metrics data. The key detail here is that both services need to inject tenant context into their log messages and metric events. However, it would be less than ideal to have each service implementing these policies on their own. Instead, we’ve introduced a layer that includes code that manages the publishing of this data. 

![\[alt text not found\]](http://docs.aws.amazon.com/wellarchitected/latest/saas-lens/images/image4.png)


* Figure 3: Lambda layers hide away tenant details *

 You’ll notice that our layer includes logging and metrics helpers that accept a JWT token. This allows each microservice to simply call these functions and supply the token without any concern for which tenant they are working with. Then, within the layer, the code uses the JWT token to resolve the tenant context and inject it into the log messages and metric events. 

 This is just a snippet of how layers can be applied to hide tenant context. The real value is that the policies and mechanisms for injecting tenant context are completely removed from the developer experience. They’re also updated, versioned, and deployed separately. This allows these policies to be more centrally managed in your environment without introducing separate microservices that could add latency and create bottlenecks. 