The final feature of my DSL I want to talk about is support for Apollo Federation. I see this directive driven approach working well for organizations that adopt Apollo Federation to build a unified graph distributed across many different teams. Each team that owns a single complex domain, whether that's payments, inventory, ratings and reviews, can use this approach to efficiently expose a GraphQL API on top of any preexisting APIs, just by writing a schema file. And then, they can compose their APIs together to build a single companywide API using Federation.
The two semantics I wanted to support are the ability to return references to entities and the ability to expose entities as entry points into the graph, which allows joining data across subgraphs. In this example, instead of resolving the relationship for the person type, with another fetch directive that makes a call to an API owned by a different team, part of a different organization, maybe in a different time zone. I can instead wrap the director foreign key in this really small type that does model the relationship, but using only data that I know about in this particular domain. If I add the Federation directives and identify this type as a keyed entity, Apollo Federation can use this data to fetch additional data for this type from other subgraphs. This is a useful practice in a distributed organization, because it reduces type coupling across domains and teams. Apollo Federation handles the data composition at the API layer. Instead of requiring that my team builds a bunch of synchronous requests to another team's services, which would tie my services' uptime to theirs. And lastly, I decided to allow adding the fetch directive directly to types, not just to fields. This is equivalent to the resolve reference hook in Apollo Federation. By providing a key entity this way, Apollo Federation can join data from this type with references and other data from other subgraphs.
In addition to supporting federation, I think what this points out is another benefit of the directive-driven approach, the ability to combine different behaviors just by adding different directives in the same file. Apollo has a built-in cache control directive that we could easily add here. We're also working on directives for operation cost, authorization, and many other behaviors that would all play nicely in the system. There's definitely a lot more to do in my prototype before it's ready for production. Some of the things on my to-do list include support for GraphQL unions, where Protobuf has this oneof keyword and it's not quite the same, but I'm pretty sure there's a way we can translate between them with the DSL. gRPC errors and GraphQL errors actually have more in common than you might think, but I haven't spent much time trying to figure out how to translate between them either. I think there's some really powerful ideas in this approach, including the ability to use gRPC streaming RPCs to power subscriptions for real-time data, but there's a lot of work that I have to do to figure out how to do that. And I'm sure I've missed a lot. I would love to know what use cases and features that you would want that I haven't addressed in this DSL so far.
To wrap things up, I want to revisit the values that I talked about earlier and score each of the approaches. The two traditional approaches, schema first and code first, are great. They're not going away anytime soon. They allow for a lot of flexibility in schema design and the ability to evolve your API and tie it to any data source under the hood. But using my rubric, they fail because they're really expensive. Writing all those resolvers is a ton of upfront investment and results in a lot of code that you have to maintain for a long time. Anecdotally, most companies I work with choose schema first.
Comments