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

はじめよう!Swift OpenAPI Generatorによるスキーマ駆動開発:導入手順と活用のコツ

kamimi
August 23, 2023

はじめよう!Swift OpenAPI Generatorによるスキーマ駆動開発:導入手順と活用のコツ

iOSDC 2023 のパンフレット原稿です。

Zenn により詳しい記事を書いているので、あわせてご覧ください。🤗
https://zenn.dev/kamimi01/scraps/ddad5212081b1a

kamimi

August 23, 2023
Tweet

More Decks by kamimi

Other Decks in Programming

Transcript

  1. ͸͡ΊΑ͏ʂ
    4XJGU0QFO"1*(FOFSBUPSʹΑΔ
    εΩʔϚۦಈ։ൃɿ
    ಋೖखॱͱ׆༻ͷίπ
    88%$Ͱ͸4XJGUಛԽͷ(FOFSBUPSɺ4XJGU0QFO"1*(FOFSBUPS͕ൃද͞Ε·͠
    ͨɻ4XJGU1BDLBHFQMVHJOͱͯ͠ఏڙ͞Εͨ͜ͱͰίʔυͷੜ੒͔Βར༻Λɺ9DPEFΛ཭
    ΕΔ͜ͱͳ͘εϜʔζʹߦ͑·͢ɻ
    ຊߘͰ͸4XJGU0QFO"1*(FOFSBUPSͷಋೖखॱ΍׆༻ͷίπΛ࣮ࡍͷίʔυΛަ͑ͯ͝঺
    հ͠·͢ɻ
    0QFO"1*ʹ͍ͭͯ

    0QFO"1*4QFDJ
    fi
    DBUJPOʢ0"4ʣ͸ɺ
    )551"1*ͷΠϯλʔϑΣʔεఆٛͰ͢ɻ
    αʔόʔαΠυͱͷ࢓༷ͷೝࣝ߹Θͤʹ΋ͬ
    ͍ͯ͜ͰɺυΩϡϝϯτ΍ίʔυੜ੒ͳͲΤ
    ίγεςϜ΋ॆ࣮͍ͯ͠·͢ɻ
    :".-΍+40/ܗࣜͰ"1*ͷ࢓༷Λهࡌ͢
    Δ͜ͱ͕Ͱ͖·͢ɻ
    4XJGU0QFO"1*(FOFSBUPSͱ͸

    88%$Ͱ͸4XJGUಛԽͷ(FOFSBUPSɺ
    4XJGU0QFO"1*(FOFSBUPS͕ൃද͞Ε·͠
    ͨɻ
    4XJGU1BDLBHFQMVHJOͱͯ͠ఏڙ͞Εͨ͜
    ͱͰίʔυͷੜ੒͔Βར༻Λɺ9DPEFΛ཭
    ΕΔ͜ͱͳ͘εϜʔζʹߦ͏͜ͱ͕Ͱ͖·
    ͢ɻ
    4XJGU1BDLBHFQMVHJO͸ɺࡢ೥ͷ
    88%$Ͱൃද͞Εͨ4XJGU1BDLBHF·
    ͨ͸9DPEF্Ͱಈ͘4XJGUεΫϦϓτͰ
    ͢ɻࠓ·ͰΞϓϦଆͰ͸#VJME1IBTFTͰ
    3VO4DSJQUΛ࣮ߦ͢Δ͜ͱ͕Ͱ͖·ͨ͠
    ͕ɺ4XJGU1BDLBHFͰ͸Ϗϧυલ΍Ϗϧυத
    ʹ࣮ߦ͢ΔॲཧΛఆٛͰ͖·ͤΜͰͨ͠ɻ
    QMVHJOͷηογϣϯͰ͸Ϣʔεέʔεͱ͠
    ͯɺίʔυੜ੒΍ϦϦʔε࡞ۀͷࣗಈԽͳͲ
    ͕঺հ͞Ε͍ͯ·ͨ͠ɻ
    ࠓճͷ4XJGU0QFO"1*(FOFSBUPSͷొ৔
    Ͱɺࡢ೥ͷൃද͸͜ͷͨΊͩͬͨͷ͔ͱ఺͕
    ઢʹͳΔײ֮ΛຯΘ͍·ͨ͠ɻ
    4XJGU1BDLBHF(FOFSBUPS͸0QFO"1*
    5PPMT͕ఏڙ͍ͯ͠Δ0QFO"1*(FOFSBUPS
    ͳͲଞͷࣗಈੜ੒πʔϧͱൺֱ͢Δͱಋೖ΍
    ར༻ͷख͕ܰ͞ϝϦοτͰ͢ɻ
    ࣮ࡍʹ࢖ͬͯΈΔ

    ࠓճ͸ΫϥΠΞϯτίʔυͷੜ੒ɺͭ·Γ
    J04ΞϓϦͰ࢖༻͞ΕΔ4XJGUίʔυͷੜ੒
    Λߦ͍·͢ɻ
    αʔόʔαΠυͰ࢖༻͢Δ4XJGUίʔυͷੜ
    ੒΋ՄೳͰ͕͢ɺࢴ໘ͷ౎߹্ׂѪ͠·͢ɻ
    ಋೖฤ

    kamimi / ͔ΈΈ
    (גࣜձࣾϠϓϦ)

    Twitterɿ@kamimi_01

    GitHubɿ@kamimi01

    View Slide

  2. 1BDLBHF%FQFOEFODJFTʹ̏ͭͷґଘ
    ؔ܎Λ௥Ճ͢Δ
    ͭ໨͸4XJGU1BDLBHF(FOFSBUPSຊମ
    Ͱ͢ɻ࣮ࡍʹίʔυΛੜ੒͢ΔͨΊͷϩδο
    Ϋ͕࣮૷͞Ε͍ͯ·͢ɻ
    IUUQTHJUIVCDPNBQQMFTXJGUPQFOBQJ
    HFOFSBUPS
    ͭ໨͸4XJGU0QFO"1*3VOUJNFͰ͢ɻ
    ੜ੒͞Εͨίʔυ͔Β࢖༻͞ΕΔڞ௨ͷܕ΍
    ந৅ԽΛఏڙ͍ͯ͠·͢ɻ
    IUUQTHJUIVCDPNBQQMFTXJGUPQFOBQJ
    SVOUJNF
    ͭ໨͸4XJGU0QFO"1*63-4FTTJPOͰ
    ͢ɻੜ੒͞Εͨίʔυ͸ಛఆͷ)551ΫϥΠ
    ΞϯτϥΠϒϥϦʹґଘ͍ͯ͠ͳ͍ͷͰɺඥ
    ෇͚͍ͨϥΠϒϥϦΛબ୒͢Δ͜ͱ͕Ͱ͖·
    ͢ɻࠓճ͸J04ΞϓϦͷΫϥΠΞϯτίʔυ
    Λੜ੒͢ΔͷͰ͜ΕΛબͼ·͢ɻ
    IUUQTHJUIVCDPNBQQMFTXJGUPQFOBQJ
    VSMTFTTJPO
    #VJME1IBTFTʹॲཧΛ௥Ճ͢Δ
    ࣍ʹ#VJME1IBTFTͷ3VO#VJME5PPM
    1MVHJOTʹɺ0QFO"1*(FOFSBUPSΛ௥Ճ͠
    ·͢ɻ͜ΕͰίʔυͷੜ੒͕ίϯύΠϧͷલ
    ʹߦΘΕ·͢ɻ
    0QFO"1*ͷυΩϡϝϯτ̎ͭΛ௥Ճ͢Δ
    ࣍ʹίʔυੜ੒ͷͨΊͷॏཁͳ̎ͭͷυΩϡ
    ϝϯτΛϓϩδΣΫτʹ௥Ճ͠·͢ɻ
    ͭ໨͸ɺ:".-·ͨ͸+40/ܗࣜͷ
    0QFO"1*υΩϡϝϯτͰ͢ɻ
    ͭ໨͸ɺPQFOBQJHFOFSBUPSDPO
    fi
    HZBNM
    Ͱ͢ɻ͜Εʹ͸ҎԼͷΑ͏ʹੜ੒͢Δίʔυ
    ͷछྨΛఆٛ͠·͢ɻ
    QMVHJOΛ৴པ͢Δ
    ॳΊͯQMVHJOΛ࢖༻͢Δͱ͖͸ɺ9DPEF্
    ͰҎԼͷը໘͕දࣔ͞ΕΔͷͰʮ5SVTU
    &OBCMF"MMʯΛબ୒͠·͢ɻ
    Ϗϧυ͢Δ
    ͜͜·Ͱ׬ྃͨ͠ΒϏϧυ͠·͢ɻ
    ϏϧυϩάΛ֬ೝ͢Δͱɺ(FOFSBUPS͕Ͳͷ
    Α͏ͳઃఆͰίʔυੜ੒·Ͱߦ͓͏ͱ͍ͯ͠
    Δ͔͕Θ͔Γ·͢ɻ
    0QFO"1*υΩϡϝϯτͷఆ͕ٛਖ਼͘͠ͳ͍
    ৔߹΍ඞཁͳϑΝΠϧ͕ଘࡏ͠ͳ͍ͱ͍ͬͨ
    ͜ͱ͕ݪҼͰϏϧυΤϥʔʹͳͬͨ৔߹ɺϩ
    άΛ֬ೝ͢Δ͜ͱͰσόοά͕ḿΓ·͢ɻ
    generate:
    - types
    - client

    View Slide

  3. ·ͨίʔυ͸%FSJWFE%BUBʹੜ੒͞ΕΔͨ
    ΊɺHJUJHOPSFʹؚΊΔͳͲ͢Δඞཁ͸͋Γ
    ·ͤΜɻ
    ͜ΕͰಋೖ׬ྃͰ͢ʂQMVHJOͰఏڙ͞Εͯ
    ͍Δ͜ͱʹΑΓɺ9DPEFΛ཭ΕΔ͜ͱͳ͘
    εϜʔζʹಋೖͰ͖·ͨ͠ɻ
    ίʔυར༻ฤ

    ·ͣ͸ඞཁͳϞδϡʔϧΛΠϯϙʔτ͠·
    ͢ɻ
    ࣍ʹੜ੒͞Εͨίʔυ͕ఏڙ͢Δ$MJFOUͱ
    ͍͏ܕΛੜ੒͠·͢ɻ͜͜Ͱ͸Ҿ਺ʹαʔ
    όʔͷ63-ͱ࣮ࡍʹωοτϫʔΫΛ࢖༻͠
    ͯ)551ૢ࡞Λߦ͏)551ϥΠϒϥϦΛ౉
    ͠·͢ɻ$MJFOU͸"1*1SPUPDPMʹ४ڌͯ͠
    ͓Γɺ͜ͷͭҎ֎ʹ΋ड͚औΕΔҾ਺͕͋
    Γ·͢ɻଞͷҾ਺ʹ͍ͭͯ͸ޙड़͠·͢ɻ
    ࣗಈੜ੒ʹΑͬͯ0QFO"1*υΩϡϝϯτͷ
    QBUITͰఆٛͨ͠ϝιουʢ͜͜Ͱ͸
    UFYU$PNQMFUJPOTϝιουʣ͕ੜ੒͞Ε͍ͯ
    ΔͷͰɺͦΕΛݺͼग़͠·͢ɻ
    ࠓճݺͼ͍ͨ"1*͸1045ϝιουͰϦΫ
    ΤετϘςΟ͕ଘࡏ͢ΔͷͰҾ਺ʹ౉͍ͯ͠
    ·͢ɻ͜͜Ͱ͸লུ͠·͕͢ɺྫ֎Λ౤͛Δ
    ͜ͱ͕͋ΔͷͰ࣮ࡍ͸EPDBUDIจΛ࢖༻͢
    ΔͳͲ͍ͯͩ͘͠͞ɻ
    ࣍ʹϨεϙϯεͷॲཧͰ͢ɻϨεϙϯε͸ɺ
    FOVNͰ࣮૷͞Ε͍ͯ·͢ɻTXJUDIจͰέʔ
    ε͝ͱʹॲཧΛ͍͖ͯ͠·͢ɻ
    ࣮૷͸͜ΕͰ׬ྃͰ͢ʂ
    Ҿ͔͔ͬͬͨϙΠϯτ

    4XJGU0QFO"1*(FOFSBUPSʹ͸஫ҙ͢΂͖
    ϙΠϯτ͕ෳ਺͋Γ·͢ɻ࣮ࡍʹ௚໘ͨ͠
    ͭΛ঺հ͠·͢ɻ
    ϙΠϯτ0QFO"1*υΩϡϝϯτͷॻ
    ͖͔ͨ
    ίʔυͷࣗಈੜ੒ʹ͓͍ͯ0QFO"1*υΩϡ
    ϝϯτΛਖ਼͘͠ॻ͘͜ͱ͸ඇৗʹॏཁͰ͢ɻ
    4XJGU0QFO"1*(FOFSBUPSͷݪଇʹ΋
    ʮ'BJUIGVMMZSFQSFTFOUUIF0QFO"1*
    EPDVNFOUʯͭ·Γʮ0QFO"1*υΩϡϝϯ
    τΛ஧࣮ʹදݱ͢Δʯ͕ڍ͛ΒΕ͍ͯ·͢ɻ
    ͨͩ0QFO"1*ͷυΩϡϝϯτ͸ඞਢͷ߲໨
    ͕ଟ਺ଘࡏ͍ͯͨ͠Γɺ(FOFSBUPSಛ༗ͷॻ
    ͖ํͷίπ͕͋ΔͳͲิॿͳ͠Ͱॻ͘͜ͱ͸
    ೉͍͠ͱࢥ͍·͢ɻͦ͜Ͱ͜͜Ͱ͸ͳΔ΂͘
    // লུ
    switch response {
    case let .ok(okResponse):
    switch okResponse.body {
    case .json(let json):
    print(json)
    }
    case let .unauthorized(error):
    print("unauthorized: \(error)")
    case let .badRequest(json):
    print("bad request:", json)
    case .undocumented(statusCode: let
    statusCode, let payload):
    print("undocumented: \
    (statusCode)\n \(payload)")
    }
    self.client = Client(
    ɹɹɹɹserverURL: try!
    Servers.server1(),
    ɹɹɹɹtransport: URLSessionTransport()
    )
    let response = try await
    client.textCompletions(body: body)
    import OpenAPIRuntime
    import OpenAPIURLSession

    View Slide

  4. ਖ਼͍͠υΩϡϝϯτΛఆٛ͢ΔͨΊͷ޻෉Λ
    ঺հ͠·͢ɻ
    74$PEFͰ֦ுػೳΛ׆༻͠ͳ͕Βॻ͘
    ࠷ॳͷҰาͱͯ͠9DPEFΛ࢖Θͣɺ
    0QFO"1*ͷυΩϡϝϯτΛॻ͘؀ڥ͕੔͑
    ΍͍͢*%&Ͱॻ͘͜ͱΛ͓קΊ͠·͢ɻ
    ࢲ͸74$PEFͰॻ͘͜ͱ͕΄ͱΜͲͰ͢ɻ
    :".-ͷ৔߹ɺ9DPEFΑΓγϯλοΫεϋ
    ΠϥΠτ͕ޮ͖·͢ɻ·ͨ74$PEFͰ͸
    0QFO"1*ͷυΩϡϝϯτΛॻͨ͘Ίʹศར
    ͳ֦ுػೳ͕͍͔ͭ͋͘Γ·͢ɻ
    ͓͢͢Ί͸ҎԼͰ͢ɻ
    ɾ4XBHHFS7JFXFS
    ɾPQFOBQJMJOU
    ɾ0QFO"1* 4XBHHFS
    &EJUPS
    ɾ0QFO"QJ4OJQQFUT
    4QFDUSBMͷΑ͏ͳ$-*πʔϧΛ࢖༻͢Δ
    ผͷํ๏ͱͯ͠ɺ4QFDUSBMͱ͍͏OQNϞ
    δϡʔϧΛ࢖͏͜ͱ΋Ͱ͖·͢ɻ
    ҎԼͷίϚϯυΛ࣮ߦ͢Δ͜ͱͰɺυΩϡϝ
    ϯτͷόϦσʔγϣϯΛͯ͘͠Ε·͢ɻ
    0QFO"1*,JUΛ࢖͍ɺ4XJGUͰόϦσʔτ
    ͢Δ
    4XJGUͰόϦσʔτΛߦ͏ͱ͍͏ํ๏΋͋Γ
    ·͢ɻ
    0QFO"1*,JUͱ͍͏ϥΠϒϥϦΛ࢖ͬͯ
    0QFO"1*υΩϡϝϯτͷόϦσʔγϣϯΛ
    ͢Δ͜ͱ͕ՄೳͰ͢ɻ
    ͜ͷϥΠϒϥϦ͸4XJGU0QFO"1*
    (FOFSBUPS΋಺෦Ͱ࢖༻͍ͯ͠ΔϥΠϒϥϦ
    Ͱ͢ɻϝΠϯͷػೳ͸0QFO"1*υΩϡϝϯ
    τͷΤϯίʔυɺσίʔυΛߦ͏͜ͱͰ͢
    ͕ɺόϦσʔγϣϯ΋ՄೳͰ͢ɻ
    ಛ௃͸4XJGUͰόϦσʔγϣϯΛ࣮૷͢Δ͜
    ͱ͕Ͱ͖Δ͜ͱͰ͢ɻࢴ໘ͷ౎߹্ৄ͘͠આ
    ໌Ͱ͖·ͤΜ͕ɺผهࣄͰ࣮૷ʹ͍ͭͯղઆ
    ͍ͯ͠·͢ͷͰɺຊߘ·ͱΊͷ23ίʔυ͔Β
    ͥͻ֬͝ೝ͍ͩ͘͞ɻ
    ϙΠϯτࣗಈੜ੒͕ະରԠͷ৔߹ͷ࣮

    4XJGU0QFO"1*(FOFSBUPS͸ͱͯ΋ศརͰ
    ͕͢ɺ·ͩαϙʔτ͍ͯ͠ͳ͍͜ͱ΋ଟ͋͘
    Γ·͢ɻ͜͜Ͱ͸ͦͷ͏ͪͷҰͭͱͦͷରԠ
    ํ๏ͷ࣮ྫΛ঺հ͠·͢ɻ
    0QFO"1*ͷఆٛʹ͸TFDVUJSZ4DIFNFTͱ
    ͍͏"1*ͷೝূʢ#BTJDೝূɺ0"VUIͳ
    ͲʣΛఆٛ͢Δ߲໨͕ଘࡏ͠·͕͢ɺ4XJGU
    0QFO"1*(FOFSBUPS͸·ͩରԠ͓ͯ͠Βͣ
    ͦΕΒͷίʔυ͸ࣗಈੜ੒͞Ε·ͤΜɻ
    Ͱ͸ࣗಈੜ੒ίʔυͷ࢖༻͸ఘΊͳ͚Ε͹͍
    ͚ͳ͍ͷ͔ͱ͍͏ͱͦΜͳ͜ͱ͸ͳ͘ɺ޾͍
    ʹ΋ରԠํ๏͕͋Γ·͢ɻͦΕ͕ϛυϧ΢Σ
    ΞΛ࢖༻͢Δํ๏Ͱ͢ɻ
    $MJFOU.JEEMFXBSFͱ͍͏ϓϩτίϧʹ४ڌ
    ͯ͠JOUFSDFQUϝιουΛ࣮૷͢Δ͜ͱͰ
    )551ϦΫΤετʹ్தͰׂΓࠐΈɺॲཧΛ
    ஫ೖ͢Δ͜ͱ͕Ͱ͖·͢ɻ
    npx spectral lint openapi.yaml

    View Slide

  5. ҎԼͷΑ͏ʹ࣮૷͠·͢ɻ
    ͦͯ͠$MJFOUΛॳظԽ͢Δͱ͖ʹΦϓγϣφ
    ϧͷҾ਺Ͱ͋ΔNJEEMFXBSFTʹ౉͠·͢ɻ
    ഑ྻΛड͚औΔͷͰɺෳ਺ͷϛυϧ΢ΣΞΛ
    ࣮૷ͯ͠౉͢͜ͱ͕Ͱ͖·͢ɻ
    ͜ͷΑ͏ʹࣗಈੜ੒ʹରԠ͍ͯ͠ͳ͍έʔε
    ͕͋ͬͯ΋ɺ4XJGU0QFO"1*(FOFSBUPSͷ
    ϧʔϧʹԊ࣮ͬͨ૷Λ஫ೖ͢Δ͜ͱͰɺࣗಈ
    ੜ੒ίʔυΛ࢖͍ͳ͕Β(FOFSBUPSͷະର
    Ԡ෦෼ΛΧόʔ͢Δ͜ͱ͕Ͱ͖·͢ɻ
    import Foundation
    import OpenAPIRuntime
    final class AuthMiddleware: ClientMiddleware {
    func intercept(
    _ request: OpenAPIRuntime.Request,
    baseURL: URL, operationID: String,
    next: (OpenAPIRuntime.Request, URL)
    async throws -> OpenAPIRuntime.Response
    ) async throws -> OpenAPIRuntime.Response {
    var request = request
    request.headerFields.append(.init(
    name: "Authorization", value:
    "Basic "
    ))
    return try await next(request, baseURL)
    }
    }
    self.client = Client(
    serverURL: try! Servers.server1(),
    transport: URLSessionTransport(),
    middlewares: [AuthMiddleware()]
    )

    View Slide

  6. ·ͱΊ
    ͜͜·Ͱ͓ಡΈ͍͖ͨͩ͋Γ͕ͱ͏͍͟͝·͢ʂ

    4XJGU1BDLBHFQMVHJOͷϏϧυπʔϧϓϥάΠϯͱͯ͠࢖༻Մೳͳ͜ͱͰɺ4XJGU0QFO"1*
    (FOFSBUPSͷಋೖ͸ඇৗʹ؆୯Ͱͨ͠ɻ࢖͍׳Ε͍ͯΔ9DPEF͔Β཭Εͣʹಋೖɾར༻͕Մ
    ೳͳͷ͸J04ΤϯδχΞʹͱͬͯخ͍͜͠ͱͩͱࢥ͍·͢ɻ
    ;FOOʹຊߘͷݪߘ΍ΑΓৄࡉͳղઆهࣄɺࢀߟจݙͷϦϯΫΛ·ͱΊ͍ͯ·͢ɻ
    ͜͜Ͱ͸આ໌͖͠Εͳ͔ͬͨ$MJFOU.JEEMFXBSFΛ࢖༻ͨ͠ผͷϢʔε
    έʔε΍0QFO"1*,JUͷόϦσʔγϣϯϝιουͷ࢖͍ํͳͲΛղઆ͠
    ͍ͯ·͢ɻͥͻ͝ཡ͍ͩ͘͞ɻίϝϯτ΍ײ૝͓଴͍ͪͯ͠·͢ɻ

    ͜ΕΛػʹ4XJGU0QFO"1*(FOFSBUPSʹڵຯΛ࣋ͬͯͩ͘͞Δํ͕૿
    ͑Ε͹޾͍Ͱ͢ɻ
    ஶऀʹ͍ͭͯ
    struct Kamimi {
    var organization: String = "Yappli, Inc."
    let twitter: String = "kamimi_01"
    let github: String = "kamimi01"
    var approvedProposals = [
    "͸͡ΊΑ͏ʂSwift OpenAPI GeneratorʹΑΔεΩʔϚۦಈ։ൃɿಋೖखॱͱ׆༻ͷίπ",
    "جૅ͔Βཧղ͢Δʂདྷ೥य़·ͰʹରԠ͢΂͖ϓϥΠόγʔͷมߋ఺"
    ]
    }

    View Slide