使用Swagger开发REST API

REST API在业界的应用越来越普及,大量的规范、可复用的基础设施、新工具,让基于REST API的开发体验非常好。Azure、GCP等大型云计算服务基于REST API规范提供成千上万个API,足以体现REST API丰富的表达能力。

鉴于REST API的优秀特性,所以OpenAPI使用REST规范来构建。OAI致力于OpenAPI的标准化。微软和Google等巨头都是OAI的成员。2017年7月OAI发布了OAS 3.0。

OAS 3.0是基于Swagger 2.0规范改进而来的,所以使用Swagger的工具链来做OpenAPI开发是一个不错的选择。Swagger提了一个PetStore的demo供用户体验。

image.png
image.png
image.png

接入配置

首先引入依赖。

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

接着在Application同级目录创建下面这个类即可打开Swagger。设置@Profile("dev"),用以控制在测试环境才打开Swagger。

@Profile("dev")
@Configuration
@EnableSwagger2
public class Swagger2 {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                            .apiInfo(apiInfo())
                            .select()
                            .apis(RequestHandlerSelectors.basePackage("com.aliyun.ram.sdk.validate.control.ramsdkvalidatecontrol.controller"))
                            .paths(PathSelectors.any())
                            .build();
    }

    private ApiInfo apiInfo() {

        Contact contact = new Contact("阿呆少爷", "https://1.2.3.4", "xxx@xxx.com");

        return new ApiInfoBuilder()
                .title("TaskManager")
                .description("TaskManager")
                .contact(contact)
                .version("1.0")
                .build();
    }
}
image.png

打开http://localhost:8080/swagger-ui.html,即可看到swagger的界面。界面上可以看到Controller和Model。在Swagger-UI里面可以测试API,挺方便的。

Swagger注解

Swagger针对接口和实体类提供很多注解,用于做更加精细的描述。

@ApiOperation(value = "Add a new pet to the store", nickname = "addPet", notes = "", authorizations = {
    @Authorization(value = "petstore_auth", scopes = {
        @AuthorizationScope(scope = "write:pets", description = "modify pets in your account"),
        @AuthorizationScope(scope = "read:pets", description = "read your pets")
        })
}, tags={ "pet", })
@ApiResponses(value = { 
    @ApiResponse(code = 405, message = "Invalid input") })
@RequestMapping(value = "/pet",
    produces = { "application/xml", "application/json" }, 
    consumes = { "application/json", "application/xml" },
    method = RequestMethod.POST)
ResponseEntity<Void> addPet(@ApiParam(value = "Pet object that needs to be added to the store" ,required=true )  @Valid @RequestBody Pet body);

@ApiOperation(value = "Find pet by ID", nickname = "getPetById", notes = "Returns a single pet", response = Pet.class, authorizations = {
    @Authorization(value = "api_key")
}, tags={ "pet", })
@ApiResponses(value = { 
    @ApiResponse(code = 200, message = "successful operation", response = Pet.class),
    @ApiResponse(code = 400, message = "Invalid ID supplied"),
    @ApiResponse(code = 404, message = "Pet not found") })
@RequestMapping(value = "/pet/{petId}",
    produces = { "application/xml", "application/json" }, 
    method = RequestMethod.GET)
ResponseEntity<Pet> getPetById(@ApiParam(value = "ID of pet to return",required=true) @PathVariable("petId") Long petId);
image.png

即使不使用注解,Swagger依旧对Java支持良好,比如识别枚举类型。

@ApiModel
public class TaskStatus {

    @ApiModelProperty(value = "任务ID")
    private Long id;

    @ApiModelProperty("任务运行状态")
    private TaskWorkStatus status;

    @ApiModelProperty("任务详细状态")
    @Size(max = 5120)
    private String detailStatusJson;
}
image.png

生成代码

根据规范生成各种语言的SDK是一个大大的福利。可以在https://editor.swagger.io/里面生成代码,或者在终端使用swagger-codegen生成代码。

$ brew install swagger-codegen
$ export package_name=xxx
$ swagger-codegen generate -i api.json -l java -o /tmp/ramsdk/ --api-package ${package_name}.api --model-package ${package_name}.model --invoker-package ${package_name}

服务器端

Java

我们来分析一下Swagger生成的Java Spring的代码。Swagger生成的代码支持XML和JSON两种格式的输入和输出,考虑很周全。

代码结构很清晰,接口描述都在PetApi接口里面,PetApiController实现PetApi接口。具体的业务逻辑需要自己实现Service层来做。

image.png

客户端

Java

Swagger生成的Java客户端使用OkHttp调用服务器端API,并且提供同步和异步的接口。

ApiClient apiClient = new ApiClient();
apiClient.setBasePath(task.getServiceEndpoint());
apiClient.setDebugging(true);

ControllerApi controllerApi = new ControllerApi();
controllerApi.setApiClient(apiClient);

//同步调用
ApiResponse<Task> response = controllerApi.taskUsingPOSTWithHttpInfo(createTaskRequest);

//异步调用
//api.taskUsingPOSTAsync(createTaskRequest, callback);

return response.getData();

Java客户端提供debug开关,打开之后可以看到HTTP的收发信息。

image.png

React

使用swagger-codegen生成typescript-fetch代码,使用标准的Fetch API,调用服务器端接口。

image.png

React项目中,import生成的代码,就可以通过ControllerApi请求相应的API,非常方便。使用Ant Design的表格显示任务列表。

import { Table } from 'antd';
import * as React from 'react';
import { Task, TaskControllerApi } from './api/ram-sdk-validate-control'
import './App.css';

interface IState {
  tasks: Task[],
}

class App extends React.Component<any, IState> {

  constructor(props: any) {
    super(props);
    this.state = {
      tasks: [],
    }
  }

    const taskControllerApi = new TaskControllerApi();
    taskControllerApi.listTasksUsingGET().then(tasks => {
        this.setState({
          tasks
        });
    });
  }

  public render() {

    const { tasks } = this.state; 
    
    const columns = [
      {
        dataIndex: 'id',
        key: 'id',
        title: 'ID',
      },
      {
        dataIndex: 'name',
        key: 'name',
        title: '任务名称',
      },
    ];

    return (
      <div className="App">
        <Table 
          dataSource={ tasks } 
          columns={ columns }
          rowKey="id"
        />
      </div>
    );
  }
}

export default App;
image.png

NG-ZORRO

在Swagger Editor生成代码,选择typescript-angular。将代码下载、解压、拖入工程。在app.module.ts里面增加模块的配置及注入方式。

import {ApiModule, Configuration, ConfigurationParameters} from './ram-sdk-validate'

export function apiConfigFactory (): Configuration {
  const params: ConfigurationParameters = {
    basePath: "http://10.101.90.71:8080/"
  }
  return new Configuration(params);
}

@NgModule({
  declarations   : [
    AppComponent,
    TodoComponent,
    TodoMenuComponent,
    TodoMenuChildComponent,
    TodoModalEditComponent
  ],
  imports        : [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    HttpClientModule,
    AppRoutingModule,
    NgZorroAntdModule,
    ApiModule.forRoot(apiConfigFactory)
  ],
  entryComponents: [
    TodoModalEditComponent
  ],
  providers      : [ { provide: NZ_I18N, useValue: zh_CN } ],
  bootstrap      : [ AppComponent ]
})
export class AppModule {
}

然后就可以在component中使用模块了。

import { Task, TaskControllerService } from '../ram-sdk-validate'

taskService.listTasksUsingGET().subscribe(tasks =>
  {
    this.tasks = tasks;
  }
)

tasks: Task[];

使用Ant Design的表格显示任务列表。

<nz-table #basicTable [nzData]="tasks">
  <thead>
    <tr>
      <th>ID</th>
      <th>Name</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let data of basicTable.data">
      <td>{{data.id}}</td>
      <td>{{data.name}}</td>
      <td>
        <a>Pause</a>
        <a>Delete</a>
      </td>
    </tr>
  </tbody>
</nz-table>
image.png

更正规的做法是将client发布到仓库,然后在项目中引入相关的依赖。发布模块请参考:将Swagger生成的Angular client发布到npm仓库

性能问题

一般管控API不会太在意性能,但是业务API,比如消息服务的API,调用频繁,对RT也有要求,所以使用HTTP协议时性能会是一个问题。为了解决性能问题,GCP还支持gRPC协议。

Multiple surfaces: REST and gRPC
All our Cloud APIs expose a simple JSON REST interface that you can call directly or via our client libraries. Some of our latest generation of APIs also provide an RPC interface that lets clients make calls to the API using gRPC: many of our client libraries use this to provide even better performance when you use these APIs. You can find out more about API interfaces and library types in Client Libraries Explained.

Regardless of interface type, Cloud APIs use resource-oriented design principles as described in our Google API Design Guide.

考虑到用户测试方便和复杂的需求,可以通过gRPC REST Gateway以提供REST API。

image.png

一点感悟

云计算的本质就是API。云产品通过API提供服务能力。控制台和SDK均使用同一套API。OpenAPI应运而出,让业界迅速对API设计规范达成广泛的共识,并且产生了一批优秀的工具,帮助大家高效率设计和实现API。

OpenAPI基于HTTP协议,而OAuth 2.0同样也基于HTTP协议,所以REST API+OAuth 2.0是最佳组合。云计算的后起之秀,比如Azure和GCP都采用了这套解决方案。

未来的开发模式就是先定义好API,然后生成服务器端的框架,服务开发完成之后,自动生成各种SDK提供给客户使用。

image.png

相关链接

  1. OpenAPI 3.0, And What It Means for the
    Future of Swagger
    ,slideshare,需翻墙。
  2. Swagger 2与OpenAPI 3
  3. Azure REST API Reference
  4. Google Cloud APIs
  5. 将Swagger生成的Angular client发布到npm仓库
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容