Resolve tenants by hostname in a function
Serve every tenant from one function that reads the hostname, loads the tenant from KV Store, and reads only its rows in SQL Database.
You serve every tenant of a product from one function that resolves the tenant from the hostname of each request, reads the tenant’s configuration from KV Store, and reads only that tenant’s rows from SQL Database. Onboarding a tenant is then a data change and a domain change, with no deployment.
Prerequisites
- An application with Application Accelerator on, served by a workload on the production infrastructure. To create them, refer to Applications quickstart, and to turn on Application Accelerator, refer to Turn on Application Accelerator.
- A certificate on the workload that covers the tenant hostnames, such as a wildcard certificate. To request one, refer to Request a wildcard certificate.
- A KV Store namespace for tenant configuration. To create one, refer to Namespaces.
- A SQL Database database for tenant data. To create one, refer to Databases and queries.
- A personal token, for the API calls. To create one, refer to Personal tokens.
- The Azion CLI installed and authorized, to store the environment variable.
The examples use example.com for the zone, *.app.example.com for the tenant hostnames, acme.app.example.com and globex.app.example.com for two tenants with the IDs 1 and 2, saas-tenants for the namespace, saas-data for the database, and a projects table as the tenant data. Replace each value with yours.
Store the admin token
The function writes a tenant’s configuration on an admin path that checks a token held in an environment variable, so the token never enters the code.
To create the token first, run this command with a value of your own. A function reads a variable’s new value only after it is deployed again, so the variable exists before the function does:
The command prints the UUID of the variable:
The Run multi-tenant SaaS applications use case uses the values of this example.
Create the tenant function
The function, tenant-app, runs on every path and does two jobs. On a tenant hostname, it resolves the tenant and answers with that tenant’s projects. On PUT /_admin/tenants/<hostname>, it writes a tenant’s configuration, which is how onboarding reaches KV Store: a key is written only from a function.
Create a function named tenant-app with this code, and instantiate it on the application. For the steps in each interface, refer to Functions quickstart, which creates a function and instantiates it on an application.
The tenant ID must be an integer, because a query refuses a JavaScript string as a parameter. The lookup key is the hostname, which the request always carries, because KV Store has no operation that lists keys.
The tenant-app instance is on the application, and it answers 404 for a hostname with no key.
The Run multi-tenant SaaS applications use case uses the values of this example.
Run the function on every path
Add the rule that runs the instance on every path, as Run a function on an application describes, with these values:
- Name:
tenants - run tenant-app. - Phase: Request Phase.
- Criterion:
${uri}starts with/, so every path of every tenant hostname runs the function. - Behavior: Run Function with the
tenant-appinstance.
In the API, the body of POST /v4/workspace/applications/<application-id>/request_rules carries the ID of the tenant-app instance in attributes.value:
Every request to the workload runs tenant-app. A new rule takes a few minutes to reach every data center.
Onboard a tenant
Onboarding a tenant is four changes, and none of them is a deployment: the tenant’s rows, its configuration key, its hostname on the workload, and its DNS record. This section onboards acme.app.example.com with the tenant ID 1.
To onboard the tenant:
Replace <database-id> with the ID of saas-data. The first statement creates the table on the first onboarding and does nothing afterward. For how the query endpoint runs statements, refer to Create tables and query data:
The API answers 200 with one entry per statement. Read data[].error on each entry, because a failed statement also returns 200.
Send the configuration to the admin path of the tenant application, on the workload domain, with your token:
The function answers 201 with Tenant stored. A request without the token answers 401.
Send every hostname the workload answers, the new one included, because domains replaces the list. For the same change in Azion Console or the Azion CLI, refer to List the domain on the workload:
The API accepts the update, and the workload lists the hostname in domains.
In the example.com zone of Edge DNS, add a CNAME record named acme.app whose value is the workload domain. For the steps, refer to Add, edit, or delete a record.
The tenant answers on acme.app.example.com once the workload change propagates, which takes several minutes. KV Store makes a new key visible everywhere within 60 seconds, so a tenant can answer 404 in some locations during its first minute.
The Run multi-tenant SaaS applications use case uses the values of this example.
Confirm each tenant sees its own data
Each check requests a tenant hostname or the workload domain:
-
A tenant sees its own data. Request each tenant hostname:
The body names the tenant and holds only its rows:
The same request to
globex.app.example.comnamesGlobexand holds none of Acme’s projects. -
An unknown hostname gets nothing. Request the workload domain, which has no tenant key:
The command prints
404. -
The admin path refuses a request without the token. Send the
PUTfrom Onboard a tenant without theAuthorizationheader. The function answers401. -
The certificate covers the tenant. Send
GET https://api.azion.com/v4/workspace/tls/certificates/<certificate-id>. The certificate readsactive, because the workload uses it, and an HTTPS request to each tenant hostname completes its handshake.
A tenant that answers Azion’s placeholder 404 page instead of Unknown tenant is still waiting for the workload change to propagate. When the function does not answer at all, turn on Debug Rules to see which rules ran.
These checks confirm the Run multi-tenant SaaS applications use case.