Comment on a payé notre dette technique avec GraphQL
Luc Momal
Staff Engineer @
Luc Momal
Staff Engineer @
The birth of legacy code.
Entities, such as workers, companies, contracts, invoices ..
Our story starts in 2015
Some years
later
I don't like spaghetti.. bye bye !
Let's reconstruct
everything! 🥳
I don't like spaghetti.. bye bye !
6 months
1 year later
BIG BANG!!!!!
Synchronization!!
BIG BANG!!!!!
BIG BANG!!!!!
BIG BANG!!!!!
GraphQL to the rescue.
GraphQL
GraphQL is a PATTERN not a technology
GraphQL is a query language for your API
GraphQL is a replacement for REST
GraphQL
type Query {
worker(id: ID!): Worker
}
type Worker {
id: ID!
firstName: String
lastName: String
contracts: [Contract]
}
type Contract {
from: Date
to: Date
company: Company
}
type Company {
name: String
}
query {
worker(id: 42) {
id
firstName
contracts {
company {
name
}
}
}
}
GraphQL
type Query {
worker(id: ID!): Worker
}
type Worker {
id: ID!
firstName: String
lastName: String
contracts: [Contract]
}
type Contract {
from: Date
to: Date
company: Company
}
type Company {
name: String
}
query {
worker(id: 42) {
id
firstName
contracts {
company {
name
}
}
}
}
{
worker: {
id: 42,
firstName: "John",
contracts: [
{
company: {
name: "cie"
}
}
]
}
}
GraphQL
type Query {
worker(id: ID!): Worker
}
type Worker {
id: ID!
firstName: String
lastName: String
contracts: [Contract]
}
type Contract {
from: Date
to: Date
company: Company
}
type Company {
name: String
}
query {
worker(id: 42) {
id
firstName
contracts {
company {
name
}
}
}
}
{
worker: {
id: 42,
firstName: "John",
contracts: [
{
company: {
name: "cie"
}
}
]
}
}
GraphQL
type Query {
worker(id: ID!): Worker
}
type Worker {
id: ID!
firstName: String
lastName: String
contracts: [Contract]
}
type Contract {
from: Date
to: Date
company: Company
}
type Company {
name: String
}
query {
worker(id: 42) {
id
firstName
contracts {
company {
name
}
}
}
}
{
worker: {
id: 42,
firstName: "John",
contracts: [
{
company: {
name: "cie"
}
}
]
}
}
const resolvers = {
Query: {
worker: (parent, args, ctx) => db.worker.get(args.id)
},
Worker: {
id: (parent) => parent.id,
firstName: (parent) => parent.firstName,
lastName: (parent) => parent.lastName,
contracts: (parent) => db.contracts.getFromWorker(parent.id)
},
Contract: {
from: (parent) => parent.from,
to: (parent) => parent.to,
company: (parent) => db.company.get(parent.companyId),
},
Company: {
name: (parent) => parent.name
}
}
GraphQL
type Query {
worker(id: ID!): Worker
}
type Worker {
id: ID!
firstName: String
lastName: String
contracts: [Contract]
}
type Contract {
from: Date
to: Date
company: Company
}
type Company {
name: String
}
query {
worker(id: 42) {
id
firstName
contracts {
company {
name
}
}
}
}
{
worker: {
id: 42,
firstName: "John",
contracts: [
{
company: {
name: "cie"
}
}
]
}
}
const resolvers = {
Query: {
worker: (parent, args, ctx) => db.worker.get(args.id)
},
Worker: {
id: (parent) => parent.id,
firstName: (parent) => parent.firstName,
lastName: (parent) => parent.lastName,
contracts: (parent) => db.contract.getFromWorker(parent.id)
},
Contract: {
from: (parent) => parent.from,
to: (parent) => parent.to,
company: (parent) => db.company.get(parent.companyId),
},
Company: {
name: (parent) => parent.name
}
}
GraphQL
type Query {
worker(id: ID!): Worker
}
type Worker {
id: ID!
firstName: String
lastName: String
contracts: [Contract]
}
type Contract {
from: Date
to: Date
company: Company
}
type Company {
name: String
}
query {
worker(id: 42) {
id
firstName
contracts {
company {
name
}
}
}
}
{
worker: {
id: 42,
firstName: "John",
contracts: [
{
company: {
name: "cie"
}
}
]
}
}
const resolvers = {
Query: {
worker: (parent, args, ctx) => db.worker.get(args.id)
},
Worker: {
id: (parent) => parent.id,
firstName: (parent) => parent.firstName,
lastName: (parent) => parent.lastName,
contracts: (parent) => db.contracts.getFromWorker(parent.id)
},
Contract: {
from: (parent) => parent.from,
to: (parent) => parent.to,
company: (parent) => db.company.get(parent.companyId),
},
Company: {
name: (parent) => parent.name
}
}
GraphQL
type Query {
worker(id: ID!): Worker
}
type Worker {
id: ID!
firstName: String
lastName: String
contracts: [Contract]
}
type Contract {
from: Date
to: Date
company: Company
}
type Company {
name: String
}
query {
worker(id: 42) {
id
firstName
contracts {
company {
name
}
}
}
}
{
worker: {
id: 42,
firstName: "John",
contracts: [
{
company: {
name: "cie"
}
}
]
}
}
const resolvers = {
Query: {
worker: (parent, args, ctx) => db.worker.get(args.id)
},
Worker: {
id: (parent) => parent.id,
firstName: (parent) => parent.firstName,
lastName: (parent) => parent.lastName,
contracts: (parent) => db.contracts.getFromWorker(parent.id)
},
Contract: {
from: (parent) => parent.from,
to: (parent) => parent.to,
company: (parent) => db.company.get(parent.companyId),
},
Company: {
name: (parent) => parent.name
}
}
AMAZING!
GraphQL
type Query {
worker(id: ID!): Worker
}
type Worker {
id: ID!
firstName: String
lastName: String
contracts: [Contract]
}
type Contract {
from: Date
to: Date
company: Company
}
type Company {
name: String
}
query {
worker(id: 42) {
id
firstName
contracts {
company {
name
}
}
}
}
{
worker: {
id: 42,
firstName: "John",
contracts: [
{
company: {
name: "cie"
}
}
]
}
}
const resolvers = {
Query: {
worker: (parent, args, ctx) => db.worker.get(args.id)
},
Worker: {
id: (parent) => parent.id,
firstName: (parent) => parent.firstName,
lastName: (parent) => parent.lastName,
contracts: (parent) => db.contracts.getFromWorker(parent.id)
},
Contract: {
from: (parent) => parent.from,
to: (parent) => parent.to,
company: (parent) => db.company.get(parent.companyId),
},
Company: {
name: (parent) => parent.name
}
}
AMAZING!
Mmh, no!!!!
It's time to pay our debt
Mmh, no!!!!
Migration of our last v1 feature.
GET /workers/42/contracts
{
worker: {
id: 42,
firstName: "John",
lastName: "Doe",
birthDate: "31/08/1989",
//etc...
contracts: [
{
company: {
name: "cie",
invoiceSettings: {},
contacts: {},
//etc...
}
}
]
}
}
❌ Mmh, no!!!!
read only
GraphQL to create a new interface
REST call
read only
Data model improvement
read only
type Mission {
from: String
to: String
contracts: [Contract]
company: Company
worker: Worker
}
type Contract {
type: String
from: String
to: String
}
GraphQL to migrate
{
"company_name": "ABC Corp",
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345"
},
"invoicing_settings": {
"currency": "USD",
"tax_rate": 0.08,
"invoice_terms": "Payment due within 30 days",
"invoice_footer": "Thank you for your business!"
},
"jobs": [...],
"contacts": [...],
...
}
All information related to a company is stored
in a single location in v1.
All information related to a company is stored
in a single location in v1.
GraphQL to migrate
const Company = {
"company_name": "ABC Corp",
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345"
},
}
const CompanyInvoicingSettings = {
"invoicing_settings": {
"currency": "USD",
"tax_rate": 0.08,
"invoice_terms": "Payment due within 30 days",
"invoice_footer": "Thank you for your business!"
},
}
const CompanyJobs = {
"jobs": [...],
}
const CompanyContacts = {
"contacts": [...],
}
These data can be easily split into several parts.
GraphQL to migrate
type Company {
name: String
street: String
city: String
state: String
zip: String
invoicing: CompanyInvoicingSettings
job: CompanyJob
contacts: CompanyContacts
}
type CompanyInvoicingSettings {
currency: String
tax_rate: String
invoice_terms: String
invoice_footer: String
}
type CompanyJob {
title: String
location: String
}
type CompanyContacts {
name: String
email: String
phone: String
}
And we can easily create
a graphql schema
for those data
GraphQL to migrate
GraphQL to migrate
With Federation
you can merge
your graph! 🚀
GraphQL to migrate
With Federation
you can merge
your graph! 🚀
Thank you for listening
How we have paid our debt with GraphQL
By Luc MOMAL
How we have paid our debt with GraphQL
- 388