¿Pensando en montar una arquitectura de microservicios?¿O quizás ya estás en mitad de una y sientes que estás perdiendo el control o te produce ansiedad cualquier subida a producción? En esta charla hablaré de cómo podemos volver a tomar el control de la situación combinando tres cosas: GraphQL, service directory and contract testing.
Find out more at https://madrid2018.codemotionworld.com/speakers/
12. WHAT'S INSIDE A CONTRACT?WHAT'S INSIDE A CONTRACT?
DATA MODELDATA MODEL
message PersonalInfo {
required string name;
optional string email;
}
message Employee {
required number employeeId;
required PersonalInfo info;
}
13. WHAT'S INSIDE A CONTRACT?WHAT'S INSIDE A CONTRACT?
DATA OVER THE NETWORKDATA OVER THE NETWORK
{
"employeeId": 33,
"info": {
"name": "John",
"email": null
}
}
14. WHAT'S INSIDE A CONTRACT?WHAT'S INSIDE A CONTRACT?
OPERATIONSOPERATIONS
service EmployeeService {
rpc hire(PersonalInfo) returns (Employee);
}
15. WHAT'S INSIDE A CONTRACT?WHAT'S INSIDE A CONTRACT?
OPERATIONS OVER THE NETWORKOPERATIONS OVER THE NETWORK
POST /employee/rpc HTTP/1.1
Host: example.org
Authorization: Bearer AbCdEf123456
Content-Type: application/json; charset=utf-8
{
"op": "hire",
"args": [{
"name": "John",
"email": null
}]
}
16. WHAT'S INSIDE A CONTRACT?WHAT'S INSIDE A CONTRACT?
OPERATIONS OVER THE NETWORKOPERATIONS OVER THE NETWORK
HTTP/1.1 200 OK
Date: Sun, 10 Oct 2010 23:26:07 GMT
Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g
Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT
ETag: "45b6-834-49130cc1182c0"
Content-Type: application/json; charset=utf-8
{
"employeeId": 33,
"info": {
"name": "John",
"email": null
}
}
17. WHAT'S INSIDE A CONTRACT?WHAT'S INSIDE A CONTRACT?
INPUT/OUTPUT INVARIANTSINPUT/OUTPUT INVARIANTS
findById(id: string): Employee -> If
found, we expect the result to have the same id as
specified
updateName(id: string, name:
string): Employee -> If successful, we expect
the returned employee to have the supplied name
18. SO WHAT HASSO WHAT HAS GraphQLGraphQL TO DOTO DO
WITH CONTRACTS?WITH CONTRACTS?
21. BUT GIVES A JSON SERIALISATIONBUT GIVES A JSON SERIALISATION
RECOMMENDATIONRECOMMENDATION
https://facebook.github.io/graphql/June2018/#sec-
Serialization-Format
22. GRAPHQL DOESGRAPHQL DOES NOTNOT DEFINEDEFINE
HOW TO TRANSPORT IT OVERHOW TO TRANSPORT IT OVER
THE NETWORKTHE NETWORK
23. BUT THERE IS ABUT THERE IS A DE FACTODE FACTO STANDARDSTANDARD
Single endpoint: /graphql
Use GET and POST ignoring their semantics
Use application/json and JSON serialisation
4xx for validation and security errors
200 for anything else (partial success)
https://graphql.org/learn/serving-over-http/
28. GraphQLGraphQL + CONVENTIONS = CONTRACT?+ CONVENTIONS = CONTRACT?
SDL
for extra details like errors
De facto standard:
De facto standard:
GraphQL spec
GraphQL over HTTP
GraphQL serialised as JSON
30. WE SHOULD TEST ... WHATWE SHOULD TEST ... WHAT
EXACTLY?EXACTLY?
31. NOTNOT A CONTRACT TESTA CONTRACT TEST
Correct implementation of business logic
In the server
In the clients
The full end to end behaviour
32. BOTH SIDES ADHERE TO THE CONTRACTBOTH SIDES ADHERE TO THE CONTRACT
Valid means it complies with the contract
Clients:
Perform valid requests
Can process any valid results
Servers:
Accept valid requests
Always return a valid result
36. GraphQLGraphQL TO THE RESCUE!TO THE RESCUE!
Strong Typed SDL give us automation
Test data generators
Mock Servers
Test scenarios scaffolding
No need to test type validation on the server
38. GENERATING TYPES & SCAFFOLDINGGENERATING TYPES & SCAFFOLDING
For example with GraphQLGen
type ID = string
type ProductRef = string
type Rating = number
enum ReviewStatus {
'PENDING',
'ACCEPTED',
'REJECTED'
}
type ProductReview = {
id: ID
productRef: ProductRef
rating: Rating
commentary: string | null
status: ReviewStatus
}
39. FAKE DATA FOR TESTSFAKE DATA FOR TESTS
A MAINTENANCE BURDENA MAINTENANCE BURDEN
[
{
"id": "2f4dc6ba-bd25-4e66-b369-43a13e0cf150",
"productRef": "urn:ishop:product:2c497439-7802-4cd5-8fb6-f7fa679c
"rating": 4,
"commentary": null,
"status": "PENDING"
},
{
"id": "a9af522d-0d62-46f3-8c1d-71eff807fcc7",
"productRef": "urn:ishop:product:bb509f54-e20f-4fbb-8df7-adeb7b83
"rating": 2,
"commentary": "Vel et rerum nostrum quia. Dolorum fuga nobis sit
"status": "ACCEPTED"
}
]
40. FAKE DATA GENERATORSFAKE DATA GENERATORS
CUSTOM SCALARSCUSTOM SCALARS
import * as casual from 'casual'
const someID = (): ID => casual.uuid
const someProductRef = (): ProductRef =>
`urn:ishop:product:${someID()}`
const someRating = (): Rating => casual.integer(1, 5)
43. FAKE DATA GENERATOR GENERATORSFAKE DATA GENERATOR GENERATORS
REMOVING BOILERPLATEREMOVING BOILERPLATE
Most of the code of the generators are highly regular
Only custom scalars need to be defined
And some minor customisations
44. FAKE DATA GENERATOR GENERATORSFAKE DATA GENERATOR GENERATORS
REMOVING BOILERPLATEREMOVING BOILERPLATE
// Not (yet) a real library
import { load, nullable, someString } from 'gql-faker'
const myFaker = load('../schemas/reviews.graphql')
myFaker.customScalars({
someID: (): ID => casual.uuid,
someProductRef: (): ProductRef =>
`urn:ishop:product:${myFaker.someID()}`,
someRating: (): Rating => casual.integer(0, 5)
})
myFaker.customiseObject('ProductReview', {
commentary: nullable(
() => someString({
kind: 'description'
})
)
})
45. CONSIDER THESE OPERATIONSCONSIDER THESE OPERATIONS
input ReviewData {
productRef: ProductId!
rating: Rating!
commentary: String
}
type Query {
productReview(id: ReviewId!): ProductReview
}
type Mutation {
postReview(review: ReviewData!): ProductReview!
deleteReview(id: ReviewId!): boolean
}
48. TEST SCENARIOS DEFINITIONTEST SCENARIOS DEFINITION
WHAT ABOUTWHAT ABOUT deleteReviewdeleteReview??
// No invariants
// Return value is just a boolean
// Test scenario can be automatically inferred
myFaker.testScenarios({
Mutation: {
// deleteReview(id: ReviewId!): boolean
deleteReview: [
{ name: 'returns true', result: true },
{ name: 'returns false', result: false },
{ name: 'failure', result: () => someSystemError() }
]
}
})
49. NO BOILERPLATENO BOILERPLATE
ONLY NEED TO DEFINE WHAT ISONLY NEED TO DEFINE WHAT IS NOTNOT IN THE SDLIN THE SDL
Custom Scalars
Domain customisation for Objects and Inputs
Invariants inputs/outputs
Errors (not in SDL)
Edge cases
54. CONTRACT DRIFT PROBLEMCONTRACT DRIFT PROBLEM
1. Provider change contract
2. Consumer is not aware of the change
3. Mock server is not updated
4. False passing tests
5. Bug!
56. DON'TDON'T SEMVERSEMVER
Only contract version in production is relevant
Fix clients ASAP
Save money, don't maintain several versions at the
same time
57. EVERGREENEVERGREEN
Deprecate and notify
Measure usage of deprecated operations & types
Drop them when usage is low enough
Contract changed? -> Run contract tests for all
services consuming that contract
58. NEEDNEED SERVICE DIRECTORYSERVICE DIRECTORY TO KNOW FORTO KNOW FOR
WHICH SERVICES THEIR CONTRACTWHICH SERVICES THEIR CONTRACT
TESTS NEED TO BE RUNTESTS NEED TO BE RUN
59. SERVICE DIRECTORYSERVICE DIRECTORY
Unique source of truth regarding Contracts
Keeps tracks of:
Which services provide which Contracts
Which services consume which contracts
Contracts = SDL + Test Scenario Definitions
Well known test cases
62. SERVICE DIRECTORYSERVICE DIRECTORY
HUMANS!HUMANS!
Search & consult the contracts and their test cases
Report service dependencies
Report services with broken contract tests (provider
& consumer)
Report services using deprecated contract features