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

はじめよう!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
  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
  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
  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
  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 <token>" )) return try await next(request, baseURL) } } self.client = Client( serverURL: try! Servers.server1(), transport: URLSessionTransport(), middlewares: [AuthMiddleware()] )
  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ʹΑΔεΩʔϚۦಈ։ൃɿಋೖखॱͱ׆༻ͷίπ", "جૅ͔Βཧղ͢Δʂདྷ೥य़·ͰʹରԠ͢΂͖ϓϥΠόγʔͷมߋ఺" ] }