通过生成Api sdk提升开发体验和效率(让bug飞一会儿)

本文概述:

  1. 通过声明式简化接口请求的使用。
  2. 通过代码生成降低开发者的重复的工作。
  3. 提高开发者的开发体验,效率,Happy Coding。

在日常的开发中,不论是web项目,移动端/服务端,或者是微服务,总是会涉及到多人协作或者跨团队甚至跨部门的配合,如何高效的联调接口就会很大程度的上影响项目的进度和总体质量。

痛点(针对http接口)
  1. 由于文档描述不够准确或者看文档的人不够细心引起的问题
   1.1:参数是否必填、字段类型
   1.2:http 请求方法、 请求头(content-type)、路径(有路径参数)
   1.3:响应字段格式类型,例如:枚举
   1.4:低效的沟通导致相互扯皮的问题
  1. 由于接口变更引起的问题
由于在开发阶段因为设计或者需求的变更导致接口变更,对接接口的人在变更接口调用的时候可能会遗漏
  1. 效率问题,如果接口有上百个甚至更多,就单纯些接口的模型对象就是一个很大的工作量

从上面列的几点可以发现这些问题:

  1. 对接接口的人需要关系太多的细节,例如:请求url,请求头,Content-Type,请求方法等
  2. 需要而外的工作,编写接口的模型对象,定义请求方法等

除了这些,如果是js端,还要面对多个运行是环境的问题,例如:浏览器、小程序(多个平台)、weex、react nateive,这无疑又是一个很重复的工作量(当然开源社区有提供跨可以js平台的请求库,例如:umi-requestaxios等)。

一种解决思路
  1. 通过提供声明式的请求工具,屏蔽接口的一些细节,让开发员专注业务。
  2. 通过代码生成,减少接口对接人员的工作
  3. 这条针对js:使用typescript,增强接口方法的可用性和安全性typescirpt中文网
声明式的接口请求

引用一段百度百科上对声明式编程的定义

声明式编程(英语:Declarative programming)是一种编程范式,与命令式编程相对立。它描述目标的性质,让计算机明白目标,而非流程。声明式编程不用告诉计算机问题领域,从而避免随之而来的副作用。而命令式编程则需要用算法来明确的指出每一步该怎么做

简单的说就是只需要定义要做什么,而不是怎么做。

社区开源的库

  1. feign
  2. spring-cloud-openfeign
  3. retrofit

来一例子

@FeigenCleint(url="http://example.com/api")
public interface ExampleFeignClient{

   @GetMapping(value="/example/{id}",consumes={"application/json"})
   Example getExample(@PathVariable("id") Long id);

   @PostMapping(value="/example",produces={"application/json"})
   void createExample(@RequestBody CreateExampleReq req);
}

这样对于使用的人只需要关心方法的入参和返回值,而不需要关心其他的,就像调用一个普通的方法一样,最大程度的简单了接口调用了复杂度(当然事物都是两面性,当我们解决了一个问题以后,很可能会来带来新的问题,例如上面这个例子,会带来而外的学习成本和一些配置等)。

来typescript的基于fetch的原始请求方式的一个对比

const getExample=(id:number):Promise<Example>=>{
   
   return fetch(`http://example.com/api/example/${id}`,{
        method:'GET',
    }).then((response)=>{
        if(response.status>=200 && response.status<300){
           return response.json();
        }
        return Promise.reject(response);
    })
}

const createExample=(req:CreateExampleReq):Promise<void>=>{
   return fetch(`http://example.com/api/example`,{
        method:'POST',
        headers:{
          'Content-Type': 'application/json'
        },
        body:JSON.stringify(req)
    })
}
 

可以看到第二种方式开发者需要关心更多的细节,例如,参数的序列化,返回值的状态、类型(Content-Type)处理等,这些应该交由框架去处理。

另外,即使我们用上了声明式的接口请求处理,上面这些代码如果都让开发者手写的话,接口数量一多工作量还是很大,而且容易由于手误而导致出错,这个时候就可以考虑祭出代码生成工具了。

来一个参照spring cloud openfeign设计思路的typescript feign client的例子

@Feign({
    value: '/v1/example',
})
class ExampleService {

    /**
     * 1:接口方法:POST
     * 2:描述的文字
     * 3:返回值在java中的类型为:ApiResp
     * 4:返回值在java中的类型为:Long
     **/
    @PostMapping({
        value: '/create',
        produces: [HttpMediaType.FORM_DATA],
    })
    create: (req: CreateExampleEntityReq, option?: FeignRequestOptions) => Promise<number>;
    /**
     * 1:接口方法:PUT
     * 2:描述的文字
     * 3:返回值在java中的类型为:ApiResp
     * 4:返回值在java中的类型为:Void
     **/
    @PutMapping({
        value: '/edit',
        produces: [HttpMediaType.FORM_DATA],
    })
    edit: (req: EditExampleEntityReq, option?: FeignRequestOptions) => Promise<void>;
    /**
     * 1:接口方法:GET
     * 2:描述的文字
     * 3:返回值在java中的类型为:ApiResp
     * 4:返回值在java中的类型为:Void
     **/
    @GetMapping({
        value: '/delete',
        produces: [HttpMediaType.FORM_DATA],
    })
    delete: (req: DeleteExampleEntityReq, option?: FeignRequestOptions) => Promise<void>;
    /**
     * 1:接口方法:GET
     * 2:描述的文字
     * 3:返回值在java中的类型为:ApiResp
     * 4:返回值在java中的类型为:Pagination
     * 5:返回值在java中的类型为:ExampleEntityInfo
     **/
    @GetMapping({
        value: '/query',
        produces: [HttpMediaType.FORM_DATA],
    })
    query: (req: QueryExampleEntityReq, option?: FeignRequestOptions) => Promise<PageInfo<ExampleEntityInfo>>;
    /**
     * 1:接口方法:GET
     * 2:描述的文字
     * 3:返回值在java中的类型为:ApiResp
     * 4:返回值在java中的类型为:ExampleEntityInfo
     **/
    @GetMapping({
        value: '/{id}',
        produces: [HttpMediaType.FORM_DATA],
    })
    detail: (req: ExampleServiceDetailReq, option?: FeignRequestOptions) => Promise<ExampleEntityInfo>;
}

export default new ExampleService();

dart的例子

import 'dart:io';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:fengwuxp_dart_basic/index.dart';
import 'package:fengwuxp_dart_openfeign/index.dart';

import '../domain/order.dart';
import '../resp/page_info.dart';
import '../evt/query_order_evt.dart';
import '../evt/create_order_evt.dart';
import '../../../../../serializers.dart';


/// 订单服务
/// 接口:GET
@Feign
@FeignClient(value: "/order",)
class OrderFeignClient extends FeignProxyClient {

  OrderFeignClient() : super() {

  }


  /// 1:获取订单列表
  /// 2:接口方法:GET
  /// 3:返回值在java中的类型为:List
  /// 4:返回值在java中的类型为:Order
  @GetMapping(value: "get_order",)
  Future<BuiltList<Order>> getOrder(List<String> names,
      List<int> ids,
      Set<Order> moneys,
      [UIOptions feignOptions]) {
    return this.delegateInvoke<BuiltList<Order>>("getOrder",
        [names, ids, moneys,],
        feignOptions: feignOptions,
        serializer: BuiltValueSerializable(
            specifiedType: FullType(BuiltList, [FullType(Order)])
        )
    );
  }

  /// 1:获取订单列表
  /// 2:接口方法:POST
  /// 3:返回值在java中的类型为:PageInfo
  /// 4:返回值在java中的类型为:Order
  @PostMapping()
  Future<PageInfo<Order>> queryOrder(@RequestBody(true) QueryOrderEvt evt,
      [UIOptions feignOptions]) {
    return this.delegateInvoke<PageInfo<Order>>("queryOrder",
        [evt,],
        feignOptions: feignOptions,
        serializer: BuiltValueSerializable(
            serializer: PageInfo.serializer,
            specifiedType: FullType(PageInfo, [FullType(Order)])
        )
    );
  }

  /// 1:获取订单列表
  /// 2:接口方法:POST
  /// 3:返回值在java中的类型为:ServiceQueryResponse
  /// 4:返回值在java中的类型为:Order
  @PostMapping(produces: [HttpMediaType.MULTIPART_FORM_DATA],)
  Future<PageInfo<Order>> queryOrder2(@RequestParam("order_id") int oderId,
      String sn,
      [UIOptions feignOptions]) {
    return this.delegateInvoke<PageInfo<Order>>("queryOrder2",
        [oderId, sn,],
        feignOptions: feignOptions,
        serializer: BuiltValueSerializable(
            serializer: PageInfo.serializer,
            specifiedType: FullType(PageInfo, [FullType(Order)])
        )
    );
  }

  /// 1:查询分页
  /// 2:接口方法:POST
  /// 3:<pre> 
  ///参数列表:
  ///参数名称:id,参数说明:属性名称:id,属性说明:订单,示例输入:
  ///</pre>
  /// 4:返回值在java中的类型为:ServiceResponse
  /// 5:返回值在java中的类型为:PageInfo
  /// 6:返回值在java中的类型为:Order
  @PostMapping()
  Future<PageInfo<Order>> queryPage(String id,
      [UIOptions feignOptions]) {
    return this.delegateInvoke<PageInfo<Order>>("queryPage",
        [id,],
        feignOptions: feignOptions,
        serializer: BuiltValueSerializable(
            serializer: PageInfo.serializer,
            specifiedType: FullType(PageInfo, [FullType(Order)])
        )
    );
  }

  /// 1:创建订单
  /// 2:接口方法:GET
  /// 3:返回值在java中的类型为:ServiceResponse
  /// 4:返回值在java中的类型为:Long
  @GetMapping()
  Future<int> createOrder(CreateOrderEvt evt,
      [UIOptions feignOptions]) {
    return this.delegateInvoke<int>("createOrder",
        [evt,],
        feignOptions: feignOptions,
        serializer: BuiltValueSerializable(
            specifiedType: FullType(int)
        )
    );
  }

  /// 1:test hello
  /// 2:接口方法:POST
  /// 3:返回值在java中的类型为:ServiceResponse
  @PostMapping()
  Future<dynamic> hello([UIOptions feignOptions]) {
    return this.delegateInvoke<dynamic>("hello",
      [],
      feignOptions: feignOptions,
    );
  }
}


final orderFeignClient = OrderFeignClient();

以上的代码是通过代码生成codegen工具生成的,结合typescript-feignflutter-feign这个2个http请求库,从加强接口调用的准确性和易用性以及减少开发者工作量2个维度协助开发人员降低接口对接的难度、出错率,提高开发体验。

spring cloud openfeign的生成例子:

import io.reactivex.Observable;
import org.springframework.cloud.openfeign.*;
import org.springframework.web.bind.annotation.*;

import java.util.List;

import com.wuxp.codegen.swagger2.domain.Order;
import com.wuxp.codegen.swagger2.resp.PageInfo;
import com.wuxp.codegen.swagger2.resp.ServiceResponse;
import com.wuxp.codegen.swagger2.evt.CreateOrderEvt;
import com.wuxp.codegen.swagger2.example.evt.QueryOrderEvt;

import java.util.Date;
import java.util.Map;

/**
 * 订单服务
 * 接口:GET
 **/

@FeignClient(
        decode404 = false,
        name = "exampleService",
        path = "/order",
        url = "${test.feign.url}"
)
public interface OrderFeignClient {

    /**
     * 1:获取订单列表
     * 2:接口方法:GET
     * 3:返回值在java中的类型为:List
     * 4:返回值在java中的类型为:Order
     **/
    @GetMapping(value = "get_order")
    List<Order> getOrder(
            String[] names,
            List<Integer> ids,
            Set<Order> moneys
    );

    /**
     * 1:获取订单列表
     * 2:接口方法:GET
     * 3:返回值在java中的类型为:PageInfo
     * 4:返回值在java中的类型为:Order
     **/
    @GetMapping()
    PageInfo<Order> queryOrder(
            @SpringQueryMap(value = true) QueryOrderEvt evt
    );

    /**
     * 1:获取订单列表
     * 2:接口方法:POST
     * 3:返回值在java中的类型为:ServiceQueryResponse
     * 4:返回值在java中的类型为:Order
     **/
    @PostMapping(produces = {MediaType.MULTIPART_FORM_DATA_VALUE})
    ServiceResponse<PageInfo<Order>> queryOrder2(
            @RequestParam(name = "order_id") Long oderId,
            String sn
    );

    /**
     * 1:查询分页
     * 2:接口方法:POST
     * 3:<pre>
     * 参数列表:
     * 参数名称:id,参数说明:属性名称:id,属性说明:订单,示例输入:
     * </pre>
     * 4:返回值在java中的类型为:ServiceResponse
     * 5:返回值在java中的类型为:PageInfo
     * 6:返回值在java中的类型为:Order
     **/
    @PostMapping()
    ServiceResponse<PageInfo<Order>> queryPage(
            String id
    );

    /**
     * 1:创建订单
     * 2:接口方法:POST
     * 3:<pre>
     * 参数列表:
     * 参数名称:evt,参数说明:属性名称:evt,属性说明:创建订单,示例输入:
     * </pre>
     * 4:返回值在java中的类型为:ServiceResponse
     * 5:返回值在java中的类型为:Long
     **/
    @PostMapping()
    ServiceResponse<Long> createOrder(
            @RequestBody(required = true) CreateOrderEvt evt
    );

    /**
     * 1:test hello
     * 2:接口方法:POST
     * 3:返回值在java中的类型为:ServiceResponse
     **/
    @PostMapping()
    ServiceResponse<Object> hello(
    );
}

retrofit的生成例子

import io.reactivex.Observable;
import retrofit2.http.*;

import java.util.List;

import com.wuxp.codegen.swagger2.domain.Order;
import com.wuxp.codegen.swagger2.resp.PageInfo;
import com.wuxp.codegen.swagger2.resp.ServiceResponse;
import com.wuxp.codegen.swagger2.evt.CreateOrderEvt;
import com.wuxp.codegen.swagger2.evt.QueryOrderEvt;

import java.util.Date;
import java.util.Map;

/**
 * 订单服务
 * 接口:GET
 **/

public interface OrderService {

    /**
     * 1:获取订单列表
     * 2:接口方法:GET
     * 3:返回值在java中的类型为:List
     * 4:返回值在java中的类型为:Order
     **/
    @GET(value = "/order/get_order")
    List<Order> getOrder(
            String[] names,
            List<Integer> ids,
            Set<Order> moneys
    );

    /**
     * 1:获取订单列表
     * 2:接口方法:GET
     * 3:返回值在java中的类型为:PageInfo
     * 4:返回值在java中的类型为:Order
     **/
    @GET(value = "/order")
    PageInfo<Order> queryOrder(
            QueryOrderEvt evt
    );

    /**
     * 1:获取订单列表
     * 2:接口方法:POST
     * 3:返回值在java中的类型为:ServiceQueryResponse
     * 4:返回值在java中的类型为:Order
     **/
    @POST(value = "/order/queryOrder2")
    @Headers(value = {"Content-Type: application/json"})
    ServiceResponse<PageInfo<Order>> queryOrder2(
            @Field(value = "order_id") Long oderId,
            String sn
    );

    /**
     * 1:查询分页
     * 2:接口方法:POST
     * 3:<pre>
     * 参数列表:
     * 参数名称:id,参数说明:属性名称:id,属性说明:订单,示例输入:
     * </pre>
     * 4:返回值在java中的类型为:ServiceResponse
     * 5:返回值在java中的类型为:PageInfo
     * 6:返回值在java中的类型为:Order
     **/
    @POST(value = "/order/queryPage")
    @Headers(value = {"Content-Type: application/json"})
    ServiceResponse<PageInfo<Order>> queryPage(
            String id
    );

    /**
     * 1:创建订单
     * 2:接口方法:POST
     * 3:<pre>
     * 参数列表:
     * 参数名称:evt,参数说明:属性名称:evt,属性说明:创建订单,示例输入:
     * </pre>
     * 4:返回值在java中的类型为:ServiceResponse
     * 5:返回值在java中的类型为:Long
     **/
    @POST(value = "/order/createOrder")
    ServiceResponse<Long> createOrder(
            @Body CreateOrderEvt evt
    );

    /**
     * 1:test hello
     * 2:接口方法:POST
     * 3:返回值在java中的类型为:ServiceResponse
     **/
    @POST(value = "/order/hello")
    ServiceResponse<Object> hello(
    );
}

umi-request的生成例子

/* tslint:disable */
import request, {RequestOptionsInit} from 'umi-request';
import {Order} from "../../domain/Order";
import {OrderServiceGetOrderReq} from "../../req/OrderServiceGetOrderReq";
import {QueryOrderEvt} from "../../evt/QueryOrderEvt";
import {PageInfo} from "../../resp/PageInfo";
import {OrderServiceQueryOrder2Req} from "../../req/OrderServiceQueryOrder2Req";
import {OrderServiceQueryPageReq} from "../../req/OrderServiceQueryPageReq";
import {CreateOrderEvt} from "../../evt/CreateOrderEvt";
import {OrderServiceHelloReq} from "../../req/OrderServiceHelloReq";

/**
 * 订单服务
 * 接口:GET
 **/
/*================================================分割线,以下为接口列表===================================================*/


/**
 * 1:获取订单列表
 * 2:接口方法:GET
 * 3:返回值在java中的类型为:List
 * 4:返回值在java中的类型为:Order
 **/
export const getOrder = (req: OrderServiceGetOrderReq, options?: RequestOptionsInit): Promise<Array<Order>> => {
    return request<Array<Order>>(`/order/get_order`, {
        method: 'get',
        params: req,
        ...(options || {} as RequestOptionsInit)
    })
}

/**
 * 1:获取订单列表
 * 2:接口方法:GET
 * 3:返回值在java中的类型为:PageInfo
 * 4:返回值在java中的类型为:Order
 **/
export const queryOrder = (req: QueryOrderEvt, options?: RequestOptionsInit): Promise<PageInfo<Order>> => {
    return request<PageInfo<Order>>(`/order`, {
        method: 'get',
        params: req,
        ...(options || {} as RequestOptionsInit)
    })
}

/**
 * 1:获取订单列表
 * 2:接口方法:POST
 * 3:返回值在java中的类型为:ServiceQueryResponse
 * 4:返回值在java中的类型为:Order
 **/
export const queryOrder2 = (req: OrderServiceQueryOrder2Req, options?: RequestOptionsInit): Promise<PageInfo<Order>> => {
    return request<PageInfo<Order>>(`/order/queryOrder2`, {
        method: 'post',
        requestType: 'json',
        data: req,
        responseType: 'json',
        ...(options || {} as RequestOptionsInit)
    })
}

/**
 * 1:查询分页
 * 2:接口方法:POST
 * 3:<pre>
 *参数列表:
 *参数名称:id,参数说明:属性名称:id,属性说明:订单,示例输入:
 *</pre>
 * 4:返回值在java中的类型为:ServiceResponse
 * 5:返回值在java中的类型为:PageInfo
 * 6:返回值在java中的类型为:Order
 **/
export const queryPage = (req: OrderServiceQueryPageReq, options?: RequestOptionsInit): Promise<PageInfo<Order>> => {
    return request<PageInfo<Order>>(`/order/queryPage`, {
        method: 'post',
        requestType: 'json',
        data: req,
        responseType: 'json',
        ...(options || {} as RequestOptionsInit)
    })
}

/**
 * 1:创建订单
 * 2:接口方法:POST
 * 3:<pre>
 *参数列表:
 *参数名称:evt,参数说明:属性名称:evt,属性说明:创建订单,示例输入:
 *</pre>
 * 4:返回值在java中的类型为:ServiceResponse
 * 5:返回值在java中的类型为:Long
 **/
export const createOrder = (req: CreateOrderEvt, options?: RequestOptionsInit): Promise<number> => {
    return request<number>(`/order/createOrder`, {
        method: 'post',
        requestType: 'form',
        data: req,
        responseType: 'json',
        ...(options || {} as RequestOptionsInit)
    })
}

/**
 * 1:test hello
 * 2:接口方法:POST
 * 3:返回值在java中的类型为:ServiceResponse
 **/
export const hello = (req: OrderServiceHelloReq, options?: RequestOptionsInit): Promise<any> => {
    return request<any>(`/order/hello`, {
        method: 'post',
        requestType: 'form',
        data: req,
        responseType: 'json',
        ...(options || {} as RequestOptionsInit)
    })
}


  1. common-codegen是一个通过java class生成api sdk的代码生成工具,目前支持spring mvc相关的注解(通过控制器生成sdk,支持扩展其他注解)、默认swagger2和3的注解和部分的javax验证注解。支持生成retrofit\spring cloud openfeign\typescript-feign\umi-request\dart_feign 5种请求工具的代码。
  2. flutter-feign flutter的http请求工具
  3. typescript-feign是一个声明式的跨平台的http请求框架,一套sdk适配所有的js平台
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值