Openapi的惯用性SDK
#typescript #api #go #openapi

我们的SDK发电机

客户sdks有点像伏特加酒,他们要么手工制作且非常昂贵,要么便宜,只让您感到遗憾和宿醉。为了获得具有良好开发人员体验的SDK,历史上需要投入大量资源来手工制作。唯一的选择是使用裸露的OSS产品。

我们提供了一条中间路;免费使用的SDK发电机,为出色的开发人员体验奠定了基础,无需投资。 我们已经在支持GO,Python,TypeScript和Java(Alpha)的支持下启动。我们计划尽快添加Ruby以及其他语言!

构成伟大开发人员的经验当然是主观的,但我们专注于:

  • 完全键入,使SDK觉得它们是由人写的:易于阅读和调试。
  • 电池包括,从重新恢复到API的所有内容都作为SDK一代的一部分处理(在我们的封闭beta中可用)。
  • 易于使用,我们的SDK发电机是容错的,并且旨在在可能的情况下始终输出可用的SDK。如果我们无法输出可用的SDK,我们将提供人类可读的错误消息传递,以了解为什么您的OpenAPI规格验证失败(而不是仅仅默默失败或使用晦涩的错误消息或给您损坏的SDK)
  • 完整的OpenAPI覆盖范围,我们计划对OpenAPI规格进行广泛的覆盖范围,同时深入关注定义API的最常见方法,并确保您可以将SDK用作输出。

为了说明我们的SDK发电机和开源示例之间的差异,我们通过两者进行了规范的PETSHOP示例,并比较了输出。但是TLDR是:

  • Speakeasy SDK发电机安装了没有其他依赖性的啤酒
  • 没有npm。没有Java。一切都包装在一个独立的二进制中
  • 我们暂时支持更少的语言,但我们所做的语言更加惯用,因此用法使用自然语言。
  • 我们为SDK提供了一个简单的API,易于模拟和测试。
  • 我们生产的SDK具有完全键入的输出,包括枚举等。

发电机已经在数千个API上进行了战斗,我们正在分享the results in our github repo。如果您想自己尝试一下,请安装download the CLI或Brew Install并在几分钟内开始:

brew brew安装speakeasy-api/homebrew-tap/speakeasy
speakeasy生成sdk -s openapi.yaml -o ./sdk -l go

我们使用OpenAPI生成器的经验

在我们了解SDK生成器与他人进行比较的详细信息之前,我们想浏览OpenAPI生成器的体验,该发电机首先导致该产品创建。使用OpenAPI工具的人可能会很熟悉。

安装

我们的努力是GO的挣扎。这里的关键词是尝试,因为没有成功的保证。我们尚未设置多个用于使用NPM的问题:

  • 通过NPM在全球安装的权限问题
  • 出错了错误:/bin/sh:1:java:找不到第一次运行时找不到。

要解决问题,我们必须同时安装NPM和Java,然后才能使安装工作。在页面上进一步的Homebrew Install说明,我们有更好的运气。他们首次尝试安装,下载了所有所需的依赖项。

模式验证

验证我们的OpenAPI规格工作正常,是使用OpenAPI-Generator的一个很好的开始。

openapi-generator validate -i openapi.yaml           
Validating spec (openapi.yaml)
No validation issues detected.

SDK世代

即使我们的模式被该工具视为有效,我们也会遇到问题,当我们开始尝试从openapi yaml产生GO SDK时。我们收到的错误是最无益的,我们无法确定为什么我们的规格可能导致问题。

openapi-generator generate -i openapi.yaml -g go -o ../speakeasy-client-sdk-go-openapi-gen
...
Exception: Property and is missing from getVars
        at org.openapitools.codegen.DefaultGenerator.processOperation(DefaultGenerator.java:1187)
        at org.openapitools.codegen.DefaultGenerator.processPaths(DefaultGenerator.java:1078)
        at org.openapitools.codegen.DefaultGenerator.generateApis(DefaultGenerator.java:580)
        at org.openapitools.codegen.DefaultGenerator.generate(DefaultGenerator.java:915)
        at org.openapitools.codegen.cmd.Generate.execute(Generate.java:465)
        at org.openapitools.codegen.cmd.OpenApiGeneratorCommand.run(OpenApiGeneratorCommand.java:32)
        at org.openapitools.codegen.OpenAPIGenerator.main(OpenAPIGenerator.java:66)
Caused by: java.lang.RuntimeException: Property and is missing from getVars
        at org.openapitools.codegen.DefaultCodegen.addRequiredVarsMap(DefaultCodegen.java:7319)
        at org.openapitools.codegen.DefaultCodegen.addVarsRequiredVarsAdditionalProps(DefaultCodegen.java:7354)
        at org.openapitools.codegen.DefaultCodegen.fromParameter(DefaultCodegen.java:4972)
        at org.openapitools.codegen.DefaultCodegen.fromOperation(DefaultCodegen.java:4394)
        at org.openapitools.codegen.DefaultGenerator.processOperation(DefaultGenerator.java:1155)
        ... 6 more

与PetShop示例直接比较

让我们进入一个示例,该示例确实与OpenAPI-Generator(来自OpenAPI规范的规范PETSTORE API)一起使用。首先,让我们看一下用于生成SDK的命令:

OpenAPI

openapi-generator generate -i petstore.yaml -g go -o ./openapi

Speakeasy

speakeasy generate sdk -s petstore.yaml -o ./speakeasy -l go

OpenAPI和Speakeasy Generator都为PETSTORE API输出可用的SDK。我们正在使用此示例,但是我们的生成器支持GO,Python和Typescript。请注意,OpenAPI发电机支持我们计划在道路上添加到Speakeasy发电机的大量其他语言。

虽然OpenAPI发电机确实支持许多语言,因为我们一直在寻找GO语言的SDK实际上是类似Java的语言,并且对GO语言的惯用性很小:

OpenAPI

ctx := context.Background()
  client := openapi.NewAPIClient(openapi.NewConfiguration())
  ctx = context.WithValue(ctx, openapi.ContextAccessToken, "special-key")
  r := client.PetApi.FindPetsByStatus(ctx)
  r = r.Status("pending")
  pets, res, err := r.Execute()
  if err != nil {
    log.Fatal(err)
  }
  if res.StatusCode != 200 {
    log.Fatalf("unexpected status code: %d", res.StatusCode)
  }
  data, _ := json.Marshal(pets)
  fmt.Println(string(data))

Speakeasy

ctx := context.Background()
  sdk := speakeasy.New()
  status := operations.FindPetsByStatusStatusEnumPending
  queryParams := operations.FindPetsByStatusQueryParams{Status: &status}
  security := operations.FindPetsByStatusSecurity{
    PetstoreAuth: shared.SchemePetstoreAuth{
      Authorization: "Bearer special-key",
    },
  }
  res, err := sdk.FindPetsByStatus(ctx, operations.FindPetsByStatusRequest{
    QueryParams: queryParams,
    Security:    security,
  })
  if err != nil {
    log.Fatal(err)
  }
  if res.StatusCode != 200 {
    log.Fatalf("unexpected status code: %d", res.StatusCode)
  }
  data, _ := json.Marshal(res.Pets)
  fmt.Println(string(data))

由于设置请求并执行请求所需的多个方法调用,OpenAPI-Generator的SDK也很难模拟。使用SpeakeAsy SDK,单个方法调用就足够了。

>

也值得研究在存在一些差异的每个SDK的格式和样式:

  • OpenAPI-Generator输出评论以帮助使用(很快就会为Speakeasy发电机提供)。
  • OpenAPI-Generator生成了许多需要的额外的Getter/setter,实例化和序列化方法,这些方法需要降低SDKS代码的可读性。
  • OpenAPI SDK代码缺少格式,而我们的SDK代码是惯用格式的。
  • 我们的发电机为所有事物生成了完整类型(如有可能)。

我们认为的最后一点很重要。例如,OpenAPI将枚举视为字符串,而我们生成的枚举减少了使用错误。我们对枚举的支持是以支持PETSTORE API的单个边缘情况为代价的,它允许将状态枚举提供为逗号分隔字符串以过滤多重状态,可以通过在OpenAPI规格中定义类型为“枚举数组而不是仅仅是字符串。

OpenAPI

// Pet struct for Pet
  type Pet struct {
    Id *int64 `json:"id,omitempty"`
    Name string `json:"name"`
    Category *Category `json:"category,omitempty"`
    PhotoUrls []string `json:"photoUrls"`
    Tags []Tag `json:"tags,omitempty"`
    // pet status in the store
    Status *string `json:"status,omitempty"`
  }

  // NewPet instantiates a new Pet object
  // This constructor will assign default values to properties that have it defined,
  // and makes sure properties required by API are set, but the set of arguments
  // will change when the set of required properties is changed
  func NewPet(name string, photoUrls []string) *Pet {
    this := Pet{}
    this.Name = name
    this.PhotoUrls = photoUrls
    return &this
  }

  // NewPetWithDefaults instantiates a new Pet object
  // This constructor will only assign default values to properties that have it defined,
  // but it doesn't guarantee that properties required by API are set
  func NewPetWithDefaults() *Pet {
    this := Pet{}
    return &this
  }

  // GetId returns the Id field value if set, zero value otherwise.
  func (o *Pet) GetId() int64 {
    if o == nil || o.Id == nil {
      var ret int64
      return ret
    }
    return *o.Id
  }

  // GetIdOk returns a tuple with the Id field value if set, nil otherwise
  // and a boolean to check if the value has been set.
  func (o *Pet) GetIdOk() (*int64, bool) {
    if o == nil || o.Id == nil {
      return nil, false
    }
    return o.Id, true
  }

  // ... And continues with getters/setters for every field

Speakeasy

type PetStatusEnum string

  const (
    PetStatusEnumAvailable PetStatusEnum = "available"
    PetStatusEnumPending   PetStatusEnum = "pending"
    PetStatusEnumSold      PetStatusEnum = "sold"
  )

  type Pet struct {
    Category  *Category      `json:"category,omitempty"`
    ID        *int64         `json:"id,omitempty"`
    Name      string         `json:"name"`
    PhotoUrls []string       `json:"photoUrls"`
    Status    *PetStatusEnum `json:"status,omitempty"`
    Tags      []Tag          `json:"tags"`
  }

概括

希望人们发现比较有趣/有用。我们期待听到有关如何改善SDK发电机以及接下来应该构建的内容的反馈。如果您有任何疑问,please join our slack community,您可以直接给我们发消息。