He who has a “why” to live for can bear almost any “how” –尼采

前言

此前一直在关注swagger,不过也一直处于看看案例,读读文档的状态。关于写api接口文档和mock的事,多次头疼,看了许多工具都不甚满意,最近在使用Mock(在线体验)的时候

发现它在文档里写道:

支持同步 Swagger 文档,1秒便能生成 Mock 数据。之后接口文档更新也能通过更新操作重新生成 Mock 数据

这太强大了,心动不已。

我自己很喜欢easy-mock这个工具,但写REST api的时候,感觉重复数据声明太多,很不爽,我们知道post/put的大部分属性是相同的,而get/post也有大量重复的属性。如何能以一种更加方便的方式在easy-mock(其他的mock工具也一样)定义接口,以便于更好地做到DRY, Swagger是绝佳的解决方案,而且它能带给你的远不止于此

why的问题一解决,how to do 只是细节问题了

于是重新花了2个晚上的时间,集中地读了文档,也动手写了几个demo,很符合预期

接下来以经典的3w问题为线索来记录下我的学习过程

Swagger是什么

关于swagger是什么的问题,swagger的首页说的再清楚不过:

Swagger is the world’s largest framework of API developer tools for the OpenAPI Specification(OAS), enabling development across the entire API lifecycle, from design and documentation, to test and deployment.

因为swagger庞大的生态系统,我们在api的整个生命周期中都能从中受益: from design and documentation, to test and deployment

前头我的easy-mock属于test部分,同时它也有效地帮助前后端分离

如果你想对Swagger有更深的了解,可以看下对Tony Tam的这段采访(Tony Tam影响了Swagger的诞生):通过Swagger进行API设计,与Tony Tam的一次对话

OpenAPI Specification

上述这段引用中有一个名词值得一提:OpenAPI Specification(OAS),它本身开放在OpenAPI-Specification,它的目的是:

The goal of The OpenAPI Specification is to define a standard, language-agnostic interface to REST APIs

为何需要用它

如果你在为一个系统设计API,你希望更好的管理你的API,你希望有一个工具能一站式地解决API相关的所有事情,从设计到文档再到mock,甚至能直接从设计文档中生成代码(声明式编程),这确实是可能的,如果你的描述信息是完备的,自动化生成mock接口,同时也可生成各种语言与api交互的SDK

这些便是你选择Swagger的理由

how to do…

最后便是怎么做的问题,这反而不太难,在互联网高度发达的今天,海量的资料遍布网络

我接下来把我的学习过程和资料做个笔记,希望对你有用

起步

尽管Swagger本身是开源的,你可以自行搭建整个工具链,但从在线工具开始尝试,会让你更快接触核心的东西,而不是在搭建工具上忙活半天

我们从swaggerhub开始

swaggerhub是个非常棒的项目,你在这里可以:

  • 在线编辑你的API文档(用的是swagger-editor)
    • 可使用使用json或yaml来书写(设计)你的API,推荐yaml,对人类更友好(无论是书写、阅读还是添加注释)
    • 可导出为json或是yaml,easy-mock中要求json文档的url,所以你可以把导出的json放到github gist,然后贴到easy-mock里,之后就自动生成mock接口了
    • 可直接生成可运行20几种server代码(诸如flask),当然也能自动生成client可执行代码
  • swaggerhub自带了mock插件,可以直接在线测试,UI页里有curl的请求例子
  • 和github相似,你可以分享以及fork别人的API项目:search,这样对学习很有帮助
  • 你可以邀请你的团队成员一起来编辑

上边的截图是官方的demo:Swagger Petstore,这是一个很完整的API项目(同时也比较复杂),我们可以一边试运行,一边做些修改看看每个参数的作用,如果你想对swagger的语法有个整体的认识,推荐这篇文档Swagger从入门到精通

我的做法是先通读一遍(粗读)上边的文档,之后对着别人的API项目学习,遇到不懂的去查阅这个文档

另外文档结尾处有个彩蛋:OpenAPI Specification Visual Documentation,这是查阅swagger写作语法的神器

如何把握Swagger

Swagger生态极其庞大,各种工具层出不穷,如何确定一个清晰工作流。而不会在庞大的生态链里迷失

我觉得核心是理解:Swagger的生态系统是围绕Swagger文档(json/yaml)构建的,就是说我们书写的纯文本(json/yaml)便是我们输出的所有东西,它是全息的,我们手里只需有这个纯文本,就能做所有事情,此外的工具都是围绕它构建的,把这个纯文本导入到响应的工具里,就可获得其他功能,诸如漂亮的UI,自动化的mock等等

你熟悉markdown的话,你会发现他们在设计思路上有不少相似点

编写API文档,其实我们只是在写一个简单的纯文本文件。你可以用任何你喜欢的文本编辑器来写(vim/emacs/atom/vscode/sublime)。但是为了提高效率,建议选择Swagger Editor(swaggerhub默认是它),诸如语法检查之类的功能能帮你省下大量排错的时间

一个小案例

学习一个新技术,我喜欢用它写一个简单的blog,我们接下来试试用swagger来写博客文章的API,我们假设Article有四个属性:

  • userid
  • title
  • content
  • tag

API是RESTful风格(可以参考我此前的文章RESTful-Api),支持:

  • get /articles : 获取文章列表
  • post /articles : 新建一篇文章(登录用户)
  • get /articles/<id> : 获取文章详情(指定文章id)
  • put /articles/<id> : 更新某篇的文章
  • delete /articles/<id> : 删除某篇的文章
  • get /articles/search?title=python : 搜索文章

下边是它的api描述

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
swagger: "2.0"
info:
  version: 1.0.0
  title: "blog"
  # description支持markdown
  description: |
    # blog API documentation

    这篇文档描述了的blog(article)的api接口    

# host: localhost:8000 #localhost blog 
# basePath: /api
schemes:
  - https
  - http
consumes:
  - application/json
produces:
  - application/json

paths:
  /:
    get:
      summary: Service root
      description: 列出所有api的描述信息.
      operationId: root
      responses:
        '200':
          description: Success
      security: []
  /articles:
    get: 
      summary: list all articles
      operationId: list_all_articles #后端函数
      responses:
        '200':
          #todo
          description: Annotation successfully created
          schema:
            $ref: '#/definitions/ArticleList'
        '400':
          description: Could not create article from your request
          schema:
            $ref: '#/definitions/Error'

    post:
      summary: Create a new Article
      operationId: createArticle
      parameters:
        - name: Article
          in: body
          description: article to be created
          required: true
          schema:
            $ref: '#/definitions/NewArticle'
      responses:
        '200':
          description: article successfully created
          schema:
            $ref: '#/definitions/Article'
        '400':
          description: Could not create article from your request
          schema:
            $ref: '#/definitions/Error'
  /articles/{id}:
    get:
      summary: Fetch an Article
      operationId: fetchArticle
      parameters:
        - name: id
          in: path
          description: ID of article to return
          required: true
          type: string
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/Article'
        '404':
          description: article not found or no permission to view
          schema:
            $ref: '#/definitions/Error'
    patch:
      summary: Update an Article
      description: |
        This endpoint is available under both the `PATCH` and `PUT`
        request methods. Both endpoints have PATCH-characteristics
        as defined in [RFC5789](https://tools.ietf.org/html/rfc5789#section-1),
        meaning the request body does not have to include the whole Article
        object.

        New implementations should use the `PATCH` request method, and existing
        implementations continue to work under `PUT` but should switch to `PATCH`.        

      operationId: updateArticle
      parameters:
        - name: id
          in: path
          description: ID of article to return
          required: true
          type: string
        - name: Article
          in: body
          description: Updated article body
          required: true
          schema:
            $ref: '#/definitions/NewArticle'
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/Article'
        '400':
          description: Could not create article from your request
          schema:
            $ref: '#/definitions/Error'
        '404':
          description: article not found or no permission to update
          schema:
            $ref: '#/definitions/Error'
    delete:
      summary: Delete an Article
      operationId: deleteArticle
      parameters:
        - name: id
          in: path
          description: ID of article to return
          required: true
          type: string
      responses:
        '200':
          description: Success
          schema:
            type: object
            required:
              - deleted
              - id
            properties:
              deleted:
                type: boolean
                enum:
                  - true
              id:
                type: string
        '404':
          description: article not found or no permission to delete
          schema:
            $ref: '#/definitions/Error'

  /search:
    get:
      summary: Search for annotations
      operationId: search #后台对应的函数名
      parameters:
        - name: limit
          in: query
          description: The maximum number of annotations to return.
          required: false
          type: integer
          minimum: 0
          maximum: 200
          default: 20
        - name: offset
          in: query
          description: >
            The minimum number of initial annotations to skip. This is
            used for pagination.            
          required: false
          type: integer
          default: 0
          minimum: 0
        - name: sort
          in: query
          description: The field by which annotations should be sorted.
          required: false
          type: string
          default: updated
      responses:
        '200':
          description: Search results
          schema:
            $ref: '#/definitions/SearchResults'

definitions:
  NewArticle:
    # todo json 2 yaml: https://www.json2yaml.com/
    # 也可以是在线的
    #$ref: './schemas/annotation-schema.json' #https://h.readthedocs.io/en/latest/api/schemas/annotation-schema.json 
    type: object
    properties:
      userid:
        type: string
        #"pattern": "^acct:.+$"
      title:
        type: string
      content:
        type: string
      tags:
categories: ["工具"]
        type: array
        items:
          type: string
  Article:
    allOf:
      - $ref: '#/definitions/NewArticle' # NewArticle属性展开在这里
      - required:
        - id
        properties:
          id:
            type: string
  Error:
    type: object
    required:
      - status
    properties:
      status:
        type: string
        enum:
          - failure
      reason:
        type: string
        description: A human-readable description of the reason(s) for failure.
  SearchResults:
    #用作list
    type: object
    required:
      - rows
      - total
    properties:
      rows:
        type: array
        items:
          $ref: '#/definitions/Article'
      total:
        description: Total number of results matching query.
        type: integer
  ArticleList:
    #用作list
    type: object
    required:
      - rows
      - total
    properties:
      rows:
        type: array
        items:
          $ref: '#/definitions/Article'
      total:
        description: Total number of results matching query.
        type: integer
# Added by API Auto Mocking Plugin
host: virtserver.swaggerhub.com
basePath: /pwnote/blog/1.0.0

如果你对其中的语法有所不解可以查阅: Swagger从入门到精通

我同时做了以下工作:

围绕Swagger的生态

可以在github上搜swagger,会发现有许多相关项目,我列出几个我恰好看到或关注的

django-rest-swagger

Django REST Framework是写api的神器

配合django-rest-swagger更是如虎添翼

connexion

swagger可自动生成的20多种server代码,其中包括flask版本的,在flask的版本代码里,主要用到的就是connexion

connexion是一个漂亮的框架,用于在Python中实现API First方法,如果你对此有兴趣可以参考Crafting effective Microservices in Python

如果你想看一个带有数据库和真实后端的例子,可以参考:connexion/examples/sqlalchemy,这个例子让我们看到一个完整的项目:api的文档以及后端实现

ReDoc

我最近在使用的一个是ReDoc,有不少有名的项目在用它作为api文档的展示工具:

参考