Requirements & API: Design
This chapter explores the structure and API design specifics of HTTP API and its definition with the OpenAPI specification. Then, we distinguish a traditional code-first, design-first, and fresh-new AI approach to API development.
The Structure
The Contract is an essential part of the API design, which relies on a protocol and architecture style.
For instance, HTTP-based API design differs from its SOAP counterpart. Even within a single protocol, the design of REST(ful) and GraphQL is different. They are based on HTTP, but how they are designed is driven by corresponding architecture styles.
Note: As a Product person (product manager, product owner, business analyst), you don't necessarily need to dive into the protocol and know the specifics. The previous chapters (Definition and Analysis) would be sufficient for you.
But if you work on an API-driven product and your organization uses an API design-first approach, that will be helpful to know more.
Your organization is likely taking one or two API architecture approaches. You need to know the rules, but again, the specifics will come as soon as you gain experience working with them.
HTTP example
Let's see how a simple API endpoint to return Customer details looks with raw HTTP:
GET /api/customer/{customerId} HTTP/1.1
Host: example.com
Authorization: Bearer <access_token>
Content-Type: application/json
GET
is an HTTP method to retrieve the information./api/customer/{customerId}
is part of the URL (aka path) required to call an endpoint{customerId}
is a placeholder for an object identifier (aka path parameter)
Host
,Authorization
, andContent-Type
are headers that deliver request metadata about the destination, access, and content type for a server to consume.
As a response, the server returns a JSON response body with metadata (HTTP status code):
200 OK
{
"customerId": "12345",
"internalId": "913ec1e3-4952-31a6-a24d-9ff71794ae40",
"firstName": "John",
"lastName": "Doe",
"email": "johndoe@example.com",
"phoneNumber": "+1-555-123-4567",
"address": {
"addressLine": "123 Main St",
"city": "Anytown",
"state": "CA",
"postalCode": "90210",
"country": "US"
},
"accountStatus": "Active",
"createdDate": "2023-02-15T12:34:56Z"
}
If you want to learn more about HTTP, then I suggest reading:
From a structural perspective, we now have:
- request data
- request metadata
- response data
- response metadata
However, we lack overall context about that API endpoint and the use cases it covers. We also don't know the exact meaning of its data attributes.
If the demographic data we get in the response body is basic and quite evident, we can only assume what internalId
implies and how it differs from customerId
. Also, we don't know the specifics of accountStatus
. What statuses apply to a Customer, and what do they mean?
OpenAPI example
HTTP can't answer such questions, but the OpenAPI specification (OAS) does. It provides additional information layers over HTTP, comprehensively defining how API works.
Let's see our example done with OAS:
openapi: 3.1.0
info:
title: Legacy CRM API
description: API to retrieve data from Legacy CRM (READ ONLY)
version: 1.0.0
servers:
- url: https://example.com/api
paths:
/customer/{customerId}:
get:
summary: Retrieve Customer details
description: Retrieves basic information about a customer by their customer ID
parameters:
- name: customerId
in: path
required: true
description: The external identifier assigned to the customer
schema:
type: string
responses:
'200':
description: Customer details retrieved successfully
content:
application/json:
schema:
type: object
properties:
customerId:
type: string
description: The external identifier assigned to the customer
examples:
- "12345"
internalId:
type: string
format: uuid
description: The internal identifier autogenerated by the system
examples:
- "913ec1e3-4952-31a6-a24d-9ff71794ae40"
firstName:
type: string
description: The customer's first name
examples:
- "John"
lastName:
type: string
description: The customer's last name
examples:
- "Doe"
email:
type: string
format: email
description: The customer's email address
examples:
- "johndoe@example.com"
phoneNumber:
type: string
description: The customer's phone number in E.164 format
examples:
- "+1-555-123-4567"
address:
type: object
description: The customer's address details
properties:
addressLine:
type: string
description: The first line of the customer's address
examples:
- "123 Main St"
city:
type: string
description: The city where the customer resides
examples:
- "Anytown"
state:
type: string
description: The state or region of the customer's address
examples:
- "CA"
postalCode:
type: string
description: The postal or ZIP code of the customer's address
examples:
- "90210"
country:
type: string
description: The ISO 3166-1 alpha-2 country code of the customer's address
examples:
- "US"
accountStatus:
type: string
description: The current status of the customer's account
enum:
- "Active"
- "Inactive"
- "Suspended"
- "Closed"
examples:
- "Active"
createdDate:
type: string
format: date-time
description: The date and time when the customer account was created, in ISO 8601 format
examples:
- "2023-02-15T12:34:56Z"
'401':
description: Unauthorized - invalid or missing access token
content:
application/json:
schema:
type: object
properties:
error:
type: string
examples:
- "Unauthorized access"
'404':
description: Customer not found
content:
application/json:
schema:
type: object
properties:
error:
type: string
examples:
- "Customer not found"
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
It looks vast for one API endpoint, right? I will not cover all OAS components in that definition. Instead, if you are unfamiliar with that format, you can copy-paste it to the Swagger Editor (its newer edition with 3.1 support) and look into the visualization.
If you want to learn more about OpenAPI, I suggest:
Let's quickly check what we've got here compared with the raw HTTP:
- We know some context about that API definition, as it comes from a Legacy CRM system now used for read-only operations. So we can utilize that API to get historical customer data.
- We know more about the data structure. We can differentiate
customerId
frominternalId
, see the format specifics ofphoneNumber
andcountry
, and understand whataccountStatus
implies. - We know which errors to expect from a Server in particular cases to handle them on a Client side.
Layers of the Design structure
We still have the request/response data and metadata we saw in the HTTP example now extended with documentation and context details:
This is how that design structure can be visualized by adding documentation and context to the request/response data and metadata.
The described OAS example could be done in many ways, from another path to different naming of response body attributes. I am not even talking about moving the response body to the Component object. In the production, that schema might look very different.
There are two major problems in software engineering: naming and caching. Naming greatly concerns API design. It is easy to deal with a dozen APIs and keep the structure consistent and understandable. But when there are hundreds or even thousands of APIs organization-wide, there must be an explicit set of rules for naming and managing things. That is an entire discipline called API Governance.
So, when you proceed with API definition, you better have guidelines provided by Software architects that represent the protocols, architecture approaches, and background with practices applied in the organization.
Development approaches
There are a few ways to develop API. Those ways impact how we proceed with the design.
In the code-first approach, a business analyst works with stakeholders to write down API-specific or general functional and quality requirements in a traditional format (e.g., user story). A developer usually combines the design phase with implementation, describing an API endpoint in source code.
Of course, that is oversimplified. After the implementation, an OpenAPI file can be generated based on the source code annotations for documentation purposes.
The API design-first approach means a business analyst works with stakeholders to create a formal machine-readable API definition (e.g., OpenAPI). Someone from the technical side may assist and guide the process. Code-generation tools can be used to take OpenAPI as input and produce source code. A developer is likely to be involved in finalizing the development.
In the end, we can compare API definitions prior to the implementation and the one doc-generated to check for differences.
I previously wrote about the API design-first approach, so check it out for more information.
AI also introduced a new perspective. It can create an OpenAPI definition from human input or generate code directly. However, you might still require some skills to adjust the final result.
It is realistic to be able to integrate with API using a chatbot or design and expose a new API with it. That does not come without some trade-offs, though. Whether the AI approach will take the others, we will see. We may discuss that topic in detail on different occasions.