API Blueprint速習会 @Wantedly

API Blueprint速習会 @Wantedly

Wantedly( https://www.wantedly.com/ )が行っている社内速習会

9bf153d8c0ce36ba6d9d20c2914b70f4?s=128

Go Takagi

May 12, 2016
Tweet

Transcript

  1. API Blueprint ଎शձ @Wantedly଎शձ Go Takagi github:@shimastripe 2016/5/12

  2. ຊεϥΠυίʔυ • https://github.com/shimastripe/apib-sample 

  3. Web API Documentation 

  4. ࡞Δͷ͕໘౗͍͘͞ 

  5. ໘౗ͳཁҼ • ࢓༷ॻͷϑΥʔϚοτ͕ఆ·͍ͬͯͳ͍ɻ • JSON Schemaʁ Swaggerʁ • ࣅͨΑ͏ͳ͜ͱΛ܁Γฦ͠ॻ͘͜ͱ΋ଟ͍ •

    ։ൃ༻ͷϞοΫ΋͍Δ • υΩϡϝϯτͱݱঢ়ͱͷ੔߹ੑ(maintenance) • υΩϡϝϯτ͸͋Δ͕ɺ·࣮ͩࡍʹಈ͘΋ͷ͕࣮૷͞Ε͍ͯͳ͍ 
  6. API Blueprint 

  7. API Blueprint • MarkdownͰهड़͞ΕͨWeb API(ಛʹRESTful ͳAPI)ͷ࢓༷Λղऍͯ͘͠ΕΔ • ਓ͕ಡΈ΍͍͢ʂ • ԿΛ࡞ΕΔ͔

    • υΩϡϝϯτ(aglio) • ϞοΫαʔόʔ(api-mock) • ςετ࣮ߦ(dredd) • ·ͱΊͯੜ੒Ͱ͖Δ͔ΒapibϑΝΠϧΛ؅ཧ͢Δ͚ͩͰࡁΉ 
  8. ࠓ೔ͷ͓͠ͳ͕͖ • apiblueprintΛମݧʂ • Hello world! • γϯϓϧͳRestAPIΛհͯ࣍͠ͷ3ͭΛ΍ͬͯΈΔ • υΩϡϝϯτ

    • ϞοΫαʔόʔ • ؆୯ͳςετ • rails gͰapibϑΝΠϧΛੜ੒͢ΔGemͷ঺հ 
  9. Install Battle λʔϛφϧͰҎԼΛೖྗ $ gem install rails dredd_hooks $ brew

    install node npm $ npm install -g aglio api-mock dredd • API Blueprint͸࢓༷ॻͳͷͰॲཧܥ͸ผ • ࠓճ͸Node.jsΛ࢖࣮ͬͯ૷͞Εͨaglioͱapi-mockͱdreddΛར༻ • aglio υΩϡϝϯτੜ੒ • api-mock ϞοΫαʔόʔੜ੒ • dredd ςετ࣮ߦ 
  10. Editor Package • Sublimetext • API Blueprint
 https://github.com/apiaryio/api-blueprint-sublime-plugin • Atom

    • language-api-blueprint
 https://github.com/danielgtaylor/atom-language-api-blueprint • Vim • apiblueprint.vim
 https://github.com/kylef/apiblueprint.vim 
  11. Hello world! 

  12. Hello world! • ͜Ε͸/messageʹGETϝιουΛ࣮ߦ͢Δͱ Content-Type͕text/plainͷ"Hello, World!"͕ฦΔ $ cat << 'EOF'

    > helloworld.apib # GET /message + Response 200 (text/plain; charset=utf-8) Hello, World! EOF 
  13. υΩϡϝϯτੜ੒ 

  14. υΩϡϝϯτੜ੒ • 2௨Γͷख๏ • HTMLʹม׵ $ aglio -i helloworld.apib -o

    helloworld.html  • Preview serverΛཱͯΔ • apibϑΝΠϧΛsave͢Δͨͼʹੜ੒͠ͳ͓ͯ͘͠ΕΔ • save࣌ʹจ๏Τϥʔ΋νΣοΫͯ͘͠ΕΔ $ aglio -i helloworld.apib -s
  15. υΩϡϝϯτੜ੒ ͱͯ΋؆୯Ͱ៉ྷʂ 

  16. ϞοΫΞοϓαʔόʔΛཱͯΔ 

  17. ϞοΫΞοϓαʔόʔΛཱͯΔ • api-mockίϚϯυʹ.apibϑΝΠϧΛ౉͢ $ api-mock helloworld.apib  • ͜ΕͰlocalhost:3000ʹϞοΫαʔόʔ͕ىಈ •

    ΞΫηεͯ͠ΈΔͱςΩετσʔλͰhello world!ͱग़ྗ͞ΕΔ • (http://localhost:3000/message)
  18. Restful API 

  19. Restful API GET /message  • HTTPϝιου͸ϦιʔεΛͲ͏ૢ࡞͍͔ͨ͠Λද͢ • ϦιʔεɿWeb্ʹଘࡏ͢Δʮ৘ใʯ •

    URL͸Ϧιʔεͷ໊લ • APIͷॲཧͷ݁Ռ͸ɺεςʔλεɾίʔυͰද͢ • ͜ͷ৔߹͸/messageʹGETͱ͍͏ૢ࡞Λ͍ͨ͠
  20. Restful API  ۩ମతͳΞΫγϣϯ • ϦΫΤετ(Request)ͯ͠ • αʔόʔॲཧͯ͠ • Ϩεϙϯε(Response)͕ฦΔ

  21. user API • ࠓճ͸Ruby on RailsΛ༻͍ͯɺγϯϓϧͳRestfulAPIΛ࡞੒͢Δ • ·ͣ͸RailsͰҎԼΛ࣮ߦ $ rails

    new apib-sample --api -T $ cd apib-sample $ rails g scaffold User name age:integer admin:boolean $ rake db:migrate  • --api͸APIϞʔυ (apiʹؔ͠ͳ͍ϑΝΠϧΛੜ੒͠ͳ͍) • -T͸--skip-test-unit (TestUnitΛ૊Έࠐ·ͳ͍) • rails g scaffold • σʔλϕʔε΁ͷςʔϒϧ΁ͷొ࿥(CREATE)ɺࢀর(READ)ɺߋ৽(UPDATE)ɺ࡟আ ʢDELETEʣͱ͍͏CRUDΛߦ͏WebΞϓϦέʔγϣϯͷͻͳܕͱͳΔίʔυΛࣗಈੜ੒ɻ
  22. user API  VTFS"1*ͷॲཧ಺༰ ର৅ͱͳΔϦιʔε ࣮ߦΞΫγϣϯ VTFSΛ௥Ճ͢Δ VTFSશମ 1045VTFST VTFSΛશͯऔಘ͢Δ

    VTFSશମ (&5VTFST *EͱҰக͢ΔVTFSΛऔಘ͢Δ *EͱҰக͢ΔVTFS (&5VTFST\JE^ *EͱҰக͢ΔVTFSΛߋ৽͢Δ *EͱҰக͢ΔVTFS 165VTFST\JE^ *EͱҰக͢ΔVTFSΛ࡟আ͢Δ *EͱҰக͢ΔVTFS %&-&5&VTFST\JE^
  23. user API  VTFS"1*ͷॲཧ಺༰ ॲཧͷ݁ՌฦΔ)551Ϩεϙϯε εςʔλείʔυ #PEZ VTFSΛ௥Ճ͢Δ  ௥Ճͨ͠σʔλ

    VTFSΛશͯऔಘ͢Δ  ର৅σʔλ *EͱҰக͢ΔVTFSΛऔಘ͢Δ  ର৅σʔλ *EͱҰக͢ΔVTFSΛߋ৽͢Δ  ߋ৽ͨ͠σʔλ *EͱҰக͢ΔVTFSΛ࡟আ͢Δ  ۭ
  24. user API • ద౰ʹuserΛ௥Ճ͢Δ $ rails console irb(main):001:0> User.create(name: "Go

    Takagi", age: 22, admin: true) irb(main):002:0> User.create(name: "Dean Fujioka", age: 35, admin: false) irb(main):003:0> User.create(name: "Kasumi Arimura", age: 23, admin: false) 
  25. user API • RailsΛ্ཱͪ͛ͯΞΫηεͯ͠ΈΔ $ rails server => Booting Puma

    => Rails 5.0.0.rc1 application starting in development on http://localhost:3000 => Run `rails server -h` for more startup options  • jsonϑΝΠϧ͕ฦ͖ͬͯͨʂ
  26. API BlueprintϑΝΠϧͷ࡞੒ 

  27. Լ४උ 

  28. user.apib • ͔͜͜Β͸࡞ۀσΟϨΫτϦΛมߋ͢Δ • apib-sample/apibϑΥϧμΛ࡞੒ͯ͠ɺ user.apibϑΝΠϧΛੜ੒ 

  29. user.apib  FORMAT: 1A HOST: http://localhost:3000 # user API Welcome

    to the user API. # users action [/users] ## Create user [POST] ## Get users [GET] # user action [/users/{id}] ## Get user [GET] ## Update user [PUT] ## Delete user [DELETE] # Data Structures ## users (object)
  30. user.apib 

  31. Header Section 

  32. Metadata section • API Blueprint͸υΩϡϝϯτͷઌ಄σʔλ͕ϝλσʔλ ηΫγϣϯͱͳ͍ͬͯΔ • কདྷతͳ֦ுͳͲʹඋ͑ͯόʔδϣϯ৘ใΛهࡌͰ͖Δ • ݱࡏ͸1A

    • ఏڙ͢Δαʔόʔͷϗετ΋هड़Մೳ  FORMAT: 1A HOST: http://localhost:3000 # user API Welcome to the user API. # users action [/users] ## Create user [POST] ## Get users [GET] # user action [/users/{id}] ## Get user [GET] ## Update user [PUT] ## Delete user [DELETE] # Data Structures ## users (object)
  33. API name & overview section • # user API ͕APIͷ໊শ

    • ԼʹAPIશମͷઆ໌͕ॻ͚Δ  FORMAT: 1A HOST: http://localhost:3000 # user API Welcome to the user API. # users action [/users] ## Create user [POST] ## Get users [GET] # user action [/users/{id}] ## Get user [GET] ## Update user [PUT] ## Delete user [DELETE] # Data Structures ## users (object)
  34. Resource sections 

  35. Resource sections • ઃܭॻͷϝΠϯ • ۩ମతͳશମ૾͸ӈͷΑ͏ʹͳΔ • Ϧιʔε͝ͱʹݟग़͠ͰάϧʔϓΛ͘͘Γɺ HTTPϝιουΛҰͭԼͷϨϕϧͰॻ͘ 

    FORMAT: 1A HOST: http://localhost:3000 # user API Welcome to the user API. # users action [/users] ## Create user [POST] ## Get users [GET] # user action [/users/{id}] ## Get user [GET] ## Update user [PUT] ## Delete user [DELETE] # Data Structures ## users (object)
  36. Get users [GET /users] 

  37. (࠶ܝ)user API  VTFS"1*ͷॲཧ಺༰ ର৅ͱͳΔϦιʔε ࣮ߦΞΫγϣϯ VTFSΛ௥Ճ͢Δ VTFSશମ 1045VTFST VTFSΛશͯऔಘ͢Δ

    VTFSશମ (&5VTFST *EͱҰக͢ΔVTFSΛऔಘ͢Δ *EͱҰக͢ΔVTFS (&5VTFST\JE^ *EͱҰக͢ΔVTFSΛߋ৽͢Δ *EͱҰக͢ΔVTFS 165VTFST\JE^ *EͱҰக͢ΔVTFSΛ࡟আ͢Δ *EͱҰக͢ΔVTFS %&-&5&VTFST\JE^
  38. (࠶ܝ)user API  VTFS"1*ͷॲཧ಺༰ ॲཧͷ݁ՌฦΔ)551Ϩεϙϯε εςʔλείʔυ #PEZ VTFSΛ௥Ճ͢Δ  ௥Ճͨ͠σʔλ

    VTFSΛશͯऔಘ͢Δ  ର৅σʔλ *EͱҰக͢ΔVTFSΛऔಘ͢Δ  ର৅σʔλ *EͱҰக͢ΔVTFSΛߋ৽͢Δ  ߋ৽ͨ͠σʔλ *EͱҰக͢ΔVTFSΛ࡟আ͢Δ  ۭ
  39. Get users [GET /users]  ## Get users [GET] Returns

    user list. + Response 200 (application/json; charset=utf-8) + Attributes (array) + Attributes (object) + id: 1 (number) - Id + name: NAME (string) + age: 1 (number) + admin: false (boolean) + created_at: `2000-01-01 00:00:00` (string) - CreatedTime + updated_at: `2000-01-01 00:00:00` (string) - UpdatedTime • #ͷ௚ޙʹ໊শɺԼʹઆ໌Λॻ͚Δ • action͸”+”Λ༻͍ͯListͰॻ͍͍ͯ͘ • ࠓճ͸഑ྻΛฦ͢Response 200
  40. Get users [GET /users] 

  41. Action sections 

  42. Header • ੜ੒͞ΕͨυΩϡϝϯτͰ͸Headerʹapplication/json; charset=utf-8͕ࢦఆ͞Ε͍ͯΔ • ΑΓઃఆ͕ඞཁͳΒ+Attributesͱಉ͡֊૚ʹ+HeadersͰهࡌ  + Response 200

    (application/json; charset=utf-8) + Headers Connection: keep-alive Content-Type: multipart/form-data, boundary=AaB03x + Attributes (array) + Attributes (object)
  43. Request, Response • ϦΫΤετ(Request)ͯ͠ • αʔόʔॲཧͯ͠ • Ϩεϙϯε(Response)͕ฦΔ ࠓճͷ৔߹͸औಘ͢Δ͚ͩͳͷͰRequest͸ͳ͍ 

  44. Attributes • MSONͱ͍͏ه๏Λ༻͍ͯmarkdownܗࣜͰJSON΍XMLͱ͍ͬͨ ҰൠతͳϚʔΫΞοϓݴޠͷߏ଄Λදࣔ͢Δ • υΩϡϝϯτੜ੒ͷࡍʹBody΋Schema΋·ͱΊͯੜ੒ͯ͘͠ΕΔ • ΑΓෳࡶͳه๏͸͜͜΁ 

  45. Attributes 

  46. Create user [POST /users] 

  47. Create user [POST /users]  ## Create user [POST] Create

    a new user + Request users (application/json; charset=utf-8) + Attributes + name: NAME (string) + age: 1 (number) + admin: false (boolean) + Response 201 (application/json; charset=utf-8) + Attributes + id: 1 (number) - Id + name: NAME (string) + age: 1 (number) + admin: false (boolean) + created_at: `2000-01-01 00:00:00` (string) - CreatedTime + updated_at: `2000-01-01 00:00:00` (string) - UpdatedTime
  48. Data Structures 

  49. Data Structures • ಉ͡σʔλΛԿ౓΋࢖͏ͷ͸໘౗ͩ͠ͳΜ͔಄ѱ͍ • Data Structuresʹఆ͓͚ٛͯ͠͹ݺͼग़͢͜ͱ͕Մೳ  # Data

    Structures ## users (object) + id: 1 (number) - Id + name: NAME (string) + age: 1 (number) + admin: false (boolean) + created_at: `2000-01-01 00:00:00` (string) - CreatedTime + updated_at: `2000-01-01 00:00:00` (string) - UpdatedTime FORMAT: 1A HOST: http://localhost:3000 # user API Welcome to the user API. # users action [/users] ## Create user [POST] ## Get users [GET] # user action [/users/{id}] ## Get user [GET] ## Update user [PUT] ## Delete user [DELETE] # Data Structures ## users (object)
  50. Data Structures • ͢Δͱ͜ͷΑ͏ʹݺ΂Δɻ͖ͬ͢Γ  ## Create user [POST] Create

    a new user + Request users (application/json; charset=utf-8) + ……… + Response 201 (application/json; charset=utf-8) + Attributes (users) ## Get users [GET] Returns user list. + Response 200 (application/json; charset=utf-8) + Attributes (array) + (users)
  51. Parameter /users{id} 

  52. Parameter /users{id}  # user action [/users/{id}] + Parameters +

    id: `1` (enum[string]) - The ID of the desired user. + Members + `1` + `2` + `3` ## Get user [GET] • parameter͕͋Δ৔߹͸ॳΊʹ+parameterΛఆٛ͢Δͱ Ҏ߱ͷϦΫΤετશͯͰར༻ग़དྷΔ • ࠓճ͸MembersΛड͚ೖΕΔ࢓༷ʹ͕ͨ͠ଞʹॳظ஋΍ required΋ઃఆͰ͖Δ
  53. Get user [POST /users{id}] 

  54. Get user [POST /users{id}]  # user action [/users/{id}] +

    Parameters + id: `1` (enum[string]) - The ID of the desired user. + Members + `1` + `2` + `3` ## Get user [GET] Returns user. + Response 200 (application/json; charset=utf-8) + Attributes (users)
  55. Update user [PUT /users{id}] 

  56. Update user [PUT /users{id}]  ## Update user [PUT] update

    user. + Request users (application/json; charset=utf-8) + Attributes + name: NAME (string) + age: 1 (number) + admin: false (boolean) + Response 200 (application/json; charset=utf-8) + Attributes (users) • ߋ৽σʔλΛRequest • (idͷσʔλΛ্ॻ͖ͯ͠)Response 200
  57. Delete user [DELETE /users{id}] 

  58. Delete user [DELETE /users{id}]  ## Delete user [DELETE] Delete

    user. + Response 204 • (idͷσʔλΛ࡟আͯ͠) Response 204
  59. Complete!  • ΑΓৄࡉͳه๏Λ஌Γ͚ͨΕ͹͜͜΁

  60. ςετ࣮ߦ 

  61. dredd • apiary͕༻ҙͨ͠ςετπʔϧɻ • apibϑΝΠϧΛݩʹαʔόʔΛୟ͍ͯϨεϙϯ είʔυͳͲͷ੔߹ੑΛνΣοΫͯ͘͠ΕΔ • ಺෦ͰGavelͱ͍͏πʔϧΛ࢖ͬͯ੔߹ੑΛ νΣοΫ͍ͯ͠Δ 

  62. dredd_hooks • ςετ࣮ߦ࣌ͷલޙʹҰఆͷಈ࡞ΛڬΊΔ
 (before or after) • ࠓճͷ৔߹͸parameterͷ஋Λ1͔ΒPOSTͯ͠ੜ੒͞ Εͨσʔλͷidʹ্ॻ͖ͯ͠ಈ࡞͍ͤͨ͞ɻ •

    ࢦఆͷ࢓ํ͸ʮϦιʔεάϧʔϓ໊ > ϦΫΤετ໊ʯ 
  63. POSTσʔλͷҰ࣌อଘ • apibϑΥϧμʹdredd_hooks.rbϑΝΠϧΛ࡞੒ • transaction͔ΒPOSTͷσʔλΛୀආ  require 'json' include DreddHooks::Methods

    response_stash = {} after "users action > Create user" do |transaction| # saving HTTP response to the stash response_stash[transaction['name']] = transaction['real'] end
  64. idͷऔಘ • response_stash͔ΒidΛऔಘ͢Δؔ਺ͷఆٛ  def get_input(transaction, response_stash) #reusing data from

    previous response here parsed_body = JSON.parse response_stash['users action > Create user'] ['body'] machine_id = parsed_body['id'] #replacing id in URL with stashed id from previous response transaction['fullPath'].gsub! '1', machine_id.to_s end
  65. hooksͷઃఆ • parameterΛ༻͍ΔΞΫγϣϯͷલʹઌʹఆٛ ͨؔ͠਺Λݺͼग़͢  before "user action > Get

    user" do |transaction| get_input(transaction, response_stash) end before "user action > Update user" do |transaction| get_input(transaction, response_stash) end before "user action > Delete user" do |transaction| get_input(transaction, response_stash) end
  66.  require 'json' include DreddHooks::Methods response_stash = {} def get_input(transaction,

    response_stash) #reusing data from previous response here parsed_body = JSON.parse response_stash['users action > Create user']['body'] machine_id = parsed_body['id'] #replacing id in URL with stashed id from previous response transaction['fullPath'].gsub! '1', machine_id.to_s end after "users action > Create user" do |transaction| # saving HTTP response to the stash response_stash[transaction['name']] = transaction['real'] end before "user action > Get user" do |transaction| get_input(transaction, response_stash) end before "user action > Update user" do |transaction| get_input(transaction, response_stash) end before "user action > Delete user" do |transaction| get_input(transaction, response_stash) end
  67. configϑΝΠϧͷੜ੒ • λʔϛφϧͰapibϑΥϧμ্Ͱdredd initΛ࣮ߦ͠ɺ ԼઢͷΑ͏ʹઃఆ • ࢒Γ͸Enter  $ dredd

    init ? Location of the API blueprint (apiary.apib) user.apib ? Command to start API backend server e.g. (bundle exec rails server) rails server -b 0.0.0.0 ? URL of tested API endpoint http://localhost:3000 ? Programming language of hooks ruby ? Do you want to use Apiary test inspector? (Y/n)
  68. configϑΝΠϧͷੜ੒ • ੜ੒͞Εͨdredd.ymlͷҎԼͷͱ͜ΖΛEdit  hookfiles: ./dredd_hooks.rb sorted: true

  69. ࣮ߦ • ੜ੒͞Εͨdredd.ymlͷҎԼͷͱ͜ΖΛEdit  $ dredd complete: 5 passing, 0

    failing, 0 errors, 0 skipped, 5 total complete: Tests took 2173ms complete: See results in Apiary at: https://app.apiary.io/public/ tests/run/f961c845-e6c6-4020-9d06-5c5545841b16 info: Sending SIGTERM to the backend server - Gracefully stopping, waiting for requests to finish === puma shutdown: 2016-05-10 21:04:36 +0900 === - Goodbye! Exiting
  70. Testͷਫ਼౓ • Responseίʔυ͸൑ఆ͢Δ • apibϑΝΠϧͷSchemaʹఆٛ͞Εͨkey͕ଘࡏ͠ɺͦͷvalueͷ ܕ͕ҟͳΔͱ͖ʹΤϥʔΛग़͢ • ٯʹͲͪΒ͔Ͱଘࡏ͠ͳ͍key͕͋ͬͯ΋ΤϥʔʹͳΒͳ͍ • arrayͷதͷitem͸νΣοΫ͠ͳ͍

    • Headerͷ൑ఆ΋ᐆດ ˒ ؆୯ͳνΣοΫͱͯ͠ར༻͢΂͖ 
  71. apiblueprint-rails 

  72. apiblueprint-rails • rails generateίϚϯυͰRestAPIͷapibϑΝΠϧͷ ςϯϓϨʔτΛdocϑΥϧμʹੜ੒͢ΔGem • railsͷGemfileʹҎԼΛ௥Ճͯ͠bundleΛ࣮ߦ  gem 'apiblueprint-rails'

    $ bundle
  73. apiblueprint-rails • ੜ੒͢Δͱ͖ʹscaffoldίϚϯυ΋Ұॹʹݺͼग़͢ • ΦϓγϣϯͰ੾Γସ͑Մೳ • —apidoc-dirͰੜ੒ϑΥϧμΛมߋ • —generate-typeͰੜ੒ίϚϯυΛมߋ(none, model,

    …)  $ rails g apiblueprint Person name age:integer admin:boolean create doc/person.apib invoke active_record create db/migrate/20160511104619_create_people.rb create app/models/person.rb invoke resource_route route resources :people invoke scaffold_controller create app/controllers/people_controller.rb
  74.  FORMAT: 1A # person API Write an overall API

    discription. # people action [/people] Write a discription. ## Create person [POST] Write a [POST] discription. + Request people (application/json; charset=utf-8) + Attributes + name: MyString (string) + age: 1 (number) + admin: false (boolean) + Response 201 (application/json; charset=utf-8) + Attributes (people) ## Delete person [DELETE] Write a [DELETE] discription. + Response 204 # Data Structures ## people (object) + id: 1 (number) - Id + name: MyString (string) + age: 1 (number) + admin: false (boolean) + created_at: `2000-01-01 00:00:00` (string) - CreatedTime + updated_at: `2000-01-01 00:00:00` (string) - UpdatedTime
  75. ·ͱΊ 

  76. ·ͱΊ • apiblueprintͱͦͷपΓͷαʔϏεΛར༻ͨ͠ɻ • markdown͔ͩΒ͍͍ׅͪͪހ͕ͳͯ͘json-formatΑΓ͸Δ͔ʹݟ΍͍͢ • ͜͜਺೥ͷαʔϏε͔ͩΒ·ͩ·ͩൃలੑʹظ଴Ͱ͖Δɻ • ςετ·ΘΓʹظ଴ •

    ݸਓແྉ, ஂମ༗ྉͰapiaryαʔόʔ্ͰαʔϏεల։ • apibϑΝΠϧ͔ΒAPIར༻αϯϓϧίʔυͷੜ੒ • githubͱͷ࿈ܞ