Collaborating with Contracts

84
COLLABORATING WITH CONTRACTS @stania_ang

Transcript of Collaborating with Contracts

COLLABORATING WITH CONTRACTS

@stania_ang

REAL LIFE: COLLABORATION

REAL LIFE: COLLABORATION

• just do it

REAL LIFE: COLLABORATION

• just do it

• negotiate a contract, adhere to it

REAL LIFE: CONTRACTSProvider

Ice cream supplierConsumerSupermarket Contract

must order 1 week in advancemust pay at time of order

must deliver within 3 business daysproduct must not expire within 2 months

MOVING ALONG...

COLLABORATION: SERVICES

SERVICE

SERVICE

• a reusable software component encapsulating a business function

SERVICE

• a reusable software component encapsulating a business function

• can be exposed over HTTP, queue, ...

ProviderConsumer

SERVICE CONTRACTS

SERVICE CONTRACTS

• request - response

SERVICE CONTRACTS

• request - response

• performance characteristics

PERSON SERVICE

ProviderConsumers

A

B

{id: 1,name: “Jane”,age: 26

}

USE CASES

CASE 1

PERSON SERVICE

ProviderConsumers

A

B

{id: 1,name: “Jane”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

NEW CONSUMER, NEW TROUBLE

Provider

Consumers

A

B

{id: 1,name: “Jane”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

C

{id: 1,first_name: “Jane”,last_name: “Good”,age: 26

}

UPDATE PROVIDER DIRECTLY

Provider

Consumers

A

B

{id: 1,first_name: “Jane”,last_name: “Good”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

C

{id: 1,first_name: “Jane”,last_name: “Good”,age: 26

}

CONSUMERS A AND B BREAKS

Provider

Consumers

A

B

{id: 1,first_name: “Jane”,last_name: “Good”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

C

{id: 1,first_name: “Jane”,last_name: “Good”,age: 26

}

CONSUMER-DRIVEN CONTRACTS

ProviderConsumers

A

B

C

Contract A

Contract B

Contract C

{id: 1,name: “Jane”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

{id: 1,first_name: “Jane”,last_name: “Good”,age: 26

}

WHY IS THIS USEFUL?

WHY IS THIS USEFUL?

• services are only useful when they are used

WHY IS THIS USEFUL?

• services are only useful when they are used

• consumer-driven contracts makes consumer expectations explicit

CONSUMER-DRIVEN CONTRACT TESTS

CONSUMER-DRIVEN CONTRACT TESTS

all the goodness of consumer-driven contracts and more

rake consumer:verifyrake provider:verify

executable

can be run as part of build pipeline

unit contract functional deploy

living documentation

AN EXAMPLE

Provider

Consumers

A

B

{id: 1,first_name: “Jane”,last_name: “Good”,age: 26,friends: ...

}

{id: 1,name: “Jane”,age: 26

}

{id: 1,name: “Jane”,age: 26

}

C

{id: 1,first_name: “Jane”,last_name: “Good”,age: 26

}

$$$$

“Let me go through the 20-page contract to see if they mention ‘friends’...”

PLAYS WELL WITH TDD

PLAYS WELL WITH TDD

• write consumer-driven contract test for consumer C

PLAYS WELL WITH TDD

• write consumer-driven contract test for consumer C

• red-green-refactor : consumer side

PLAYS WELL WITH TDD

• write consumer-driven contract test for consumer C

• red-green-refactor : consumer side

• red-green-refactor : provider side

PLAYS WELL WITH TDD

• write consumer-driven contract test for consumer C

• red-green-refactor : consumer side

• red-green-refactor : provider side

• make sure consumer-driven contract tests for consumers A and B still passes too

MAKING CONSUMER EXPECTATIONS EXPLICIT

CASE 2

ProviderConsumer ServiceBoundary

Provider stub

testing around service boundary

{id: 1,name: “Jane”,age: 26

}

ProviderConsumer ServiceBoundary

Provider stub

unit test on consumer:uses a provider stub

verifies the consumer processes the stubbed response properly

{id: 1,name: “Jane”,age: 26

}

ProviderConsumer ServiceBoundary

unit test on provider :use fake consumer

tests that given the right request, it returns the right response

Fake consumer

ProviderConsumer ServiceBoundary

Provider stub

there could be a mismatch

{id: 1,name: “Jane”,age: 26,address: ...

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

ProviderConsumer ServiceBoundary

Provider stub

unit tests pass, but app will blow up

{id: 1,name: “Jane”,age: 26,address: ...

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

HOW CAN CONTRACT TESTS HELP?

ProviderConsumer ServiceBoundary

Provider stub

both sides are verified against the same contract

{id: 1,name: “Jane”,age: 26,address: ...

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

Contract

ProviderConsumer ServiceBoundary

Provider stub

write expectations from the consumer side

{id: 1,name: “Jane”,age: 26,address: ...

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

Contract

ProviderConsumer ServiceBoundary

Provider stub

generate the contract

{id: 1,name: “Jane”,age: 26,address: ...

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

Contract

ProviderConsumer ServiceBoundary

Provider stub

verify that the provider adheres to the same contract

{id: 1,name: “Jane”,age: 26,address: ...

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

Contract

fail:address is missing!

MAKING SURE PROVIDER AND CONSUMER DO THEIR JOB

CASE 3

another teamour team

a tale of 2 apps

another teamour team

in the beginning was nothing

another teamour teamService

Boundary

Contract

another teamour teamService

Boundary

Contractexecutable

ProviderConsumer ServiceBoundary

Provider stub

consumer sends the right request

{id: 1,name: “Jane”,age: 26

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

Contract

ProviderConsumer ServiceBoundary

Provider stub

provider returns the right response

{id: 1,name: “Jane”,age: 26

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

Contract

another teamour teamService

Boundary

Contract

this enables parallel development

ENABLING PARALLEL DEVELOPMENT

ProviderConsumer ServiceBoundary

Provider stub

{id: 1,name: “Jane”,age: 26

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

ProviderConsumer ServiceBoundary

Provider stub

write the tests for the consumer

{id: 1,name: “Jane”,age: 26

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

person_service. given("a person with too many friends"). upon_receiving("request for person details").

with(method: :get, path: "/person/1", query: '').

person_service. given("a person with too many friends"). upon_receiving("request for person details").

with(method: :get, path: "/person/1", query: '').

person_service. given("a person with too many friends"). upon_receiving("request for person details").

will_respond_with(status: 200, headers: {'Content-Type' => 'application/json; charset=utf-8'}, body: {

id: 1, name: “Jane”, age: 26, address: ... })

with(method: :get, path: "/person/1", query: '').

person_service. given("a person with too many friends"). upon_receiving("request for person details").

will_respond_with(status: 200, headers: {'Content-Type' => 'application/json; charset=utf-8'}, body: {

id: 1, name: “Jane”, age: 26, address: ... })

expect(subject.person_details(“1”)).to eq(expected_person)

ProviderConsumer ServiceBoundary

Provider stub

runwatch it failmake it pass

{id: 1,name: “Jane”,age: 26

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

ProviderConsumer ServiceBoundary

Provider stub

the consumer tests generates a “pact”

{id: 1,name: “Jane”,age: 26

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

Pact

ProviderConsumer ServiceBoundary

Provider stub

configure to verify selected pact(s)

{id: 1,name: “Jane”,age: 26

}

Fake consumer

{id: 1,name: “Jane”,age: 26

}

Pact

Provider

rake pact:verify

Pact

controllerserializerservicemodel

actual HTTP requests

Fake consumer

MORE ON PACT

standalone stub

Out-of-processprovider stub

{id: 1,name: “Jane”,age: 26

}

Actualconsumer

person_service. given("a person with too many friends"). upon_receiving("request for person details").

provider states

given("a person with too many friends").

person_service. given("a person with too many friends"). upon_receiving("request for person details").

provider states

provider_state "a person with too many friends" do set_up do

10_000.times doperson = Person.firstperson.friends << Person.new(...)

end end end

will_respond_with(status: 200, headers: {'Content-Type' => 'application/json; charset=utf-8'}, body: {

id: 1, name: Pact::SomethingLike.new(“Jane”), age: 26, address: ... })

loose matching

with(method: :get, path: "/person/1", query: '').

person_service. given("a person with too many friends"). upon_receiving("request for person details").

Pact::SomethingLike.new(“Jane”)

SUMMING UP

USE CASES

USE CASES

•making consumer expectations explicit

USE CASES

•making consumer expectations explicit

•making sure both consumer and provider do their job

USE CASES

•making consumer expectations explicit

•making sure both consumer and provider do their job

• enabling parallel development

THE DEVIL IS IN THE DETAILS

YMMV

SEE COLLABORATION? THINK CONTRACTS

THANK YOU