AASHTOWare API Platform Technical Standardization
Design patterns, styles, and recommended approaches for API-led architectures
Introduction
This technical guideline establishes design patterns, styles, and recommended approaches to creating API-led architectures. These guidelines are based on existing standards and conventions. In the absence of an existing well-adopted standard or when a specific aspect conflicts with practical implementation needs, the best feasible method will be used and explained with underlying rationale.
These guidelines are meant to help create great APIs, with refined developer experiences, deployed to modern platforms, and remove as much software engineering guess work as possible. APIs built from this guideline's foundation will be familiar and consistent to developers using them.
Selection Parameters
- {id}: Unique identifier with variant data typing. Distinguishes item(s) within a set or collection.
- {uid}: Specialized universally unique identifier, 128bit data type
- {*_id}, {*_uid}: Multiple, composite, or compound key ids
Pagination Parameters
Pagination parameters are prefixed with "page_" to assist in disambiguation and potential naming conflicts.
- {page_index}: one-based index, page reference location
- {page_size}: number of items to include in one page
- {page_offset}: Absolute count offset "skip" value
- {page_sortby}: Sort by clause, comma delimited
- {page_cursor}: Value for cursor-based pagination
Content Negotiation Parameters
- {filter_expression}: Filter clause to apply to a collection
- {filter_target}: Filter clause supplement
- {transform}: Result transform parameter
- {fields}: Limits model data to members listed
- {expand}: Expands model data to related members
- {expand_depth}: Expands model data to related members based on a traversal depth
Temporal/Versioning Parameters
Temporal parameters are used in time-based expressions that include versioned, historical, or changed-tracked entity models.
- {temporal_start}: Inclusive starting point for temporal range
- {temporal_end}: Inclusive ending point for temporal range
- {temporal_asof}: Singular date point used for a point-in-time expression
- {temporal_version}: Version marker, allows variant typing
Tracking Parameters
Tracking parameters are used to facilitate tracing, collation, and audit by action, operation, event, request, or response.
- {request_id}: UUID/GUID used to identify a specific request
- {correlation_id}: UUID/GUID used to identify and correlate a sequence of requests
- {event_id}: UUID/GUID used to identify an instance of a raised event
- {event}: String used to qualify and uniquely identify an originating source of an event
Data Interchange
Standard media types SHOULD be used for standard operations. The json media type MUST be supported with conventional data interchange operations where an incoming or outgoing data payload is present. The xml media type support SHOULD be made available as a complement to the json media type support when needed.
Input Payload
Data modification object payloads SHOULD reflect their corresponding data retrieval payloads. A POST operation payload to create an item would approximate the data returned in the item resource GET operation. A partial update PATCH operation SHOULD be expressed as a subset of the data returned in the item get operation.
Data modification payloads that provide batch operation SHOULD reflect their equivalent operations with extension to array object collections.
Data modification payloads requiring additional context, metadata, properties, or information "enrichment" MAY envelope contents in an object "root" wrapper to support more complex structures.
The following root objects, subordinate collection, and enrichment field naming options are RECOMMENDED when a convention or practice does not exist:
Root/Wrapper Object Names
- input (preferred default)
- parameters
- data
- settings
Subordinate Principal Collection Names
- items (preferred default)
- values
- results
- elements
- children
- details
Common Enrichment Fields
- code
- status_code
- status
- info
- message
- correlation
- date
- source
Output Payload
Standard data operations that do not return file or file streamed content MUST firstly support standard media types. Standard data operations SHOULD reflect resource contexts and exclude mixed-function data enrichment. Data operations MAY support more than one return type object or model.
Error Object Base
The following error object base model is RECOMMENDED for operations returning 4XX status:
Information Object Base
The following information base object model is RECOMMENDED for operations that do not require a defined model result:
Enumeration
The OpenAPI Standard specification supports nullable and reusable enum definitions. Enumerations are defined as strings. Constrained option sets SHOULD be created as enum types. Enum options value declarations MUST use upper snake case.
Entity Naming
Entity naming will reflect model schema concerns and resource (path) treatments. For convention and consistency, entity naming will have implications on serialization formats and styles.
- Entity names as a model representation and a resource representation MUST be correct in syntax (rules, grammar) and semantics (meaning, context).
- Entity naming MUST prioritize resource (URL) format and styles over serialization formats and styles.
- Entity names SHOULD use lower snake casing to facilitate resource path composition.
- Entity naming conventions MUST allow for serialization/deserialization flexibility across platforms, conventions, and formats.
Entity Member Field Naming
Entity members MUST be named with a consistently enforced convention. Entity member names MUST allow for translation or mapping from one format to another format.
Entity member naming SHOULD accommodate (URL) parametric formats and styles while prioritizing serialization formats and styles.
There is no unified industry consensus on entity member naming formats. Broadly, underscores, hyphens, case variation (pascal and camel) all yield compound word naming structures. The following order represents the better practices in member naming formats:
- Lower snake case
- Camel case
- Pascal case
Entity member naming SHOULD use the above formats. Entity member names MUST be correct in syntax and semantics. Entity member names MUST express a meaningful representation of the entity model.
Graph Flattening
Related entities in a model or schema definition, including navigation traversal properties, MAY be flattened (denormalized) to simplify data payloads. Navigation key(s), property dependent value(s), and a human-readable label/descriptor/display name SHOULD be included as additional members as a de-normalizing pattern.
Graph depth traversal de-normalization SHOULD be reflected in member names to express hierarchical associations, namespace aspects, or subordinate discriminators. For example, an "order item" that includes a product and a category for that product would be expressed to include the following members:
- product_id
- product_displayname
- product_category_id
- product_category_displayname
Graph Expansion
Graph expansion provides a serialization scaffolding for a normalized, traversable, associated resource expansion. Graph expansion is not the graph model structure of a resource model entity. Graph expansion is a HATEOAS and resource discovery aspect to achieve embedding of subordinate resources.
Furthermore, graph expansion differs from an extended model (serialization) graph as the resource is distinct. That resource distinction allows for clear(er) separation of function. A resource can apply access, action, and various other behaviors that are less concise when implemented by extending an entity model and its serialization.
A graph expansion for the /orders/{id} resource could take the form /orders/{id}?expand=(order_items) as a [GET] action. A collection property of "order_items" would be populated with elements mirroring the resource /orders/{id}/order_items.
Requests
Operation Identification
All resource operations MUST be uniquely identified. A resource may support multiple actions. Each action on a resource is an operation. Each operation MUST be given an operation id reflecting the API information or abridged alias, a primary (included) OpenAPI Specification tag value, the action context, the resource, and cardinality (singular item or a collection of items pluralized).
For example, adding an order, in an eCommerce app API: the operation id of "acmecomm_ordering_add_order" would represent a [POST] action on the resource /orders, which is part of the "ordering" aspect (OpenAPI tag "ordering") for the Acme eCommerce public API. Retrieving orders would have an operation id of "acmecomm_ordering_get_orders" representing /orders [GET].
The following action designation SHOULD be used in the absence of an existing defined convention:
- [GET]: read, get, get_all, query
- [POST]: add, create, post, insert, add_batch, create_batch, post_batch, insert_batch
- [PATCH]: update, modify, patch (note, the patch action usually applies to a single, non-batched, item)
- [DELETE]: delete, remove, delete_batch, remove_batch
- [PUT]: update, upsert, modify, put, update_batch, upsert_batch, modify_batch, put_batch
- [HEAD]: append suffix "_preamble" to the corresponding [GET], read_preamble, get_preamble
- [OPTIONS]: append suffix "_options", read_options
HTTP Verb Usage
Requests MUST correctly use HTTP verbs in resource actions. Action behavior MUST correspond to its verb. Action behaviors include consideration of resource modification, implications of "replay" activity, and suitability for caching.
| Verb | Purpose | Safe | Idempotent | Cacheable |
|---|---|---|---|---|
| GET | Retrieve single items or collections | Yes | Yes | Yes |
| POST | Create new resources | No | No | No |
| PATCH | Modify part of a resource | No | No | No |
| DELETE | Remove a resource | No | Yes | No |
| PUT | Complete replacement or creation | No | Yes | No |
| HEAD | Retrieve metadata only | Yes | Yes | Yes |
| OPTIONS | Interrogate available methods | Yes | Yes | No |
[GET]
[GET] actions MUST be fundamentally read-only as it applies to the target resource. [GET] resource actions are used to retrieve single items or collections. An unresolved single item [GET] SHOULD return a 404, not found, status code. A zero-length collection result SHOULD return an empty array along with a 200, success, status code. A missing collection SHOULD return a 404, not found, status code.
[POST]
[POST] actions are used to create new resources. Item creation SHOULD target a pluralized-named resource target. [POST] actions MAY create multiple items in a batch scenario.
Successful [POST] actions MUST return a success status code, 2XX class, result. If a single item is created, the operation SHOULD return a status 201, created, result.
[PATCH]
[PATCH] actions are used to modify part of a resource, or by extension, apply a partial modification update to a resource. A [PATCH] action body payload content does not have to approximate the resource target. The [PATCH] action body content MUST express the instructions necessary to modify the resource. The [PATCH] action body content MAY use any standard, format, structure, or style. The [PATCH] action body content SHOULD align to the resource's structure or a well-known standard such as JSON Merge Patch (IETF RFC 7396) or more ideally JSON Patch (IETF RFC 6902).
[DELETE]
[DELETE] actions assert the request to remove a resource representation. This implies the deletion of a single item or a collection of items as expressed by the action. Mechanics of removal are separate from the resource representation. For example, archival, cascade/propagation, "soft deletion", and eventually consistent patterns will be outside of the [DELETE] action response status.
[DELETE] actions MUST NOT allow unconstrained removal of a resource collection. For example, this constrained [DELETE] action is allowed: [DELETE] /orders/{id}/order_items?quantity=0. This unconstrained [DELETE] action is not allowed: [DELETE] /orders, as it would semantically delete all orders.
[PUT]
[PUT] actions express a resource submission. If the resource exists, the [PUT] action performs a complete replacement. If the resource does not exist, the resource is then (newly) created. [PUT] action request body content SHOULD approximate the target resource structure(s).
A collection-based [PUT] action MAY be used to effectively "wipe & replace" when the collection is constrained. [PUT] /orders/{id}/order_items would allow for a complete add, update, delete combine on all order items, constrained to the order referenced by the parameter "id".
[HEAD]
[HEAD] actions are semantically similar to [GET] actions. [HEAD] actions do not return body content, instead, only headers are provided. [HEAD] actions SHOULD align to a [GET] operation. [HEAD] actions usage includes determining a resource's state or version leveraging the "ETag" header and related information or in interrogating metadata of a large resource instead of accessing it.
[OPTIONS]
[OPTIONS] actions are used to interrogate available methods and options for a given resource. The [OPTIONS] action SHOULD be used to provide access control hints (by excluding base methods that are not allowed), CORS conditions, and encoding support.
Links
Link objects were introduced to the OpenAPI Specification in version 3.0. Link objects help provide discoverability and a HATEOAS aspect. Link objects are design-time declarations that allow runtime expressions to define an association, relationship, or navigation traversal (resource) path based on the current operation.
A Link object may incorporate any component of the operation, using the expression syntax, to define its target (resource). The target may be defined by an existing operation id using the "operationId" property (which must be uniquely identified within the scope of its OpenAPI Specification definition document), a relative path against its definition using the "operationRef" property, or an absolute path which may include external references using that same "operationRef" property.
Canonical Extensions
Canonical extensions are meant to supplement core semantics where there are clear gaps in definitive guidance. These extensions occur when certain scenarios are recognized in common or practical situations while at the same their ideal resolution is not explicitly forbidden.
HTTP [GET] Method with Body Payload
[GET] actions do not explicitly forbid body content. Some complex [GET] actions may require parameter complexity that is not (elegantly) suitable to URL query parameter translation either due to formatting or total potential size. URL Query string length may be limited and enforced by policy or server platform.
Many browser and web application firewall (rules) will not accept [GET] actions that include a request body. In this case, a semantically accurate [PUT] or [POST] should be used. For example, [POST] query_search with the return of search results is correct.
HTTP [DELETE] Method with Payload
As illustrated with the previous example, a complex or conditional [DELETE] action (especially in a batch operation) may also benefit from a content body payload input. The same considerations, tradeoffs, and constraints apply: URL parameter length restrictions VS server/platform support for [DELETE] with content body.
HTTP [HEAD] Concurrency Check Preamble
A resource's version is delivered through the "ETag" header. A change in a resource's "ETag" value indicates a change in the resource. For resource updates, if the initial "ETag" header value has changed prior to the commit of the resource modification, there is a potential concurrency conflict where the modification will inadvertently overwrite resource data.
In the context of preemptive concurrency state validation, using the [HEAD] action enables the retrieval of the "ETag" value to ensure the modification is against the intended resource version.
HTTP [HEAD] Passive Pagination Metadata
The ability to traverse a large data set through pagination is essential for scalability and performance. However, computing the associated paging metadata can add additional overhead to the retrieval operations and may not be required in all scenarios. Providing collection-based resource metadata separate from data retrieval reduces the resource burden.
Pagination metadata data MAY be provided from a [HEAD] action against a collection-based resource. Paginated data retrieval MUST support serial and parallel fetch strategies.
Common Request Bodies
The body content of most requests will adhere to the resource model structures for [POST] and [PUT] actions. [PATCH] actions MAY also align to the resource model or a formalized standard such as the JSON Merge Patch or JSON Patch.
Batch operation collections will extend this structure as simple arrays of the underlying resource model.
Primitive parameter collections SHOULD be defined using a simple name, value pairs, where the value is either a single non-object type or an array of the same non-object typed elements.
Responses
Operation responses can be classified into a limited number of generalized categories. These include, singular object results, object array/collection results, no-content results (differs empty array results), informational digest results, and error message results.
Safe and cacheable operations SHOULD implement "ETag" header value assignments in their response. Header value assignments and status code values MUST be correct and consistent to the operation response.
Common Response Bodies
Info
An informational result body SHOULD be derived from a standardized base type object. An informational result SHOULD be provided instead of "no content" result in the absence of any special exceptions. An informational result MUST NOT be used for error results.
Error
An error result body SHOULD be derived from a standardized base type object to ensure a consistent behavior for exception conditions. An error result body SHOULD be used for error results. Error result objects MUST NOT include sensitive information in production environments.
Resource Links
As a means to achieve greater discoverability and advanced (higher maturity) API capabilities, resource links MAY be included as part of a response body data return. This approach differs from the OpenAPI Specification Link object as it is a runtime aspect delivered in the response data as compared to a design-time definition based on the operation resource and resource expressions.
Page(d) Result
A paged data model represents a result subset of items within a collection-based resource. Simple object array results SHOULD be used over a paged data model when possible. When a paged data model is required, it MUST include pagination supports for its target methodology (offset or cursor) and allow for potential high-volume/transaction iteration over the full collection.
Status Codes
API operations MUST use standard HTTP status code classes (1XX, 2XX, 3XX, 4XX, 5XX). API operation results SHOULD limit status codes to standard well-adopted conventions with expected corresponding behaviors.
| Status Code | Description | Usage |
|---|---|---|
| 200 OK | Operation succeeded | Successful GET operations |
| 201 Created | Resource created | Successful POST or PUT creating resource |
| 202 Accepted | Accepted for processing | Long-running async operations |
| 204 No Content | Success with no body | Successful DELETE, PUT updates |
| 206 Partial Content | Successful return of a (binary) data subset | GET streaming file data scenarios |
| 207 Multi-Status | Multiple resources with different statuses | Batch operations |
| 301 Moved Permanently | Redirection directive for GET operations | Deprecation/sunset backwards compatibility |
| 308 Permanent Redirect | Redirection for operations with payload | Deprecation/sunset backwards compatibility |
| 400 Bad Request | Invalid request | Validation failures |
| 401 Unauthorized | Authentication missing | No credentials provided |
| 403 Forbidden | Authorization insufficient | Lacking permissions |
| 404 Not Found | Resource doesn't exist | Invalid resource reference |
| 405 Method Not Allowed | Method is not allowed for resource | Resource exists without method support |
| 409 Conflict | Resource state conflicts | Optimistic concurrency violations |
| 423 Locked | Resource is locked | Pessimistic concurrency scheme |
| 500 Internal Error | Unexpected server error | Unhandled exceptions |
| 501 Not Implemented | Resource/method not supported | Expected but not processed |
| 503 Service Unavailable | Service temporarily unavailable | Temporary disruption |
OpenAPI RESTful Design Aspects
API designs SHOULD be non-fragile as well as robust. API designs SHOULD be conservative. API designs SHOULD be deliberate and cognizant of future extension requirements from the perspective of backward compatibility.
Richardson Maturity Model
The Richardson REST Maturity Model (RMM) has been used to measure how well a web-based API follows the principles of REST. The model has four levels, from 0 to 3:
| Level | Name | Description | Requirements |
|---|---|---|---|
| Level 0 | "Swamp of POX" | Single URI, single HTTP method | Not RESTful (anti-pattern) |
| Level 1 | Resources | Multiple URIs for different resources | Resource identification |
| Level 2 | HTTP Verbs | Correct use of HTTP methods | CRUD operations via verbs |
| Level 3 | Hypermedia Controls | HATEOAS implementation | Self-descriptive messages |
API designs MUST achieve level 2 or higher aspects.
Level 0 - "Swamp of POX"
Level 0 is the lowest level of maturity and the least conforming to REST principles. A web service at this level exposes only one URI for the entire application and uses only one HTTP method, typically POST, for all operations. The request and response bodies are usually in an object serialized format and contain information about the operation and the resource. POX represents "plain old xml", "plain old json", or any equivalent substitute.
A web service at this level is not RESTful and should be considered an antipattern reference.
Level 1 - Resources
Level 1 of the RMM begins to reflect rudimentary concepts of RESTful design, where each resource in the system is assigned a unique URI. The resource is a formal abstraction that can be accessed or manipulated by client requests. Consider an employee, a product, or a booking as resources in a proposed API.
In Level 1, the API uses different URIs to identify the different resources, but still relies on a single HTTP method (typically POST) to perform all operations on them. This means that the API does not adhere to correct standard HTTP methods (i.e., GET, PUT, PATCH, DELETE) that correspond to the common CRUD (create, read, update, delete) actions on resources.
Level 2 - HTTP Verbs
At Level 2 of the RMM, the API design uses different HTTP methods (such as GET, POST, PUT, and DELETE) to perform operations on different resources, which are each identified by unique URIs. This structure is more consistent and expressive than Level 1, which only uses one HTTP method for all operations, and aligns to correct semantic use.
Level 3 - Hypermedia Controls
Level 3 is the highest level of maturity in the RMM, and it requires the use of hypermedia as the engine of application state (HATEOAS). API responses should include links to other resources or actions that are relevant to the current state of the application.
Using hypermedia links at level 3 has several benefits for API design:
- It decouples the client and the server, as the client does not need to know the URLs or the logic of the server in advance.
- It improves discoverability and usability of the API, as the client can easily navigate through the resources and actions available without requiring additional documentation.
- It enhances evolvability and scalability of the API, as the server can change its URLs or logic without breaking existing clients.
Developer Experience
Developer experience (DX) is the term used to describe how easy and accessible it is for developers to use a certain product or service, such as an API. DX is influenced by many factors:
- Documentation - Must be accurate, complete, clear, organized, and accessible
- Examples - Should provide meaningful in-line examples and detailed descriptions
- Guardrails - Should describe validation, use limits, and restricted use
- Compatibility - Must maintain backwards compatibility when possible
- Performance - Should implement compression, caching, and optimization strategies
Pagination
Pagination SHOULD be achieved through an offset-based or cursor-based approach. Pagination aggregate or result metadata SHOULD NOT be included as the response body. Paginated results SHOULD be returned as an array.
Long-Running Asynchronous Processing
Any/All operations MUST support a long-running asynchronous pattern. However, long-running asynchronous implementation SHOULD be reserved for specific scenarios that are inherently different that continuous processing scenarios.
- All non-failing long-running asynchronous operations MUST return a status 202, accepted
- A request identifier must be generated for later use in providing an action result
- The long-running asynchronous result SHOULD be provided from a separate resource
Batch Operations
Batch operations allow for efficient processing as well as the ability to validate dependently chained operations in a contained and transaction-scoped fashion. Batch operations SHOULD follow their non-batch resource operation equivalents.
Batch operations that are not long-running asynchronous processes MUST return status 207, multi-status, when any portion of the batch completes successfully.
File Data
Unstructured data support SHOULD be provided using a web-protocol standardized file management approach.
File upload operations SHOULD use [POST] and [PUT] operations involving binary data and reserve [PATCH] operations for file metadata assignments. File request content models are defined with the appropriate media type with binary formatting.
Entity Flattening (Denormalization)
Denormalizing an entity graph structure is a useful technique in simplifying response payloads. Denormalization strategies MAY incorporate hypermedia techniques to allow resource expansion at the discretion of the caller while retaining the advantages of a compact-form model result.
Entity Graph Expansion
In comparison to entity flattening, entity graph expansion is a technique to enrich a resource data model. From a design perspective, graph expansion SHOULD NOT be used to consolidate resource concerns. Graph expansion SHOULD be used and limited to the embedding of related resources.
Entity Member Trimming
Data aspect trimming by limiting returned fields is a non-trivial implementation exercise. This endeavor is most beneficial in collection-based, large-set data retrieval scenarios. Before attempting to support expressive or dynamic entity member trimming, semantic resource extension SHOULD be explored as an alternative.
Events
The OpenAPI specification 3.0 introduces the concept of callbacks and webhooks to enable event functionality in APIs. This allows services to send asynchronous, out-of-band requests to other services in response to certain events, such as user actions, data changes, or system notifications.
Callbacks
Callbacks are defined as part of an operation that expects a callback URL in the request body or parameters. The callback URL is used to send a request in a predefined format and expected responses when an event occurs.
Webhooks
Webhooks are a way of implementing event-driven systems design, where a service can notify another service about defined events that occur asynchronously. As of OpenAPI 3.1.0, there is a new top-level element for describing webhooks that are registered and managed out of band.
Event Message Data
A base model for event data SHOULD include all necessary information to reconcile the event origin, context, and initiation. Additional metadata MAY be added to event messages as needed. Event message data MUST NOT contain sensitive information.
AI Delegation
Like the developer experience, AI Delegation accounts for API Consumer usability aspects when the API client or caller originates from an AI agent. API operations that support potential AI operation requests, on behalf of a user identity or as a service process, SHOULD be distinguished.
API design practices to support AI Delegation MUST include additional guardrails in validation, throttling, access control, instrumentation, security practices for input and output payloads.
The following example scenarios illustrate what and how AI delegation would be applied:
- Travel Planning: An AI agent could use various travel and weather APIs to plan a trip for a user.
- Financial Management: An AI agent could use financial APIs to help a user manage their finances.
- Health Monitoring: An AI agent could use health and fitness APIs to help a user monitor their health.
Operating an API
Publishing API Definitions
One of the benefits of the OpenAPI Specification is that it produces documentation that contains human-readable instructions for using and integrating with the API it defines. It provides essential information about the API endpoints, methods, resources, authentication protocols, parameters, and headers, as well as examples of common requests and responses.
Publishing and properly maintaining API documentation has several benefits when operating a commercial or enterprise API:
- Self-service quick learning for API developers and API consumers to easily understand how to use the API effectively for a given goal.
- Less time and costs spent handling support calls and queries because users can find help and answers to their API documentation questions.
- Better developer experience because the API documentation is clear, well-structured, and consistent.
- Increased awareness and adoption of the API, because the target audience can experiment with the API, see its value, and utility.
Policies
Policies are a collection of statements that are executed sequentially on the request or response of an API that are not part of the API definition nor the API implementations. They are used to modify or control the behavior and performance of the API, such as adding security, rate limiting, caching, logging, transformation, and more.
As part of the wholistic API design, within operational requirements, polices are used as a vital and advanced method to achieve functional capabilities through configuration versus code.
Business or service level requirements MAY be established through policy definition and later monitored or audited to ensure compliance.
Instrumentation
Logging, diagnostics, instrumentation, and monitoring are essential components of an API operational design. They enable the API platform team, developers, and operators to track the performance, availability, reliability, and security of an API (platform).
For a deployed API basic logging is the process of recording events and data related to an API request or response with categorization align to the API definition (with API operation granularity).
Diagnostics supports all for the processing and analyzing of real-time logs and other sources of information to identify and resolve issues or errors.
Instrumentation targets the implementation aspect by adding code or tools to an API to measure and collect metrics such as response time, throughput, error rate, etc.
Monitoring is the consolidates and informs the process of observing and reporting the metrics and alerts generated by the instrumentation.
Operational design requirements MUST include formal instrumentation definition and conventions.
Non-REST API Architectures
There exist two primary API architectural styles that are not restful. Both of which have long-established historical roots which have undergone many reincarnations in one form or the other.
GraphQL
GraphQL is a data query language and runtime that allows clients to specify the structure and content of the data they want to retrieve from an API. As detailed and discussed, OpenAPI contrasts this by being a standardized way to declare and document any RESTful API, which typically returns more predefined JSON responses based on the requested endpoint and parameters.
Both GraphQL and OpenAPI are used to define APIs, but they have different advantages and disadvantages depending on the use case.
Key Differences from OpenAPI
- GraphQL differs from OpenAPI in that it enables clients to request only the data they need, reducing bandwidth and improving performance.
- OpenAPI is better suited for RESTful API design, which follows a resource-oriented approach based on HTTP methods and status codes.
- GraphQL follows a query-oriented approach based on a single endpoint and a schema that defines the types and fields available for querying and mutating data.
- GraphQL can be used instead of OpenAPI especially as a replacement for legacy use of OData technologies.
- GraphQL offers more flexibility and expressiveness than OData, as well as better tooling and ecosystem support.
- However, GraphQL also has some drawbacks compared to OpenAPI, such as increased complexity on the server side, lack of built-in caching and versioning mechanisms.
Summary: GraphQL is a query-based paradigm. OpenAPI is a resource-based paradigm.
OData
OData, or the Open Data Protocol, is a well-established standard for building and consuming queryable RESTful APIs. It extends the capabilities of traditional REST APIs by allowing clients to perform complex queries directly through URL parameters, effectively turning the API into a composable query handler.
For software developers familiar with C# and the .NET platform, here's how OData aligns and contrasts with GraphQL and OpenAPI:
Key Features
- Composable Queries within REST: Unlike OpenAPI, which adheres strictly to core RESTful principles with predefined endpoints and responses, OData enhances REST by enabling clients to customize their queries using parameters like $select, $filter, $expand, and $orderby. This flexibility allows clients to retrieve precisely the data they need without multiple round trips.
- Balancing RESTful Adherence: While OpenAPI emphasizes a clear, resource-based approach in line with REST, OData's query composability introduces a layer of complexity that departs slightly from traditional REST norms. It overlays querying capabilities onto REST, which can blur the strict separation of concerns advocated by core RESTful principles.
- Latest Version (OData 4.01): Brings optimized data exchange, expanded query functions like $apply for aggregation and transformation, and improved integration with OpenAPI specifications.
The .NET platform offers robust support for OData, with libraries like ASP.NET Web API OData simplifying implementation. This integration makes it an accessible option for developers in the Microsoft stack.
OData serves as a bridge between the strict resource-based approach of OpenAPI and the flexible querying of GraphQL. It extends RESTful APIs with powerful querying capabilities, providing a composable way for clients to interact with data while maintaining a familiar REST structure.
gRPC
gRPC is a cross-platform open-source high performance remote procedure call (RPC) framework that can efficiently connect distributed and disparate services. gRPC was initially created by Google in 2015 as an extension of its internal RPC infrastructure which had been used for over a decade. Google has since made gRPC open source and standardized for community use.
gRPC is a technology for implementing RPC (Remote Procedure Call) APIs that uses HTTP/2 as its underlying transport protocol and Protocol Buffers as its serialization format as compared to more common JSON or even XML formats (it can be up to 8x faster than JSON serialization with messages 60-80% smaller).
gRPC vs OpenAPI
gRPC and OpenAPI differ in several aspects:
- Transport protocol: gRPC uses HTTP/2, which supports multiplexing, streaming, and bidirectional communication. OpenAPI uses HTTP/1.1, which is more widely supported but less efficient and flexible.
- Serialization format: gRPC uses Protocol Buffers, which are binary, compact, and fast to serialize and deserialize. OpenAPI uses JSON, which is human-readable, text-based, and easy to manipulate.
- Description style: gRPC uses .proto files written in a Protocol Buffer Language, which define the methods and messages of the API. OpenAPI uses JSON or YAML documents that follow the OpenAPI Specification schema.
- Error handling: gRPC uses status codes and messages defined by the gRPC protocol, which are more specific and consistent than the HTTP status codes used by OpenAPI.
When to Use gRPC
Some scenarios where gRPC may have an advantage over OpenAPI are:
- Performance: If the API needs to handle a large amount of data or support real-time communication, gRPC may offer better performance due to its binary format and HTTP/2 protocol.
- Streaming: If the API needs to support streaming data in both directions, gRPC may be more convenient due to its built-in support for bidirectional streaming.
- Cross-platform: If the API needs to support multiple languages and platforms, gRPC may be easier to use due to its code generation feature and wide range of supported languages.
When to Use OpenAPI
Some scenarios where OpenAPI may have an advantage over gRPC are:
- Interoperability: If the API needs to be compatible with various tools and platforms that support HTTP/1.1 and JSON, OpenAPI may be more suitable due to its standardization and popularity.
- Documentation: If the API needs to have a clear and comprehensive documentation that can be easily accessed and updated, OpenAPI may be more helpful due to its human-readable format and rich tooling ecosystem.
- Simplicity: If the API needs to be simple and easy to implement and maintain, OpenAPI may be more preferable due to its declarative style and minimal dependencies.
gRPC provides a procedural "on the metal" API model that is less associated with web-foundational standards. OpenAPI adheres to fundamental web semantics and formal RESTful constructs.
Appendix
Large Data Set Optimization
High-Performance Large Data Set API Operation Template
To improve the response time performance of your C# Web API using ASP.NET Core and Entity Framework, you can use asynchronous streaming. This approach allows you to send chunks of data as they're being processed, rather than waiting for the entire query to complete.
1. Modify Your Method to Return IAsyncEnumerable
First, modify your repository or service method to return IAsyncEnumerable. This way, you'll be able to yield results as they become available.
2. Create the Controller Method
Next, create a controller method that utilizes the IAsyncEnumerable and ASP.NET Core's response streaming capabilities.
3. Ensure Proper Configuration
Make sure your Entity Framework context and other services are properly configured for dependency injection in your Startup.cs or Program.cs file.
By using IAsyncEnumerable and streaming the response, you can start sending data to the client as soon as the first item is available, thus improving the perceived response time.
Advanced Optimization Techniques
Batch Processing
Instead of processing each item individually, you can batch-process items to reduce the overhead of context switching and improve throughput.
Optimized Serialization
Instead of using JsonConvert, consider using System.Text.Json, which is faster and more efficient.
Asynchronous Processing with Channels
Use System.Threading.Channels to create a producer-consumer pattern for better control over parallelism and efficient data streaming.
Parallel Processing
Using Parallel.ForEachAsync can be a viable option to improve the performance of your C# Web API. This method helps to run tasks in parallel, effectively utilizing available resources.
When implementing these optimization techniques, always monitor performance and adjust the degree of parallelism to avoid overwhelming your system. Consider factors such as database connection pool limits, memory constraints, and overall system resources.