Share
Embed
Transcript
72 slides
-
w ᴈ໌ظ w ୯ҰαʔϏεɺϞσϧڞ༗ɺ༻్͝ͱͷΠϯϑϥ w αʔϏε֦େظ w ΞϓϦέʔγϣϯංେԽͷରԠͱݶք w .POPMJUIJDظ
w ౷Ұతͳ"1*ɺڞ௨ج൫ɺαʔϏεׂ w .JDSPTFSWJDFTԽظ w αʔϏεׂͷฐΛղফ͢ΔɺׂίετͷԼ -
ΞϓϦέʔγϣϯؒͰϞσϧΛڞ༗͍ͨ͠ w ػೳ͕େ͖͘ҟͳΔΞϓϦέʔγϣϯΛ͚͍ͨ w 1$εϚʔτϑΥϯ͚αΠτ w ϑΟʔνϟʔϑΥϯ͚αΠτ w ͱͱϑΟʔνϟʔϑΥϯ͚ผαʔϏεͩͬͨͷΛ౷߹ͨ͠ w
ελοϑ͚ཧαΠτ w σʔλಉ͡ͷΛݟͯɺϞσϧϩδοΫڞ༗͍ͨ͠ -
σϓϩΠʹ͕͔͔࣌ؒΔ w DBQJTUSBOP w ཁ͢ΔʹTTIଓͯ͠σϓϩΠ w ଟ͘ճͷσϓϩΠͰҎ্͔͔Δ w TTIͰࣦഊ͢Δස͕૿Ճ w
ϦτϥΠ͢ΔӋʹͳΓ͞Βʹ͕͔͔࣌ؒΔ -
"1*Λ௨ͯ͠ϦιʔεΛѻ͏ w J04"OESPJEΞϓϦ͕ߴػೳԽ w 8FC7JFXͰఏڙ͍ͯͨ͠ͷωΠςΟϒ࣮Խ w "1*ͰຊମͷϦιʔεΛࡉ੍͔͘ޚͰ͖ΔΑ͏ʹ͢Δ w ΠϯλʔϑΣΠεΛ౷Ұ͍ͨ͠ w
ϑΥʔϚοτͷҟͳΔ"1*͕ෳଘࡏ͍ͯͨ͠ w "1*͝ͱʹYYYDMJFOU -
3&45GVMIZQFSNFEJB"1* GET /v1/users/123 { "id": 123, "name": "ύυඒ", "kitchen": {
"id": 15, "created": "2016-03-10T10:59:24+09:00", } }, "_links": { "self": { "href": "/v1/users/123" }, "recipes": { "href": "/v1/users/123/recipes" }, } } Ϧιʔεؒͷؔ࿈Λ ϦϯΫͱͯ͠දݱͰ͖Δ -
(BSBHF w HJUIVCDPNDPPLQBEHBSBHF class Employee < ActiveRecord::Base include Garage::Representer belongs_to
:division has_many :projects property :id property :title property :division, selectable: true collection :projects, selectable: true link(:division) { division_path(division) } link(:projects) { employee_projects_path(self) } def self.build_permissions(perms, other, target) perms.permits! :read end end Ϧιʔεͱͯ͠ͷ ଐੑϦϯΫΛఆٛ ೝՄ݅ఆٛͰ͖Δ -
(BSBHF$MJFOU w HJUIVCDPNDPPLQBEHBSBHF@DMJFOU # GET https://garage.example.com/v1/me user = client.get("/me") user.id
user.name # GET https://garage.example.com/v1/recipes recipes = client.get("/recipes") recipes.total_count recipes[0].id recipes[0].name ϦιʔεΛจࣈྻͰࢦఆ "1*ͷ࣮͕૿͑ͯ ΫϥΠΞϯτͦͷ··͑Δ ͲͷαʔϏεΛ͏ͱ͖ ͍ํಉ͡ ֤ΤϯυϙΠϯτͷ༷͚ͩ Θ͔ΕΑ͍ -
"VUPEPD w HJUIVCDPNSLBNVSBBVUPEPD # spec/requests/users_spec.rb describe "Users" do describe "GET
/v1/me", autodoc: true do it "returns a current_resource_owner" do get "/v1/me", params: {}, headers: headers expect(response).to have_http_status(:success) expect(response.body).to be_json_as( id: resource_owner.id, name: resource_owner.name ) end end end ςετ͔ΒυΩϡϝϯτΛ ࣗಈੜ -
ߏཧ͕େม w 8FCόονͳͲ༻్ʹԠͯ͡ߏཧΛ͚ΔελΠϧ w BQQαʔόʔಠཱͨ͠SPMF w όονδϣϒڞ௨ͷόονϫʔΧʔͷҰ෦ͱͯ͠ཧ w ઃఆͷ࿙Ε w
ΞϓϦέʔγϣϯ͕ಉډ͢Δͱݖݶཧ͕ࠔ w *".3PMFൿಗͷཧͳͲ -
%PDLFS w ϙʔλϒϧͳ࣮ߦڥ w TUBHJOHQSPEVDUJPOόονಉ͡Πϝʔδ w खݩͰݕূ͢Δ͜ͱͰ͖Δ w %PDLFSpMFΞϓϦέʔγϣϯϦϙδτϦʹஔ͘ w
ΞϓϦέʔγϣϯݻ༗ͷใ͕ू w ڥґଘڥมͱͯ͠σϓϩΠ࣌ʹೖ -
FUDFOWFUDWBVMU w HJUIVCDPNTPSBIFUDFOW w FUDEͰઃఆΛཧ w HJUIVCDPNTPSBIFUDWBVMU w FUDEͷΛ҉߸Խ w
ͱͱFUDEʹ"$-͕ແ͔ͬͨͨΊ࡞ΒΕͨ w ࢦఆͷ伴Λ͍࣋ͬͯΔΠϯελϯεͰ͔͠෮߸Ͱ͖ͳ͍ -
%PDLFSӡ༻ͷચ࿅ w ॳখ͞ͳπʔϧΛΈ߹Θͤͯཧ͍ͯͨ͠ w ౷Ұͯ͠ίϯτϩʔϧ͢Δͷ͕ͳ͍ w ઃఆ͢Δͷ͕݁ߏଟ͍ w ϗετͷཧͷखؒ w
&$4ͷొ w "84ϚωʔδυͳίϯςφαʔϏε -
)BLP w HJUIVCDPNFBHMFUNUIBLP w %PDLFSΞϓϦ༻ͷσϓϩΠπʔϧ w ΫοΫύουͰόοΫΤϯυʹ&$4Λར༻ w ΞϓϦέʔγϣϯ͝ͱͷઃఆ:".-Ͱཧ w
ڥ͝ͱʹڥมΛઃఆͰ͖Δ w ൿಗͷΈFUDFOWFUDWBVMUͰཧ -
ࡶͳཧ͔Βͷղ์ w ͱʹ͔͘%PDLFSΠϝʔδ͕͋Ε༷ʑͳ༻్Ͱ࣮ߦͰ͖Δ w Ϋϥυ্ͷݖݶཧ͕γϯϓϧʹ w &$45BTLʹ*".3PMFͰݖݶ༩ w όονδϣϒσϓϩΠͷ֓೦͕ͳ͘ͳͬͨ w
ͦͷ࣌Ͱ࠷৽ͷΠϝʔδΛऔಘ࣮ͯ͠ߦ͞ΕΔ w TTIͯ͠ϩάௐࠪ͢Δ͜ͱͳ͘ͳΓɺ8FC6*Ͱ֬ೝ -
"1*༷ͷᴥᴪʹΑΔࣄނ w ഁյతͳ༷มߋʹΑΔࣄނ w ଟ͘ͷ߹ҙਤͨ͠ͷͰͳ͘ൃੜ w ҙਤ͍ͯ͠Δ߹Ͳ͔͜Βར༻͞Ε͍ͯΔͷ͔Ѳͮ͠Β͍ w ςετʁ w
ࣗಈςετͰଞαʔϏεͷ"1*ελϒ͍ͯ͠Δ w "1*ϓϩόΠμଆ͕༷มߋͯ͠ελϒߋ৽͞Εͳ͍ͷͰݕͰ͖ͳ͍ -
$POTVNFS%SJWFO$POUSBDUUFTUJOH w ΫϥΠΞϯτʢ$POTVNFSʣ͕"1*Λελϒͯ͠ςετ w ظ͢ΔৼΔ͍Λܖʢ$POUSBDUʣͱͯ͠"1*ଆʹ͢ w "1*ϓϩόΠμଆ͕ࣗ$POUSBDUΛຬ͍ͨͯ͠Δ͔Λςετ͢ Δ w "1*ϓϩόΠμଆͷ$*ͰςετͰ͖ΔͷͰɺʹͳΔϦϏδϣ
ϯΛσϓϩΠ͢ΔલʹݕͰ͖Δ http://martinfowler.com/articles/consumerDrivenContracts.html -
1BDU w HJUIVCDPNSFBMFTUBUFDPNBVQBDU w ΫϥΠΞϯτଆ w $*ͰQBDUϑΝΠϧΛੜ w "1*ϓϩόΠμଆ w
$*ͰQBDUϑΝΠϧΛWFSJGZ IUUQTHJUIVCDPNSFBMFTUBUFDPNBVQBDUIPXEPFTJUXPSL -
1BDU $POTVNFS describe 'get_all' do let(:recipe_a) { { id: Pact.like(1),
name: Pact.like('Curry') } } let(:recipe_b) { { id: Pact.like(2), name: Pact.like('Salada') } } before do provider_app.given('there are 2 recipes'). upon_receiving('a request for recipes'). with(method: :get, path: '/v1/recipes'). will_respond_with( status: 200, headers: { 'Content-Type' => Pact.term( generate: 'application/json', matcher: %r{application/json} ), }, body: [recipe_a, recipe_b] ) end it 'returns recipes' do recipes = described_class.get_all expect(recipes.size).to eq(2) expect(recipes.first.name).to eq('Curry') end end ςετ௨ৗͱಉ͡ 1BDUͰελϒ -
1BDUpMF { "consumer": { "name": "ConsumerApp" }, "provider": { "name":
"ProviderApp" }, "interactions": [ { "description": "a request for recipes", "provider_state": "there are 2 recipes", "request": { "method": "get", "path": "/v1/recipes" }, "response": { "status": 200, "headers": { "Content-Type": { "json_class": "Pact::Term", "data": { "generate": "application/json", "matcher": { "json_class": "Regexp", "o": 0, "s": "application/json" } ϦΫΤετͱ Ϩεϙϯεͷ ༰ΛKTPOͰग़ྗ -
1BDU 1SPWJEFS w "1*ଆ w γφϦΦʹԊͬͨηοτΞοϓίʔυ͚ͩΛ༻ҙ Pact.provider_states_for 'ConsumerApp' do provider_state
"there are 2 recipes" do set_up do %w[Curry Salada].each {|name| Recipe.create!(name: name) } end end end -
&YQFEJUPS w HJUIVCDPNDPPLQBEFYQFEJUPS EXPEDITOR_SERVICE = Expeditor::Service.new( non_break_count: 20, threshold: 0.2,
period: 10, sleep: 5, ) def notification_counts Expeditor::Command.new(service: EXPEDITOR_SERVICE, timeout: 3) { response = client.get("/notifications/counts", params, options) response.notification_count }.set_fallback {|e| Raven.capture_exception(e) 0 }.start(current_thread: true).get end $JSDVJU#SFBLFSͷᮢΛઃఆ Τϥʔ࣌ϩάΛهͯ͠ σϑΥϧτΛฦ͢ -
αʔϏεׂ࣌ʹࠔΔϙΠϯτ͕ղܾ͞Ε͖ͯͨ w "1*͕ཚཱͯ͠Ͳ͜ΛͲ͏͍͍͑ͷ͔Θ͔Βͳ͘ͳΔ w (BSBHFͰͲͷαʔϏεಉ͡Α͏ʹ͑Δ w ຖճαʔόʔ࡞ͬͯߏཧ͢Δͷ͕େม w %PDLFSpMFͱ:".-Λॻ͚)BLPͰڥ༻ҙͰ͖Δ w
αʔϏεڥքͰόάোʹͳΓ͍͢ w 1BDU͕ޓੑΛ୲อ͠&YQFEJUPS͕োͷΛ͙ -
αʔϏεΛׂ͢Δ͜ͱͰಘΒΕΔྑ͍͜ͱ w ίʔυϕʔε͕খ͍͞ w ςετ։ൃڥͷಈ࡞͕͍ w มߋʹΑΔӨڹൣғ͕খ͍͞ w طଘ࣮ʹΑΔ੍͕খ͍͞ w
νʔϜʹ࣮͋ͬͨϧʔϧ w ػೳʹదͨ͠ϥΠϒϥϦɺݴޠͷબ