Extending a GraphQL-based CRUD store with microservices

I’ve been building a few microservices to support an existing GraphQL based store extend it’s capabilities, and perform mutations that don’t fall into the usual CRUD model using a GraphQL.

If you use a tool like scaphold.io, by building your schema, you get all the basic CRUD functionality on GraphQL for free.

For an instance — by defining the “User” schema, I get getUser query, createUser mutation, updateUser mutation, and if the user has a “friends” relationship — getUserFriends (id: userId) query as well.

But anything that requires custom code — like sending a welcome e-mail to the user, requires an external service. In this instance, you can use a service that’s callable on say POST /user-created that will send the welcome email, plus do other grunt work after the user creation.

I’m a big fan of the monolith-first approach to designing things because of obvious reasons. But by having a CRUD layer that takes care of the bulk of the business logic — I think we’ve found some good candidates to spin off microservices invokable on service hooks.

So, let’s say that we decided on building microservices to take care of the handful of the custom tasks.

When starting on this exercise, you will see that your service endpoints get invoked with subsets of graphql data, with the structure of the completed action. For an example, if you call POST /user-created after mutation createUser (...) {...}, your POST /user-created will get invoked with the body:

Here are a few patterns that I’ve found useful when spinning off a large number of microservices to support such a system.

  1. Keep your endpoints separate from controller logic.

Seems a bit too obvious — but it’s extremely important you establish this pattern earlier on.

Your graphql endpoints will invoke your services with heterogeneous payloads. And more often than not, your controller logic — for an example to send a e-mail to a user — will need re-using. To reconcile this, always de-couple your endpoint handler from controller logic.

When using something like serverless, that would mean your deployable services will have shared pieces of code, and your service handlers looking like this:

2. GraphQL everything!

When you need to write data back to the GraphQL data store, just write with GraphQL queries and mutations. You don’t need anything fancy. Just a wrapper that can fetch an endpoint with a graphql query over a POST call will do.

This is something I’ve spun off with minimal effort for one of my projects:

so, when an e-mail is sent to the user — as in the previous example — I just do:

3. Secure things with a pre-shared key and HTTPS

Having a long-lived pre-shared key that is sent across HTTPs to prevent MITM sniffing is mandatory, and probably enough security for most applications.

It’s also possible and a little bit overkill to have a mechanism to push and pull a shared short-lived token out of some shared storage (like S3) or be generated from a lambda-esque function that is accessible to services independently. Or, you could always rely on your identity provider (like Auth0).

4. Everything else…

…doesn’t differ much from the same patterns that we would use when architecting SoA-based (lazily/asynchronously evaluated) services. For an example, fault tolerance and failover can be achieved with task queues that listen to the service hooks. You can also build a shared caching layer by having a Redis-esque writer that writes interesting data points from GraphQL responses (optimistically if you want. scaphold.io has a “pre” task that can execute a invoke a service hook before data hits the store) and a Redis reader from your services. But now — I think I’m going overboard.