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