Liberent-Dev’s blog

株式会社リベル・エンタテインメントのテックブログです。

公式のgo-swaggerに機能追加した

https://static1.smartbear.co/swagger/media/assets/images/swagger_logo.svg

こんにちは。
システム開発部ネットワーク課のsupercontinueです。

swaggerとは?

  • swaggerは定義したスキーマからAPIコードを生成してくれるジェネレータです。
  • オープンソースです。
  • go-swaggerは定義したスキーマからgo言語のサーバのコードを生成します。
  • 弊社でも実際にリリースしたゲームで使いました。

  • もうずいぶん前になりますが、公式のgo-swaggerに機能追加したプルリクエストを送って、取り込まれた話をします。

何を追加したのか?

  • 例として、swaggerでは下記のようにAPIスキーマを定義します。
  sessionData:
    type: object
    properties:
      sessionId:
        type: string
      deviceId:
        type: string
      uMain:
        $ref: '#/definitions/u_main'
  • すると、下記のようなコードが生成されます。
type SessionData struct {

    DeviceID string

    SessionID string

    UMain *UMain
}
  • プロパティの順番に注目してください。

    • スキーマでは、sessionId、deviceId、uMain の順番になっています。
    • ところが、go-swaggerでは、アルファベット順にプロパティを並べ替えてコードを生成します
    • 生成されたコードでは、DeviceID、SessionID、UMain の順番になっています。
  • 順番が違うと何が困るのか?

    • MsgpackやProtobufではプロパティ名でなく、プロパティの順番や番号でシリアライズを行います。
    • 型と順番さえ同じなら、プロパティが追加されても、シリアライズ・デシリアライズすることが可能です。もちろん定義が無いプロパティは扱えませんが、存在するプロパティは扱えます。
    • スキーマ変更の前後で、プロパティの順番が違ってしまうと、コンパチビリティが失われます
    • また、スキーマにプロパティを追加したり、順番を変更すると、生成されるコードに意図ぜず大きな差分が発生し、差分が見づらくなります
  • そこで、プロパティの順番を明示的に指定する、x-orderというカスタム属性を追加することにしました。

    • swaggerでは、x-なんとかという属性は、プラットフォームごとの独自拡張の属性として扱われています。
    • たとえば、下記のようなカスタム属性があります。
      • x-go-name: "string": give explicit type name to the generated model
      • x-nullable: true|false (or equivalently x-is-nullable:true|false): accepts null values (i.e. rendered as a pointer)
  • x-orderを定義したスキーマ

  sessionData:
    type: object
    properties:
      sessionId:
        x-order: 0
        type: string
      deviceId:
        x-order: 1
        type: string
      uMain:
        x-order: 2
        $ref: '#/definitions/u_main'
  • すると、下記のようなコードが生成されます。
    • 構造体のプロパティの順番がx-orderで指定した順番になります
type SessionData struct {

    SessionID string

    DeviceID string

    UMain *UMain
}

実際に機能追加提案したコード

  • まずはレポジトリをForkします。

  • 主要な変更は下記です。

// 変更前

func (g GenSchemaList) Less(i, j int) bool { return g[i].Name < g[j].Name }
// 変更後

func (g GenSchemaList) Less(i, j int) bool {
    a, ok := g[i].Extensions[xOrder].(float64)
    if ok {
         b, ok := g[j].Extensions[xOrder].(float64)
       if ok {
            return a < b
       }
    }
    return g[i].Name < g[j].Name
}
  • 変更点はわずかですが、「どこを変更すればいいのか?」を正確に把握するには、swaggerのコードを読み込む必要があります。

  • 今回は、たまたまLessというメソッドが実装されていたので、変更が最小限ですみました。

  • テスト用のコードの方が多いです。

  • 実際のプルリクエストは下記です。

  • 結構さくっとマージしてもらえました。

結果

  • 公式のマニュアルにもx-orderカスタム属性が記述されています。

  • 当時はさくっと採用されて嬉しかったです!

    • オープンソースへの貢献は嬉しいものですが、負荷が高くなると辛くなるので。
  • 独自レポジトリで改変して利用していると、公式が更新された場合、追っかけなくてはなりません。公式にマージしてもらうほうが楽です。

  • さっき見たら、下記のような2020年のissueがありました。(まだOpenしたまま)


リベル・エンタテインメントでは、このような最新技術などの取り組みに興味のある方を募集しています。もしご興味を持たれましたら下記サイトにアクセスしてみてください。 https://liberent.co.jp/recruit/