$30 off During Our Annual Pro Sale. View Details »

OpenAPIで 楽に始めるスキーマ駆動開発実践論 - PHP Conference 2022

taki komiyama
September 21, 2022

OpenAPIで 楽に始めるスキーマ駆動開発実践論 - PHP Conference 2022

スキーマ駆動開発してますか?

一応OpenAPI(Swagger)書いてるけど、実装と乖離して放置されてませか?

レスポンスクラスなどをきれいに書こうとしたものの、データクラスが増えて面倒になっていませんか?

これまで2年以上開発してきた経験から、スキーマ駆動開発の勘所をご紹介します。

正しくOpenAPIを書いて、OpenAPI GeneratorやPostmanを使いこなせば、リクエスト&レスポンスのデータクラスやAPIクライアント、テストコードが自動生成できます。

最大限OpenAPIを使い倒して、楽に効率化することを目指します。もちろんスキーマと実装の乖離は絶対におきず、複雑な設定ファイルは必要ありません。

PHPを前提に話しますが、多くの部分は他の言語で応用可能です。結合テストの実行で少しDI(Dependency Injection)が出てきますが、DIの考え方など基礎的な内容に触れません。

■話すこと
・スキーマ駆動開発とはなにか
・スキーマ駆動開発のメリットとデメリット
・OpenAPIとは
・OpenAPI GeneratorでAPIクライアントの自動生成
・スキーマと実装をずれないようにする
・Postman(Newman)で結合テストの自動生成
・DIで副作用を防ぐ

■話さないこと
・OpenAPIの詳しい書き方
・DIの考え方
・DIのライブラリについて
・npmについて

■想定対象者
・スキーマ駆動開発をやってみたい人
・スキーマ駆動開発をしているが、実装がスキーマとズレて困っている人
・APIの結合テストを自動作成したい人

taki komiyama

September 21, 2022
Tweet

More Decks by taki komiyama

Other Decks in Programming

Transcript

  1. Bengo4.com, Inc.
    OpenAPIͰָʹ࢝ΊΔ
    εΩʔϚۦಈ։ൃ࣮ફ࿦
    খٶࢁ ଠथ

    View Slide

  2. Bengo4.com, Inc.
    หޢ࢜υοτίϜגࣜձࣾ ࡏ੶4೥൒
    PHPྺ 6೥
    TypeScriptྺ 2೥
    ίϯςφΞϓϦ։ൃ&ӡ༻͕ಘҙ
    খٶࢁ ଠथʢ͜Έ΍· ͖ͨʣ
    2
    ࣗݾ঺հ

    View Slide

  3. Bengo4.com, Inc.
    1. εΩʔϚۦಈ։ൃ
    ○ OpenAPIͱ͸
    ○ ։ൃͷྲྀΕ
    ○ ϝϦοτɾσϝϦοτ
    2. OpenAPI GeneratorͰAPIͱΫϥΠΞϯτΛܨ͙
    3. OAS͔ΒAPIςετΛࣗಈੜ੒
    ໨࣍
    3
    ·ͣεΩʔϚۦಈ։ൃͷ֓
    ཁΛ೺Ѳͨ͠ޙɺ۩ମత
    ͳ࣮ફํ๏ʹೖΓ·͢ɻ

    View Slide

  4. εΩʔϚۦಈ։ൃ
    4

    View Slide

  5. Bengo4.com, Inc.
    5
    Answer
    ● ݫີͳఆٛ͸·ͩͳ͍


    ● HTTP APIΛهࡌͨ͠࢓༷


    ○ URLɺϝιου


    ○ ϦΫΤετɺϨεϙϯε
    Question


    ࠷ۙʮεΩʔϚۦಈ։ൃʯΛΑ͘ฉ͖·͢ɻεΩʔϚ͸
    Ͳ͏͍͏ҙຯͰ͔͢ʁ

    View Slide

  6. Bengo4.com, Inc.
    ● Restful HTTP APIΛఆٛ
    ͢Δۀքඪ४ͷ࢓༷


    ● yaml / jsonΛ࢖͍Open
    API Specification(OAS)Ͱ
    ఆٛ
    OpenAPIͱSwagger
    6
    ● ࢓༷Λ࣮૷͢ΔͨΊͷπʔ
    ϧ܈


    ● yaml / jsonΛ࢖͍Swagger
    SpecificationͰఆٛ

    View Slide

  7. Bengo4.com, Inc.
    OpenAPI Specification(OAS)
    7
    /pets/{petId}:


    get:


    operationId: showPetById


    tag:


    - pets


    parameters:


    - name: petId


    in: path


    required: true


    schema:


    type: integer
    openapi: "3.0.0"


    info:


    version: 1.0.0


    title: Swagger Petstore


    license:


    name: MIT


    servers:


    - url: http://petstore.swagger.io/v1


    paths:


    /pets/{petId}:


    get:

    View Slide

  8. Bengo4.com, Inc.
    ● Generate UI


    ● Visual Editor


    ● Mock Server


    ● Codegen
    OpenAPIͷΤίγεςϜ
    8

    View Slide

  9. Bengo4.com, Inc.
    OpenAPI.Tools
    9

    View Slide

  10. Bengo4.com, Inc.
    10
    Answer
    ● v3.0͕͓͢͢Ί


    ● v3.1͸େ͖͍มߋ͕͋Δ


    ○ ηϚϯςΟοΫόʔδϣχϯά


    ● ΤίγεςϜ͕v3.1ʹ௥͍͍ͭͯͳ͍


    ○ OpenAPI Generator΋ະରԠ
    Question


    OpenAPI͸࠷৽ͷv3.1ʢ2021-02-15 ϦϦʔεʣ͔v3.0
    ͲͪΒΛ࢖͏΂͖Ͱ͔͢ʁ

    View Slide

  11. Bengo4.com, Inc.
    1. ίʔυϑΝʔετ
    ○ ίʔυ͔ΒOpenAPI SpecΛੜ੒
    2. εΩʔϚϑΝʔετ
    ○ OpenAPI Spec͔ΒίʔυΛੜ੒
    OpenAPIΛ࠾༻͢Δ࣌ͷ2ͭͷબ୒ࢶ
    11

    View Slide

  12. ࠓճ࿩͢ͷ͸ɺ
    εΩʔϚϑΝʔετ
    12

    View Slide

  13. Bengo4.com, Inc.
    ● ίʔυΛࣗಈੜ੒͢Δ࢓૊Έ͕͋Δ
    ○ OpenAPI GeneratorͳͲ
    ● ίΞϩδοΫͷ։ൃʹूதͰ͖ͯɺϦϦʔεߴ଎Խ
    ● OAS͸ͨͩͷ࢓༷ॻͰ͸ͳ͍
    ͳͥεΩʔϚϑΝʔετ͔
    13

    View Slide

  14. Bengo4.com, Inc.
    1. APIͷURL΍Ϩεϙϯεͷ࢓༷ΛܾΊΔ
    2. OASͰyamlΛఆٛ͢Δ
    3. OAS͔ΒAPIΫϥΠΞϯτͱςετΛࣗಈੜ੒
    4. APIΛ࣮૷͢Δ
    5. ςετΛ࣮ߦ͢Δ
    εΩʔϚϑΝʔετͷ։ൃͷྲྀΕ
    14

    View Slide

  15. Bengo4.com, Inc.
    ● APIΫϥΠΞϯτͳͲίʔυΛࣗಈੜ੒Ͱ͖Δ
    ● API ϞοΫαʔόʔΛཱͯΒΕΔ
    ○ API ͕ͳͯ͘΋ϑϩϯτΤϯυͷ։ൃ͕Ͱ͖Δ
    ● ࢓༷͕Θ͔Γ΍͍͢
    ϝϦοτ
    15

    View Slide

  16. Bengo4.com, Inc.
    ● OASͷॻ͖ํΛ֮͑Δඞཁ͋Γ
    ○ OASͱੜ੒͞ΕΔίʔυͷରԠ
    ● OpenAPI Generator
    ○ ग़ྗݴޠ͝ͱʹରԠ͢Δػೳ͕ҟͳΔ
    ○ ྫɿOneOfͳͲੜ੒Ͱ͖ͳ͍هड़͕͋Δ
    σϝϦοτ
    16

    View Slide

  17. Bengo4.com, Inc.
    17
    Answer
    ● OAS͔ΒԼهΛࣗಈੜ੒͠·͠ΐ͏
    ○ APIΫϥΠΞϯτ
    ○ APIςετ
    ● CIͰAPIςετΛ࣮ߦ
    ● ဃ཭͸ݫېͰ͢ɻ
    Question


    OASͱ࣮૷͕ဃ཭͍ͯ͠·͢ɻɻͲ͏͢Ε͹࣮૷ͱOAS
    ͕ҰॹʹͳΓ·͔͢ʁ

    View Slide

  18. OASΛ࠷େݶ׆༻ͯ͠
    ίʔυΛࣗಈੜ੒͢Δͷ͕
    εΩʔϚۦಈ։ൃ
    18

    View Slide

  19. OpenAPI Generator
    ͰAPIͱΫϥΠΞϯτΛܨ͙
    19

    View Slide

  20. Bengo4.com, Inc.
    ● OAS͔Βίʔυ࡞੒͢Δπʔϧ
    ○ APIΫϥΠΞϯτ& Mock Server
    ○ ରԠݴޠ/Framework 100Ҏ্
    ● Docker ίϯςφ͋Γ
    ● ੲSwagger Codegen͔ΒϑΥʔΫͨ͠
    ● ରԠݴޠ΋ଟ͘ɺDoc΋ஸೡ
    OpenAPI Generatorͱ͸
    20

    View Slide

  21. Bengo4.com, Inc.
    ● ໊લۭؒ͸σϑΥϧτͰࢦఆՄೳ
    ● templateΛ࢖͑͹ɺࣗ෼ͰclassΛఆٛͰ͖Δ
    APIΫϥΠΞϯτΛࣗಈੜ੒
    21
    $ openapi-generator-cli generate \


    --additional-properties=invokerPackage=Bengo4\\API\\Sdk, \


    variableNamingConvention=camelCase \


    -t ./templates/php \


    -i openapi.yaml -g php -o ./api/app/sdk

    View Slide

  22. Bengo4.com, Inc.
    ● APIΫϥΠΞϯτ
    ○ Guzzle Http
    ● σʔλϞσϧ
    ● ۭͷPHPUnitͷςετ
    ● υΩϡϝϯτ
    PHP: ੜ੒͞ΕΔίʔυ
    22
    .


    ├── README.md


    ├── composer.json


    ├── docs


    ├── lib


    │ ├── Api


    │ │ └── PetsApi.php


    │ ├── ApiException.php


    │ ├── Configuration.php


    │ ├── HeaderSelector.php


    │ ├── Model


    │ │ ├── Error.php


    │ │ ├── ModelInterface.php


    │ │ └── Pet.php


    │ └── ObjectSerializer.php


    ├── phpunit.xml.dist


    └── test


    ├── Api


    │ └── PetsApiTest.php


    └── Model


    ├── ErrorTest.php


    └── PetTest.php

    View Slide

  23. Bengo4.com, Inc.
    PHP: Data Model class -> __construct
    23
    class Pet implements ModelInterface, ArrayAccess,
    \JsonSerializable


    {


    public static function setters()


    public static function getters()


    public function __construct(array $data = null)


    public function listInvalidProperties()


    public function valid()


    public function getId()


    public function setId($id)
    .


    ├── README.md


    ├── composer.json


    ├── docs


    ├── lib


    │ ├── Api


    │ ├── ApiException.php


    │ ├── Configuration.php


    │ ├── HeaderSelector.php


    │ ├── Model


    │ │ ├── Error.php


    │ │ ├── ModelInterface.php


    │ │ └── Pet.php


    │ └── ObjectSerializer.php


    ├── phpunit.xml.dist


    └── test

    View Slide

  24. Bengo4.com, Inc.
    PHP: Data Model class -> __construct
    24
    /**


    * Constructor


    *


    * @param mixed[] $data Associated array of property values


    * initializing the model


    */


    public function __construct(array $data = null)


    {


    $this->container['id'] = $data['id'] ?? null;


    $this->container['name'] = $data['name'] ?? null;


    $this->container['tag'] = $data['tag'] ?? null;


    }


    View Slide

  25. Bengo4.com, Inc.
    PHP: Data Model class -> validation
    25
    public function listInvalidProperties()


    {


    $invalidProperties = [];


    if ($this->container['id'] === null) {


    $invalidProperties[] = "'id' can't be null";


    }


    if ($this->container['name'] === null) {


    $invalidProperties[] = "'name' can't be null";


    }


    return $invalidProperties;


    }

    View Slide

  26. Bengo4.com, Inc.
    PHP: PHPUnit Test
    26
    use PHPUnit\Framework\TestCase;


    class PetTest extends TestCase


    {


    /**


    * Test "Pet"


    */


    public function testPet()


    {


    // TODO: implement


    $this->markTestIncomplete('Not implemented');


    }
    .


    ├── README.md


    ├── composer.json


    ├── docs


    ├── lib


    ├── phpunit.xml.dist


    └── test


    ├── Api


    │ └── PetsApiTest.php


    └── Model


    ├── ErrorTest.php


    └── PetTest.php


    View Slide

  27. Bengo4.com, Inc.
    ● هड़͢΂͖ϓϩύςΟ


    ○ operationId(ؔ਺໊)


    ○ tags(Ϋϥε໊)


    ○ readonly


    ○ required


    ○ format(ਖ਼نදݱ)
    ៉ྷʹग़ྗ͞ΕΔOASͷॻ͖ํ
    27
    /pets/{petId}:


    get:


    operationId: showPetById


    tag:


    - pets


    parameters:


    - name: petId


    in: path


    required: true


    schema:


    type: integer

    View Slide

  28. 1. validateίϚϯυͰOASΛकΔ
    2. ࣗ෼ͷAPI ΫϥΠΞϯτͰϦΫΤετ&Ϩεϙϯ
    εΛറΔ
    OpenAPI GeneratorΛ࢖͏
    2ͭͷϙΠϯτ
    28

    View Slide

  29. Bengo4.com, Inc.
    ● OASΛνΣοΫ͢ΔvalidateίϚϯυ
    ● generateίϚϯυͷલʹ࣮ߦඞਢ
    ϙΠϯτ1.validateίϚϯυͰOASΛकΔ
    29
    $ openapi-generator-cli validate -i openapi.yaml


    Validating spec (openapi.yml)


    Errors:


    - attribute components.schemas.Response.desc is unexpected


    [error] Spec has 1 errors.

    View Slide

  30. Bengo4.com, Inc.
    ● APIΫϥΠΞϯτͷதʹ
    σʔλΫϥε͕ग़ྗ


    ● σʔλΫϥεΛ௨͢


    ○ ϦΫΤετ


    ○ Ϩεϙϯε


    ● ܕʹΑΔԸܙ
    ϙΠϯτ2.ࣗ෼ͷAPIΫϥΠΞϯτͰࣗ෼ΛറΔ
    30
    use Bengo4\App\Sdk\Model\Pet;


    class PetController {


    public function get() {


    $response = new Pet([


    'id' => 1,


    'name' => 'Doggie',


    ]);


    $this->withJson($response);


    }


    }

    View Slide

  31. OAS͔Β
    APIςετΛࣗಈੜ੒͢Δ
    31

    View Slide

  32. Bengo4.com, Inc.
    ۜͷ஄ؙ͸ͳ͍ͷͰɺཁ݅ʹ߹ΘͤͯબͿ
    ● OpenAPI Generator
    ● Postman
    ● Dredd
    APIςετΛࣗಈੜ੒͢Δબ୒ࢶ
    32
    ࣗ༝౓&
    ෳࡶੑ

    View Slide

  33. Bengo4.com, Inc.
    ● JavaͷMustacheͰهड़ͨ͠templateΛ࢖͏
    ● ࣗ༝ͳهड़͕Մೳ͕ͩɺෳࡶʹͳΔ
    OpenAPI GeneratorͰAPI Test
    33
    /**


    * Test case for {{{operationId}}}


    *


    * {{{summary}}}.


    */


    public function test{{vendorExtensions.x-test-operation-id}}()

    View Slide

  34. Bengo4.com, Inc.
    ● APIΛ։ൃ͢ΔͨΊͷϓϥοτϑΥʔϜ
    ○ APIΫϥΠΞϯτɺςετɺυΩϡϝϯτͷੜ੒
    ● Newmanͱͯ͠CLI΋ఏڙ
    ● OpenAPIΛPostmanͷJsonʹม׵Մೳ
    ○ ࣗಈͰςετΞαʔγϣϯ͸௥Ճͯ͘͠Εͳ͍
    ○ ϦΫΤετ͢Δ͚ͩ
    Postman
    34

    View Slide

  35. Bengo4.com, Inc.
    GUI: Postman Test
    35

    View Slide

  36. Bengo4.com, Inc.
    CLI: Newman Test
    36
    $ npm i newman


    $ npx newman run echo.postman_collection.json


    ❏ Auth: Digest


    ↳ Delete Cookies


    GET https://postman-echo.com/cookies/delete?foo1=&foo2= [200 OK, 364B, 1171ms]


    ✓ Status code is 302 or 200


    ✓ Body contains cookies


    1. Body contains cookie foo1


    2. Body contains cookie foo2


    ✓ foo1 cookie is set


    ✓ foo2 cookie is set


    View Slide

  37. Bengo4.com, Inc.
    ● API Buleprint͔ΒOpenAPIʹରԠ
    ○ OepnAPI v3͸·ͩExperimental feature
    ● ΦϒδΣΫτͷϓϩύςΟ΋νΣοΫ
    Dredd (Http API Testing Framework)
    37
    $ npm i dredd


    $ npx dredd ./pet2.yml localhost:4010


    fail: GET (200) / duration: 64ms


    info: Displaying failed tests...


    fail: GET (200) / duration: 64ms


    fail: body: At '/status' Missing required property: status

    View Slide

  38. PostmanͰ
    APIςετΛߏங͢Δ
    38

    View Slide

  39. Bengo4.com, Inc.
    PostmanςετͷྲྀΕ
    39
    OASΛॻ͘ TestγφϦΦ
    ΛJSͰbuild
    ςετ࣮ߦ

    View Slide

  40. Bengo4.com, Inc.
    ● APIϦΫΤετ࣌ʹϨεϙϯεͰςετՄೳ
    ● Postmanͷઃఆ͸Json͕ͩJavaScript͕ࠩ͠ࠐΊΔ
    ○ Chai
    Postman͸JSͰςετεΫϦϓτ͕ઃఆՄೳ
    40
    pm.test("εςʔλείʔυ͕ 200", function () {


    pm.response.to.have.status(200);


    });

    View Slide

  41. Bengo4.com, Inc.
    ྫ1. HTTPεςʔλείʔυ͚ͩςετ
    41
    const convertData = conversionResult.output[0].data;


    convertData.event.push({


    listen: "test",


    script: {


    exec: [


    'pm.test("Ϩεϙϯε͸εςʔλε ίʔυ͕ 200ܥ Ͱ੒ޭ", function () {',


    " pm.expect(pm.response.code).to.be.oneOf([200, 201, 202]);",


    "});",


    ],


    type: "text/javascript",


    },


    });


    View Slide

  42. Bengo4.com, Inc.
    ● Apideck͕ఏڙ͍ͯ͠ΔϥΠϒϥϦ
    ● OAS͔ΒࣗಈͰςετΞαʔγϣϯΛࠩ͠ࠐΉ
    ○ OAS v2, v3ରԠ(v3.1ඇରԠ)
    ྫ2.PortmanͰσʔλܕ΋ࡉ͔͘ςετ
    42
    ✓ [GET]::/pets/ - Status code is 2xx


    ✓ [GET]::/pets/ - Content-Type is application/json


    ✓ [GET]::/pets/ - Response has JSON Body


    ✓ [GET]::/pets/ - Schema is valid

    View Slide

  43. Bengo4.com, Inc.
    PortmanͰ૊ΈཱͯΒΕΔεΩʔϚݕূJS
    43
    // Response Validation


    const schema = {


    "type":"object","required":["status"],"properties":{"status":{"type":"string"}}


    }


    // Validate if response matches JSON schema


    pm.test("[GET]::/ - Schema is valid", function() {


    pm.response.to.have.jsonSchema(


    schema,


    {unknownFormats: ["int32", "int64", "float", "double"]}


    );


    });


    View Slide

  44. 1. શମͷίʔυςετઓུΛཱͯΔ
    2. ςετ࣮ߦ࣌ʹDIͰ෭࡞༻ΛݮΒ͢
    3. OASͰશͯͷσʔλʹexample
    APIςετΛࣗಈੜ੒͢ΔͨΊͷ
    3ͭͷϙΠϯτ
    44

    View Slide

  45. Bengo4.com, Inc.
    ● OAS͔Βࣗಈੜ੒Ͱ͖Δͷ͸ɺ200ܥͷ੒ޭςετ
    ○ 400ܥ΋࡞ΕΔ͕֦ு͕ඞཁ
    ● ͦΕͧΕͷςετͰΧόʔ͢ΔൣғΛܾΊΔ
    ○ ୯ମςετ
    ○ APIςετ
    ○ E2Eςετ
    ϙΠϯτ1. શମͷίʔυςετઓུΛཱͯΔ
    45

    View Slide

  46. Bengo4.com, Inc.
    ● ೝূ΍DBͳͲ͸DIͯ͠mock͢Δ
    ○ ςετ͕࢝ΊΔ࣌ɺσʔλ͸͍ͭ΋ಉ͡
    ○ τϥϯβΫγϣϯ͸ͬͯɺ࠷ޙʹϩʔϧόοΫ
    ● ॱংґଘ͕͓͖ͳ͍
    ϙΠϯτ2.1ͭͷςετ಺Ͱ෭࡞༻Λด͜͡ΊΔ
    46

    View Slide

  47. Bengo4.com, Inc.
    ● example͔ΒϦΫΤε
    τΛੜ੒͢ΔͨΊ
    ● ࣄલʹ༻ҙͨ͠Mock
    σʔλͱ߹ΘͤΔ
    ϙΠϯτ3.OASͰશͯͷσʔλʹexample
    47
    /pets/{petId}:


    get:


    operationId: showPetById


    parameters:


    - name: petId


    in: path


    example: 1


    required: true


    schema:


    type: integer

    View Slide

  48. ·ͱΊ
    48

    View Slide

  49. 1. OpenAPI GeneratorͰAPIΫϥΠΞϯτΛੜ੒ˍ
    ഑෍&ࣗ਎ΛറΔ
    2. APIςετΛࣗಈੜ੒&CIͰ࣮ߦ͢Δ
    εΩʔϚۦಈ։ൃΛ੒ޭͤ͞Δ
    ϙΠϯτ
    49

    View Slide

  50. Bengo4.com, Inc.
    ● ࢓༷͕࠷ॳʹ͔ͬ͠Γܾ·ΔͷͰΘ͔Γ΍͍͢
    ● OAS͔ΒίʔυΛࣗಈੜ੒Ͱ͖ɺ։ൃίετ࡟ݮ
    ○ API ΫϥΠΞϯτɺAPIςετɺυΩϡϝϯτ
    ● ίΞͷ࣮૷ʹूதͰ͖Δ
    εΩʔϚۦಈ։ൃΛ࢝Ί·ͤΜ͔ʁ
    50

    View Slide

  51. Bengo4.com, Inc.
    51
    એ఻
    ࠾༻΍ͬͯ·͢!!
    ͸ͯϒͰൃ৴ͯ͠·͢!!
    creators.bengo4.com

    View Slide

  52. END
    ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠
    52

    View Slide

  53. Bengo4.com, Inc.
    ● Npm Library
    ○ openapi-to-postmanv2 - npm
    ○ newman - npm
    ○ @apideck/portman - npm
    ○ dredd - npm
    ● OpenAPI
    ○ OpenAPI-Specification/petstore.yaml at main
    ○ What Is the Difference Between Swagger and OpenAPI?
    ○ What's the Difference Between OpenAPI 2.0, 3.0, and 3.1?
    ● OpenAPIͷΤίγεςϜ
    ○ OpenAPI.Tools
    ○ GitHub - Redocly/redoc
    ○ GitHub - OpenAPITools/openapi-generator
    ○ Prism | Open-Source HTTP Mock and Proxy Server | Stoplight
    ● Postman
    ○ Test script examples | Postman Learning Center
    ࢀߟURL
    53

    View Slide