federation-demo icon indicating copy to clipboard operation
federation-demo copied to clipboard

Federation Mutation Example

Open cheepo2109 opened this issue 5 years ago • 20 comments

It's an awesome example for Federation! I have 1 question on my mind, after trying it out. How do I write resolver for federated mutations? Let's say I want to create or update reviews and products. They are on 2 different services? What would be the best approach? It would be nice to have a mutation example in the demo :) Thanks!

cheepo2109 avatar Nov 13 '19 12:11 cheepo2109

I think this is extremely important to demo. It's actually one of the main drawbacks that I'm seeing right now. In a monolithic GraphQL server, it's easy to perform complicated logic in a single mutation, but right now the only solution I can seem to find when using a federated graph is to push that logic onto the client, which IMO is not a good solution at all.

Federation seems like a great technology for querying data from multiple sources, but doesn't seem to hold up too well for anything more complex.

Would be really good to get some input from the Apollo team, although this repo seems stale and the only contributor looks like they're not a dev anymore judging from their GitHub status...

joemckie avatar Dec 29 '19 14:12 joemckie

any suggestions on this?

Orgil avatar Apr 17 '20 09:04 Orgil

Yeps would be great to have an explanation on how mutations can be accomplished when the federation schemas is being used.

While the story is well defined for queries, having the feeling that there is no good example - and maybe implementation - for mutations.

For example, regarding the reviews and product use cases, two separates schemas that got federated and allow the user to make optimal queries that touch both services with still providing a good way of isolating both areas. How for example the addition of a new review would need to be expressed as a mutation?

The naive approx would be adding to the reviews the schema the following mutation:

type Mutation {
  addReview(productId: String, review: ReviewInput)
}

But the first question that you could ask for yourself is where the productId should be really retrieved - and implicitly validated? With the current proposal would be the responsibility of the user of providing a valid id. Besides not being the most optimal thing - you would need two round trips, one for checking the existence of the product and another one for the mutation - you would be on the mercy of the caller, so if the caller would use invalid ids you would be populating invalid ids to the database.

I'm wondering if there is any way of moving that query to the Apollo GW, so Apollo GW would continue with the mutation if and only if the query for retrieving the production would not return an error.

pfreixes avatar Aug 28 '20 15:08 pfreixes

bumping this. would really love an example of this as well

irvinktang avatar Nov 23 '20 23:11 irvinktang

I was wondering the same thing. Best answer i could find was this: https://spectrum.chat/apollo/apollo-federation/mutations-for-federated-services~b45f6d65-2785-4e26-87ce-03ce23676fbf

and for a more in depth explanation: https://spectrum.chat/apollo/apollo-federation/graphql-microservices-what-is-the-accepted-approach-for-inter-service-communication~ffc5fb60-639f-4d11-af54-ea3fcb4a447b?m=MTU3NTk0Njk3NjIyNQ==

StefanNeuberger avatar Feb 21 '21 17:02 StefanNeuberger

I understand that there is currently no real support in GraphQL Federation for mutations... and I know why: it's much harder (if not impossible) to find an as elegant generic solution as we have for queries, and still be fast enough.

The best option is probably to 'stich' your mutations, i.e. program the full orchestration yourself. It will be hard to balance between eventual consistency, retries, timeouts, two-phase commits, etc. A generic example would not help a lot, as there are too many use-case specific parameters that have to be taken into account.

Just my 2¢

t1 avatar May 11 '21 15:05 t1

This doesn't solve the whole issue of needing a more in-depth example, but there's another thread here with some info on mutations in Apollo Federation: https://github.com/apollographql/apollo-server/discussions/4194#discussioncomment-631331

In case the link breaks later, here is the gist of the linked comment:

Yes, they [mutations] are supported. In the same/similar way that services/subgraphs should extend type Query to expose top-level entry-points into the API schema (that's exposed to the client), the subgraphs/services need to extend type Mutation with the fields they want to expose:

type Result {
 success: Boolean
}

extend type Mutation {
  review(date: String review: String): Result
}

DevonStern avatar May 11 '21 17:05 DevonStern

While technically mutations are supported exactly like queries, it's not explicitly mentioned in the spec, so all the interesting stuff is not specified: e.g. what happens if one of the backend services fails? Maybe it would be okay for an example where these things don't matter, but in real life, there has to be a solution that matches the business requirements. And as there is AFAIK no support to specify these details, it's generally not usable in the real world. I think it's better to just manually write an orchestrator.

Still it would be interesting to see how you can split the input data among the different services. E.g. how do I update one review and add a new one in the same request? How do I update one review to be about a different product? How do I update, e.g., the price of the product of two reviews if they refer to the same product?

t1 avatar May 12 '21 03:05 t1

I'm considering using graphql federation and this is my only issue with it, I feel like I need to rely on other microservice/event-driven architecture patterns to communicate between the microservices within the mutations to properly update the databases using some message queue or kafka via something like a Saga pattern, but ofcourse managing rollbacks and errors are much more complex than a normal setup, so its pushing me towards an event sourcing architecture with database replication/cqrs which is obviously quite complicated

khaledosman avatar Sep 22 '21 13:09 khaledosman

Is there any update?

iamfotx avatar Nov 01 '21 02:11 iamfotx

This concept is also block me of using federated graphql, so an example how to do this properly would benefit me greatly.

JaapWeijland avatar Nov 02 '21 16:11 JaapWeijland

The benefits of a federated approach far outweigh the disadvantages. What would be the alternative? There's nothing else on the market (that I'm aware of) that offers what you presumably propose Federated Migrations would support. I'm sticking with this architecture and I'm confident it will mature.

PBTests avatar Jan 28 '22 21:01 PBTests

Any new update or idea? 😥

carlocorradini avatar May 09 '22 14:05 carlocorradini

Any new update or idea? 😥

unfortunately I don't think its a graphql or apollo problem, but a microservice architecture one, more specifically with database per service, the problem wouldn't exist with a shared database.

microservices.io has great explanation of these architecture patterns and their pros & cons https://microservices.io/patterns/data/database-per-service.html , microservices are difficult unfortunately and there's no easy solution, stay away from them unless you really need them. for smaller apps you could maybe rely on message queues and deal with eventual consistency.

khaledosman avatar May 09 '22 14:05 khaledosman

unfortunately I don't think its a graphql or apollo problem, but a microservice architecture one, more specifically with database per service, the problem wouldn't exist with a shared database.

a single shared database is often not possible, or even an antipattern in some cases at this point. Think active directory.

That said, I'm giving my +1 for some kind of example. I need to ask another set of services that I don't control for input and at least validate the id for the database we do control.

xenoterracide avatar Aug 22 '22 15:08 xenoterracide

Any updates here?

jlongo-encora avatar Dec 16 '22 13:12 jlongo-encora

If you're interested in how Apollo Federation can handle cross-service mutations, check out this repository Apollo-Federation-Mutation-Demo. It provides a practical example of accessing external data in a seamless way.

filwaline avatar Jan 09 '23 19:01 filwaline

If you're interested in how Apollo Federation can handle cross-service mutations, check out this repository Apollo-Federation-Mutation-Demo. It provides a practical example of accessing external data in a seamless way.

It's an approach but the whole point of querying a subgraph from another subgraph breaks the point of federation is not it?

tummalah avatar Jan 24 '23 21:01 tummalah

The document Contributing computed entity fields describes how to query data from external subgraphs and contribute a new computed field to the entity.

I have adopted this approach to help ensure consistency in mutations, and I believe that this does not go against the principles of federation. @tummalah

filwaline avatar Jan 26 '23 07:01 filwaline

I guess that with one InputType or the parent TypeReference in the subgraph could will resolve (I don't know if the spec enable this). I mean: imagine us that we have two uncouple microservices: User & Product (for this example I'll use Strawberry Python as a reference).

### User
@strawberry.federation.type(keys=["id"])
class UserType:
    id: strawberry.ID
    name: str

    @classmethod
    def resolve_reference(cls, **representation) -> "UserType":

        id = strawberry.ID(representation["id"])
        user = db.session.get(User, id)

        return cls(id=user.id, name=user.name)
### Product
@strawberry.federation.type(keys=["id"])
class UserType:
    id: strawberry.ID = strawberry.federation.field

    @classmethod
    def resolve_reference(cls, id: strawberry.ID):
        return UserType(id)

@strawberry.federation.type(keys=["id"], description="Product Type definition")
class ProductType:
    id: strawberry.ID
    name: str

    @strawberry.field
    def created_by(self) -> typing.Optional[UserType]: ### Store the User.id value

        return UserType(id=self.created_by)

@strawberry.type
class Mutation:

    @strawberry.mutation
    def product_create(self, name: str, created_by: UserType) -> ProductType:

        product = Product(name=name, created_by=created_by.id) ### And be able to use the parent value here!
        db.session.add(product)
        db.session.commit()

        return product

The problem with this approach now is that:

TypeError: Mutation fields cannot be resolved. Argument type must be a GraphQL input type.

Why with the UserType in the Product microservice? Because the value already resolve in the resolve reference in the User microservice.

nietzscheson avatar Apr 02 '23 23:04 nietzscheson