7. REST-Based API Design

The REST interface is designed to be efficient for large-grain hypermedia data transfer, optimizing for the common case of the Web, but resulting in an interface that is not optimal for other forms of architectural interaction.

Dr. Roy Fielding

As teams move from the modeling to the design phase, they will be faced with a variety of decisions. Some of these will resolve easily, while others will take time and deliberation. Just know that it is difficult to get an API design right the first time. Therefore, teams are encouraged to spend time designing and prototyping their APIs to gain feedback from early adopters before coding begins.

This chapter presents an overview of REST, along with a step-by-step process for migrating an API profile, created during the API modeling phase, into a REST-based API design. Along the way, various decisions and common design patterns are explored. The result will be a high-level API design (Figure 7.1) that apply REST-based principles.

Images

Figure 7.1 The Design Phase offers several options for API styles. This chapter covers REST-based API design.

Principle #3: Select the API design elements that matches the need

Trying to find the perfect API style is a fruitless endeavor. Instead, seek to understand and apply the API elements appropriate for the need, whether that is REST, GraphQL, gRPC, or an emerging style just entering the industry. The next three chapters provide insights into popular API styles to help teams select the right style or styles that fit the need. This includes when to apply each style, when to select synchronous or asynchronous interaction models, and whether to offer SDK libraries.

What is a REST-based API?

Representational State Transfer (REST) is an architectural style for distributed hypermedia systems. Unlike HTTP, REST is not a specification that is managed by a standards group. The term was coined by Dr. Roy Thomas Fielding as part of a dissertation titled, “Architectural Styles and the Design of Network-based Software Architectures” in 2000. This paper outlined the core concepts and constraints for understanding an architectural style, then proceeded to outline how these constraints were applied in varying degrees to architect the World Wide Web.

When discussing REST APIs or REST-based APIs, many will reference Dr. Fielding’s work without realizing that it extends far beyond web-based APIs. The paper seeks to establish fundamental constraints for designing evolvable and scalable distributed systems. Those interested in software architecture, particularly distributed software, should read this paper as part of their studies.

The paper does not require the use of HTTP as the underlying protocol for REST-based architecture. It does, however, discuss how the authors of the HTTP specification relied on the outlined architectural constraints to make it more evolvable. Since HTTP is the preferred protocol of choice for most network-based APIs, the details offered in this, and future chapters relies on HTTP as the protocol of choice.

The set of architectural properties outlined in the paper serve to establish constraints to create agreement around flexibility and evolvability when applied to distributed systems, including network-based APIs:

Client-server: The client and the server act independently and the interaction between them is only in the form of requests and responses.

Stateless: The server does not remember anything about the user who uses the API, so all necessary information to process the request must be provided by the client on each request. Note: this isn’t about storing server-side state.

Layered system: The client is agnostic as to how many layers, if any, there are between the client and the actual server responding to the request. This is a key principle of HTTP, allowing for client-side caching, caching servers, reverse proxies, and authorization layering – all transparent to the client sending the request.

Cacheable: The server response must contain information about whether or not the data is cacheable, allowing the client and any middleware servers to cache data outside of the API server.

Code-on-demand (optional): The client can request code from the server, usually in the form of a script or binary package, for client-side execution. This is performed today by browsers requesting JavaScript files to extend the behavior of a web page. Code-on-demand is an opportunity for API teams to provide JavaScript files for clients to retrieve that perform form validation and other responsibilities. Thus, evolvability can be extended to clients through code-on-demand.

Uniform interface: Encourages independent evolvability by leaning upon resource-based identification, interaction using representations, self-descriptive messages, and hypermedia controls.

The architectural constraints outlined in Dr. Fielding’s paper are important when considering the design of a web-based API. When taken together, these constraints contribute to the design of evolvable web APIs.

REST Was Never About CRUD

As already mentioned, REST is not a specification or a protocol. Contrary to popular opinion, a REST-based API does not require JSON or the use of the create-read-update-delete (CRUD) pattern of data interaction. REST is simply a set of constraints and agreements on how the individual components should work together. This offers flexibility to address architectural issues. Many of today’s web-based APIs use JSON and the CRUD pattern as design elements.

Unfortunately, a challenge emerges when people apply the REST label to an API that uses CRUD and JSON but may not intentionally apply the constraints as originally described in Dr. Fielding’s paper. This has resulted in many disagreements on what is “RESTful” and if something is “REST enough.”

Frankly, these disagreements are not beneficial. Instead, it is best to approach an API labeled as RESTful or REST-based with a mindset of grace and understanding that not everyone has read and fully applied the original REST paper. Use the opportunity to gently coach teams on how their architecture and API design can be improved over time. Whatever you do, please don’t use the opportunity to show how much more you know about REST than they do.

REST is Client/Server

The client/server architecture is an essential REST constraint. The server hosts available resources, supporting operations through synchronous, message-based interactions that use one or more representations as the client interacts with it.

Separating the client and server allows the user interface of the client to change over time. New devices and interface styles may be used without requiring changes to the server.

Most important is that the client is able to evolve independently of the server. The server may offer new resources or additional representation formats without negatively impacting the client. This is fundamental to why APIs may be offered as products, with or without a pre-existing user interface provided by the vendor.

REST is Resource-Centric

The key abstraction of information in REST is a resource. As mentioned in Chapter 1, a resource consists of a unique name or identifier that can references documents, images, collections of other resources, or a digital representation of anything in the real world such as a person or thing.

Resource representations capture the current state or intended state of a resource. Every resource must support at least one representation format but may support more than one. These representations may include a data format such as JSON, XML, CSV, PDF, image, and other media types.

The representation formats supported for any given resource may vary based on the needs of the client. For example, JSON may be a default media format offered by a REST-based API. However, some resources may need to be manipulated in a spreadsheet and therefore offer an alternative CSV-based representation of the same resource.

For example, a resource may represent a person named “Vaughn Vernon". The resource may have one or several representations. There may be a JSON-based representation, along with an XML representation. If a historical record is kept of all changes, each change may also exist as a representation that is available in JSON and XML media formats.

REST is Message-Based

Readers of Dr. Fielding’s dissertation may have noticed that it focused on the message exchange between client and server. Notice the use of the terms “REST messages” and “self-descriptive messages” in the paper. REST-based API design goes beyond the properties within a JSON or XML representation.

A resource representation is the message body within the overall message. Transport protocol design is also part of a complete REST-based API design. The URL path, URL query parameters, and HTTP request/response headers must all be considered as part of the design process. Focusing only on the message body results in an incomplete design.

The combination of the HTTP method, URL, request headers, and request body is a command message sent from the client to the server. It tells the server what you would like to do. The response headers, response status code, and response payload comprise the reply message back to the client. By thinking of REST-based APIs as exchanging messages with clients, API designs become more capable of evolving over time as the message evolves as the API grows and matures.

REST Supports a Layered System

The REST architecture style is a layered system, which means that a client should not be built on the assumption it is communicating directly with the server. There may be multiple middleware layers between the client and server that offer caching, logging, access controls, load balancing, and other infrastructure needs as shown in Figure 7.2.

Images

Figure 7.2 REST supports a layered architecture, allowing middleware servers to exist between the client and server.

REST Supports Code on Demand

Code on demand is a powerful but underutilized constraint. When a client requests a resource, it may also request code to act upon it. The client does not have to know what is in the code, it just has to understand how to execute it. The primary benefit is that the API can extend itself without requiring the client applications to perform a specific upgrade.

This technique is something that browsers do every day by downloading JavaScript files to execute locally within the browser. The browser does not need to know what is in the JavaScript files it downloads, only that they require the built-in JavaScript engine and therefore may be executed within the confines of the security sandbox offered by the browser. As new features and functionality become available, they are immediately available to the user without requiring a browser upgrade.

While used heavily by web-based applications, this REST constraint is one of the least utilized for REST-based web APIs but one of the most powerful. Imagine an API that offers the option of downloading code to create web forms and client-side validation behavior without the need to code and maintain it on the client side!

Hypermedia Controls

A hypermedia API is one driven by self-descriptive links in the representation. These links point to other, related API operations that reference related resources. They may also be used to reference other possible uses of the API, commonly referred to as affordances. Fielding considered hypermedia important to a REST-based architecture.

APIs that use hypermedia controls extend the conversation between client and server by offering runtime discovery of operations. They may also be used to convey server-side state to the client through the presence or absence of links in the representation. This is a powerful concept that is explored later in this section.

Hypermedia controls help connect the dots of various resources within and across APIs, making it operate more like the web. Imagine using a search engine to find some results, only to never be offered links to click to explore the results. Unfortunately, that is the way most design their APIs, offering only data and not hypermedia controls that afford the client the opportunity to explore the depth of the data and operations offered by the API.

A common use of hypermedia controls includes pagination, as shown in the HAL-based response below:

{
 "_links": {
 "self": {"href": "/projects" },
 "next": {"href": "/projects?since=d266f6cd-fddf-41d8-906f355cbecfb2de&maxResults=20" },
 "prev": {"href": "/projects?since=43be807d-d518-41f3-9206- e43b5a8f0928&maxResults=20" },
 "first": {"href": "/projects?since=ef24266a-13b3-4730-8a79- ab9647173873&maxResults=20" },
 "last": {"href": "/projects?since=4e8c74be-0e99-4cb8-a473- 896884be11c8&maxResults=20" },
  }
}

API clients can be designed to use the next link to follow the search results page-by-page until the next link is no longer present, indicating all search results have been processed.

APIs that offer hypermedia controls help to create context-driven responses. These controls are able to indicate what operations are possible, or not possible, based on the presence or absence of hypermedia links. This avoids the need to send boolean fields or state-related fields that the client interprets to decide what actions may be taken. Instead, the server determines this ahead of time and conveys what can and cannot be done by the presence or absence of the links provided.

What is HATEOAS?

Hypermedia as the engine of application state, or HATEOAS, is a constraint within REST that originated in Fielding’s dissertation. It describes the absence or presence of links as indicators of what operations the client may perform. Since the server understands both the user executing the operation and the authorization requirements of the operation itself, it is better positioned to determine what the client is able to do. Without this constraint, clients are required to re-implement the same server-side business logic and keep that logic in sync at all times.

It is important to note that Dr. Fielding has expressed a preference to use the term hypermedia controls rather than HATEAOS. Through the remainder of the book, the term hypermedia controls will be used in place of HATEOAS for clarity.

Below is an example HAL-based response for an article within a content management system that offers hypermedia links to valid operations based on the status of the article and the user’s role as an author:

{
 "articleId":"12345",
 "status":"draft",
 "_links": [
     { "rel":"self", "url":"..."},
     { "rel":"update", "url":"..."},
     { "rel":"submit", "url":"..."}
],
 "authors": [ ... ],
 ...
}

Once the author is ready to submit the article for editorial review, the editor would retrieve the article and receive the following actions based on the submitted status of the article and the editor’s role:

{
 "articleId":"12345",
 "status": "submitted",
 "_links": [
     { "rel":"self", "url":"..."},
     { "rel":"reject", "url":"..."},
     { "rel":"approve", "url":"..."}
],
 "authors": [ ... ],
 ...
}

Hypermedia controls have big implications for API-driven workflows that use context-driven hypermedia controls. They help to reduce the amount of business logic that has to be repeated in the client to mimic intended server behavior. Without them, the client would need to be coded to know what actions are allowed based on the status of the article and the role of the user. Instead, the client may be coded to look for specific hypermedia links that indicate if specific buttons are displayed or disabled for the end user, avoiding the need to keep in sync with the server-side business logic. This ensures the API is evolvable without breaking client code.

There are four primary hypermedia control types offered by REST-based APIs:

Index hypermedia controls: Offer a list of all available API operations, typically as an API homepage

Navigation hypermedia controls: Includes pagination links within payload responses or by using the Link header

Relationship hypermedia controls: Links that indicate relationships with other resources or for master-detail relationships

Context-driven hypermedia controls: Server state that informs the client what actions are available

It is important to note that any API style that does not encourage a unique URL per resource is unable to take advantage of hypermedia controls. This is the case for GraphQL and gRPC API styles, detailed in Chapter 8, along with older network API styles and messaging specifications such as SOAP and XML-RPC.

Measuring REST using The Richardson Maturity Model (RMM)

The Richardson Maturity Model, or RMM for short, is a maturity model created by Leonard Richardson that describes four levels of REST-based API design maturity. The four levels are generally defined as the following:

Level 0: A single API operation, or endpoint, that receives all requests. Further, the name of the desired action is reflected with some sort of action parameter or even embedded within the payload of the request, e.g., POST /api?op=getProjects

Level 1: Incorporation of resource-based design through URL-based naming but with additional action parameters where needed, .e.g., GET /projects?id=12345

Level 2: Addition of properly applied HTTP methods, such as GET, POST, PUT, etc. and response codes to improve client/server interaction

Level 3: APIs are self-descriptive and include hypermedia controls to suggest related resources and client affordances based on server-side state

RMM was meant to be used as a general classification of an API’s improvement as designers seek to reach a design that uses hypermedia controls. Unfortunately, it has been used to denigrate the efforts of designers by proving that an API labeled as REST-based has not met sufficient criteria to be truly labeled as REST.

Richardson addressed the confusion and intent of RMM in a 2015 RESTFest sub-talk titled “What Have I Done?". Richardson described the whole idea of RMM as "very embarrassing" and that it was meant simply as one possible measurement of improvement and maturity when attempting to target a hypermedia API. It was not meant to be a canonical method of classifying all APIs as REST-compliant. Instead, design to meet the needs of clients rather than trying to measure a specific level of design maturity.

When to Choose REST

Dr. Fielding’s dissertation explicitly defines REST as an architectural style for course-grained data transfer:

The REST interface is designed to be efficient for large-grain hypermedia data transfer, optimizing for the common case of the Web, but resulting in an interface that is not optimal for other forms of architectural interaction.

While Dr. Fielding doesn’t define the term “large grain” explicitly, the web is a good example. An HTML page is sent as a single, complete resource. It is not split into separate assets and retrieved separately. Once the HTML resource has been received and parsed, all referenced images, JavaScript, and style sheets are retrieved individually.

REST is a good architectural style choice when the interaction level requires course-grained resources over HTTP. This is common for APIs exposed over the internet that may encounter additional network latency or unpredictable connectivity.

For finer-grained interaction styles, RPC or other styles may be a better fit. Some RPC styles offer improved performance and long-running connections that do not meet the REST constraints outlined by Dr. Fielding. This includes the choice of gRPC and asynchronous API styles, discussed in Chapters 8 and 9, for service-to-service and client-to-service interactions.

Additionally, many organizations default to REST for their API style choice when offering customer and partner-facing web APIs. Most select the REST-based API style internally due to the abundance of tooling, infrastructure, operational, and management support. Since REST builds upon the patterns of the web, it is familiar with developers, easily managed by ops teams, and offers an abundance of tools and libraries for producing and consuming APIs.

REST API Design Process

Now that one or more APIs have been modeled, as described in Chapter 6, it is time to start the API design process. While the goal of API modeling is to explore and capture the API requirements into a series of API profiles, the API design process maps the API profiles into HTTP using the REST-based principles discussed earlier in this chapter. Since most of the effort involved in API design was focused in modeling the API to produce the API profile, there are five quick steps to a high-level API design.

Step 1: Design Resource URL Paths

The first step uses the API resources and resource relationships identified in the API modeling process, captured previously in Figure 6.6 of Chapter 6. Migrate the list of resources identified in the API profile into a tabular list, as shown in Figure 7.3. For dependent resources, indent the name slightly to denote the relationship. This will help when establishing the URL paths for each resource.

Images

Figure 7.3 Begin by migrating the list of resources from the API profile created during modeling into a preliminary API design. Note that Cart Items is a dependent relationship, as detailed in Chapter 6, so it is indented slightly.

Next, convert each resource name into a URL-friendly name using all lowercase and hyphens in place of spaces. Start the path with a leading slash, followed by the resource name in pluralized form to denote that this is a collection of resource instances.

Dependent resources are nested under the parent, requiring the parent identifier in the path to interact with the dependent resources.

A Warning About Dependent Resources

It is common to see a considerable number of dependent resources when APIs are designed from the bottom-up from a relational database or by using a relational style to resource design.

Nesting dependent resources is for constraining the navigability of children to the scope of the parent. While it may be tempting to create dependent resources, keep in mind that each nested level requires the API consumer to include the parent identifier in the path. For example:

GET /users/{userId}/projects/{projectId}/tasks/{taskId}

For an API consumer to retrieve a task by a given identifier, they will also need to have the parent project and user identifiers. This places additional work on the client to track these parent identifiers.

Nesting dependent resources is a useful design option but should be used only when it improves the usability of the API.

The results are shown in Figure 7.4. Notice that the resource collections are plural names. While not required, this convention is commonly found with REST APIs.

Images

Figure 7.4 Convert each resource name into a URL-friendly name with dependent resources nested under the parent.

Migrate the list of operations, including their descriptions and request/response details, into a new tabular format designed to capture the high-level design. This is shown in Figure 7.5.

Images

Figure 7.5 Migrate the API profile’s list of operations, descriptions, and request/response details into a new design-centric tabular format.

Step 2: Map API Operations to HTTP Methods

The next step is to determine which HTTP method is appropriate for each operation. Chapter 6 outlined three safety classifications that each HTTP method may be assigned: safe, idempotent, or unsafe. Table 7.1 outlines the safety classifications for common HTTP methods based on their intended use.

Table 7.1 Safety classifications for common HTTP methods

Images

During modeling, common verbs were likely identified in the operation name and/or description of the API profile. These verbs often provide clues to the HTTP method that best matches the operation. By combining the safety classification of HTTP methods, as outlined in Table 7.1, with the operation mappings listed in Table 7.2, the appropriate HTTP method can be selected.

Table 7.2 Mapping common verbs found in operation name/descriptions to HTTP

Images

Using Table 7.2 as a reference, along with the list of resource paths created previously in step 1, assign the appropriate path and HTTP method to each operation based on the intended usage. If the operation is interacting with a specific resource instance, include the resource identifier in the path. The results are shown in Figure 7.6.

Images

Figure 7.6 Using the list of paths identified earlier, assign the appropriate path and HTTP method to each operation based on the intended usage.

Step 3: Assign Response Codes

The API design is starting to emerge. The next step is to assign the expected response codes for each operation. HTTP response status codes belong to three primary response code families:

200 codes indicate success, some with more clarity (e.g. 201 CREATED vs. 200 OK)

400 codes indicate a failure in the request that the client may wish to fix and re-submit

500 codes indicate a failure on the server that is not the fault of the client. The client may attempt a retry at a future time, if appropriate

Be sure to use the right code for the right reason. When in doubt, refer to the most current RFC that details the intended usage for the code. If a specific code isn’t available within the response code family, then use the default 200, 400, or 500 code as appropriate.

Don’t invent your own response codes

Over the years, some strange decisions have been made by API designers. This includes the use of Unix-style codes, where 0 indicates success and 1-127 indicates an error, for HTTP response code. Please do not do invent your own response codes. HTTP is designed to be layered, which means that middleware servers that you don’t own might be involved between the client and server. Creating your own codes will only serve to cause problems with these intermediary layers.

While the list of HTTP response codes is quite large, there are several that are commonly used in API design. These are detailed in Table 7.3.

Table 7.3 Common HTTP response codes used in API design

Images

While API clients should be prepared for any kind of response code, it is not necessary to capture every possible response code. Start by identifying at least one success response code for each operation, along with any error codes that the API may explicitly return. While the list of errors may not be comprehensive, start by identifying the typical error codes that may be returned. Figure 7.7 shows the possible success and error codes for the Shopping API.

Images

Figure 7.7 Expanding the Shopping API design with success and error response codes captured in the Response column of the table.

Step 4: Documenting the REST API Design

Upon the completion of step 3, the high-level API design work has been completed. Using the work done so far, it is now time to capture the API design using an API description format. This will allow for sharing the API design within and across teams for feedback.

Organizations typically have a preferred API description format, such as the OpenAPI Specification (OAS) or API Blueprint. If a format hasn’t been selected or standardized, refer to Chapter 13, “Documenting APIs”, to learn more about the various formats available. No matter what format is selected, the result is to have a machine-readable version of the API design for review, rendering API reference documentation, and tooling support.

For the purposes of demonstrating the key areas of documentation during the API design phase, the examples in this chapter will use the OpenAPI Specification v3 (OAS v3). Screenshots will show the OAS v3 description file using the Swagger Editor to render the result side-by-side for illustrative purposes.

Start the documentation process by leveraging details about the API captured throughout the API modeling and design process. This includes an API name, description, and other details about the API. The description should include references to other APIs that may be used in collaboration with this one. Summarize the purpose of the API and the kinds of operations offered. Avoid referencing internal details of how the API is implemented, as those details can be stored outside the API description in a wiki or similar collaboration tool for future developer reference. Figure 7.8 shows the result of capturing these details in OAS v3.

Images

Figure 7.8 Capturing the Shopping API design into the OpenAPI Specification (OAS) v3, starting with the name, description, and other important details.

Next, capture the details of each operation. For OAS v3, this begins with the path, followed by each HTTP method supported at the path. It is also recommended to add an operationId property to each operation. Use the operation name from the API profile, defined in Chapter 6. This makes the documentation process effortless and helps to map the OAS description back to the API profile.

Using the details captured in the associated job stories created in Chapter 3, write a short summary of the API to help readers understand its purpose. Expand the details in the description field using the information captured in the API profile in Chapter 6. Also, ensure all path parameters and query arguments are captured. This is shown in Figure 7.9.

Images

Figure 7.9 Expanding the Shopping API design documentation to include each operation.

Finally, capture all schema elements for resource representations in the schema definitions section of the OAS v3 description. Use the resource models created during API modeling, as described in Chapter 6. This is demonstrated in Figure 7.10, where a ListBooksResponse captures the response of the ListBooks operation.

Images

Figure 7.10 Finalizing the Shopping API design documentation to include schema definitions.

Note in Figure 7.10 that the List Books operation returns an array of Book Summary instances that contain the basic details of each book in a search result. Adding schema definitions that wrap an array response or that limit the acceptable properties for each operation’s request and response payload is often necessary. Operations that create or update a resource may also require separate schema definitions to prevent read-only fields from being submitted. This is demonstrated in Figure 7.11.

Images

Figure 7.11 Keep in mind that some operations may require custom schema definitions to exclude specific fields not permitted for specific operations or to wrap search responses that contain only summary details.

Use sequence diagrams to validate that the API design meets the needs captured during the creation of job stories, EventStorming, and API modeling. Figure 7.12 shows a sequence diagram with a simplified form of HTTP for demonstrating typical interaction patterns to produce the desired outcomes.

Images

Figure 7.12 Use sequence diagramming to validate that the API design meets the needs previously modeled.

Once the API design has been captured in an API description format, the generated documentation and sequence diagrams can be shared with others to obtain feedback on the design. This is the final step in the API design process.

Step 5: Share and Gather Feedback

The final step is to share the API design for feedback from the immediate team, API architects from the organization, and internal/external teams planning to immediately integrate the API once ready.

Once the API has been officially released and integrated, the API design is locked, and only non-breaking changes may be made. New endpoints may be added, and perhaps new fields added to existing resource representations, but renaming or modifying existing endpoints will break existing API consumers. This will lead to upset customers and perhaps customer churn. Getting it right the first time is important. Sharing the API design early for feedback helps to avoid significant design changes post-release.

Mock implementations of an API are also helpful to explore API designs. Since reading API documentation only provides a basic understanding of an API, mock implementations offer a chance for developers to experience how the API may work using mock data. Tools are beginning to emerge that will accept an API description format, such as OAS, and generate a mock API implementation without writing a single line of code.

Refer to Chapter 16 regarding other API lifecycle techniques that help to gain feedback on an API, even after it has been developed and deployed.

Selecting a Representation Format

So far, the discussion of resource design has been centered on the resource names and properties. However, the representation format of an API’s resources will need to be determined as well. Selecting a representation format is an important step.

For some organizations, the preferred representation format has already been determined as part of the API style guide and standards. In that case, the decision has been made already and no further action is required. However, if this is a new API product or one of the first APIs of a new API program or API platform, then there is more work to be done to complete the design effort.

Whenever possible, select a single format as the default representation format that will be offered across all APIs. This will ensure consistency when a developer integrates with this and other existing and future APIs from the organization.

Additional formats may be added for operations over time, allowing an existing API to slowly migrate to a new format without disrupting existing integrations. A multi-format approach requires the use of content negotiation, a technique offered in HTTP to allow clients to specify the preferred representation format desired. Content negotiation is discussed further in the HTTP primer offered in Appendix I.

Table 7.4 summarizes the four categories of representation formats available.

Table 7.4 The categories of API representation formats

Images

Each category builds upon the previous one, adding more options for representing resource and messaging formats. However, with more options often comes more complexity. Each category is explained, and an example presented to inform the selection process. The examples provided in this chapter are available in the API workshop examples GitHub repository.

Resource Serialization

The resource serialization category of representation formats is the most commonly encountered. They directly map each property of the resource and its value into the desired format, often in JSON, XML, or YAML. Binary formats such as Protocol Buffers and Apache Avro are gaining acceptance as well.

These representation formats require explicit code that handles the serialization between the resource and the target format. This mapping logic may be created through code generators or hand coded. Formatter and representer libraries often help to manage some of the mapping between the target format and an object or struct that represents the resource in code.

No matter how serialization is handled, the parsing and mapping code is unique to the resource as it must be aware of the expected fields and any nested structures. Listing 7.1 provides an example of a Book resource using a serialized representation in JSON.

Listing 7.1 An example serialized representation using JSON


{
    "bookId": "12345",
    "isbn": "978-0321834577",
    "title": "Implementing Domain-Driven Design",
    "description": "With Implementing Domain-Driven Design, Vaughn has
made an important contribution not only to the literature of the Domain-
Driven Design community, but also to the literature of the broader enterprise
application architecture field.", "authors": [ { "authorId": "765", "fullName": "Vaughn Vernon" } ] }

Resource serialization-based formats offer only the properties of the resource using key-value pairs.

Hypermedia Serialization

The hypermedia serialization category is similar to resource serialization but adds specifications on how hypermedia links are represented. It may also include specifications for including related and/or nested resources, called embedded resources, in a uniform way.

Formats such as Hypertext Application Language (HAL) enable resource serialization formats to be extended with hypermedia with little-to-no changes. This prevents breaking existing API clients while migrating existing serialization-based APIs to include hypermedia controls. This is why HAL tends to be a popular choice when moving to hypermedia APIs. Listing 7.2 shows an example of a HAL-based representation that extends Listing 7.1 with hypermedia links and a related list of author resources.

Listing 7.2 An example of a hypermedia serialization approach using HAL


{
    "bookId": "12345",
    "isbn": "978-0321834577",
    "title": "Implementing Domain-Driven Design",
    "description": "With Implementing Domain-Driven Design, Vaughn has
made an important contribution not only to the literature of the Domain-
Driven Design community, but also to the literature of the broader enterprise
application architecture field.", "_links": { "self": { "href": "/books/12345" } }, "_embedded": { "authors": [ { "authorId": "765", "fullName": "Vaughn Vernon", "_links": { "self": { "href": "/authors/765" }, "authoredBooks": { "href": "/books?authorId=765" } } } ] } }

Not all hypermedia formats offer the same features. Mike Amundsen has created a list of these factors, called H-Factors, that support reasoning about the level and sophistication of hypermedia support across formats.

Hypermedia Messaging

Hypermedia messaging formats differ from serialization in that they propose a uniform message-based format to capture resource properties, hypermedia controls, and embedded resources. This makes it easy to use a single parser across all resources represented by the message format, rather than unique mapping code for each resource type to parse a serialized format such as JSON or XML.

While the differences are nuanced between serialization and message-based formats, consider the fact that teams will no longer need to argue about what the JSON or XML payload should look like. Instead, they focus on the resource representations, relationships, and hypermedia controls within the message format itself. No more meetings to decide if a data wrapper is required around a JSON-based response!

Hypermedia messaging formats include JSON:API and Siren. Both of these formats offer a single structured message that is flexible enough to include simple or complex resource representations, embedded resources, and offer hypermedia control support.

Siren’s messaging capabilities are similar to JSON:API, but it also adds meta data that is useful for creating web-based user interfaces with minimal customization effort.

JSON:API is an opinionated specification that removes the need to decide on many design options commonly included in an API style guide. Representation format, when to use different HTTP methods, and how to optimize network connections through response shaping and eager fetching of related resources are just a few of the decisions already provided by JSON:API.

Listing 7.3 provides an example of a JSON:API message-based representation.

Listing 7.3 A JSON:API example demonstrating message-based representations


{
  "data": {
    "type": "books",
    "id": "12345",
        "attributes": {
        "isbn": "978-0321834577",
        "title": "Implementing Domain-Driven Design",
        "description": "With Implementing Domain-Driven Design, Vaughn has
made an important contribution not only to the literature of the Domain-
Driven Design community, but also to the literature of the broader enterprise
application architecture field." }, "relationships": { "authors": { "data": [ {"id": "765", "type": "authors"} ] } }, "included": [ { "type": "authors", "id": "765", "fullName": "Vaughn Vernon", "links": { "self": { "href": "/authors/765" }, "authoredBooks": { "href": "/books?authorId=765" } } } } }

Semantic Hypermedia Messaging

Semantic hypermedia messaging is the most comprehensive category as it adds semantic profile and linked data support, making APIs part of the Semantic Web.

By applying semantics of resource properties through linked data, more meaning is assigned to each property without requiring an explicit name to be used. Linked data usually relies on a shared vocabulary from Schema.org or other resources. With the growth of data analytics and machine learning, linking data to shared vocabularies enable automated systems to easily derive value of the data provided from APIs. Common formats that support semantic hypermedia messaging include Hydra, UBER, Hyper, JSON-LD, and OData.

Listing 7.4 provides an example of the UBER representation format.

Listing 7.4 An example of the UBER semantic hypermedia messaging format


{
  "uber" :
  {
    "version" : "1.0",
    "data" :
      [

          {"rel" : ["self"], "url" : "http://example.org/"},
          {"rel" : ["profile"], "url" :
"http://example.org/profiles/books"},
          {
            "name" : "searchBooks",
            "rel" : ["search","collection"],
            "url" : "http://example.org/books/search?q={query}",
            "templated" : "true"
          },
          {
              "id" : "book-12345",
              "rel" : ["collection","http://example.org/rels/books"],
              "url" : "http://example.org/books/12345",
              "data" :
              [
                 {
                     "name" : "bookId",
                     "value" : "12345",
                      "label" : "Book ID"
                 },
                 {
                     "name" : "isbn",
                     "value" : "978-0321834577",
                     "label" : "ISBN",
                     "rel" : ["https://schema.org/isbn"]
                 },
                  {
                     "name" : "title",
                     "value" : "Example Book",
                     "label" : "Book Title",
                     "rel" : ["https://schema.org/name"]
                 },
                 {
                     "name" : "description",
                     "value" : "With Implementing Domain-Driven Design,
Vaughn has made an important contribution not only to the literature of
the Domain-Driven Design community, but also to the literature of the
broader enterprise application architecture field.", "label" : "Book Description", "rel" : ["https://schema.org/description"] }, { "name" : "authors", "rel" : ["collec
tion","http://example.org/rels/authors"], "data" : [ { "id" : "author-765", "rel" : ["http://schema.org/Person"], "url" : "http://example.org/authors/765", "data" : [ { "name" : "authorId", "value" : "765", "label" : "Author ID" }, { "name" : "fullName", "value" : "Vaughn Vernon", "label" : "Full Name", "rel" : "https://schema.org/name" } ] } ] }, ] } ] } }

Notice how the size of the representations grow compared to the more compact resource serialization formats. With the increased size comes the addition of linked data and more powerful interactions with API clients. These representation formats offer more insight into how to navigate related resources and tap into new operations, including operations that were not available when the client was built.

The goal is to enable generic clients to interact with APIs without the need for custom code or user interfaces. Instead, a client can interact with an API it has never seen before, all using the details provided in a semantic, message-based resource representation.

Remember that is always better to include additional details in the message than force clients to write more code that infers behavior. This is the essence of why HTML works so well, as browsers are not required to implement custom code for every website that exists. Instead, the browser implements rendering logic, and the HTML message is crafted to deliver the desired result. While this may result in a more verbose message, the result is a more resilient API client that avoids hard-coded behavior.

Common REST Design Patterns

While covering REST API design patterns is the subject of a separate book, this section provides some basic patterns commonly encountered in REST-based API designs. Each of the following patterns offers an overview of when they should be applied to help API designers address commonly encountered design requirements.

Create-Read-Update-Delete (CRUD)

CRUD-based APIs refer to APIs that offer resource collections that contain instances. The resources instances will offer some or all of the create, read, update, and delete lifecycle pattern.

The CRUD pattern may offer a complete or partial CRUD lifecycle around a resource collection and its instances in a consistent way. The CRUD pattern follows this familiar pattern:

GET /articles – List/paginate/filter the list of available articles

POST /articles – Create a new article

GET /articles/{articleId} – Retrieve the representation of an article instance

PUT /articles/{articleId} – Replace an existing article instance

PATCH /articles/{articleId} – Update specific fields (i.e., a selective update) for an article instance

DELETE /articles/{articleId} – Delete a specific article instance

It is recommended to avoid fine-grained CRUD-based APIs result in multiple API calls that cross transactional boundaries. Not only does it force the client to orchestrate multiple API requests across fine-grained resources, but clients will be unable to rollback previously successful requests when encountering failures in subsequent API requests. Instead, design resources around digital capabilities rather than based on backend data models.

Extended Resource Lifecycle Support

It is not uncommon to identify a state transition that goes beyond the typical CRUD interaction model. With the limited selection of HTTP methods, designers must find new ways to offer the extended lifecycle while honoring the HTTP specification.

For example, consider a content management system that managed a resource collection, Articles, that now needs to add basic review and approval workflows beyond the standard CRUD-based lifecycle. Additional operations may be provided to facilitate the workflow, such as:

POST /articles/{articleId}/submit

POST /articles/{articleId}/approve

POST /articles/{articleId}/decline

POST /articles/{articleId}/publish

Using this functional operation approach, article resource instances are able to support the workflow necessary. Additionally, it offers a few advantages:

■ Fine-grained access control can be enforced at the API management layer, since each specific action is a unique URL that can be assigned different authorization requirements. This avoids coding specific authorization logic into a single update operation, such as a PUT or PATCH, when the state of a field is changed

■ Use of hypermedia controls to signal to clients the possible action(s) available based on the user’s authorization scope, as discussed earlier in this chapter

■ The workflow supported by the API is more explicit, as clients don’t have to look at the PATCH endpoint documentation to understand the valid status values available, along with recreating the state machine rules for every API client

For teams that prefer to avoid this style of functional operations for a resource instance or collection, an alternative approach is to support hypermedia controls that reference the same PUT or PATCH operation but support different message structures based on the type of action being taken.

Singleton Resources

Singleton resources represent a single resource instance outside of a resource collection. Singleton resources may represent a virtual resource for direct interaction of an existing resource instance within a collection, e.g., a user's one and only profile.

APIs may also offer nested singleton resources, when there is a one and only one instance in the relationship between the parent resource and its child resource, e.g., a user's configuration. The examples below illustrate possible uses of a singleton resource:

GET /me - Used in place of GET /users/{userId}, avoiding the need for the consumer to know their own user identifier or risk accessing another user’s data due to an insecure security configuration

PUT /users/5678/configuration - used to manage a single configuration resource instance for a specific account

Singleton resources should already exist and therefore should not require a client to create them ahead of time. While singleton resources may not offer the full spectrum of CRUD-style lifecycles like their collection-based brethren, they may still offer GET, PUT, and/or PATCH HTTP methods.

Background (Queued) Jobs

HTTP is a request/response protocol, requiring that a response be returned for any submitted request. For operations that take a long time to complete, it may not be optimal for applications to block with an open connection waiting for a response. HTTP provides the 202 Accepted response code for this purpose.

For example, suppose an API operation exists to support bulk importing user accounts. The API client could submit the following valid request:

POST /bulk-import-accounts
Content-Type: application/json

{
    "items": [
        { ... },
        { ... },
        { ... },
        { ... }
        ]
}

The server could respond with the following respond to indicate that the request was valid but that that it could not be processed fully:

HTTP/1.1 202 Accepted
Location: https://api.example.com/import-jobs/7937

The client could then follow up to determine the status by submitting a request to the URL provided in the Location header:

HTTP/1.1 200 OK

{
    "jobId": "7937",
    "importStatus": "InProgress",
    "percentComplete": "25",
    "suggestedNextPollTimestamp": "2018-10-02T11:00:00.00Z",
    "estimatedCompletionTimestamp": "2018-10-02T14:00:00.00Z"
}

This is called a fire-and-follow-up pattern. If the client doesn’t need to monitor the job status, then it can ignore the URL provided and move on to other tasks. This is known as the fire-and-forget pattern.

Long Running Transaction Support in REST

There may be times when a transaction requires more than one API operation to complete. During the days of SOAP, the WS-Transaction specification was provided to manage transactions across one or more requests. This often required a transaction manager that was costly both in terms of licensing and integration effort. To avoid this requirement with REST-based APIs, the builder design pattern can be applied to support similar semantics.

For example, imagine an API that is meant to support reserving seats for a music or sporting event. Perhaps the API must require payment within a specific time frame before the seats are placed back into the pool of available seats. A Seats resource may be used for searching for that favorite group of four seats together in a premium area:

GET /seats?section=premium&numberOfSeats=4

Perhaps a group of four seats are available. Yet, we cannot use the Seats resource to reserve the seats, as it would require four separate API calls that are not able to be wrapped in a transaction:

PUT /seats/seat1 to reserve seat #1
PUT /seats/seat2 to reserve seat #2
PUT /seats/seat3 to reserve seat #3 <-- this one failed. What now?
PUT /seats/seat4 to reserve seat #4

Instead, consider creating a Reservation resource that represents the transaction:

POST /reservations
{
  "seatIds": [ "seat1","seat2", "seat3", "seat4"]
}

If successful, a new Reservation is created and can be used to complete the payment process. It may also be used to further customize the reservations with add-ons, group meal plans, etc. Alternatively, if the time limit is exceeded, the reservation is invalidated or deleted from the system and the API client begins again.

Looking For More Patterns?

These are only a few of the many design patterns useful for REST and other API styles. Refer to the API workshop examples GitHub repository for more pattern resources.

Summary

When speaking about REST-based APIs, many conflate the idea of CRUD-based APIs that use JSON with the REST architectural style. However, REST defines a set of architectural constraints that help APIs mimic the best aspects of the web. Of course, REST APIs may apply various design patterns, including CRUD, to produce an approachable interaction model for resources.

By applying a five-step design process, a resource-based API design is created that applies the REST constraints to the API profiles creating during the API modeling process. Mapping the design into machine-readable API description allows tools to generate documentation for review by the team and the initial set of developers that will consume the API.

What if REST isn’t the right API style for some or all the APIs identified during API modeling? The next chapter will examine how GraphQL and gRPC are two additional API styles available when REST may not be the best choice or needs to be expanded with new interaction styles.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset