Tips on modular (micro-serviceified) GraphQL server?


#1

I’m starting development on a GraphQL-based project (Apollo, hopefully). One of the imperatives from above is to embrace a micro-services architecture.

So far I’m designing server with each module has models (using knex, similar to Apollo GitHunt API server) but then we need to aggregate data from different modules. This could be done in an aggregation layer, or one module needs to reach into the data layer of another.

The fact that each module is not completely self-contained is frowned upon. Personally, I think the microservices trend has gone too far, when you have to go over HTTP to get data from another services (or probably multiple services) in order to accomplish anything. However, there are some people who firmly believe that microservices are the only way to go (e.g. CTOs, architects) and anything else is a slippery slope to unmaintainable code.

What’s your experience and recommendation? How do you modularize your GraphQL server, and deal with inevitable coupling?

Is it practical to create pure GraphQL microservices? Meaning, each service has its own queries and mutations, and talks to other microservices only through GraphQL. I’m thinking this is impractical, but maybe I’m wrong?

Another question: Has anyone experimented with running a GraphQL system AWS Lamda?


#2

I think the way to do it is to have regular REST microservices and then put one GraphQL server over all of them. Then you get the best of both worlds - the decoupled system you want with microservices but also one unified way to access them all.


#3

Thanks @sashko. REST is a pretty good way to create microservices, but it’s not very optimal to create and call REST services and then have to convert the result to GraphQL.

What we finally settled on is that we should have a single server, and separate modules that basically emit chunks of graphQL schema, and then at the root level we have to merge the schema (also queries and mutations) and merge the resolvers (similar to what the GitHunt API example does).

Originally we thought it would be nice to have separate services that each handle a subset of the queries, mutations, but I guess that’s not really possible at this point.

I believe this issue is close to what I was looking for : https://github.com/apollostack/apollo-server/issues/4 (Feature Request: graphql Server Chaining)


#4

I would definitely breakup your schema and resolvers into a separate module (I organize mine by resource). Then these get imported by a single /server/resolvers/index.js, where the index stitches them together into one exportable object.

Regarding microservices that use GraphQL to query/mutate data, i’ve done this with a previous project and at the end of the day I think it was a lot of work and it really depends on the project to determine if this extra wiring is worth it.

Also this system was written in Elixir and using the Absinthe GraphQL library but should still apply. The main difference is that it’s using the Erlang Distribution Protocol instead of HTTP so it’s much more efficient.

The app was broken into an API server and a couple services. All of the database mutations are made with GraphQL and then each service can connect to the API and query/mutate as needed.

Permissions are a bit different when doing server/server so you can setup some kind of token or role in the context and when the resolver does a permissions check it can deny it as business rules dictate… however typically the services have full access. You could also have a separate ‘private’ schema that only services connect to but this turns out to create repetition.

Also worth noting that typically microservices have their own database but for all of my apps so far this is really overkill.


#5

Perhaps this package might help


#6

Thanks @SkinnyGeek1010 for sharing your experience. This is kind of what I thought.

Interesting, cool. I’m not ready to learn another language at this point but Erlang feature sounds great. Still, like you said all the extra wiring, not sure it’s worth it. The thing you get from microservices, enforcement of strict boundaries, ability to deploy and scale services more granularly, I think it only makes sense if these services are relatively independent and decoupled. Otherwise I don’t see any advantage of having 5 sets of node instances that each handles one service vs one large set of node instances that each handles 5 different “services”.

The database isolation makes sense (usually), but again, there are cases where you have to pull data from disparate tables, where having this artificial level of isolation is more of a nuisance than anything.


#7

Thanks @jitterbop. Yep, this is a similar solution to what I arrived at on my own.

This ioSchema is interesting and looks kind of crazy though, I don’t get what it does:

${ioSchema(`Exercise$Input {
    _id: String
    name: String
    instructions: String
    group: String
    questions: [Question$Input]
  }`)}

As I side note, I don’t see the advantage of the string-based traditional GraphQL schema, vs the GraphQL-JS object-based notation, which is more natural in javascript. Having these two options is also confusing.


#8

I think having more options can be a good thing.

Personally I prefer my schema to be as static as possible, so that I can look at my code and see exactly what types I have. But others could have different preferences, and it’s great that there are different ways to define a GraphQL schema.


#9

Will you expand on this for us? I know I at lease have not read about the said imperatives. Who’s given them, what exact are they?

I’ve heard this terms bandied about lately, will someone please give us a useful explanation?

Will you explain this more? Who is frowning?

This sound just the opposite of Meteor (and awful). Is this the “recommended” way to interact with GraphQL? Will you expand on this more, and if so, who is recommending this architecture, FB by chance?

They don’t, no one around here has built anything with GraphQL and stuck around long enough to deal with the fallout – haha – just kidding. :slight_smile:

Don’t be too quick to call yourself wrong, if you feel that something isn’t right, there’s a good chance you’re on to something.

So would this look like Saturn => Apollo => GraphQL?


#10

@aadams I will try to answer your questions.

I have a boss, who loves microservices. Not that that’s a bad thing, but sometimes people latch on to a buzzword and can’t see past it.

It’s generally accepted that code that is self-contained and has clear boundaries is better and more maintainable. In the OOP or component-based programming world this is called “encapsulation”. On a somewhat larger scale, with the trend of containers (docker, etc.) and also AWS lambda, etc. this means a service is completely encapsulated, and it only communicates with other services via REST, or some other network protocol.

When you build a product, it’s not always easy or feasible to break it up into fully encapsulated services, when you have a lots of relationships between objects (think database tables with relationships). A single query can span several different tables.

GraphQL and Apollo provides a way to create a single API endpoint and interface, but it doesn’t really provide any way or recommendations to create modules/sub-services, and therefore break up your code into smaller pieces. Likely because, in most cases, it doesn’t make sense.


#11

@sashko are you going to provide some guidance on this? Also, I love the way you provide the minimum.


#12

I think your GraphQL API should be a single endpoint, probably provided by one server codebase. What’s underneath is up to you - it could be a host of REST microservices, a set of modules that represent different parts of the schema, or anything else.

Once you get into really big schemas, I think it’s a good idea to look into the GraphQL type machinery and figure out what’s easiest for your case to define the schema.

This talk goes into a lot of great detail about the concepts:


#13

Apollo-feathers throwing another wrench in my works. One of my developers was telling me this morning we should use feathers. I look at this feathers-apollo project, and it says it will give me all these advantages:

  • Feathers cli tool to generate boilerplate and new services (I really don’t need another CLI to generate boilerplate for me)
  • JWT based auth (Ok, useful, but I don’t know if this saves me more than a few lines of code)
  • Permissions via feathers hooks (user can only remove and modify own posts, etc.) (What are “feather hooks”, do I really need to add a whole new web framework to get this?)
  • Consistent query syntax independent of database (I could change the database from Mongo to SQL with no change to the resolvers) (am I really going to need this?)
  • Automatic REST endpoint generation (it is nice to have REST as a fallback or for public facing APIs) (more stuff that could potentially break).
  • Express compliant (use any Express middleware without hacks) (what does this mean?)
  • Automatic Socket.io integration (way to use websockets for real-time features before Apollo’s solution is completed) (I’d rather wait for Apollo solution).

I felt pissed because new ideas seemed to be spiraling out of control. We’re getting into the old familiar pattern of bringing in pointless technologies (having flashback to my Java days with all my maven dependencies). Because there are few best practices and instead there seems to be a proliferation of different projects and integrations.

The docs of this project reads “The Eagle Has Landed - Special thanks to Sashko Stubailo for reviewing this post and for asking me to write this!” Too man eagles are landing. I prefer simplicity.


#14

You don’t need to use feathers if you don’t want to, though.


#15

Here’s the feathers post just in case someone is so inclined.

@sashko, how many integration points are you guys specifically targeting for Apollo?


#16

We don’t have a grand plan. In our open source projects, we’re focused on enabling the community to build the tools people need to work effectively with GraphQL. We’re using apollo-server, apollo-client, graphql-tools, and react-apollo in production today, so those are the ones we keep the closest eye on.


#17

@vonvao that schema just generates an Input/Output schema type.
That is if you need your schema type to flow from both ends. Your example in the final schema would lead to:

type Exercise {
    _id: String
    name: String
    instructions: String
    group: String
    questions: [Question]
  }

input ExerciseInput {
    _id: String
    name: String
    instructions: String
    group: String
    questions: [QuestionInput]
  }

Not much magic :wink: Just keep the definition shorter and less prone for errors when you change type or input.


#18

Yes, I can see that. I think that can be a good thing, it’s the open source model, but it requires more leadership from the community.

Ok, but if the two schemas are identical I’m not sure why you need two of them… I’m sure there’s a reason that I’m just not getting yet :wink:

On feathers.js, I finally looked at it and one thing I do like is the common interface to different DBs:

Every database adapter supports a common syntax for pagination, sorting and selecting and advanced querying out of the box and can be easily extended with custom functionality. Errors from the adapters (like ORM validation errors) will be passed seamlessly to clients.

Seems that something like this could be useful for creating common packages, like accounts.


#19

Hello

I am exactly at this point myself. I am building things in php with the concept of schema stitching where you introspect several schemas on remote graphql servers and use a general extension schema to define rules for stitching types from one graphql to another.

I have a concern though: When we split microservices, how should we define a type in one server that is actually referring at another microservice main type.

Imagine users microservice and articles microservice. They are obviously connected as you have all articles for a user on one side and the writer for an article on the other side.

Any suggestions?