The Endpoint Responsibility Checklist
What API frameworks all (should) do
What does an API endpoint do?
In our series on Clean API Architecture, we have explained how there are multiple layers involved in responding to an HTTP request. Broadly speaking, there are two kinds of requests — the kind that mutate state by writing to a database (POST/DELETE/PUT), and the kind that merely return state from a database (GET).
(For brevity, when we say POST from now on, we implicitly mean POST/DELETE/PUT).
For both GET and POST endpoints, you must fulfill the following responsibilities:
Endpoint responsibility checklist
- 🔲 RESTful Routing (Which endpoint should we execute based on verb and URL pattern?)
- 🔲 Control Flow (Order our execution, also handle errors)
- 🔲 Logging (Write out request info for debugging and analysis)
- 🔲 Parameter Extraction and Coercion (Read inputs and store in variables)
- 🔲 Syntactic Validation (Are the inputs in the correct format?)
- 🔲 Semantic Validation (Do the inputs align with our business rules?)
- 🔲 Authentication (Which profile is making this request?)
- 🔲 Authorization (Does this profile have access to the requested data?)
- 🔲 Presentation (Format the data into the correct format for clients, if data is being returned)
Write Endpoint Responsibilities Checklist
The write operations we listed earlier each need to update columns in an RDBMS like Mysql. As anyone who has managed RDBMS systems at scale can tell you, they can be unreliable — bad queries can cause slowdowns of other requests, schema changes can lock tables for short or long periods of time, disk slowdowns lead to query slowdowns which lead to thread pool exhaustion.
To prevent potential data loss and reduce the perceived user impact of each of these scenarios, in our architecture, all write operations are queued and performed by a task running on a separate machine (or in a separate cluster). We call these machines DBWriter machines.
Additional responsibilities of write endpoints, over and above what read endpoints are doing, include:
- 🔲 Job Configuration (Creating a hash with the necessary data to perform work in the background)
- 🔲 Job Enqueuing (Writing a message to a queue, like AWS SQS)
- 🔲 Queue Processing (DBWriter processes that pull Job Configs from the queue and process Jobs)
- 🔲 State mutation (Writing to one or more data sources)
- 🔲 Async Relay (Returning write confirmations back to clients via WebSockets or chat cluster)
We’ve identified no fewer than 14 separate responsibilities of an endpoint.
Connecting it back to Clean
In our previous post we discussed the Clean API architecture. We will now explain how and when in our Clean API architecture we address each of the responsibilities in the checklist above.
Clean API for GET requests
Let’s start by visually identifying the execution path of a GET request in our Clean API architecture.
The steps in this execution path can be summarized as:
🔵Receive→🟢Validate (Syntactic)→🔴Validate (Semantic)→🟢Enqueue→🟢Respond
The request is processed on an API server. Note the two stages of validation — one syntactic, one semantic. After validation, the job is enqueued and returns a success code to the client. In case of failure, no job is enqueued and an error code is returned to the client.
Clean API for POST requests
When the API server receives a POST request, it sends that request to a queue, in our case AWS SQS. Later, on a DBWriter server, the execution path is summarized as:
🟢Dequeue→🔴Mutate→🟢Notify
Checklist x Execution Path
Let’s now connect our checklist with the two execution paths for a POST request.
Circles 🟢🔴🔵 come from our execution path; beneath each we identify which parts of our ✅ checklist they address.
Path 1: Request Receipt
On the API server
- 🔵 Receive: Route the request to the correct Controller
- RESTful routing ✅
- Control flow ✅
2. 🟢 Validate: Extract the input parameters, validate their syntax, and authenticate the profile in the Request, using Params and Validators. Return the inputs required to perform the operation at the service layer.
- Parameter Extraction and Coercion ✅
- Authentication ✅
- Syntactic validation ✅
3. 🔴 Validate: Validate the inputs against business rules using a Service
- Semantic Validation ✅
- Authorization ✅
4. 🟢 Enqueue: Configure the JobConfig and enqueue the job.
- Job Configuration ✅
- Job Enqueuing ✅
5. 🟢 Respond: Hand back receipt confirmation, with a 200 OK status code in the Response, that the write operation has been successfully enqueued and the optional JSON data.
- Presentation ✅
Path 2: Request processing
On the DBWriter server
- 🟢 Dequeue: Receive enqueued job with Job or Service
- Queue Processing ✅
2. 🔴Mutate: Change the database via operations defined in the Service
- State mutation ✅
- Logging ✅
3. 🟢 Notify: Send back an asynchronous Socket Message to the client that the operation has succeeded or failed.
- Async Relay ✅
Next up
In the section above we defined a checklist of operations that all endpoints must fulfill. We then connected that checklist to the execution paths suggested by our Clean API architecture. Now we are going to see what this looks like in practice.
More in this series
- A visual history of web API design
- Patterns of web API execution flows
- Railway Oriented Programming
- Clean API Architecture
- Endpoint Responsibility Checklist ← you are here
- Code example: Saving a favorite
Other series you might like
Android Activity Lifecycle considered harmful (2021)
Android process death, unexplainable NullPointerExceptions, and the MVVM lifecycle you need right now
Kotlin in Xcode? Swift in Android Studio? (2020)
A series on using Clean + MVVM for consistent architecture on iOS & Android
About the author
Eric Silverberg is CEO of Perry Street Software, publisher of the LGBTQ+ dating apps SCRUFF and Jack’d, with more than 20M members worldwide.