Swagger使用笔记

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描述

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:
        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文档的展示工具:

参考




Fork me on GitHub