Giter Site home page Giter Site logo

movio / bramble Goto Github PK

View Code? Open in Web Editor NEW
491.0 21.0 54.0 838 KB

A Federated GraphQL Gateway

Home Page: https://movio.github.io/bramble/

License: MIT License

Dockerfile 0.21% Go 99.79%
graphql federation graphql-golang graphql-server bramble api-gateway golang

bramble's People

Contributors

anar-khalilov avatar andyyu2004 avatar asger-noer avatar azadasanali avatar benzolium avatar codedge avatar dependabot[bot] avatar djameson42 avatar facecrusher avatar gmac avatar haysclark avatar hsblhsn avatar jainpiyush19 avatar joshiefu avatar karatekaneen avatar leonhuston avatar lucianjon avatar mai00cmi avatar pasdeloup avatar pkqk avatar quantumplation avatar robinminso avatar robrohan avatar seeday avatar segfault88 avatar suessflorian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bramble's Issues

Impossible to use as a library

Hi team,

I would like to use bramble as a library. It's currently impossible because some types are not exported.

  1. As per the docs, I should be able to implement bramble.Plugin without having to embed bramble.BasePlugin. This is currently impossible because bramble.queryExecution is not exported, but is present in the signature of bramble.Plugin.ModifyExtensions method.
  2. To create a custom main function, I need to create an instance of bramble.Gateway. This is impossible because neither bramble.newExecutableSchema nor bramble.ExecutableSchema.plugins are exported.

Could you please export the listed types/functions so bramble could be used as a library?

Thanks for the help!

RFC: carve out Bramble's core into an importable package

Bramble is currently a monolithic and opinionated gateway, which makes supporting all use cases quite difficult. Separating out Bramble's core gateway logic into its own importable package would allow more use cases to be implemented by the community.

Fill type data from multiple services

Hi Bramble,

Working with shared types, I was able to get multiple services to fill the same type when it's in the root query, but could not get this to work when the object is in an inner query.

For example assume I have a "tasks" service and a "profiles" service, and each task can have an "owner" field which is a Profile.
Given the following schema for tasks:

directive @boundary on OBJECT | FIELD_DEFINITION

type Query {
  tasks(done: Boolean): [Task]
  task(id: String): Task
  service: Service!
  tasks__profile(id: ID!): Profile @boundary
}

type Profile @boundary {
  id: ID!
}

type Task {
  _id: String
  text: String
  done: Boolean
  owner: Profile
}

type Service {
  name: String! # unique name for the service
  version: String! # any string
  schema: String! # the full schema for the service
}

And this schema for a Profile service:

directive @boundary on OBJECT | FIELD_DEFINITION

type Query {
  service: Service!
  profile(id: ID!): Profile @boundary
  someone: Profile
}

type Profile @boundary {
  id: ID!
  first_name: String
  last_name: String
}

type Service {
  name: String! # unique name for the service
  version: String! # any string
  schema: String! # the full schema for the service
}

I have no problem using the someone query to get a profile and fill its fields from both services, but when I try to read the tasks and data about their owners such as:

query {
  tasks {
    text
    owner {
       id
       first_name
    }
  }
}

I will get just "null" as the result data.

If I remove first_name field from the query everything will work and I will get the correct id of the owner, so I assume tasks service works ok.

Is there a way to get bramble gateway to fill inner owner query from a different service?

Benchmarks comparison with Nautilus

Hi!

This projects looks incredibly interesting! In our team we use Nautilus for federating our Graphql schemas. Bramble docs contains info about differences with Nautilus and it's super useful! What do you think about adding some performance benchmarks?

Optimize multiple @boundary fields lookup

Currently undocumented is the ability to use a @boundary resolver that looks up a list of objects instead of a single object, i.e.

type Query {
  getFoos(ids: [ID!]!): [Foo!]! @boundary
}

instead of

type Query {
  getFoo(id: ID!): Foo @boundary
}

However there are a few issues:
While there is some code that expects the list boundary lookup, the validator complains about the signature incorrectly. Seems like it also expects the return type signature to be [Foo!] instead of [Foo!]!.

In some cases it then fails to merge the fields onto the object from another service.

There are some example services on the federation-id-deduplication branch. toomanyfoos exposes some lookup fields to get a list of Foo objects. zoot adds some fields to the Foo object.

Run with:

PORT=8091 go run ./examples/toomanyfoos/ &
PORT=8092 go run ./examples/zoot/ &
BRAMBLE_SERVICE_LIST="http://localhost:8091/query http://localhost:8092/query" go run ./cmd/bramble

With the foos resolver present in zoot the query:

query {
  toManyFoos {
    id
    bar
  }
}

returns

{
  "errors": [
    {
      "message": "Cannot query field \"bar\" on type \"Foo\".",
      "locations": [
        {
          "line": 8,
          "column": 5
        }
      ],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    }
  ],
  "data": null
}

with the ID list resolver removed it returns the correct:

{
  "data": {
    "toManyFoos": [
      {
        "id": "0",
        "bar": false
      },
      {
        "id": "1",
        "bar": false
      },
      {
        "id": "2",
        "bar": false
      },
      {
        "id": "3",
        "bar": false
      },
      {
        "id": "4",
        "bar": false
      },
      {
        "id": "5",
        "bar": false
      },
      {
        "id": "6",
        "bar": false
      },
      {
        "id": "7",
        "bar": false
      },
      {
        "id": "8",
        "bar": false
      },
      {
        "id": "9",
        "bar": false
      },
      {
        "id": "0",
        "bar": false
      },
      {
        "id": "1",
        "bar": false
      },
      {
        "id": "2",
        "bar": false
      },
      {
        "id": "3",
        "bar": false
      },
      {
        "id": "4",
        "bar": false
      },
      {
        "id": "5",
        "bar": false
      },
      {
        "id": "6",
        "bar": false
      },
      {
        "id": "7",
        "bar": false
      },
      {
        "id": "8",
        "bar": false
      },
      {
        "id": "9",
        "bar": false
      }
    ]
  }
}

Once this is working it would also be good if bramble could deduplicate the ids it looks up on the second service if the set is not unique, reducing the number of looks sent downstream. i.e. TooManyFoos returns a list of 20 items but there are only 10 unique items, this should cause either 10 foo lookups on zoot instead of 20, or a lookup of id ids on foos if the bulk boundary resolver is present.

Possible data race in the query executor

Running the test suite with the data race detector enabled reveals a possible data race:

WARNING: DATA RACE
Write at 0x00c000153858 by goroutine 105:
  sync/atomic.AddInt32()
      /usr/local/go/src/runtime/race_amd64.s:292 +0xb
  github.com/movio/bramble.(*queryExecution).executeChildStep()
      /Users/nmaquet/Workspace/bramble/query_execution.go:152 +0x6b
  github.com/movio/bramble.(*queryExecution).executeChildStep.func1()
      /Users/nmaquet/Workspace/bramble/query_execution.go:190 +0x69
  golang.org/x/sync/errgroup.(*Group).Go.func1()
      /Users/nmaquet/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:57 +0x94

Previous read at 0x00c000153858 by goroutine 44:
  github.com/movio/bramble.(*queryExecution).executeChildStep()
      /Users/nmaquet/Workspace/bramble/query_execution.go:153 +0x7c
  github.com/movio/bramble.(*queryExecution).executeChildStep.func1()
      /Users/nmaquet/Workspace/bramble/query_execution.go:190 +0x69
  golang.org/x/sync/errgroup.(*Group).Go.func1()
      /Users/nmaquet/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:57 +0x94

Goroutine 105 (running) created at:
  golang.org/x/sync/errgroup.(*Group).Go()
      /Users/nmaquet/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:54 +0x73
  github.com/movio/bramble.(*queryExecution).executeChildStep()
      /Users/nmaquet/Workspace/bramble/query_execution.go:189 +0x6fc
  github.com/movio/bramble.(*queryExecution).executeRootStep.func1()
      /Users/nmaquet/Workspace/bramble/query_execution.go:126 +0x69
  golang.org/x/sync/errgroup.(*Group).Go.func1()
      /Users/nmaquet/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:57 +0x94

Goroutine 44 (running) created at:
  golang.org/x/sync/errgroup.(*Group).Go()
      /Users/nmaquet/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:54 +0x73
  github.com/movio/bramble.(*queryExecution).executeChildStep()
      /Users/nmaquet/Workspace/bramble/query_execution.go:189 +0x6fc
  github.com/movio/bramble.(*queryExecution).executeRootStep.func1()
      /Users/nmaquet/Workspace/bramble/query_execution.go:126 +0x69
  golang.org/x/sync/errgroup.(*Group).Go.func1()
      /Users/nmaquet/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:57 +0x94
==================
--- FAIL: TestQueryExecutionMultipleServicesWithArray (0.01s)
    testing.go:1092: race detected during execution of test

introspection query possible types not returned for interace

The current implementation of possible types in the introspection query does not return values for interface types.
Ex:
Schema

interface Person { name: String! }
type Cast implements Person {
	name: String!
}

Introspection query

{
    __type(name: "Person") {
        kind
        name
        possibleTypes {
	    name
	}
    }
}

Result :

{
  "__type": {
    "kind": "INTERFACE",
    "name": "Person",
    "possibleTypes": null
  }
}

Expected Result

{
  "__type": {
    "kind": "INTERFACE",
    "name": "Person",
    "possibleTypes": [
      {
        "name": "Cast"
      }
    ]
  }
}

cmd: make “go gettable”

Typically go binaries are placed in subdirectories of the cmd directory. Doing this makes the go get and go install commands work well.

By moving the 'package main' implementation to a bramble sub directory a more ergonomic experience can be presented to folks installing bramble.

Micro integration

Super interesting stuff. I'm looking to find a way to integrate this into micro. We have an API gateway that does http/json and path based resolution for grpc services but the api layer is always a pain. This looks pretty great.

codahale/hdrhistogram repo url has been transferred under the github HdrHstogram umbrella

Problem

The codahale/hdrhistogram repo has been transferred under the github HdrHstogram umbrella with the help from the original author in Sept 2020 (new repo url https://github.com/HdrHistogram/hdrhistogram-go). The main reasons are to group all implementations under the same roof and to provide more active contribution from the community as the original repository was archived several years ago.

The dependency URL should be modified to point to the new repository URL. The tag "v0.9.0" was applied at the point of transfer and will reflect the exact code that was frozen in the original repository.

If you are using Go modules, you can update to the exact point of transfer using the @v0.9.0 tag in your go get command.

go mod edit -replace github.com/codahale/hdrhistogram=github.com/HdrHistogram/[email protected]

Performance Improvements

From the point of transfer, up until now (mon 16 aug 2021), we've released 3 versions that aim support the standard HdrHistogram serialization/exposition formats, and deeply improve READ performance.
We recommend to update to the latest version.

Union of @boundary Types

I can't find details about Union support: neither documented nor reported as unsupported.
But I can't make it work:

Graph1

type Album @boundary {
  id: ID!
}

type Artist @boundary {
  id: ID!
}

union CatalogEntity = Album | Artist

type FeedCard {
  type: String!
  catalogEntity: CatalogEntity
}

type Query {
  (...)
  feed: [FeedCard]
}

Graph2

type Album @boundary {
  id: ID!
  title: String!
}

type Artist @boundary {
  id: ID!
  name: String!
}

When I query with

feed {
    type
    catalogEntity {
      __typename      
      ... on Artist {
        id
        name
      }    	
      ... on Album {
        id
        title
      }
      
    }
  }
}

I receive

"message": "Cannot query field \"title\" on type \"Artist\"".

Did I miss something? maybe declare the Union in graph 2 but how?

Use introspect instead of Service->schema

Hi there,

we are currently considering using bramble. However, in the services we are combining, there is no Service endpoint yet and the schema is also generated and not so easy available as string (to make it available via the required Service Query).

We are wondering why the schema needs to be available via a query and is not retrieved via introspection. Is there any reason why that would not work?

We have used nautilus and afaik nautilus is doing exactly that.

Also, we are willing to contribute this as a feature - if nothing speaks against this. Just wanted to hear your opinions about this up front.

I am also on gophers slack as Berend Kapelle

Cannot query field "service" on type "Query". Did you mean "_service"?

Implementing a federated service subgraph based on the official spec https://www.apollographql.com/docs/federation/federation-spec/ results in bramble error from title above.

I just gave it a quick try locally:

docker run --net="host" -v $(pwd)/config.json:/config.json ghcr.io/movio/bramble

with a config.json which contains two services listed on my local machine:

{
  "services": ["http://localhost:8010/graphql", "http://localhost:8020/graphql"]
}

I saw https://movio.github.io/bramble/#/getting-started?id=preparing-your-services-for-federation but this does not respect the official spec.

Subscriptions

So the roadmap will or will not support subscriptions ?

so but I found the reasons to be ambiguously describing this

getting GRAPHQL_VALIDATION_FAILED with the nodeJS example

Hello,
I'm having a hard time running the gateway, I did follow the below steps:

  • did npm install and npm start on the node JS example and it's working fine in the browser and able to do the fo example query
  • did run the gateway with the below config
  "services": ["http://localhost:8080/graphql"],
  "gateway-port": 8082,
  "private-port": 8083,
  "metrics-port": 8084,
  "log-level": "info",
  "poll-interval": "5s",
  "max-requests-per-query": 50,
  "max-client-response-size": 1048576,
  "plugins": [

    {
      "name": "admin-ui"
    },
    {
      "name": "cors",
      "config": {
        "allowed-origins": ["*"],
        "allowed-headers": ["*"],
        "allow-credentials": true,
        "max-age": 3600,
        "debug": true
      }
    },
    {
      "name": "playground"
    }
  ]
}

but getting the below error in the gateway console

{"error":"error during request: Post \"http://localhost:8080/graphql\": read tcp [::1]:57847-\u003e[::1]:8080: read: connection reset by peer","level":"error","msg":"unable to update service","service":"nodejs-service","time":"2021-10-08T18:16:34.91806-05:00","url":"http://localhost:8080/graphql","version":"1.0.0"}

also in the browser, it gives the below error

Screen Shot 2021-10-08 at 6 18 31 PM

appreciate your help

Repeated selections for distinct field names are dropped during merge

Low-priority edge case: when making a query selection with multiple selections on a distinct field, data is only returned for the first selection...

query {
  shop1 {
    products {
      name
    }
    products {
      manufacturer {
        name
      }
    }
  }
}

This query only returns data for the first field selection, while technically it should return a union of the two:

{
  "data": {
    "shop1": {
      "products": [
        {
          "name": "iPhone"
        },
        {
          "name": "Apple Watch"
        }
      ]
    }
  }
}

The query plan looks about like I'd expect, and my remote servers are executing the appropriate steps:

{
    "RootSteps": [{
        "ServiceURL": "http://localhost:4003/graphql",
        "ParentType": "Query",
        "SelectionSet": "{ shop1 { _id: id products { _id: id } products { _id: id } } }",
        "InsertionPoint": null,
        "Then": [{
            "ServiceURL": "http://localhost:4002/graphql",
            "ParentType": "Product",
            "SelectionSet": "{ _id: id name }",
            "InsertionPoint": ["shop1", "products"],
            "Then": null
        }, {
            "ServiceURL": "http://localhost:4002/graphql",
            "ParentType": "Product",
            "SelectionSet": "{ _id: id manufacturer { _id: id } }",
            "InsertionPoint": ["shop1", "products"],
            "Then": [{
                "ServiceURL": "http://localhost:4001/graphql",
                "ParentType": "Manufacturer",
                "SelectionSet": "{ _id: id name }",
                "InsertionPoint": ["shop1", "products", "manufacturer"],
                "Then": null
            }]
        }]
    }]
}

Therefore, I'd venture to guess that the loss of data happens in the post-request assembly process. Logging this here as a backlog item.

Custom HttpClient for queries to downstream services

Currently there is no way to customize the underlying HTTPClient used by the GraphQLClient.

There are many different usecases where HTTPClient needs to be customzied like introducing circuit breakers or custom roundtrippers.

I would like to introduce a new option to set custom HTTPClient.

RFC: execution batching

I'm curious where execution batching could fit within Bramble's roadmap, and if the feature as a whole aligns with the green path for the tool. Bear with me please for a slightly contrived example... I've previously framed this scenario in a video if you'd like to skip the written introduction. We have two simple schemas:

# -- shop schema

type Shop {
   products: [Product]!
}
type Product @boundary {
  id: ID!
}
type Query {
  shop1: Shop
  product(id: ID): Product @boundary # << useless accessor
}

# -- product schema

type Product @boundary {
  id: ID!
  name: String!
}
type Query {
  products(ids: [ID!]!): [Product]! @boundary
}

Then we have this query:

query {
  shop1 {
    products {
      name
    }
  }
  shopA: shop1 {
    products {
      name
    }
  }
}

This contrived example demonstrates an inefficiency: we've initiated two separate requests to the products service during the same resolver generation for the two products fields. This is traditionally avoidable using batch execution, which consolidates multiple operations created during the same execution frame into one outbound request, such as:

query($_0_ids: [ID!]!, $_1_ids: [ID!]!) {
  _0_products: products(ids: $_0_ids)
  _1_products: products(ids: $_1_ids)
}

This combined operation issues a single network request; which guarantees the operations arrive at the destination together and get multiplexed; which in turn maximizes the opportunities for database batching. Correctly implemented, this service would only perform one database lookup for both products accessors thanks to batching.

All that's to say – is there anything prohibitive in the Bramble architecture that should prevent this from working? The pattern simply wraps each Service client in a dataloader that will merge and unpack the individual requests made to it.

I don't mean this as a feature request so much as a discussion point for an optimization roadmap. Is this a feature that would harmonize with Bramble's goals in the long term? We built this into GraphQL Tools, though we stole it from Gatsby, and it works great. Rah, rah, open source!

Bramble Meta plugin improvements

The Meta plugin could be improved in the following ways:

  • Add type(id: ID!): BrambleType to BrambleMetaQuery
  • Add service(id: ID!): BrambleService to BrambleMetaQuery
  • Add fields: [BrambleField!]! to BrambleService
  • Add types: [BrambleType!]! to BrambleService

Add Fedaration gateway example

The document is lacking of examples. Can you add a simple working example of how to implement Federation gateway with @Bramble? I can't manage to do it. Thank you!

Allow public boundaries

@nmaquet something for the v2 roadmap: I think the strategy of hiding boundary queries from the gateway schema is a good default, however it thwarts dog-fooding when there’s a query method explicitly intended to perform double-duty for both public and gateway traffic. This is particularly important when federating with locked API releases that forbid schema changes.

My suggestion would be to include an option for boundary accessors that opts them into the public schema. Conflicting public accessors would simply fail validation.

Always listens to 0.0.0.0 and few other configuration issues.

Hi there!

Bramble provides a nice way to configure itself. But there has some issue.

  • The config file is in json. And json is not config language friendly. yaml, toml is far better.
  • No way to specify network interface to listen. It always listens to 0.0.0.0.
  • Accepts ports as int. It should accept the whole listen address (ex: 0.0.0.0:8082, 192.0.0.120:8083) as string.

Allow @boundary enum, @boundary scalar, and @boundary input

Shared enums would be really useful to define standard values across services.
For example:

enum Gender @boundary {
    MALE
    FEMALE
    OTHER
    UNKNOWN
}

Similarly, shared scalars would be really useful to standardize the encoding of dates and timestamps for example.
For example:

scalar ISODate @boundary

Shared input types are a bit less useful, but one great use case is to standardize pagination:

input PageInput @boundary {
     cursor: ID
     pageSize: Int! = 100
}

I think the proper semantics for these new boundary types is that they each appear exactly once in the resulting merged schema. The schema merge operation would fail if the definitions of the boundary enums / scalars / inputs differ in any way. This does mean that changing the definition of any of these down the line requires a simultaneous upgrade of all the affected services, but I think that's a price worth paying.

Thoughts @Ackar @pkqk ?

Broken boundary when aliasing id field

The Problem

Similar to #90. There still appears to be sensitivity around id being aliased within the execution pipeline, even while running the reserved namespace safeguards in #92. For example, this query still fails:

query {
  shop1 {
    products {
      id: name
    }
  }
}

Here's the message:

{
  "message": "got a null response for non-nullable field \"id\"",
  "path": [
    "shop1",
    "products",
    0,
    "id"
  ]
}

This issue is very specific to the id field, which suggests that the special execution handling of the id field causes the problem. Ideally, id should operate as a normal member of the GraphQL schema without restrictions.

Proposed fix

The work in #92 is a big step in the right direction here, however it leaves the special execution rule around id in place. To close this out once and for all, I'd suggest removing all special considerations from the id field and to rely exclusively on the reserved __id namespace for federation mapping. With that, the complete schema will be an open book for standard GraphQL patterns, and only __id and __typename will impose restrictions about customizing their use.

Support configuration of the "id" key name

The fact that Bramble hard-codes the federation key name as id is prohibitively strict; and it should be pretty simple to make that configurable. This would unlock Bramble for graphs that don't call their primary key id.

Interesting backstory – I actually worked on such a graph. In its early days as a collection of decoupled services, each type in each service had implemented its own unique id signature – sometimes as Int, sometimes as String, sometimes as ID. Once our federation intentions were solidified, it was already too late to standardize on one id field format because so much was built around the existing type-specific IDs. Thus, a new field called uid (unique id) was introduced across the graph as the federation key. It would be a shame not to support such a graph simply due to its history around the name "id".

Filtering base interface selections is not type-aware

Another crazy one along the lines of #87, and confirms the issue hypothesized in #91.

The Problem

During final selection filtering in the execution pipeline, type awareness seems to be lost on base interface selections (i.e.: fields selected from an interface without going through a typed fragment). For example, here myInterface is an interface type that offers myUnion as a base field:

query {
  myInterface {
    myUnion {
      ...on Product {
        name
        manufacturer {
          name
        }
      }
    }
  }
}

Stacking these abstract selections atop one another results in the following unsuccessful outcome... while the underlying queries look accurate, the final filtered result is off:

{
  "data": {
    "myInterface": {
      "myUnion": {
        "name": "iPhone",
        "manufacturer": {
          "_bramble_id": "1"
        },
        "_bramble_id": "1",
        "_bramble__typename": "Product"
      }
    }
  }
}

HOWEVER, this issue can be avoided by wrapping the second-level union in a typed fragment. The following selection works perfectly, implying that the problem is specifically with base interface selections:

query {
  myInterface {
    ...on Product {
      myUnion {
        ...on Product {
          name
          manufacturer {
            name
          }
        }
      }
    }
  }
}

Probable cause

I believe this requires a complimenting fix to the changes made in #91. While #91 assured that abstract types always have access to their type information, I believe that the execution pipeline must still be updated to leverage this base type information.

Question: variables get inlined... where?

@nmaquet @lucianjon @pkqk – I've been surprised to find very little code involving variables and arguments in the codebase. And yet, variables seem to work as expected – I can specify multiple query variables bound for different services, and Bramble seems to inline those values into their appropriate field arguments:

query($_1: String!, $_2: String!) {
  svc1(input: $_1)
  svc2(input: $_2)
}

It appears that sub-service requests are never made with proper GraphQL variables – are gateway values always inlined into sub-queries? If so, where in the codebase does this happen? I've scoured all mentions of Variables and Arguments, but have found nothing that seems to perform this etherial transformation. Is this somehow being done by another package?

Thanks for the insight. This has really got me stumped.

Fragment not working as expected for interface types

Problem Statement

When an interface type is implemented in graphql schema, we are able to query with spread operator and on Type pattern.
But the same cannot be achieved by using Fragments as exemplified below

Example Request

interface Book {
  title: String!
}

type Textbook implements Book {
  title: String! # Must be present
  author: String!
}

type Magazine implements Book {
  title: String! # Must be present
  publisher: String!
}

type Query {
  books: [Book!] # Can include Textbook objects
}

fragment TextBookFragment on Textbook {
      author # Only present in Textbook
}

fragment MagazineFragment on Magazine {
      publisher # Only present in Magazine
}

# this fails
query GetBooks {
  books {
    __typename
    title
    ... TextBookFragment
    ... MagazineFragment
  }
}

# this passes
query GetBooks {
  books {
    __typename
    title
    ... on Textbook {
     author
    }
    ... on Magazine {
      publisher
    }
  }
}

Response if TextBook is not found in federated query we are getting

{
  "errors": [
    {
      "message": "got a null response for non-nullable field \"author\"",
    }
}

Support the subscription operation type

There are no current plans for implementing subscriptions.

This is a placeholder issue for discussing implementing subscriptions.

The main issue to consider is how long held connections (eg. via websockets) would affect the stateless design of the main bramble server process.

Refactor merge script to streamline `@boundary` directives

Bramble's present implementation of the @boundary directive has several indirections, and could be greatly streamlined. Myself and @exterm are considering some uses and would be interested in pursing some refactors of the merge script. While there's discussion of supporting config alternatives to directives, we believe that a refined SDL could work in tandem with this config objective, and improve the overall implementation of the tool in a backwards-compatible manner.

Current SDL limitations

  • The @boundary directive is required on both types and queries. This mapping is complex and allows devs to create config errors for themselves. It should only be necessary on boundary queries, and the types inferred from the queries.
  • Also, @boundary configuration is required on types and queries in all services. This is awkward when a service has an ID-only boundary type (type Gizmo { id:ID! }) that will never require an inbound request to fetch it, yet must provide a query for the boundary contract.
  • All queries with a @boundary directive are hidden from the gateway; they should be allowed to remain public (#84).
  • Boundary queries should allow an abstract interface to be used (#96).

Proposed SDL refactors

We propose remedying these problems by refactoring the @boundary directive and merge script to use the following signature:

directive @boundary(public: Boolean=false, types: [String!]) on FIELD_DEFINITION

With this new signature, the following happens:

  1. The @boundary directive no longer appears on types (we'll just ignore it on types, thus being backwards compatible)
  2. Boundary types are inferred as all types that provide a @boundary query somewhere in the graph, with some rules:
  • A @boundary query that yields a type appearing in only one service isn't actually a boundary; we'll allow the directive though because you may be incrementally rolling out service schemas with this new boundary.
  • A type that contains unique fields beyond just id without a @boundary query is an error (a boundary query is required to access unique data).
  • This pattern allows id-only types to be considered boundaries without needing to provide a useless query.
  1. Setting @boundary(public: true) will include the query in the gateway schema, and omit the boundary directive.
  2. Setting @boundary(types: ["Gizmo", "Gadget"]) will limit the scope of boundary types provided by a query, allowing for boundary queries to (eventually) return abstract types. This is mainly thinking around the scenario where a type may be available through multiple queries in the service, and we want to direct the gateway to a specific query for the type. This is kind of an edge case, and probably doesn't need to be a first-round feature.

All told these rules are backwards compatible, simplify the SDL, and lend themselves to being represented in a configuration-only manner.

Namespace

The downside here is that this does create inconsistencies with the @namespace directive, which I’d probably leave untouched for now. In general, namespace seems finicky to me because it makes assumptions about type root-ness, which isn’t guaranteed in the GraphQL type system (the object type assigned as the root query may again be returned from anywhere in the graph, thus root access is circumstantial).


Curious on @nmaquet and the Movio team’s take on this refactor before pursuing it. This has been gnawing me for a while, and now we have multiple teams prototyping with Bramble. Would be nice to get boundaries spruced up for them.

persisted queries

So my backend services use gqlgen's automatic persisted queries, seems like this isn't compatible with bramble, as the extensions part doesn't make it through. Is there any way to get this to work? or to enable persisted queries in bramble?

Does Bramble support resolving one field from multiple federated services?

Hi, as you mentioned in the doc:

With the exception of namespaces and the id field in objects with the @boundary directive, every field in the merged schema is defined in exactly one federated service.

Does this mean any field (even it's a boundary object) should be resolved by only one service? Or in other words, does Bramble support resolving a boundary object with unlimited recursion?

Here is the use case for your reference:

## foo service

type Foo @boundary {
  id: ID!
  title: String!
  bar: Bar!
}

type Bar @boundary {
  id: ID!
}


## bar service

type Bar @boundary {
  id: ID!
  subtitle: String!
}

The schema above works well in Bramble, as we could resolve Foo.bar within the bar service.

However, it starts failing when we add a new field in Bar, which can only be resolved in the third service:

## foo service

type Foo @boundary {
  id: ID!
  title: String!
  bar: Bar!
}

type Bar @boundary {
  id: ID!
}


## bar service

type Bar @boundary {
  id: ID!
  subtitle: String!
  baz: Baz!
}

type Baz @boundary {
  id: ID!
}


## baz service

type Baz @boundary {
  id: ID!
  status: String!
}

The query fails when trying to resolve Foo.bar.baz.status: Cannot query field status on type Baz.

Allow Bramble to store its gateway state externally

In High Availability deployments, it would be desirable to allow Bramble to store the merged schema and the other bits of gateway state into an external consistent storage service (e.g. etcd). At Movio, we have struggled to keep separate instances of Bramble synchronised and this would go a long way in solving that issue.

Strict validation for enum types

First of all, thanks for making this! Our organisation is using apollo for schema stitching since a long time, and we were looking at alternatives to it. This looks really promising 😀.

I was trying this out with one micro-service, and ran into this error:
enum value isn't ALL_CAPS

I understand that enum values ideally should be all caps, but it is not strictly enforced by the graphql specification. Is this restriction really necessary ?

Proposed fix: lock down aliases that may break queries

The Problem

Field aliases are extremely difficult in federation design because they permit clients to break their own requests by manipulating reserved selection hints. Case of point, here's a busted query:

query {
  shop1 {
    products {
      boo: id
      name
    }
  }
}

Here's another...

query {
  shop1 {
    products {
      id: name
    }
  }
}

And another...

query {
  shop1 {
    products {
      id
      _id: name
    }
  }
}

As demonstrated, there are lots of creative ways to disrupt key selections. This same situation applies when __typename is hijacked as a custom alias on abstract types.

Proposed solution

To lock this down, validation errors are necessary to reserve key aliases for implementation details. I would propose that __id and __typename are standardized and reserved as system aliases on boundary types (current _id becomes __id, and the double-underscore becomes a convention of reserved system fields).

With that, errors are inserted into the query planner to invalidate requests that conflict with reserved aliases:

// while planning field selections...
if selection.Alias == "__id" && selection.Name != "id" {
  gqlerror.Errorf("invalid alias: \"__id\" is a reserved alias on type %s", parentType)
}
if selection.Alias == "__typename" && selection.Name != "__typename" {
  gqlerror.Errorf("invalid alias: \"__typename\" is a reserved alias on type %s", parentType)
}

Lastly, __id is always added to boundary types, and __typename is always added to abstract types (there's a PR in the queue for the later change).

@nmaquet and @lucianjon – feelings on this proposal? If we're aligned on the approach, I'll put a PR together after #89 goes out.

Support for ID sharding

Hi,

first, thanks for the great work on this project!

I wonder if there is a way to implement sharding with this? We'd like a way (maybe via a plugin?) to map an ID to a shard number. A query of the ID would than end up at the service that is configured under that shard number. Are there any plans on this? Is there already way how I can implement it myself?

Best regard

Initial Docker pull does not run? (on macOS)

Very new to Go and GraphQL I wanted to check this out and play with it myself locally using the Docker image.

Ran into this from the initial docker command from the root of the git cloned repo. The directory contains the config.json file and $(PWD)/config.json resolves to the right path on tab completion.

Unable to find image 'ghcr.io/movio/bramble:latest' locally
latest: Pulling from movio/bramble`
60775238382e: Pull complete
621ff49b391a: Pull complete
Digest: sha256:c02a8d4977b6e2c49decbabdbba695ca702a44038554cbdde8cdeb1d64deb4a7
Status: Downloaded newer image for ghcr.io/movio/bramble:latest
standard_init_linux.go:219: exec user process caused: no such file or directory ```

Support alternative to `@boundary` directive that is introspection-compatible.

Bramble relies on a custom directive called @boundary to determine which object types are shared across services and how to look up boundary objects by ID in queries. This was inspired in large part by Apollo Federation's syntax which relies on 4 custom directives for a similar purpose (see https://www.apollographql.com/docs/federation/federation-spec/#federation-schema-specification).

The issues we have faced with this approach are substantial and are due the fact that directives are only part of the GraphQL IDL and not the GraphQL type system (as explained by Lee Byron in graphql/graphql-js#746 (comment)). This means that any tool that doesn't use the GraphQL IDL (e.g. any code-first implementation like graphql-js or Graphene) is de facto incompatible with Bramble.

In addition, even for systems that do support directives and can specify @boundary on objects and fields, we've had to rely on services communicating the IDL as a string through the Service.schema field (Apollo Federation does a similar thing with _Service.sdl). This is a hack and makes life hard both for the services and Bramble itself.

The alternative we should consider for 2.0 is extending Bramble's Service object type to expose fields describing which object types are boundary types and which root fields should be used to look them up.

This change can be made in a backwards-compatible way. We should support both the directive and its alternative going forward.

Support for more complicated server params

Hi,

We use envoy in our service mesh - so to communicate to any of the upstream services we do it via the same URL and modify a header (to identify the targeted cluster). Seems like supporting more custom configurations per service could be useful (e.g. adding headers), but couldn't see how I cold configure that with bramble.

Is this possible/do you have an example?

If not could is this something you'd consider as a feature request?

Cheers!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.