The rationale behind forcing identical shared ENUMs in federation

As of now, for value types like Enums and interfaces, if they are used by more than one service, they will need to be copied over to every single service with the exact same definition. I’m curious to know the rationale behind this design decision, because it is rather troublesome for those that don’t have a schema registry running yet. For me, as a federated schema maintainer, I will have to make sure that when some developer wants to use an Enum in more than one places, they add it to all the services, and this is inefficient as well as error prone. I’m wondering why there isn’t a way to reference an Enum, or have a dedicated shared storage space for these shared values.

Any help in explaining the decision making would be greatly appreciated!! Thanks in advance.

Pretty sure the issue is simply due to global namespace collision.

In normal schemas multiple enums with the same name throw an error. In federation, it’s completely possible that multiple services use the same enum for everything, so it’s deduplicated in the federated schema with the caveat that they have to be exactly the same or deduplication will fail.

Personally I would really like federation to have a configurable renaming rule, where you can detect duplicates in the schema that differ from each other and rename them appropriately.

I’m pretty sure you could do this with a visitor (and probably a little bit of state during schema composition), and some fairly straightforward defaults I could think of would be:

  • Duplicate enums are deduplicated
  • Enums with the same name but different values are automatically prefixed, which would default to the name in serviceList (or equivalent). The method of postfix should be configurable, as some people use CONSTANT_CASE for enums and others might use PascalCase.
  • Enums that meet a certain naming convention (prefix, postfix, regex), or via a directive, can be forced to deduplicate and throw an error if they cannot be deduplicated. You should also be able to enable/disable/force this behavior in the gateway.

Thanks Kevin! That might have explained why these enums have to be exactly the same, but still I don’t understand why we can’t just keep one copy of the enum definition, and when other services need it, let them reference it remotely?

Probably a limitation of GraphQL.

In GraphQL, your schema isn’t valid if it has references to things that don’t exist in its full schema, so you wouldn’t be able to start your child services in order to perform remote composition.

Federation is also not two-way; sure, the gateway sends requests to the child services, but the child services don’t actually know about the gateway. Making them require functionality that exists inside of the gateway causes them to depend on the gateway, which already depends on them in the first place, so you’d have a circular dependency between services.

Unless Apollo finds a way federation composes schemas in a way that solves this while also being fully compliant with the GraphQL spec, there’s not a whole lot to do. Using git submodules or a common folder for shared type definitions and resolvers is a small price to pay when compared to the architectural overhaul of resolving partial schemas and circular dependencies.

Even doing something seemingly simple, like “have the gateway process these specific things” opens a very wide and deep can of worms. For example, what happens if you have thousands of references to a directive or enum across various schemas, and a custom resolver respectively? You’d have to make possibly thousands of requests at once back to the gateway to resolve them, which creates a ton of network traffic and also causes the gateway to be doing more work than any other service, creating a potentially huge bottleneck.

As a result, a single custom resolver being added to the gateway could clog up the pipes, causing your autoscaler to scale up massively, meaning that it potentially becomes an ordeal to add a custom resolver, rather than saying “here’s a common file/folder for these X services; if the algorithm is too slow we’ll update it”, but the bottleneck stays on the child service.

I agree that it would be nice to not have to have such duplicate definitions and then have to deduplicate them, too, but the scope of such a task and is large and vaguely defined, to the point where taking steps to mitigate certain issues might create an environment for unsustainable feature creep. I think that these solutions should definitely exist, though, but there would probably need to be a whole new API introduced into the gateway to allow users to perform arbitrary schema manipulations for such “vanity” purposes as this, allowing the GraphQL ecosystem to fill in certain gaps that are too large or have too many opinions to “succeed” at implementing.

If a winner emerges, it can be added to the docs or pulled into @apollo/gateway if it’s deemed too much boilerplate.

It’s true that you cannot reference something that doesn’t exist your schema. But we can extend an enum to create a stub (just like what we are doing to the entity types), and this way each child graphql server can still keep their schema valid.

As for runtime enum resolving, it should be very similar to the entity types.

I was unaware enum extension was added to GQL, gonna take a look at that. Might be a decent feature request, for @apollo/federation/@apollo/gateway.

You’d probably still have to make sure implementation is the same on all of the child services, but I can see your point. I’d probably shy away personally because the other enum values would be merged in that case, which would probably remove certain enum cases when used with graphql-codegen.

For example, service 1 has enum with A, B, C, service 2 extends with D, E, F, the codegen on each service is unaware of the other enum values respectively.