the case for consumer-driven contracts

87
The case for consumer-driven contracts @matthewfellows

Upload: dius

Post on 16-Jan-2017

142 views

Category:

Technology


2 download

TRANSCRIPT

Page 1: The case for consumer-driven contracts

The case for consumer-driven contracts@matthewfellows

Page 2: The case for consumer-driven contracts

Picture the scene...

Page 3: The case for consumer-driven contracts

Monday morning

Page 4: The case for consumer-driven contracts

9am

Page 5: The case for consumer-driven contracts

Phone rings

Page 6: The case for consumer-driven contracts

It’s our VC

Page 7: The case for consumer-driven contracts

We just got the news....

Page 8: The case for consumer-driven contracts
Page 9: The case for consumer-driven contracts

We’re going to build a startup!

Page 10: The case for consumer-driven contracts
Page 11: The case for consumer-driven contracts

Soundify®�

Page 12: The case for consumer-driven contracts

Everyone is doing microservices!

Page 13: The case for consumer-driven contracts
Page 14: The case for consumer-driven contracts

Metric Value

No. teams 1

No. components 1

Test environments 1

Time in pipeline (commit to prod) 5 minutes

Risk 2.5%

Deployments per day 10

CD health check

Page 15: The case for consumer-driven contracts
Page 16: The case for consumer-driven contracts

Metric Value

No. teams 2

No. components 2

Test environments 4

Time in pipeline (commit to prod) 10 minutes

Risk 5%

Deployments per day 10

CD health check

Page 17: The case for consumer-driven contracts
Page 18: The case for consumer-driven contracts
Page 19: The case for consumer-driven contracts

Metric Value

No. teams 3

No. components 6

Test environments 18

Time in pipeline (commit to prod) 30 minutes

Risk 10%

Deployments per day 10

CD health check

Page 20: The case for consumer-driven contracts

ACQUIRED

Page 21: The case for consumer-driven contracts
Page 22: The case for consumer-driven contracts

Metric Value

No. teams 10

No. components 20

Test environments 20 200

Time in pipeline (commit to prod) 120+ minutes

Risk 20%

Deployments per day 1

CD health check

Page 23: The case for consumer-driven contracts

So what did we learn?

Page 24: The case for consumer-driven contracts
Page 25: The case for consumer-driven contracts

Where did we go wrong?

Page 26: The case for consumer-driven contracts

We pretended microservices were colocated libraries

Page 27: The case for consumer-driven contracts

Cohesive services, loosely coupled

vs

Cohesive teams, tightly coupled by services

Page 28: The case for consumer-driven contracts

Current tooling and strategies are not good enough

Page 29: The case for consumer-driven contracts

“Integration tests are a scam” - JB Rainsberger

Page 30: The case for consumer-driven contracts

Scam, you say? Justify!Integrated tests are:

● Slow● Fragile ● Hard to manage

When they fail, you can’t point to the problem!

Page 31: The case for consumer-driven contracts

Scam, you say? Justify!

“But my integration tests run in Docker, why can’t I use them?”

- People

Page 32: The case for consumer-driven contracts

Scam, you say? Justify!

“Because Maths” - Me

Page 33: The case for consumer-driven contracts
Page 34: The case for consumer-driven contracts

Branches per box vs test cases required

2 code branches = 128 tests5 code branches = 78,125 tests10 code branches = 10M tests

Page 35: The case for consumer-driven contracts

Good tests have the exact opposite properties

Page 36: The case for consumer-driven contracts
Page 37: The case for consumer-driven contracts

Dictator Driven Contracts

Page 38: The case for consumer-driven contracts

Dictator Driven Contracts

Page 39: The case for consumer-driven contracts

1. Sit in ivory tower and postulate2. Document perfect API (Swagger, API blueprint etc.)3. Create said API4. Publish said document to consumers5. Request dictate consumers update6. Repeat steps 1-5

How to: Dictator Driven Contracts

Page 40: The case for consumer-driven contracts
Page 41: The case for consumer-driven contracts
Page 42: The case for consumer-driven contracts
Page 43: The case for consumer-driven contracts

Crap, this didn’t work either!

Page 44: The case for consumer-driven contracts

Dictator Consumer Driven Contracts

Page 45: The case for consumer-driven contracts
Page 46: The case for consumer-driven contracts

Benefits?

Page 47: The case for consumer-driven contracts

You’ll know when you break a consumer

Page 48: The case for consumer-driven contracts

You have a form of documentation

Page 49: The case for consumer-driven contracts

You can test things independently

Page 50: The case for consumer-driven contracts

Pactwww.pact.io

Page 51: The case for consumer-driven contracts

Evolved from combining these two principles

Page 52: The case for consumer-driven contracts

Step 1: Define Consumer expectations

Page 53: The case for consumer-driven contracts

Step 1: Define Consumer expectations

Page 54: The case for consumer-driven contracts

Step 1: Define Consumer expectations

Page 55: The case for consumer-driven contracts

Step 1: Define Consumer expectations

Step 2: Verify expectations on Provider

Page 56: The case for consumer-driven contracts

Start with a consumer test

Page 57: The case for consumer-driven contracts

Given “User A exists”When I Receive “a GET request for user A”

With “these headers and query”Respond with “200 OK”

And “User A’s details in the body”

Page 58: The case for consumer-driven contracts

Given “User A does not exist”When I Receive “a GET request for user A”

Respond with “404 Not Found”

Page 59: The case for consumer-driven contracts

Example

Page 60: The case for consumer-driven contracts

// Setup our expected interactions on the Mock Service.

pact.

AddInteraction().

Given("User billy exists").

UponReceiving("A request to login with user 'billy'").

WithRequest(dsl.Request{

Method: "POST",

Path: "/users/login",

Body: loginRequest,

}).

WillRespondWith(dsl.Response{

Status: 200,

Headers: map[string]string{

"Content-Type": "application/json",

},

Body: `

{

"user": {

"name": "billy"

}

}

`,

})

Page 61: The case for consumer-driven contracts

// Run the test and verify the interactions.

err := pact.Verify(func() error {

client := Client{

Host: fmt.Sprintf("http://localhost:%d", pact.Server.Port),

}

client.loginHandler(rr, req)

// Expect User to be set on the Client

if client.user == nil {

return errors.New("Expected user not to be nil")

}

return nil

})

if err != nil {

t.Fatalf("Error on Verify: %v", err)

}

// Write pact to file `<pwd>/pacts/my_consumer-my_provider.json`

// NOTE: This also is a good candidate for use in TestMain(m *testing.M)

pact.WritePact()

Page 62: The case for consumer-driven contracts

Specification by example

Page 63: The case for consumer-driven contracts

{

"consumer": {"name": "MyConsumer"},

"provider": {"name": "MyProvider"},

"interactions": [

{

"description": "Some name for the test",

"provider_state": "Some state",

"request": {

"method": "GET",

"path": "/foobar",

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "foo"

}

},

"response": {

"status": 200,

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "bar"

}

}

}

...

]

Page 64: The case for consumer-driven contracts

{

"consumer": {"name": "MyConsumer"},

"provider": {"name": "MyProvider"},

"interactions": [

{

"description": "Some name for the test",

"provider_state": "Some state",

"request": {

"method": "GET",

"path": "/foobar",

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "foo"

}

},

"response": {

"status": 200,

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "bar"

}

}

}

...

]

Page 65: The case for consumer-driven contracts

{

"consumer": {"name": "MyConsumer"},

"provider": {"name": "MyProvider"},

"interactions": [

{

"description": "Some name for the test",

"provider_state": "Some state",

"request": {

"method": "GET",

"path": "/foobar",

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "foo"

}

},

"response": {

"status": 200,

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "bar"

}

}

}

...

]

Page 66: The case for consumer-driven contracts

{

"consumer": {"name": "MyConsumer"},

"provider": {"name": "MyProvider"},

"interactions": [

{

"description": "Some name for the test",

"provider_state": "Some state",

"request": {

"method": "GET",

"path": "/foobar",

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "foo"

}

},

"response": {

"status": 200,

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "bar"

}

}

}

...

]

Page 67: The case for consumer-driven contracts

{

"consumer": {"name": "MyConsumer"},

"provider": {"name": "MyProvider"},

"interactions": [

{

"description": "Some name for the test",

"provider_state": "Some state",

"request": {

"method": "GET",

"path": "/foobar",

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "foo"

}

},

"response": {

"status": 200,

"headers": {

"Content-Type": "application/json"

},

"body": {

"s": "bar"

}

}

}

...

]

Page 68: The case for consumer-driven contracts

Next publish your pacts

Page 69: The case for consumer-driven contracts
Page 70: The case for consumer-driven contracts
Page 71: The case for consumer-driven contracts
Page 72: The case for consumer-driven contracts

// Publish the Pacts...

p := dsl.Publisher{}

err := p.Publish(types.PublishRequest{

PactURLs: []string{"../pacts/myconsumer-myprovider.json"},

PactBroker: os.Getenv("PACT_BROKER_HOST"),

ConsumerVersion: "1.0.0",

Tags: []string{"latest", "production"},

BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),

BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),

})

Page 73: The case for consumer-driven contracts

Then verify your provider

Page 74: The case for consumer-driven contracts
Page 75: The case for consumer-driven contracts

// Verify the Provider from tagged Pact files stored in a Pact Broker

response = pact.VerifyProvider(types.VerifyRequest{

ProviderBaseURL: fmt.Sprintf("http://localhost:%d", providerPort),

BrokerURL: brokerHost,

Tags: []string{"latest", "prod"},

ProviderStatesURL: fmt.Sprintf("http://localhost:%d/states", providerPort),

ProviderStatesSetupURL: fmt.Sprintf("http://localhost:%d/setup", providerPort),

BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),

BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),

})

if response.ExitCode != 0 {

t.Fatalf("Got %d, Want exit code 0", response.ExitCode)

}

Page 76: The case for consumer-driven contracts

Verifying a pact between billy and bobby

Given User billy exists

A request to login with user 'billy'

with POST /users/login

returns a response which

has status code 200

has a matching body

includes headers

"Content-Type" with value "application/json"

Given User billy does not exist

A request to login with user 'billy'

with POST /users/login

returns a response which

has status code 404

includes headers

"Content-Type" with value "application/json"

...

Finished in 0.03042 seconds

7 examples, 0 failures

Page 77: The case for consumer-driven contracts

Verifying a pact between billy and bobby

Given User billy exists

A request to login with user 'billy'

with POST /users/login

returns a response which

has status code 200

has a matching body

includes headers

"Content-Type" with value "application/json"

Given User billy does not exist

A request to login with user 'billy'

with POST /users/login

returns a response which

has status code 404

includes headers

"Content-Type" with value "application/json"

...

Finished in 0.03042 seconds

7 examples, 0 failures

Page 78: The case for consumer-driven contracts

Verifying a pact between billy and bobby

Given User billy exists

A request to login with user 'billy'

with POST /users/login

returns a response which

has status code 200

has a matching body

includes headers

"Content-Type" with value "application/json"

Given User billy does not exist

A request to login with user 'billy'

with POST /users/login

returns a response which

has status code 404

includes headers

"Content-Type" with value "application/json"

...

Finished in 0.03042 seconds

7 examples, 0 failures

Page 79: The case for consumer-driven contracts

Verifying a pact between billy and bobby

Given User billy exists

A request to login with user 'billy'

with POST /users/login

returns a response which

has status code 200

has a matching body (FAILED - 1)

includes headers

"Content-Type" with value "application/json"

Failures:

1) Verifying a pact between billy and bobby Given User billy exists A request to login with user 'billy' with POST

/users/login returns a response which has a matching body

Failure/Error: expect(response_body).to match_term expected_response_body, diff_options

Actual: {"user":{"user":"billy"}}

@@ -1,6 +1,5 @@

{

"user": {

- "name": "billy"

}

}

Page 80: The case for consumer-driven contracts

No Silver Bullet

Page 81: The case for consumer-driven contracts

Does not replace communication

Page 82: The case for consumer-driven contracts
Page 83: The case for consumer-driven contracts

What about systems maintained by other teams?

Page 84: The case for consumer-driven contracts

What about systems built in outdated technologies?

Page 85: The case for consumer-driven contracts

Scary outsideworld!

3rd Party

Mainframe

Page 86: The case for consumer-driven contracts

Recapping...

● Business impact of integrated tests● Cause and explanation of those effects● Alternative approach - isolation + contracts● Contract-testing as an approach, Pact as a tool (in this order!)

Page 87: The case for consumer-driven contracts

Thank you

- @matthewfellows

Given “The presentation is over”Upon Receiving “A request for an answer”With “A valid question”Respond With “A valid answer”