Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apollo Federation Support #115

Closed
1 of 3 tasks
patrick91 opened this issue Jul 26, 2019 · 16 comments
Closed
1 of 3 tasks

Apollo Federation Support #115

patrick91 opened this issue Jul 26, 2019 · 16 comments
Labels
discussion enhancement New feature or request help wanted Extra attention is needed

Comments

@patrick91
Copy link
Member

patrick91 commented Jul 26, 2019

I was thinking of adding support for Apollo Federation, in order to make this happen the following TODOs need to be tackled:

  • Allow support for custom directives Custom directives #109
    not really true, but having a pythonic way of defining directives will help
  • Output types in schema even if they are not used by a query
    Right now we only output fields that are used (directly and indirectly) in a query. Federated schemas might not define a query at all, so we need to output types anyway.
  • Add support for custom scalars Add support for scalar types #3

Federation specification

scalar _Any
scalar _FieldSet

# a union of all types that use the @key directive
union _Entity

type _Service {
  sdl: String
}

extend type Query {
  _entities(representations: [_Any!]!): [_Entity]!
  _service: _Service!
}

directive @external on FIELD_DEFINITION
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
directive @key(fields: _FieldSet!) on OBJECT | INTERFACE

directive @extends on OBJECT | INTERFACE

Federation spec: https://www.apollographql.com/docs/apollo-server/federation/federation-spec/

Python API

Initial Ideas

type Review {
  body: String
  author: User @provides(fields: "username")
  product: Product
}

extend type User @key(fields: "id") {
  id: ID! @external
  reviews: [Review]
}

extend type Product @key(fields: "upc") {
  upc: String! @external
  reviews: [Review]
}
@strawberry.type
class Review:
  body: str
  author: User = strawberry.field(directives=[provides(fields="username")])
  product: Product


@strawberry.type(extend=True, directives=[key(fields="id")])
class User:
  id: ID = strawberry.field(directives=[external()])
  reviews: typing.list[Review]


@strawberry.type(extend=True, directives=[key(fields="upc")])
class Product:
  upc: str = strawberry.field(directives=[external()])
  reviews: typing.list[Review]
@patrick91 patrick91 added discussion enhancement New feature or request help wanted Extra attention is needed labels Jul 26, 2019
@patrick91
Copy link
Member Author

Another example, a bit more abstracted:

import strawberry
import typing


@strawberry.federation.extend(keys=["upc"])
class Product:
    upc: str = strawberry.federation.external_field()
    reviews: typing.list["Review"]


@strawberry.federation.extend(keys=["id"])
class User:
    id: strawberry.ID = strawberry.federation.external_field()
    reviews: typing.list["Review"]


@strawberry.type
class Review:
    body: str
    author: User = strawberry.federation.provider_field("username")
    product: Product


schema = strawberry.federation.FederatedSchema()

@ethe
Copy link

ethe commented Jul 28, 2019

I think author: User = strawberry.field(directives=[provides(fields="username")]) does not make sense in Python semantics. strawberry.field(directives=[provides(fields="username")]) is not a default value to Review.author as User type. It can not pass the type checking in mypy I think.

@Ambro17
Copy link
Contributor

Ambro17 commented Mar 29, 2020

I would like to help in any task available to give support to Apollo federation to strawberry. I personally prefer the first alternative as it is more easily translatable to the SDL

@patrick91
Copy link
Member Author

Hey @Ambro17! I did an initial PR here: #259

Would you like to give me a review? Code isn't great, but it worked with a simple test-scenario 😊

@Ambro17
Copy link
Contributor

Ambro17 commented Apr 5, 2020

Hello Patrick, the other day I looked over the PR and made some comments

@patrick91
Copy link
Member Author

@Ambro17 don't see any comments 🤔 I saw your issue, but no comments on the PR

@Ambro17
Copy link
Contributor

Ambro17 commented Apr 5, 2020

My bad, I thought that merely writing the comments was enough, but apparently github requires to end your review with a comment/request changes/approve. Now you should be able to see my comments

@patrick91
Copy link
Member Author

My bad, I thought that merely writing the comments was enough, but apparently github requires to end your review with a comment/request changes/approve. Now you should be able to see my comments

No worries! It happened to me as well a few times ^^ Thanks for the comments. By the way, I put federation on hold at the moment as I was trying to understand how to make a pagination API. By the way I was thinking of using discord and maybe livestream/pair with other people since we are all at home nowadays :D Here’s the link: https://discord.gg/ZkRTEJQ if you want to join ^^

@japrogramer
Copy link

japrogramer commented May 21, 2020

@patrick91 Relay does pagination fairly well. I made a pagination that extends relay

connection_type(
edges=edges,
page_info=page
)

where connection_type returns an object type with fields edges and page_info,
the connection takes a page and per_page argument by default.

edges is an array of types and page_info is a type that describes the results

class PaginatorInfo(graphene.ObjectType):
    count = Int(
        name='Count',
        description='The total number of objects, across all pages.'
    )

    current_page = Int(
        name='CurrentPage',
        description='The current page number.'
    )

    num_pages = Int(
        name='NumPages',
        description='The total number of pages.'
    )

    page_range = List(
        Int,
        name='PageRange',
        description='A 1-based range iterator of page numbers, e.g. yielding [1, 2, 3, 4].'
    )

    has_next = Boolean(
        description='Returns True if there\'s a next page.')

    has_previous = Boolean(
        description='Returns True if there\'s a previous page.')

    has_other_pages = Boolean(
        description='Returns True if there\'s a next or previous page.')

    next_page_number = Int(
        description='Returns the next page number. Raises InvalidPage if next page'
                    'doesn\'t exist.')

    previous_page_number = Int(
        description='Returns the previous page number. Raises InvalidPage if'
                    'previous page doesn\'t exist.')

    start_index = Int(
        description='Returns the 1-based index of the first object on the page,'
                    'relative to all of the objects in the paginator\'s list. For example,'
                    ' when paginating a list of 5 objects with 2 objects per page, the'
                    'second page\'s start_index() would return 3.')

    end_index = Int(
        description='Returns the 1-based index of the last object on the page,'
                    'relative to all of the objects in the paginator\'s list. For'
                    'example, when paginating a list of 5 objects with 2 objects'
                    'per page, the second page\'s end_index() would return 4.')

@patrick91
Copy link
Member Author

@japrogramer did mean to write a comment here: #175?

@japrogramer
Copy link

@patrick91 I was responding to your comment in this issue, was not aware of #175

@patrick91
Copy link
Member Author

@japrogramer my bad, I forgot what I wrote :D

@patrick91
Copy link
Member Author

This is done, we'll add documentation and examples soon, I'll close this now 😊

@Speedy1991
Copy link
Contributor

Hey @patrick91!

Any chance to get some docs on federation in the next time?
I would write some docs myself and give you a PR but I'm not sure, that I fully understand federation yet.
A minimal working example would be great with some introducing words to the decorators.

@patrick91
Copy link
Member Author

Hi @Speedy1991 I have some docs here: #440

I wanted to make a full example[1], but didn't manage to make time, would you like to make one? We can put it in our strawberry-graphql/examples repo 😊

Feel free to join our discord and ask questions there too 😊 https://strawberry.rocks/discord

[1] https://github.com/patrick91/strawberry-federation-demo

@Sheikhharis50
Copy link

Sheikhharis50 commented Jun 11, 2022

Hi @patrick91,

can you help me out with the N+1 problem in the federation? I have used the same approach given in the documentation of strawberry and it was extremely helpful, but I have observed that it's causing an N+1 problem, is there any example in which I can use Dataloader to resolve this issue.

I tried using Dataloader but it seems when I try to use in resolve_reference method and change it to async it doesn't work at all.

Followings are my findings, they may help you:
RuntimeWarning: coroutine 'ExecutionContext.execute_fields..get_results' was never awaited
RuntimeWarning: coroutine 'ExecutionContext.execute_field..await_completed' was never awaited
RuntimeWarning: coroutine 'ExecutionContext.complete_list_value..get_completed_results' was never awaited
RuntimeWarning: coroutine 'ExecutionContext.complete_list_value..await_completed' was never awaited
RuntimeWarning: coroutine 'ProductType.resolve_reference' was never awaited

The last one is my method and it says it was never awaited and I am not calling it, I think it calls in strawberry.

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

7 participants