API Gateways
An API gateway provides a layer of abstraction for the Application Programming Interfaces (APIs) that your applications expose. For microservice-based applications that operate at scale, this becomes almost indispensable, although, in many ways, an API gateway predates the popularity of microservice architectures. API gateways can be software-defined, self-managed platforms that you deploy on top of infrastructure, or they can be a fully managed service delivered by a cloud provider. Regardless of the model, at its core, an API gateway is a service that provides a facade around one or more of your applications’ APIs, with added features. Common features offered by API gateways include the capability to implement a common and consistent authentication and authorization model across the back-end APIs, rate limiting for APIs, caching, monitoring, API versioning, and the capability to transform both requests and responses.
The API Gateway service in OCI is a fully managed service that is implemented as a virtual network appliance that you can deploy to a regional subnet. Regional subnets are required because API gateways are always highly available, with fault tolerance built in. When deployed in regions with multiple availability domains, API gateways are automatically configured across multiple ADs for fault tolerance. In single-AD regions, an API gateway is configured across fault domains. API gateways can be public (accessible from anywhere on the Internet) or private (accessible from within the VCN). API gateways always have a private endpoint, and this is optionally exposed publicly to create a public API endpoint. You can use a single API gateway to link multiple back-end services and route inbound traffic to them. These back-end services can include HTTP APIs exposed through compute instances, load balancers, external API providers, and OCI Functions.
Components of an API Gateway
When working with the API Gateway service, you generally start with the gateway resource. This is the infrastructure component, the virtual network appliance that is managed by OCI. You can then deploy API deployment specifications on this gateway resource. An API deployment specification is a way to describe the back-end APIs as a set of routes. A route is the mapping from a path to one or more methods and then a back-end service. Routes capture the type of resources that provide the underlying API and how to reach that resource. For instance, an HTTP URL exposed by an application that you are running on a compute instance captures the private IP address or domain name, the port on which the service is available, and the path under which to expose or present the API and the HTTP operations that the gateway supports for the route. The API deployment spec also describes the policies that can validate and transform the requests or responses. The policies are applied to every request or response. You can also use policies to add authentication, authorization, and monitoring. When the deployment specification is deployed to a gateway resource, it becomes an API deployment. The API deployment causes the gateway to expose the API as defined in the spec and is ready to direct traffic to the back ends described in the spec. As API traffic flows in, the gateway applies the policies that are specified by the API deployment spec. You can add policies to an API deployment specification that applies globally to all routes in the API deployment specification, as well as policies that apply only to particular routes. Figure 3-9 shows how an API gateway can contain multiple API deployments with policies and routes that connect it to various back ends.
FIGURE 3-9 An API Gateway Can Host Multiple API Deployments, with Routes and Policies That Connect It to Various Back Ends
Apart from resources such as the gateway and an API deployment, the service exposes another resource called the API. As shown in Figure 3-9, an API resource can be used to create an API deployment as well. The API resource is a representation of an API description in an open format such as OpenAPI 3.0 or 2.0 (also known as Swagger 2.0). An API description like this establishes the public contract for your API, which automatically documents the endpoints, paths, HTTP operations, and type of responses to expect from the API. Representing the API contract in an open format such as OpenAPI 3.0 helps to ensure the portability and tooling for working with these APIs; these industry-standard formats have attracted an established ecosystem of tools around them. The API description format is machine readable and can be processed by tools with which you can generate documentation for the API, generate stubs for clients to call the API without an actual implementation, generate test cases and Software Development Kits (SDKs), and so on. An API resource is created by uploading an API description. The API resource can be deployed to an API gateway, creating an API deployment. When an API resource is deployed to an API gateway, the routes are created from the API description because it provides the paths, the HTTP methods supported, and expected responses. Policies and references to the actual API implementations are added to the API description when creating an API deployment. Creating API resources in the API Gateway service is optional, but it is highly recommended. You can also create an API deployment that does not initially have an API description and then add an API description later.
Working with the API Gateway Service
The workflow for using an API gateway is best demonstrated by starting with a simple API. This example shows a minimal API for product data. The API will have methods by which users can request a list of products or get the details of a single product. To build this API, a developer can start with the infrastructure resources and build an ad-hoc deployment. This is done by creating an API deployment from scratch, defining its routes, back ends, and so on. Alternatively, a developer can first define the API contract and then create the infrastructure resources and deploy the API definition to it. The best practice is to use an API-first approach, to define the API and its behavior without focusing on the implementation. This API definition defines an interface that potential consumers can start consuming even before an implementation is created. After all, the implementation for the API simply materializes the behavior described by the API definition with concrete back-end systems. The actual implementation is hidden from consumers and can potentially be swapped out, if needed. This is because the consumers always consume the API through the API Gateway, which maintains the API behavior expressed in the API definition and routes the requests to a back end that implements that behavior. After the API is defined, a developer can create the infrastructure and deploy the API definition to that infrastructure. The example presented here uses OpenAPI Spec 3.05 to define the API, and the code snippet in Listing 3-2 shows how an API of this nature would appear.
Listing 3-2 An Example API Definition Expressed Using the OpenAPI Spec v3 Standard
{ "openapi": "3.0.0", "info": { "version": "1.0.0", "title": "Minimal Product API" }, "paths": { "/products": { "get": { "summary": "get all products", "operationId": "getProducts", "responses": { "200": { "description": "An array of products", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Products" }}}}}} }, "/products/{productId}": { "get": { "summary": "Info for a specific product", "operationId": "getProductById", "parameters": [{ "name": "productId", "in": "path", "required": true, "description": "The id of the product to retrieve", "schema": { "type": "string" }} ], "responses": { "200": { "description": "Expected response to a valid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" }}}}}}} }, "components": { "schemas": { "Product": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }} }, "Products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" }}}} }
OpenAPI definitions always start with the version of the OpenAPI Spec used. Explicitly setting the version is mandatory. Here, the spec version used is 3.0.0. This is followed by some API metadata in the info object. The paths section is one of the most important in the API definition; it defines the various URL paths or routes that the API exposes. The example here shows two paths: /products and /products/{productId}. Each path then defines the HTTP methods that the path will support. For each method, the definition identifies the request parameters and request body, where applicable, as in the case of PUT and POST methods. Paths can contain path parameters such as /products/{productId} or other parameter types, such as query parameters, cookie parameters, or header parameters. Each HTTP method also identifies the various possible response codes and response structures.
With the API defined, the developer can now create an API resource and upload the API definition to it. After the API definition is uploaded, the API resource validates the definition to ensure that it conforms to the specification. Once validated, the API definition is ready to be deployed to an API gateway. The developer can create infrastructure resources such as the gateway at this point or use pre-existing resources. Deploying the API to an API gateway creates the API deployment resource. During deployment, the service parses the API definition and creates the routes and methods based on the definition. At this stage, the developer can specify additional information about these APIs, such as the back ends that implement the APIs or the policies that need to be applied. Figure 3-10 shows how an API deployment infers the routes and HTTP methods from the paths specified in an API spec. To get started, developers can use a stubbed-out back end; as real back-end services are built out, they can simply be switched in.
FIGURE 3-10 The Service Can Parse the API Spec and Populate the Routes in an API Deployment