How I Created OpenApi (and why it still sucks)
Owen Rubel - API EXPERT
Original Amazon team (95-98) / Creator of API Chaining(R)
Sounds unbelievable right? But back when I was creating Java plugins to abstract the api communication logic from the business logic (see new api pattern), I showed a future OpenAPI steering committe, Kin Lane, about how to separate I/O State; this is what I called it (and still do) as it separates ALL DATA related to the request/response so that it could be shared with the services.
I first met Kin Lane at a talk he was doing with Tony Tam (creator of Swagger):
As you notice, Emmanual Paraskakis and Kin Lane both are on the steering committee:
I pointed out flaws in their work which caught the eye of Kin Lane.
On May 5, 2015 Kin Lane stated the following after reading my work on API Abstraction and API Chaining:
'?The shit you do is genius level stuff dude.?I had your API chaining stuff open for 2 months in a tab, and still haven't fully grocked everything, but am using it as core basis for the work I'm doing with Swagger, and next gen of mapping. I could use a personal walkthrough.'
He then asked me to write an article for his site going over these concepts.
At this time, OpenApi was still called Swagger 2.0 (and OpenAPI 3 was released until 2017). You can see the differences in the structure form one to the next here:
Post viewing my work, they changed:
In comparison, you can see an example Swagger 2.0 implementation here:
paths:
/users:
post:
summary: Creates a new user.
consumes:
- application/json
parameters:
- in: body
name: user
description: The user to create.
schema:
type: object
required:
- userName
properties:
userName:
type: string
firstName:
type: string
lastName:
type: string
responses:
201:
description: Created
And then in Swagger 3/OpenApi 3, they suddenly changes to have response data in their document and also have abstracted the common schema used for request/response.
paths:
/users:
get:
summary: Gets a list of users.
response:
200:
description: OK
schema:
$ref: '#/definitions/ArrayOfUsers'
401:
$ref: '#/responses/Unauthorized' # <-----
/users/{id}:
get:
summary: Gets a user by ID.
response:
200:
description: OK
schema:
$ref: '#/definitions/User'
401:
$ref: '#/responses/Unauthorized' # <-----
404:
$ref: '#/responses/NotFound' # <-----
# Descriptions of common responses
responses:
NotFound:
description: The specified resource was not found
schema:
$ref: '#/definitions/Error'
Unauthorized:
description: Unauthorized
schema:
$ref: '#/definitions/Error'
definitions:
# Schema for error response body
Error:
type: object
properties:
code:
type: string
message:
type: string
required:
- code
- message
And if you look at my solution at the time, I had already internalized this properly (and even had ROLES associated so you could consume/produce different datasets per endpoint based on ROLE (it is also far less verbose):
"CURRENTSTABLE": "1"
"VERSION": {
"1": {
"DEFAULTACTION":"show",
"URI": {
"list": {
"METHOD":"GET",
"DESCRIPTION":"Show IOState",
"ROLES":["ROLE_ADMIN","ROLE_ARCH"],
"BATCH":["ROLE_ADMIN","ROLE_ARCH"],
"REQUEST": {
"permitAll":[]
},
"RESPONSE": {
"permitAll":["name"]
}
},
Criticism on 'consumes' : I don't require 'consumes' for mime-type as this is function (not data); backend should always check the mime-type for the request and (if possible) automate the response to return data based on requested mime-type. So if someone requests in JSON, it returns JSON. If someone requests in XML, it returns XML, if someone requests in an unsupported mimetype, it throws an error. This is all function... not data. So the sent mime-type is not important to the document.
Criticism on Error codes: Error codes are internationalized in the api backend. By trying to duplicate this data in the document, you would need to internationalize EVERY SINGLE ONE of these error messages. This level of duplication shows that ERROR CODES are FUNCTION, not data and again do not belong in said doc.
Criticism on Headers: the only headers an api backend is REALLY concerned with is when dealing with CORS. And again, this is function and is standardized so does not need to be in the doc as it is a redundancy.
Plus abstraction of variables was added prior to OpenAPI's implementation:
领英推荐
"NAME": "person"
"NETWORKGRP": "public",
"VALUES": {?
"firstName": {
"type": "String",
"description": "",
"mockData": "null_fname",
"constraints": {"order":3,"isNullable":false},
},
"passwordExpired": {
"type": "boolean",
"description": "",
"mockData": "false",
"constraints": {"order":9,"isNullable":false},
},
"accountExpired": {
"type": "boolean",
"description": "",
"mockData": "false",
"constraints": {"order":10,"isNullable":false},
},
"oauthProvider": {
"type": "String",
"description": "",
"mockData": "",
"constraints": {"order":7,"isNullable":true},
},
"username": {
"type": "String",
"description": "",
"mockData": "test",
"constraints": {"isUnique":true,"order":1,"isBlank":false,"isNullable":false},
},
"accountLocked": {
"type": "boolean",
"description": "",
"mockData": "false",
"constraints": {"order":11,"isNullable":false},
},
"password": {
"type": "String",
"description": "",
"mockData": "password",
"constraints": {"order":2,"isBlank":false,"isNullable":false},
},
"lastName": {
"type": "String",
"description": "",
"mockData": "null_lname",
"constraints": {"order":4,"isNullable":false},
},
"oauthId": {
"type": "String",
"description": "",
"mockData": "",
"constraints": {"order":6,"isNullable":true},
},
"enabled": {
"type": "boolean",
"description": "",
"mockData": "true",
"constraints": {"order":12,"isNullable":false},
},
"avatarUrl": {
"type": "String",
"description": "",
"mockData": "",
"constraints": {"order":8,"isNullable":true},
},
"email": {
"type": "String",
"description": "",
"mockData": "[email protected]",
"constraints": {"isUnique":true,"maxSize":100,"order":5,"isEmail":true,"isNullable":false},
},
"id": {
"key": "PKEY",
"type": "Long",
"description": "",
"mockData": "112",
},
"version": {
"type": "Long",
"description": "",
"mockData": "0",
},
},,
NOTE: Keep in mind I already had working implementations in 2015 and people were using my tools. The beginning of my spec looked like this
SpringOne Conference talk
I also talked about these principles (as well as the abstraction requirements) at SpringOne, APIDays, APIWorld and other conferences.
The Netflix API manager at the time stated:
'This fixes everything we are currently having issues with'
So why doesn't OpenAPI give credit where credit is do? It's called Intellectual Property Theft and you can actually report this to the FBI (an office that Kin Lane has personal experience with):
Active working projects prior to OpenAPI
The first real implementation of 'shared IO state' was in the Grails api-toolkit plugin and a more mature version in the BEAPI-Framework.
The api-toolkit was using this methodology in 2014 (well before Kin Lane gave me credit for OpenAPI) so the functionality pre-dates OpenAPI by years.
Issues with Their Implementation
OpenAPI took some of the best parts of what I was doing and the worst parts of Swagger and slammed them together.
For example, I allow an api endpoint to declare various ways of calling them from the different ROLES that are sent in the token:
"show":
"METHOD": "GET",
"DESCRIPTION": "Description for show",
"ROLES": {
"BATCH": ["ROLE_ADMIN"]
},
"REQUEST": {
"permitAll":[],
"ROLE_ADMIN":["id"]
},
"RESPONSE": {
"permitAll": ["id","version","username","email","enabled","accountExpired"]
}
}
So in the above, I can have 'permitAll' for all other ROLES calling this and then I can have ROLE_ADMIN declared so I can send an ID and get back data related to said ID and those not using an ID use their own user info sent from the token to return the data.
This allows for a common cathed object to be manipulated in different ways depending on the requesting ROLE
OpenAPI creates a security hole however by not supporting this (per the lead on the OpenAPI project).
OpenAPIalso adds in unecessary data like 'error codes' and messages; if you are calling this site from different countries, you would normally internationalize error messages in the application... but because of this, OpenAPI requires internationalization in BOTH PLACES (which can cause your data to get out of sync if it is not PERFECTLY up to date.
The difference between IO State and OpenAPI is that the data is properly abstracted away from the application so that the IO State doc IS the central version of truth; OpenAPI merely trys to duplicate the data that is HARDCODED thus making them impossible to sync.
Outcome
Regardless, I have forwarded this on to the FBI and contacted lawyers for prosecution of stealing my tools and ideas without proper compensation.
Checkmate openapi.