A "good" API design envelops a lot of principles. This article will walk you through an approach to API design following the domain-event design pattern taught by Vaughn Vernon in his IDDD workshop.

Define Events


First, enumerate all the events that the domain would raise. For example:

  • StudentAdmitted
  • OrderReceived
  • AddressChanged
  • ClassAdded

Typically, events contain a noun coupled with a past intransitive verb phrase.

Note: To help you get a feel of naming events, search through a list of existing events here.

Define Commands


After you've listed all the domain's events, list the commands that would produce the events. For example:

  • admitStudent
  • placeOrder
  • updateAddress
  • enrollInClass

According to the principles in Domain Driven Design, these commands should not be "anemic". Name your commands according to real business processes or actions that would be expressed by your business domain experts. Martin Fowler describes more on an Anemic Domain Model here.

Define Entities


After you've listed the possible events and described the commands that produce them, identify the who or what behind the commands. Ask yourself, 'Who invokes the commands?', or, 'What is encapsulating the command?'

Possible answers in this step, associated with our previous examples, might be:

  • a student who is admitted to the University
  • a customer who places an order
  • a shipment on which an address is updated
  • a student who enrolls in a class

The "University" in this case represents the entity that is providing the service and will be represented by the host name: byu.edu.

From these descriptions, you can identify what Eric Evans describes in his book on Domain Driven Driven as "entities". Entities are objects that are usually tracked over time or lifecycle and are not identified by property values alone. Some objects are described by Eric Evans to be "value objects". Value objects are identified by their properties or computed value. They are usually immutable and are neither tracked over time nor persisted for the business process lifecycle.

From our examples, we will extract the following entities:

  • Student
  • Class
  • Customer
  • Order
  • Shipment

We can also identify one value object:

  • Address

 

Create RESTful Resources and Methods


Overview of REST

Roy Fielding was the creator of the REST architecture. He wrote:

“REST is defined by four interface constraints: identification of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state.”

Leonard Richardson describes levels of implementation maturity of the REST architecture in his "Richardson Maturity Model" (RMM).To read a summary of the RMM written by Martin Fowler, click here.

In this step we will follow the RMM as we use the entities, value objects and commands to create URL resources and HTTP methods that make sense semantically and align with the HTTP specification.

URL Resource Design

The entities/value objects that we identified above will become the URL resources and/or part of the resource representation. To help associate resources to our commands, we can create a table with the following columns: "Command," "URL fragment," and "HTTP method." List the commands in the "Command" column. In the "URL fragment" column, express the URL in the plural form of the entity. Then, considering how the command is acting on the resource you've defined, choose the HTTP method that best communicates the manipulation of the resource.

 

Here is a summary of the most commonly-used HTTP methods and their implied meaning:

HTTP Method Common meaning/usage
GET Retrieve the resource or resource collection
POST Create a new resource or resource collection
PUT Update the resource or make the resource look exactly as provided (idempotent)
DELETE Delete the resource

 

There is some discussion on when to use POST versus PUT. The University API Standard specifies that POST should be used to retrieve information that you do not currently have. For example, if you create a resource by entering all of its properties excepting the system assigned identifier, then you should use POST to create the resource and receive the identifier in the response. Contrarily, if you are creating a resource or updating a resource and you hold all the information required in the request, you should use PUT.

 

The following table shows our URL resource creation and HTTP method assignments:

Command URL fragment (/entity_plural/{id} HTTP Method
admitStudent /students POST
placeOrder /orders POST
updateAddress    /shipments/{id} PUT
enrollInClass /students/{id}/enrolledClasses PUT

 

The first command, "admitStudent," was mapped to the "/students" resource collection with a "POST" method. The business logic determined that when a student is admitted is the time they actually become a student. We would perform a "POST" request on the "/students" resource collection and receive a "student" representation back, with some identifier that the system has allocated to that new student.

 

The second command, "placeOrder," was mapped to the "/orders" resource collection with a "POST" method. The business logic determined that when an order is placed is when an order is created. We would perform a "POST" request on the "/orders" resource and receive a "order" representation back, with some identifier that the system has allocated to that new order.

 

The third command, "updateAddress," was mapped to a specific shipment resource (designated by "/{id}" [a placeholder for the specific identifier]) under the "/shipments" resource collection. The business logic determined that "updateAddress" entailed updating the value object or property "address" in the shipment representation, therefore requiring the use of the "PUT" method. Because we do not need to receive any information back from the request, and because we already have all of the information necessary to update the resource, we use "PUT" instead of "POST."

 

The fourth command, "enrollInClass," was mapped to the "/students" resource collection, under a specific student "{id}", and under another resource collection "/enrolledClasses". The business logic determined that there were actually two entities involved in this command: student and class. We could decide to represent the intersection of these two entities with a URL resource named "enrolledClasses". Because you would have to know the specific class in which to enroll the student, we would use the "PUT" method again to create a specific "enrolledClasses" resource in the collection.

 

Corollary: Resource-names don't follow our Data Model - on purpose

The intent of the API is to model resources (objects) and not relational database tables. These resources will not only combine data from multiple relational tables but also most likely multiple enterprise systems. For example, a student resource could potentially contain data derived from the AIM, Student Financials, HR, and PRO systems. This use of the data represented in many underlying systems should not be directly exposed and/or correlated to such by the API consumer.

 

Create Representations of Resources


Representations

Now that you've identified the URL resource path and HTTP methods, you need to design the properties of each resource. The aggregated properties of a resource combine to help define the representation of a resource. The representation is provided as the response to retrieval GET requests or as input to PUT and POST requests.

 

Here are representations of resources from our examples:

Student:

/students/{id}

{
  "id": "mdh11",
  "name": "Matthew Hailstone",
  "email_address": "mdh11@byu.edu",
  "phone": "555-555-5555"
}

 

Class:

/classes/{id}

{
  "id": "math110",
  "name": "Mathematics 110"
}
 

Customer:

/customers/{id}

{
  "id": "mdh11",
  "name": "Matthew Hailstone",
  "email_address": "mdh11@byu.edu",
  "phone": "555-555-5555"
}
 

Order:

/orders/{id}

{
  "id": "11111",
  "customer_id": "mdh11"
  "item_id": "111"
}
 

Shipment:

/shipments/{id}

{
  "id": "1111",
  "address": "11 North Brigham Blvd, Provo, UT 84602"
}

 

Collection of Resources

/shipments
{
  "metadata": {
    "collection_size": 1,
    "page_start": 1,
    "page_end": 1,
    "page_size": 50,
    "default_page_size": 50,
    "max_page_size": 1000
  },
  "values": [
    {
      "id": "1111",
      "address": "11 North Brigham Blvd, Provo, UT 84602"
    }
  ]
}

 

HATEOAS (Hypermedia As The Engine Of Application State)

The last constraint that Roy Fielding describes in his definition of REST is "hypermedia as the engine of application state", or HATEOAS (pronounced 'hay-tee-os'). So, what is hypermedia? And how should it be used as the "engine" of driving the transitions in application state?

Mike Amundsen, the creator of the Collection+JSON hypermedia solution, said this about hypermedia:

"The creation of the Web was heavily influenced by the notion of hypermedia and the ability to link related material and easily follow these links in real time."

Linking data through hypermedia is essentially the power behind the World Wide Web and how we interact with many websites every day. When we click on a link, we are taken to a more detailed or intended representation of that link. So, how is this related to APIs? Mike states the following:

"Media types that exhibit native hypermedia controls can be used by client applications to control the flow of the application itself by activating one or more of these hypermedia elements. In this way, hypermedia types become, as Fielding stated, 'the engine of application state'."

Representations which also include linking properties that are associated with possible state transitions and/or possible actions for the resource in its current state can be defined under a media type definition. The media type can trigger a client application to be able to determine the expected result, and also by inspecting the linking properties inside the representation, the potential actions that it will be able to take from that HTTP response under that media type.

Markus Lanthaler, the creator of the JSON-LD+Hydra hypermedia solution explains the responsibility of the client application when employing hypermedia solutions:

Instead of relying on upfront agreement of all aspects of interaction, parts of the contract can be communicated or negotiated at runtime. Furthermore, instead of relying on implicit state control-flows ..., all communication is stateless, meaning that each request from the client to the server must contain all the information necessary for the server to understand the request; a client cannot take advantage of any stored context on the server as the server does not keep track of individual client sessions. The session state is kept entirely on the client.

Hypermedia solutions provide the abstraction that client applications gain to simplify their interactions with the server and focus on tracking the state transitions reported by servers. Clients, then, need only to interpret the representations of resources that include simple attribute properties coupled with hypermedia links. These links that are inside the resource representations are commonly called "HATEOAS" links.

 

Our definition and use of a HATEOAS link includes the following properties:

Property Description
rel The rel contains the name of the HATEOAS link or "self" as a special name for the link that you would invoke to get back the resource that was just returned. The value should be in the form of <resource-name>__<action>.
href

The href contains the URL used to perform the HATEOAS link action (this may also be a templated URL according to RFC 6570).

method

The method contains the HTTP method used when invoking the URL in the href property.

Using state transitions and commands to derive HATEOAS link names

In order to implement the commands and state transitions we have just created, we will need to put them in the form of HATEOAS links. To create HATEOAS links, first order all the events that you have identified sequentially according to when each event should happen in relation to each other. Then, either use the command identified previously that would initiate the state transition, or create a name to represent the transition from one state to another. The command or name you have will be the name of the HATEOAS link property under the "links" object in the resource representation.

Command URL fragment (/<entity_plural>/{id} HTTP Method
admitStudent /students POST
placeOrder /orders POST
updateAddress /shipments/{id} PUT
enrollInClass /students/{id}/enrolledClasses PUT

 

Here is an example:

/shipments/{id}
{
  "links": {
    "shipments__updateAddress": {
      "rel":"shipments__updateAddress",
      "href":"https://api.byu.edu/domains/shipments/{id}",
      "method":"PUT"
    }
  },
  "id": "1111",
  "address": "11 North Brigham Blvd, Provo, UT 84602"
}

 

Implement the University API Standard


BYU has written a University API Standard, to which all BYU-related APIs are expected to comply. To learn how to get your API in compliance with the University API standard, click here.

 

Use Standard Data Elements


To facilitate information governance, BYU has created an ever-expanding list of standard data elements. Each standard data element is connected to a business definition, which allows it to be properly defined in data sharing agreements. In order for your API to return the intended data, you will need to make sure that the URL fragments within your API match the standard data elements defined here.

A good API should be able to be diagrammed. Graphically representing the API enables you, as well as the consumer, to see the overall picture. For an example of a good API diagram, click here

Create Swagger Definition


Using all the previous constructs, we can now build a Swagger 2.0 document.

Here is an example of a Swagger document for an API that would implement the students resource above:

{
    "swagger": "2.0",
    "info": {
        "version": "v1",
        "title": "Students",
        "license": {
            "name": "Apache-2.0"
        }
    },
    "host": "api.byu.edu",
    "basePath": "/domains/students",
    "schemes": [
        "https"
    ],
    "consumes": [
        "application/json"
    ],
    "produces": [
        "application/json"
    ],
    "paths": {
        "/": {
            "post": {
                "summary": "Admit student",
                "operationId": "admitStudent",
                "parameters": [
                    {
                        "name": "body",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/admit_student"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Null response",
                        "schema": {
                            "$ref": "#/definitions/student"
                        },
                        "examples": {
                            "application/json": {
                                "id": "mdh11",
                                "name": "Matthew Hailstone",
                                "email_address": "mdh11@byu.edu",
                                "phone": "555-555-5555"
                            }
                        }
                    },
                    "401": {
                        "description": "Not authorized to POST data",
                        "schema": {
                            "type": "string"
                        }
                    },
                    "403": {
                        "description": "Forbidden to POST data",
                        "schema": {
                            "type": "string"
                        }
                    }
                }
            }
        },
        "/{id}/enrolled_classes": {
            "put": {
                "summary": "Enroll student in class",
                "operationId": "enrollInClass",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "The student id",
                        "type": "integer"
                    },
                    {
                        "name": "body",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/new_enrollment"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The student's enrollment of the class.",
                        "schema": {
                            "$ref": "#/definitions/enrollment"
                        },
                        "examples": {
                            "application/json": {
                            }
                        }
                    },
                    "404": {
                        "description": "Requested enrollment not found.",
                        "schema": {
                            "type": "string"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "admit_student": {
            "properties": {
                "name": {
                    "type": "string"
                },
                "email_address": {
                    "type": "string"
                },
                "phone": {
                    "type": "string"
                }
            }
        },
        "student": {
            "properties": {
                "id": {
                    "type": "string"
                },
                "name": {
                    "type": "string"
                },
                "email_address": {
                    "type": "string"
                },
                "phone": {
                    "type": "string"
                }
            }
        },
        "new_enrollment": {
            "properties": {
                "class": {
                    "type": "string"
                }
            }
        },
        "enrollment": {
            "properties": {
                "id": {
                    "type": "string"
                },
                "class": {
                    "type": "string"
                }
            }
        }
    }
}