GraphQL Microservice Architecture

I’m working on a new GraphQL API within Serverless and would like to solicit the opinions from others on a good approach to architecting this project. For the sake of simplicity, let’s just focus two types (Users and Contacts)

type Contact {
    id: ID!
    user_id: String!
    name_first: String
    name_last: String
    email: String
}

type User {
    id: ID!
    username: String!
    email: String
    contacts: [Contact]
}

The initial thought was to create a series of microservices for these resources and tie them all together with a gateway API that would be exposed to the public. I would like to keep all the code in a single repo for tooling and common library reuse. I’ve been waffling on two different approaches…

  1. Create sub-directories for each of these resources that includes the the model, schema and resolvers for that resource and then merge those all together within a single gateway handler. This would have a single serverless config to deploy the whole API and all the resources for Dynamo, API Gateway, Elasticsearch, etc.

  2. Still create subdirectories for each of the resources, but each sub directory would be its own Serverless project with its own SLS config and deploy scripts. The service would provision the necessary resources and generate a GraphQL endpoint. I could then take these endpoints and create remote schemas in the gateway API.

I’m leaning towards option 2 for its scalability, but wanted to bounce it off others before I jump in whole hog. Open to variations or other suggestions as well.

Do you really need a microservices architecture today?

My default reaction is:

  1. Use AppSync instead of the API Gateway.
  2. Use DynamoDB and ElasticSearch data sources as much as possible.

Once your application does become large enough to justify a microservices architecture then:

  1. Continue using AppSync for the API
  2. Move the services into their own projects
  3. Reconfigure AppSync to use Lambda data sources and invoke Lambda from individual microservices.

I went with an architecture similar to your approch 1 and used it in production for few months.
I suggest you start with approach 1 and then extract micro-services out one by one as described in the approach 2 when you will need it.

I have a mono-repo with a graphql folder representing my graphql service with two endpoints:
graphql.example.com/graphql
graphql.example.com/graphiql
The Graphql service is just one Serverless project connecting to a mongo database.
In your case you would be connecting to 2+ Dynamo tables.

In the future you could extract a service for one or more parts of the domain model (ex User).
You would then need your graphql service to call the users service somehow (json-over-http, gRPC, Lamdba event). This would complicate things so I would post-pone it for when you’ll actually need it.

If your project is supposed to be a company that will last many years I would not go with a solution like AWS AppSync but rather would re-build the entire stack piece by piece.
In the end AWS AppSync is just some glue using Apollo, Dynamo, Lambda and MQTT.


What happens when in 3 years you want to change one of the pieces?

Thank you for the feedback, the mapping templates were my original hesitation with going the AppSync route, but after a little digging they aren’t as bad as I expected. Having Lambda as a bailout to perform more customized login before connecting to DynamoDB is definitely worth it. I have some additional questions specifically related to working with the mapping templates, but I’ll ask those in the appropriate forums.

This is also good input, I have scaled the project back down to keeping the individual “micro-services” more as project organization rather than maintaining individual serverless projects and deployments.

Isn’t AppSync supposed to be an enterprise level GraphQL service? I am replacing an existing REST API that will definitely need to be around for several years. Is your suggestion to avoid just based on the fact that I’ll be tightly coupled to Dynamo, Elasticsearch and Lambda? Seems like the subscriptions, auth and logging features are pretty nice add-ons to Apollo.

I think AppSync is just comparable to solutions like Meteor, Firebase and Graphcool/Prisma.
They aggregate many different backend/database technologies in one solution.
In my opinion this is generally a bad idea. Imagine you went with Firebase 3 years ago and then graphql came along. You would be stuck with Firebase and couldn’t add graphql to the mix.
The same is going to happen again in the upcoming years: One piece of AppSync will become obsolete and you will be stuck with the entire thing. For example AppSync uses MQTT instead of websockets for the subscriptions part.
I would just start easy with a graphql Lambda a few dynamo tables, already that scales very well.
I used the node npm package apollo-server-lambda

2 Likes

I’ve answered a similar question on Stackoverflow that might be relevant to you:
Serverless Web App Architecture

I think the arguments for not using AppSync are misguided. Firebase and Graphcool are complete solutions while AppSync is the API Gateway of the GraphQL world.

For example: I could build a REST API using the API Gateway, DynamoDB and Cognito User Pools. The same DynamoDB table and Cognito User Pool can be used with AppSync so the REST and GraphQL API now reference the same data.

It’s also much faster to get started with AppSync than trying to build your own solution with Lambda and the API Gateway.

1 Like

I know I said I’d ask these elsewhere and I will if necessary, but when dealing with resolvers directly connected to DynamoDB and Elasticsearch data sources I don’t know quite how to handle writing unit tests or debugging locally in my IDE. Maybe this isn’t even necessary for these data sources and can be reserved for Lambda function data sources.

Overall, AppSync has been quite easy to get up and running, especially with the help of the Serverless AppSync plugin. Thanks for the suggestion

I’m not aware of any frameworks for unit testing the mapping templates but it would be useful.

I believe my point still stands given your clarification.
AppSync is not the same as Firebase/Graphcool/Meteor but it’s a comparable level of easy-to-use, flexibility. What would you compare it too in non-AWS otherwise?
Yes I agree AppSync is probably faster to get started with than building everything from scratch.
Firebase/Graphcool are probably easier to use though.
It’s true that if you have already have DynamoDB tables for a REST API than migrating to AppSync is a good incremental step.
I’ve built my own stack similar to AppSync just using Serverless, apollo-server-lambda, Apollo and pusher.com. I’ve used it for a large SaaS software that is already 5 years old and will likely go on for at least 5 more years.

How would an individual service invoke a lambda in this setup? We have lambdas calling other services/lambdas going through the AppSync front-end and that adds an unacceptable overhead.

This is going to happen with every technology. In the meantime, you can get pretty far with AppSync we are finding.

Something that works well from a practical perspective is treating the AppSync as a gateway service. When you deploy it you’re deploying the gateway (AppSync) with authentication and a schema. You then expose the API ID to other services that add data sources and resolvers.

By default when AppSync is deployed to you get null back for all queries/mutations but as you deploy the other services the resolvers start to return data.

Yeah sure, but we explicitly don’t want to go through AppSync for microservice composition.