diff --git a/_posts/2019-09-26-73.md b/_posts/2019-09-26-73.md new file mode 100644 index 000000000..485ae7681 --- /dev/null +++ b/_posts/2019-09-26-73.md @@ -0,0 +1,223 @@ +--- +layout: post +comments: true +title: "#73 - Experimental machine-readable API Design Guide with OpenAPI spec" +date: 2019-09-25 03:32:44 +image: '/assets/img/day73/blog.png' +description: "Configuration for the API design tool" +handles: "" +tags: +- DX +- 100DaysDX +- OpenAPI spec +- Swaggerhub +- API Design Guide +- machine-readable +- configuration +- Design tool + +categories: +- DX +- 100DaysDX +twitter_text: "#73 - Experimental machine-readable API Design Guide with OpenAPI spec" +--- + +I started to run out of (theoretical) ideas for the posts so I had to start doing some experimentations. I wrote couple of days ago about [the idea of machine-readable API Design Guide](https://100daysdx.com/71/). I wanted to take the idea further. + +You might remember the times when http APIs were documented in PDF files which you were given in response in email. Then came the idea of online API documentation and eventually Swagger, RAML and so on. We can now generate somewhat ok-ish documentation from machine-readable spec files. + +## API Desing guides are still only for humans + +What about API Design Guides? Those are not always PDF files, but some of them are just created with Gitbook or other documentation management solutions and frameworks. But this those are not machine-readable (without scraping and crawling). What if the API Design Guide would be similar to API spec files? + +I wanted to explore the idea. I'm lazy and I wanted to do the experimentation in matter of hours. I decided to try OpenAPI spec and use that in defining API Design Guide. Probably a goofy idea, but something I can do without defining yet another spec format. Result might not be optimal and for sure not ready for "production". But it probably is enough to test the idea. + +## Why? + +I'm responsible among other things for the **design of 8+ APIs in our platform**. The amount is getting bigger and bigger. I'm still lacking a good API design editor, but I can survive with the spec driven solutions such as Swagger editor for now. I'm also responsible for the **API Design Guide** we have for the APIs. That is created and maintained as gitbook now. In the API design work I need to check things from the Design Guide occasionally. + +Often the things are the same, some small part of the API desing such as parameter name needs to be checked. I find that far from optimal. I would like the Design tool to know the content of our API Design Guide. + +
In my dreams the API Design Guide is machine-readable and can be used as configuration for the API design tool.
+ +Thus I wanted the explore the idea a little further. The result of the experiment is visible at [https://app.swaggerhub.com/apis-docs/kyyberi/API-Design-Guide/1.0.1](https://app.swaggerhub.com/apis-docs/kyyberi/API-Design-Guide/1.0.1) and as [yaml file](/assets/img/day73/openapi.yaml) + + +## Some possible benefits + +If the Design Guide would be machine-readable just like API spec files are now: + +- We could create automation to validate the uniformity of API family more easily +- No need to jump from one tool to another while designing APIs (to check little things) +- Easier to version and fork for others to use as well (open source thinking). +- could be used as "our platform configuration" for all APIs in a API design tool. + +## Take the good old Swaggerhub in use + +I created empty API spec in Swaggerhub. I could have used local swagger or online swagger editor here too, but since I'm already using swaggerhub in Platform of Trust API designs I decided to use same space for this small experiment. + +I started from the obvious aspects of API Design Guide: +- Error handling +- Pagination +- Sorting +- Rate-limiting + +```yaml +openapi: 3.0.0 +info: + version: "1.0.1" + title: Machine readable API Design Guide experiment + description: >- + This is an experiment to see how well API Design Guide could be created with + OAS only. +paths: + /errors: + get: + summary: Error handling for GET methods + description: >- + Because we are using HTTP methods, we should use HTTP status codes. + Although a challenge here is to select a distinct slice of these codes, + and then depend on response data to detail any response errors. Keeping + a small set of codes helps you consume and handle errors consistently. + tags: + - Error handling + responses: + '400': + description: 'for when the requested information is incomplete or malformed.' + '401': + description: 'for when an access token isn’t provided, or is invalid.' + post: + summary: Error handling for POST methods + description: >- + Because we are using HTTP methods, we should use HTTP status codes. + Although a challenge here is to select a distinct slice of these codes, + and then depend on response data to detail any response errors. Keeping + a small set of codes helps you consume and handle errors consistently. + tags: + - Error handling + responses: + '400': + description: 'for when the requested information is incomplete or malformed.' + '401': + description: 'for when an access token isn’t provided, or is invalid.' + '403': + description: 'for when an access token is valid, but requires more privileges.' +``` + + +## Overview of the API Design Guide + +The all closed view of the result is like below. I used tags to group "specs for endpoints". Tags like "Error handling" and "Pagination of lists". That way I get the headers for the folding content. + +{{site.name}} + +## Error handling + +When you click the "Error handling" rules part, it opens like below. I defined error handling with HTTP status codes to each method separately. Does it make sense is debatable or should all the error handling be all together. In this experiment those are separate since I figured it would be needed to have dedicated error codes for different methods. And besides that is more logical to me. + +{{site.name}} + +If you open for example the GET method error handling rules, you'll see familiar codes: + +{{site.name}} + +## Pagination + +Pagination is almost in every good API which returns some kind of lists. Returning all the results in one batch would make the API slow and consume bandwidth while the consumer of the data wants to should just part of the results too. I added the two most commong parameters "offset" and "limit". I also added placeholders for the list objects in responses. To find a way to list things in similar fashion in all APIs might be hard due to distinct data models, but it might be useful in general to have it there. + +I added the default value for the list size (50). This could used in API design tool easily and the API designer would not need to check from separate document what was the default value. + +```yaml +/pagination: + get: + summary: How to paginate + description: >- + Returning huge amount of list entries in response results to bad Developer eXeprience (long response time for example). Thus you should paginate long lists to smaller chunks. +

+
  • Default value for list size is 50.
  • + tags: + - Pagination of lists + parameters: + - name: offset + in: query + description: Offset value to start from in providing the list items + required: false + schema: + type: integer + format: int32 + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + default: 50 + responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/List' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +``` + +And as rendered result it looks like: +{{site.name}} + + +## Rate-limiting + +With rate-limiting I tried the OAS headers, which seemed to work pretty nicely out of the box + +```yaml + /response.headers: + get: + summary: How to notify rate-limiting information in headers + description: >- + Headers for rate-limiting. + +
    +        X-RateLimit-Limit: 60
    + X-RateLimit-Remaining: 25
    + X-RateLimit-Reset: 1546841865
    +
    + tags: + - Rate limiting + responses: + '200': + description: OK + headers: + X-RateLimit-Limit: + schema: + type: integer + description: Request limit per hour. + X-RateLimit-Remaining: + schema: + type: integer + description: The number of requests left for the time window. + X-RateLimit-Reset: + schema: + type: string + format: date-time + description: The UTC date/time at which the current rate limit window resets. + +``` +{{site.name}} + + +## Simple YAML reader and renderer for API Design Guide spec + +Using the default rendering of Swagger resulted to pretty ok result as such. At this point I almost started to write own renderer for the API Design Guide spec file to make it look like I want, but then I ran out of time. Perhaps someone else picks the task and continues with the idea. The more mature solution most likely would need some extensions to the default OAS spec. + +The result of the experiment is visible at [https://app.swaggerhub.com/apis-docs/kyyberi/API-Design-Guide/1.0.1](https://app.swaggerhub.com/apis-docs/kyyberi/API-Design-Guide/1.0.1) and as [yaml file](/assets/img/day73/openapi.yaml) + +If you would like to continue the experiment let me know. Our platform would be your first "customer" and work with you on this. \ No newline at end of file diff --git a/assets/img/day73/blog.png b/assets/img/day73/blog.png new file mode 100644 index 000000000..c3a8e70b1 Binary files /dev/null and b/assets/img/day73/blog.png differ diff --git a/assets/img/day73/errors.png b/assets/img/day73/errors.png new file mode 100644 index 000000000..4a30aa3d8 Binary files /dev/null and b/assets/img/day73/errors.png differ diff --git a/assets/img/day73/openapi.yaml b/assets/img/day73/openapi.yaml new file mode 100755 index 000000000..bcd5779e0 --- /dev/null +++ b/assets/img/day73/openapi.yaml @@ -0,0 +1,299 @@ +openapi: 3.0.0 +info: + title: Machine readable API Design Guide experiment + description: This is an experiment to see how well API Design Guide could be created + with OAS only. + version: 1.0.1 +servers: +- url: / +paths: + /errors: + get: + tags: + - Error handling + summary: Error handling for GET methods + description: Because we are using HTTP methods, we should use HTTP status codes. + Although a challenge here is to select a distinct slice of these codes, and + then depend on response data to detail any response errors. Keeping a small + set of codes helps you consume and handle errors consistently. + responses: + 400: + description: for when the requested information is incomplete or malformed. + 401: + description: for when an access token isn’t provided, or is invalid. + 403: + description: for when an access token is valid, but requires more privileges. + 404: + description: for when everything is okay, but the resource doesn’t exist. + 409: + description: for when a conflict of data exists, even with valid information. + 422: + description: for when the requested information is okay, but invalid. + put: + tags: + - Error handling + summary: Error handling for PUT methods + description: Because we are using HTTP methods, we should use HTTP status codes. + Although a challenge here is to select a distinct slice of these codes, and + then depend on response data to detail any response errors. Keeping a small + set of codes helps you consume and handle errors consistently. + responses: + 400: + description: for when the requested information is incomplete or malformed. + 401: + description: for when an access token isn’t provided, or is invalid. + 403: + description: for when an access token is valid, but requires more privileges. + 404: + description: for when everything is okay, but the resource doesn’t exist. + 409: + description: for when a conflict of data exists, even with valid information. + 422: + description: for when the requested information is okay, but invalid. + post: + tags: + - Error handling + summary: Error handling for POST methods + description: Because we are using HTTP methods, we should use HTTP status codes. + Although a challenge here is to select a distinct slice of these codes, and + then depend on response data to detail any response errors. Keeping a small + set of codes helps you consume and handle errors consistently. + responses: + 400: + description: for when the requested information is incomplete or malformed. + 401: + description: for when an access token isn’t provided, or is invalid. + 403: + description: for when an access token is valid, but requires more privileges. + 404: + description: for when everything is okay, but the resource doesn’t exist. + 409: + description: for when a conflict of data exists, even with valid information. + 422: + description: for when the requested information is okay, but invalid. + delete: + tags: + - Error handling + summary: Error handling for DELETE methods + description: Because we are using HTTP methods, we should use HTTP status codes. + Although a challenge here is to select a distinct slice of these codes, and + then depend on response data to detail any response errors. Keeping a small + set of codes helps you consume and handle errors consistently. + responses: + 400: + description: for when the requested information is incomplete or malformed. + 401: + description: for when an access token isn’t provided, or is invalid. + 403: + description: for when an access token is valid, but requires more privileges. + 404: + description: for when everything is okay, but the resource doesn’t exist. + 409: + description: for when a conflict of data exists, even with valid information. + 422: + description: for when the requested information is okay, but invalid. + options: + tags: + - Error handling + summary: Error handling for OPTIONS methods + description: Because we are using HTTP methods, we should use HTTP status codes. + Although a challenge here is to select a distinct slice of these codes, and + then depend on response data to detail any response errors. Keeping a small + set of codes helps you consume and handle errors consistently. + responses: + 400: + description: for when the requested information is incomplete or malformed. + 401: + description: for when an access token isn’t provided, or is invalid. + 403: + description: for when an access token is valid, but requires more privileges. + 404: + description: for when everything is okay, but the resource doesn’t exist. + 409: + description: for when a conflict of data exists, even with valid information. + 422: + description: for when the requested information is okay, but invalid. + patch: + tags: + - Error handling + summary: Error handling for PATCH methods + description: Because we are using HTTP methods, we should use HTTP status codes. + Although a challenge here is to select a distinct slice of these codes, and + then depend on response data to detail any response errors. Keeping a small + set of codes helps you consume and handle errors consistently. + responses: + 400: + description: for when the requested information is incomplete or malformed. + 401: + description: for when an access token isn’t provided, or is invalid. + 403: + description: for when an access token is valid, but requires more privileges. + 404: + description: for when everything is okay, but the resource doesn’t exist. + 409: + description: for when a conflict of data exists, even with valid information. + 422: + description: for when the requested information is okay, but invalid. + /pagination: + get: + tags: + - Pagination of lists + summary: How to paginate + description: 'Returning huge amount of list entries in response results to bad + Developer eXeprience (long response time for example). Thus you should paginate + long lists to smaller chunks.

  • Default value for list size + is 50.
  • ' + parameters: + - name: offset + in: query + description: Offset value to start from in providing the list items + required: false + style: form + explode: true + schema: + type: integer + format: int32 + - name: limit + in: query + description: maximum number of results to return + required: false + style: form + explode: true + schema: + type: integer + format: int32 + default: 50 + responses: + 200: + description: pet response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/List' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /sorting: + get: + tags: + - Sorting of lists + summary: How to sort responses + description: 'Sorting list of objects is in all GET endpoints if it returns + a list. ' + parameters: + - name: fields + in: query + description: comma-separated list of items which defines the sorting order. + required: false + style: form + explode: true + schema: + type: string + format: text + responses: + 200: + description: pet response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/List' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /request.headers: + get: + tags: + - Rate limiting + summary: How to notify rate-limiting information in headers + description: "Headers for rate-limiting. \n
     X-RateLimit-Limit: 60
    \ + \ X-RateLimit-Remaining: 25
    X-RateLimit-Reset: 1546841865
    " + responses: + 200: + description: OK + headers: + X-RateLimit-Limit: + description: Request limit per hour. + style: simple + explode: false + schema: + type: integer + X-RateLimit-Remaining: + description: The number of requests left for the time window. + style: simple + explode: false + schema: + type: integer + X-RateLimit-Reset: + description: The UTC date/time at which the current rate limit window + resets. + style: simple + explode: false + schema: + type: string + format: date-time +components: + schemas: + List: + allOf: + - $ref: '#/components/schemas/Item' + - required: + - id + type: object + properties: + id: + type: integer + format: int64 + Item: + required: + - name + type: object + properties: + name: + type: string + tag: + type: string + Error: + required: + - code + - message + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + securitySchemes: + BasicAuth: + type: http + scheme: basic + BearerAuth: + type: http + scheme: bearer + ApiKeyAuth: + type: apiKey + name: X-API-Key + in: header + OpenID: + type: openIdConnect + openIdConnectUrl: https://example.com/.well-known/openid-configuration + OAuth2: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: https://example.com/oauth/authorize + tokenUrl: https://example.com/oauth/token + scopes: + read: Grants read access + write: Grants write access + admin: Grants access to admin operations diff --git a/assets/img/day73/opened.png b/assets/img/day73/opened.png new file mode 100644 index 000000000..a90fa7628 Binary files /dev/null and b/assets/img/day73/opened.png differ diff --git a/assets/img/day73/pagination.png b/assets/img/day73/pagination.png new file mode 100644 index 000000000..42b363d09 Binary files /dev/null and b/assets/img/day73/pagination.png differ diff --git a/assets/img/day73/rate.png b/assets/img/day73/rate.png new file mode 100644 index 000000000..8d7b3887c Binary files /dev/null and b/assets/img/day73/rate.png differ diff --git a/assets/img/day73/spec.png b/assets/img/day73/spec.png new file mode 100644 index 000000000..1005d177b Binary files /dev/null and b/assets/img/day73/spec.png differ diff --git a/assets/img/day73/spec2.png b/assets/img/day73/spec2.png new file mode 100644 index 000000000..035c61bc6 Binary files /dev/null and b/assets/img/day73/spec2.png differ