【轉】Swagger從入門到精通
[轉自:https://legacy.gitbook.com/book/huangwenchao/swagger/details]
如何編寫基於OpenAPI規範的API文件
[TOC]
前言
編寫目的
本文介紹如何使用Swagger編寫API文件。通過閱讀本文,你可以:
- 瞭解swagger是什麼
- 掌握使用swagger編寫API文件的基本方法
涉及範圍
本文包括對swagger specification(以下譯作”規範“)的介紹,如何使用swaager協議編寫出功能完整、結構清晰的API文件,以及專案實踐中需要注意的問題。
swagger的生態完整,從文件生成、編輯、測試到各種語言的程式碼自動生成,都有很多開源工具支援。本文中不介紹
第1章 簡介
1.1 Swagger
The World's Most Popular Framework for APIs.
Starting January 1st 2016 the Swagger Specification has been donated(捐贈) to the Open API Initiative (OAI) and is the foundation of the OpenAPI Specification.
Swagger(絲襪哥)給人第一印象就是【最(hen)流(niu)行(bai)】,不懂Swagger咱就out了。它的官方網站是http://swagger.io/。
Swagger是一個簡單但功能強大的API表達工具。它具有地球上最大的API工具生態系統,數以千計的開發人員,使用幾乎所有的現代程式語言,都在支援和使用Swagger。使用Swagger生成API,我們可以得到互動式文件,自動生成程式碼的SDK以及API的發現特性等。
現在,Swagger已經幫助包括Apigee, Getty影象, Intuit, LivingSocial, McKesson, 微軟, Morningstar和PayPal等世界知名企業建立起了一套基於RESTful API的完美服務系統。
2.0版本已經發布,Swagger變得更加強大。值得感激的是,Swagger的原始碼100%開源在
1.2 OpenAPI規範
OpenAPI規範是Linux基金會的一個專案,試圖通過定義一種用來描述API格式或API定義的語言,來規範RESTful服務開發過程。OpenAPI規範幫助我們描述一個API的基本資訊,比如:
- 有關該API的一般性描述
- 可用路徑(/資源)
- 在每個路徑上的可用操作(獲取/提交...)
- 每個操作的輸入/輸出格式
目前V2.0版本的OpenAPI規範(也就是SwaggerV2.0規範)已經發布並開源在github上。該文件寫的非常好,結構清晰,方便隨時查閱。關於規範的學習和理解,本文最後還有個彩蛋。
1.3 為啥要使用OpenAPI規範?
- OpenAPI規範這類API定義語言能夠幫助你更簡單、快速的表述API,尤其是在API的設計階段作用特別突出
- 根據OpenAPI規範編寫的二進位制文字檔案,能夠像程式碼一樣用任何VCS工具管理起來
- 一旦編寫完成,API文件可以作為:
- 需求和系統特性描述的根據
- 前後臺查詢、討論、自測的基礎
- 部分或者全部程式碼自動生成的根據
- 其他重要的作用,比如開放平臺開發者的手冊...
1.4 如何編寫API文件?
1.4.1 語言:JSON vs YAML
我們可以選擇使用JSON或者YAML的語言格式來編寫API文件。但是個人建議使用YAML來寫,原因是它更簡單。一圖勝千言,先看用JSON寫的文件:
{
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Simple API",
"description": "A simple API to learn how to write OpenAPI Specification"
},
"schemes": [
"https"
],
"host": "simple.api",
"basePath": "/openapi101",
"paths": {
"/persons": {
"get": {
"summary": "Gets some persons",
"description": "Returns a list containing all persons.",
"responses": {
"200": {
"description": "A list of Person",
"schema": {
"type": "array",
"items": {
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"username": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
再來看看同一份API文件的YAML實現:
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons.
responses:
200:
description: A list of Person
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
對於普通人來說,似乎用YAML更能夠簡化書寫和閱讀。這裡我們並沒有非此即彼的選擇問題,因為:
- 幾乎所用支援OpenAPI規範的工具都支援YAML
- 有很多的工具可以實現YAML-JSON之間的轉換
所以,用自己喜歡的方式書寫即可。(後面的示例文件也都是用YAML來寫的。強烈推薦使用YAML。)
1.4.2 編輯器
編寫API文件,其實我們只是在寫一個簡單的文字檔案。我們可以用任何最普通的文件編輯器來寫。但是為了提高效率,還是建議使用專業的編輯工具。眾多工具中,最好的選擇是Swagger Editor,它能夠提供語法高亮、自動完成、即時預覽等功能,非常強大。
左邊編輯API文件,右邊實時預覽。下面這張動圖展示編輯提升功能:
我們可以使用線上版本來編輯,也可以非常簡單的本地部署,更多細節請參考另一篇文件開源倉庫上的說明。
第2章 從零開始
這一章主要介紹API的基本組成部分,包括提供給API消費者(所有可能訪問API的個體,下簡稱“消費者”)的的不同HTTP請求方法、路徑,請求和訊息體中的引數,以及返回給消費者的不同HTTP狀態及響應訊息體。
2.1 最簡單的例子
我們從一個最簡單(幾乎沒有東西)的API文件開始:
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths: {}
這個文件的內容分成四部分,下面分別來說明。
2.1.1 OpenAPI規範的版本號
首先我們要通過一個swagger
屬性來宣告OpenAPI規範的版本。
swagger: "2.0"
你沒看錯,是swagger
,上面已經介紹了,OpenAPI規範是基於Swagger的,在未來的版本中,這個屬性可能會換成別的。 目前這個屬性的值,暫時只能填寫為2.0
。
2.1.2 API描述資訊
然後我們需要說明一下API文件的相關資訊,比如API文件版本(注意不同於上面的規範版本)、API文件名稱已經可選的描述資訊。
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
2.1.3 API的URL
作為web API,一個很重要的資訊就是用來給消費者使用的根URL
,可以用協議(http或者https)、主機名、根路徑來描述:
schemes:
- https
host: simple.api
basePath: /openapi101
這這個例子中,消費者把https://simple.api/open101
作為根節點來訪問各種API。因為和具體環境有關,不涉及API描述的根本內容,所以這部分資訊是可選的。
2.1.4 API的操作(operation)
這個例子中,我們沒有寫API的操作,用一個YAML的空物件{}
先佔個位置。
2.2 定義一個API操作
如果我們要展示一組使用者資訊,可以這樣描述:
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons.
responses:
200:
description: A list of Person
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
2.2.1 新增一個路徑
(path)
我們新增一個/persons
的路徑
,用來訪問一組使用者資訊:
paths:
/persons:
2.2.2 在路徑中新增一個HTTP方法
在每個路徑
中,我們可以新增任意的HTTP動詞來操作所需要的資源。
比如需要展示一組使用者資訊,我們可以在/persons
路徑中新增get
方法,同時還可以填寫一些簡單的描述資訊(summary)或者說明該方法的一段長篇大論(description)。
get:
summary: Gets some persons
description: Returns a list containing all persons.
2.2.3 定義響應
(response)型別
對於每個方法(或操作),我們都可以在響應
(responses)中新增任意的HTTP狀態碼(比如200 OK 或者 404 Not Found等)。這個例子中我們新增上200
的響應:
responses:
200:
description: A list of Person
2.2.4 定義響應內容
get /persons這個介面返回一組使用者資訊,我們通過響應訊息中的模式
(schema)屬性來描述清楚具體的返回內容。
一組使用者資訊就是一個使用者資訊物件的陣列
(array),每一個數組元素則是一個使用者資訊物件
(object),該物件包含三個string型別的屬性:姓氏
、名字
、使用者名稱
,其中使用者名稱
必須提供(required)。
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
2.3 定義請求引數
(query parameters)
使用者太多,我們不想一股腦全部輸出出來。這個時候,分頁輸出是個不錯的選擇,我們可以通過新增請求引數來實現。
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
#START############################################################################
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
# END ############################################################################
responses:
200:
description: A list of Person
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
2.3.1 在get方法中增加請求引數
首先我們在 get 方法中增加一個引數
屬性:
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
#START############################################################################
parameters:
# END ############################################################################
2.3.2 新增分頁引數
在引數列表中,我們新增兩個名字(name)分別叫做pageSize
和pageNumber
的整型(integer)引數,並作簡單描述:
parameters:
#START############################################################################
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
# END ##############################################################################
responses:
這樣一來,消費者就可以通過 get /persons?pageSize=20&pageNumber=2 來訪問第2頁的使用者資訊(不超過20條)了。
2.4 定義路徑引數
(path parameter)
有時候我們想要根據使用者名稱來查詢使用者資訊,這時我們需要增加一個介面操作,比如可以新增一個類似 /persons/{username} 的操作來獲取使用者資訊。注意,{username} 是在請求路徑中的引數。
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A list of Person
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
#START############################################################################
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
200:
description: A Person
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
404:
description: The Person does not exists.
# END ############################################################################
2.4.1 新增一個 get /persons/{username} 操作
首先我們在 /persons 路徑後面,增加一個 /persons/{username} 的路徑,並定義一個 get (操作)方法。
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
username:
type: string
#START############################################################################
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username
# END ############################################################################
2.4.2 定義路徑引數 username
因為 {username} 是路徑引數,我們需要先像請求引數一樣將它新增到 parameters 屬性中,注意名稱應該同上面大括號( { } ) 裡面的名稱一致。並通過 in
這個屬性,來表示它是一個路徑(path)引數。
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
定義路徑引數時很容易出現的問題就是忘記:required: true
,Swagger的自動完成功能中沒有包含這個屬性定義。 如果沒有寫 require 屬性,預設值是 false,也就是說 username 引數時可選的。可事實上,作為路徑引數,它是必需的。
2.4.3 定義響應訊息
別忘了獲取單個使用者資訊也需要填寫 200 響應訊息,響應訊息體的內容就是之前描述過的使用者資訊(使用者資訊列表中的一個元素):
responses:
200:
description: A Person
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
當然,API的提供者會對 username 進行校驗,如果查無此人,應該返回 404 的異常狀態。所以我們再加上 404 狀態的響應:
404:
description: The Person does not exists.
2.5 定義訊息體引數
(body parameter)
當我們需要新增一個使用者資訊時,我們需要一個能夠提供 post /persons 的API操作。
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A list of Person
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
#START############################################################################
post:
summary: Creates a person
description: Adds a new person to the persons list.
parameters:
- name: person
in: body
description: The person to create.
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
responses:
204:
description: Persons succesfully created.
400:
description: Persons couldn't have been created.
# END ############################################################################
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username.
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
200:
description: A Person
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
404:
description: The Person does not exists.
2.5.1 新增一個 post /persons 操作
首先在 /persons 路徑下廷加一個 post 操作:
paths:
/persons:
post:
summary: Creates a person
description: Adds a new person to the persons list.
2.5.2 定義訊息體引數
接下來我們給 post 方法新增引數,通過 in
屬性顯式說明引數是在 body 中的。引數的定義參考 get /persons/{username} 的 200 響應訊息體引數,也就是包含使用者的姓氏、名字、使用者名稱。
parameters:
- name: person
in: body
description: The person to create.
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
2.5.3 定義響應訊息
最後不要忘記定義 post 操作的響應訊息。
responses:
204:
description: Persons succesfully created.
400:
description: Persons couldn't have been created.
第3章 文件瘦身
現在我們已經學會了編寫API文件的基本方法。不過上面的例子中存在一些重複,這對於程式設計師的嗅覺來說,就是程式碼的“壞味道”。這一章我們一起學習如何通過抽取可重用的定義(definitions)來簡化API文件。
3.1 簡化資料模型
我們認真觀察第2章最後輸出的API文件,很容易發現 Person 的定義出現了三次,非常的不 DRY
☹。
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A list of Person
schema:
type: array
items:
#START 第1次定義###################################################################
required:
- username
properties:
firstName: