Upgrade to Pro — share decks privately, control downloads, hide ads and more …

API仕様書から自前でコード生成して運用した話 / DroidKaigi 2018 Rejec...

API仕様書から自前でコード生成して運用した話 / DroidKaigi 2018 Reject Conference

DroidKaigi 2018 の Reject Conference での発表資料です。

Motoi Washida

February 17, 2018
Tweet

More Decks by Motoi Washida

Other Decks in Programming

Transcript

  1. 

  2. 

  3. 

  4. ⼀貫性のないレスポンス 何もない値として null と空⽂字が存在  [ { "url": "http://example.com" },

    { "url": null }, // url ͸ແ͍ { "url": "" }, // url ͸ແ͍(?) ] ※ 実際のレスポンスとは異なります
  5. ⼀貫性のないレスポンス 0 や "0" を真偽値のように扱う  { "success": "0" }

    { "success": 0 } ※ 実際のレスポンスとは異なります
  6. 

  7. 写真検索 API の例  IUUQFYBNQMFDPNQIPUPT RΩονϯQBHF { "photos": [ {

    "id": 111, "url": "http://example.com/111.jpg" }, { "id": 222, "url": "http://example.com/222.jpg" }, ... ], "total_count": 123 }
  8. YAML の定義  --- !api name: GetPhotoList request: GET /photos

    description: ࣸਅҰཡΛड͚औΔɻ param: - q: String - page: Integer? response: - photos: GetPhotoList.Photo[]* - total_count: Integer nested: - !class name: Photo properties: - id: Integer - url: String
  9. YAML の定義  --- !api name: GetPhotoList request: GET /photos

    description: ࣸਅҰཡΛड͚औΔɻ param: - q: String - page: Integer? response: - photos: GetPhotoList.Photo[]* - total_count: Integer nested: - !class name: Photo properties: - id: Integer - url: String
  10. YAML の定義  --- !api name: GetPhotoList request: GET /photos

    description: ࣸਅҰཡΛड͚औΔɻ param: - q: String - page: Integer? response: - photos: GetPhotoList.Photo[]* - total_count: Integer nested: - !class name: Photo properties: - id: Integer - url: String
  11. 出⼒例(Java) ※ 実際はもっと複雑なコードができます  /** * <p>ࣸਅҰཡΛड͚औΔɻ</p> */ @SuppressWarnings({ "unused",

    "WeakerAccess" }) public final class GetPhotoList { public static final String method = "GET"; public static final String url = "/photos"; public static final class Param<R> extends AbstractBuilder<R> { public Param(……) { …… } @Nonnull public GetPhotoList.Param<R> q(@Nonnull String value) { …… } @Nonnull public GetPhotoList.Param<R> page(@Nullable Integer value) { …… } } public static final class Response extends AbstractComposite { @Nullable public final List<GetPhotoList.Photo> photos; public final int totalCount; @Keep public Response(@Nonnull AttributeMap map) { …… } } public static final class Photo extends AbstractComposite { public final int id; @Nonnull public final String url; @Keep public Photo(@Nonnull AttributeMap map) { …… } } }
  12. 出⼒例(Java) ※ 実際はもっと複雑なコードができます  /** * <p>ࣸਅҰཡΛड͚औΔɻ</p> */ @SuppressWarnings({ "unused",

    "WeakerAccess" }) public final class GetPhotoList { public static final String method = "GET"; public static final String url = "/photos"; public static final class Param<R> extends AbstractBuilder<R> { public Param(……) { …… } @Nonnull public GetPhotoList.Param<R> q(@Nonnull String value) { …… } @Nonnull public GetPhotoList.Param<R> page(@Nullable Integer value) { …… } } public static final class Response extends AbstractComposite { @Nullable public final List<GetPhotoList.Photo> photos; public final int totalCount; @Keep public Response(@Nonnull AttributeMap map) { …… } } public static final class Photo extends AbstractComposite { public final int id; @Nonnull public final String url; @Keep public Photo(@Nonnull AttributeMap map) { …… } } }
  13. 出⼒例(Java) ※ 実際はもっと複雑なコードができます  /** * <p>ࣸਅҰཡΛड͚औΔɻ</p> */ @SuppressWarnings({ "unused",

    "WeakerAccess" }) public final class GetPhotoList { public static final String method = "GET"; public static final String url = "/photos"; public static final class Param<R> extends AbstractBuilder<R> { public Param(……) { …… } @Nonnull public GetPhotoList.Param<R> q(@Nonnull String value) { …… } @Nonnull public GetPhotoList.Param<R> page(@Nullable Integer value) { …… } } public static final class Response extends AbstractComposite { @Nullable public final List<GetPhotoList.Photo> photos; public final int totalCount; @Keep public Response(@Nonnull AttributeMap map) { …… } } public static final class Photo extends AbstractComposite { public final int id; @Nonnull public final String url; @Keep public Photo(@Nonnull AttributeMap map) { …… } } }
  14. 出⼒例(PHPおよびSwift)  <?php namespace API_Reference; /** * GetPhotoList * *

    ࣸਅҰཡΛड͚औΔɻ * * @package com.tunnel.roomclip.generated * @module items */ /** * Param * * @type AbstractForm */ class GetPhotoListParam extends API_Request { public $q = 'API_Reference\API_String'; public $page = 'API_Reference\API_Integer'; } /** * Response * * @type AbstractComposite */ class GetPhotoListResponse extends API_Object { protected $isOptional = []; protected $isEmptyAllowed = ["photos"]; public $photos = 'API_Reference\List<GetPhotoListPhoto>'; public $total_count = 'API_Reference\API_Integer'; } /** * Photo * * @type AbstractComposite */ class GetPhotoListPhoto extends API_Object { protected $isOptional = []; protected $isEmptyAllowed = []; public $id = 'API_Reference\API_Integer'; public $url = 'API_Reference\API_String'; } /// /// ࣸਅҰཡΛड͚औΔɻ /// struct GetPhotoList { static let method = ApiMethod.get static let url = "/photos" struct Param: BuilderValue { var q: String var page: Int? init( q: String, page: Int? = nil ) { self.q = q self.page = page } var apiRequestParamDictionary: [String: ApiRequestParam] { var values = [String: ApiRequestParam]() values["q"] = q.apiRequestParam if let v = page { values["page"] = v.apiRequestParam } return values } } struct Response: CompositeValue { var photos: [GetPhotoList.Photo]? var totalCount: Int init(children: MapResponseDecodable) throws { self.photos = try children.decode("photos", into: GetPhotoList.Photo.listAllowingEmpty()) self.totalCount = try children.decode("total_count", into: Int.singleRequired()) } } struct Photo: CompositeValue { var id: Int var url: String init(children: MapResponseDecodable) throws { self.id = try children.decode("id", into: Int.singleRequired()) self.url = try children.decode("url", into: String.singleRequired()) } } }
  15. 

  16. 既存の API でも適⽤できる ⾏儀の悪い既存 API にも柔軟に対応したい → GraphQL や Protocol

    Buffers は対象外 記述の簡潔さ 「良い設計」=「書きやすい設計」にしたい → 仕様書の仕様を⾃由に作れる (あと Swagger 書くの⾟そう) 捨てられビリティ 選択を失敗した場合は撤退・移⾏できる → 他のツールの仕様書に変換(コード⽣成)可能 重要視した事  ※ 選定したのは 2016 年後半
  17. 

  18. 必須属性とオプショナル属性  { "fullname": "࿯ా ج", "twitter": "@wm3" } #

    ΞΧ΢ϯτ৘ใ response: - fullname: String # required - twitter: String? # optional
  19. 必須属性とオプショナル属性  { "fullname": "࿯ా ج", "twitter": null } #

    ΞΧ΢ϯτ৘ใ response: - fullname: String # required - twitter: String? # optional
  20. 必須属性とオプショナル属性  { "fullname": null, ɹ "twitter": "@wm3" } #

    ΞΧ΢ϯτ৘ใ response: - fullname: String # required - twitter: String? # optional
  21. 必須属性とオプショナル属性  { "fullname": "",ɹɹɹ "twitter": "@wm3" } # ΞΧ΢ϯτ৘ใ

    response: - fullname: String # required - twitter: String* # empty allowed
  22. 必須属性とオプショナル属性  { "fullname": "࿯ా ج", "twitter": "" } #

    ΞΧ΢ϯτ৘ใ response: - fullname: String # required - twitter: String* # empty allowed
  23. 必須属性とオプショナル属性  { "fullname": "࿯ా ج", "twitter": null } #

    ΞΧ΢ϯτ৘ใ response: - fullname: String # required - twitter: String* # empty allowed
  24.