Apollo Federation: Is it possible (and how) to return a concatenated array from different services?

Order Service Schema:

extend type Query {
    order(id: ID!): Order
}

type Order @key(fields: "id") {
    id: ID!
    notes: [Note]
}

type Note @key(fields: "id") {
    id: ID!
    title: String!
    body: String!
}

I have two additional services, Service A and Service B. Is it possible for those services to resolve the order.notes array, and have all of the array values concatenated?

2 Likes

Hello! This pattern as described sounds risky. If multiple subgraphs in your graph can resolve the same entity field (via the @provides directive), they should all resolve that field identically. This is because it isn’t guaranteed which subgraph will resolve that field for any particular query (this is determined at runtime by query-planning logic in the gateway).

Consequently, the logic for each subgraph’s resolver would need to differ slightly to account for pulling in values from the other subgraphs that provide notes. If at all possible, I recommend populating all of a particular list field’s entries from a single subgraph.

Like @StephenBarlow mentioned, this sounds like an anti-pattern. In a scenario like this, I would start by asking “Why do Order Service, Service A, and Service B all need to resolve this field?”. If it’s the data source, then try to consolidate the data-source itself and have one service resolve the field you need.

Take a look at the Concern-based Seperation principle of federation.

In the end, we want the client to work with a schema that has an array of notes at the order level.

It sounds like there are two paths to this:

  1. Keep the schema in its current form, shifting the responsibility of the notes being part of the Order service (not Service A or Service B).
  2. If Service A and Service B truly do need their own notes data, then I’ll have to adjust the schema so that those services expose their own notes array on the hierarchy, then on the client shape the data returned by graphql endpoint to match the final schema (i.e. combine those individual note arrays into one).

We do have a reason for wanting the notes that relate to the service to also be stored and managed within that service. I was just hoping that the query builder had some way, perhaps with a custom directive, that allowed multiple services to provide the contents of an array, then combine them all together before sending down the client. It would save us from having to the data shaping on the client after receiving the graph data.

We will probably take a look again and see if we want to store the notes in its own service and not spread out over the different services.


Perhaps this can be a feature request to enhance the federation service, introduce a federation directive “@concat” or something like that, so that the query plan knows to call each service and concat the results for that field from each service into a single array.

@amrock-my did you ever come up with a good solution for this, or find a good pattern? I’m trying to do something similar, but in many places (i.e. I have a lot of “resources” from different sources I’d like to normalize and join / concatenate so the consumers of the graphql service can be blissfully unaware of the multiple sources :))

There are perfectly valid reasons for this - not an antipattern, though this would bring ordering, chunking into spotlight. For example, a case that frequently popped up for me is a single container/owner object (or type) having a collection (set) of a variety of other things that are of different subtypes of the declared base type. Separate services may handle separate types - zero risk of duplication.

If you need a concrete example, think of the following (final, merged) schema:

type Person {
   id: ID!
   pets: [Animal!]!
}
interface Animal {
   id: ID!
}
type Dog implements Animal {
   id: ID!
   barksALot: Boolean!
}
type Cat implements Animal {
   id: ID!
   meowsALot: Boolean!
}

… and have dogs and cats be served up by different component services.

I have to address exactly this case soon (well, not pets) because there are geo-political (not technical) requirements to separate “Dogs” and “Cats” with potential of more subtypes in the future. The only way I understand is available right now is to have a custom stitching service dedicated to this case that must be maintained/modified every time there is another type.

I’ll provide further context. Some countries and institutions have regulations, rules or requirements to:

  1. Keep the data stored on their soil / in their premises, property.
  2. Have restrictions or implications on communicating their data outside their boundaries - in the simplest, most restrictive case some data can’t leave at all. This requires that code/logic handling that data must be near said data.
  3. Have slight differences in properties and handling - e.g. financial/banking rules differ how they classify / break things down, handle them, etc, yet they have similarities globally that we’d like to exploit via the base type/interface and containment in the same collection.

One does not need to look beyond US (FedRAMP) and EU (GDPR) to get some “inspiration”.

2 Likes

I also need this feature as we have a organization-level concept that must be supplied by different microservices, but the data contract can remain largely the same. I’m wondering if Federation 2.0 solves this problem with sharable types?

Are there any updates on this topic?
I have a similar problem where I need to concatinate HVAC Elements in service 1 with Electrical Elements in service 2, so that I can write this query:
elements{
Id etc}
This query should contain a combined list of both hvac and electrical elements.
A workaround is also approciated :slight_smile: