-
Notifications
You must be signed in to change notification settings - Fork 3
GraphQL Summit 2018 Berlin
Intuit is at the half way mark on its multi-year journey to decompose its Quickbooks online platforms into micro services connected through a single Graph. API access is kept simple by decomposing & orchestrating complex requests at the entry point, resulted in improved developer productivity.
There are many cases where the analysis of dependencies and therefore scheduling calls to resolve them lies outside of GraphQL natural order of execution. Relational databases are good at solving these problems over sets of related tables. However, there is no generic solution of this problem when there is a need to retrieve relational data from or store it to the set of micro services.
There is a certain asymmetry in treating the resolvers. Selection of fields assumes there could be separate resolvers for relational fields and the GraphQL engine would join relational results. However conditional filtering by relational fields and sub-fields as well as cascaded writes across relational fields fall out of the selection resolver pattern and require special handling via specialized use case specific resolvers.
Our team has built a component that uses type metadata to classify objects' relationships and schedule the execution (sequence of calls to downstream services) in the correct order. This solution is not only applicable to GraphQL, but to other protocols, for example REST.
[During this talk I will walk you through our solution]
Let's discuss these use cases:
find all invoices whose customer first name is like "John".
How to solve this use case with GraphQL if the target of this query is not a relational database, but a set of micro-services, each responsible for its own data type?
If we had Invoice and Customer types defined like below:
type Invoice {
id: String!
description: String
date: Date
amount: Float
customer: Customer
account: Account
...
}
type Customer {
id: String!
firstName: String
lastName: String
...
}
type Account {
id: String!
name: String
balance: Float
...
}
the GraphQL query would look like:
query {
invoices (filterBy: "customer.firstName like '%John%'") {
id
date
amount
customer {
id
firstName
lastName
}
}
}
How to fulfill this query?
In order to filter Invoices by their customer.name one would need to:
- find all Customers whose
nameis like %John% - find all Invoices that have their
customer.idreference in a set of values obtained from matching Customers above
GraphQL spec and engine can only organize fetching dependent fields from top to bottom, i.e. resolver for customer field is executed after the resolver for invoices one.
However, invoices field needs to be filtered by a predicate depending on their customer sub-field and there are NO provisions in GraphQL to build dependencies from bottom to top.
One way to solve the filtering problem is to define a "special" resolver that "knows" how to filter Invoices by their customer name.
Then what if we slightly modify the use case?
find all invoices whose customer first name is like "John" and/or customer last name is like "Doe"
or, even more:
find all invoices whose customer first name is like "John" or account balance is >= $1000.00
We could keep adding more and more scenarios into that "special" Invoices resolver (you can imagine how complex it could become). But soon we'll figure out that number of permutations of Invoice fields and sub-fields for filtering is very large. Plus, there are other types in our dictionary that also require filtering.
write an Invoice with a new or existing Customer
Continuing with the type dictionary defined above, the GraphQL mutation could look like:
mutation UpdateInvoice (input: {
clientMutationId: "1",
invoice {
id: "1",
description: "demo invoice",
customer {
firstName: "John",
lastName: "Doe"
}
}
}){
clientMutationId
invoiceEdge {
id
date
amount
customer {
id
firstName
lastName
}
}
}
Again, how to fulfill this mutation? This mutation needs to update 2 micro-services:
- create new Customer (no
idwas specified) - update existing Invoice (
idis "1") with thecustomer.idreceived from the previous call
GraphQL allows only to register 1 resolver for mutation and that resolver needs to coordinate customer creation and invoice update with their respective micro services. And again, the same questions as above. How many permutations of fields and sub-fields we can support? And what about the other similar types in the dictionary?
[Here I will demonstrate and explain our solution to the above use cases]
Ability to orchestrate over distributed services is an extremely hot topic today as micro-services architecture becomes more and more popular. We've found out that there are no tools on the market that allow to quickly build such solution. Existing GraphQL products either automate data fetching using shared databases or require custom orchestration solution for each use case or family of use cases.
We have successfully built a solution integrated with API Gateway that already serves millions of requests per day.
Our solution is based on building an execution plan using Dependency Graph for the entire query or mutation (multiple patents filed).
When building the plan we consult services topology with an externally provided CoalescenceStrategy. API Gateway decomposes outbound requests by services boundaries and batches them by the same target URL.
On the domain side our SDK uses the same kind of decomposition but with different CoalescenceStrategy to decompose batched queries by providers registered in the class path.
Provideris a component responsible for performing CRUD operations on objects of a certain type.
Ideally micro-services are implemented using our SDK that sets up standard endpoints (we only need BATCH at this point), standard marshaling protocol and coherent type system generated from the centrally managed contract. The same contract is used to generate graphql type system to ensure consistency across components. For the non-compliant services/endpoints it is possible to register protocol adapters that translate outbound messages to the target service format and inbound response messages into the format defined by the contract.
Depending on the services capabilities and/or performance statistics there is a possibility to select an optimized execution plan for a given query. In the near future this solution will be improved with a transaction management component to ensure data integrity during mutation requests.
data:
overallExperience: "I've been working as a software development professional since late nineteen eighties.",
passionateAbout: "My passion was primarily around effective algorithms, distributed systems and compilers.",
family: "Married, have 4 daughters and 1 dog.",
hobbies: "Downhill skiing, ice skating, biking, cooking.",
relevantExperience:
edges:
- node:
bullet: |
In the late nineties, I was working for Borland Intl., Inc. where I have developed tools based
on compiler's theory to internationalize source bases for all Borland's products, like Delphi, Borland C++
Builder, Paradox, etc. In Borland, I've joined Visibroker team that has developed the best commercial CORBA
implementation on the market, later chosen by Sun Microsystems as a reference implementation of CORBA for JDK 1.2.
I was developing the family of IDL-to-XXX and XXX-to-IDL compilers, CORBA value marshaling, Interface Repository
and many other CORBA and EJB container components.
- node:
bullet: |
In the early two thousand, I've joined another great Silicon Valley company Intuit, Inc. All work
I've been doing at Intuit was related to parsers, recursive data structures and finite automata. In 2010, I've
developed a query language to pass arbitrary query criteria to Intuit's v3 RESTful services. The query language
was based on simplified JPA-QL syntax and therefore it is known at Intuit as SimpleQuery. Since 2015 I've been the
main contributor and technical lead for the foundation of the next Intuit Online Platform (v4 services) that is
built on the principles of services connected/related to each other via graph. That's how we started our journey
with GraphQL. Since then, I've contributed to building API Gateway with GraphQL, REST and Batch capabilities.
During our GraphQL integration journey, we've fixed several performance bugs in open source GraphQL Java library
that we use (10x improvement of GraphQL overhead) and contributed those improvements back to the open source
community. I am also currently involved into refactoring/optimizing open source GraphQL Java library, focusing on
simplifying/optimizing core algorithms and improving maintainability.