movio / bramble Goto Github PK
View Code? Open in Web Editor NEWA Federated GraphQL Gateway
Home Page: https://movio.github.io/bramble/
License: MIT License
A Federated GraphQL Gateway
Home Page: https://movio.github.io/bramble/
License: MIT License
Hi team,
I would like to use bramble as a library. It's currently impossible because some types are not exported.
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.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!
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.
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?
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?
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.
Introduced by the random enumeration order of this line: https://github.com/movio/bramble/pull/81/files#diff-11ffedf6a81044546aa19c8009998208f5a7afec3cdff04ca4281117b5ec0da7R228
I introduced the bug, so I'm happy to own the fix. I should have a PR by end of week.
See #94 (comment)
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
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"
}
]
}
}
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.
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.
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]
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.
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?
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
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.
Per #108 (comment), it'd be nice to improve some logistics around how federation objects are mapped and then looked up. Some specific areas of improvement would be:
Hi!
I've implemented file uploading using these library https://github.com/jaydenseric/graphql-multipart-request-spec for my graphql service. And it works correctly when I querying my service directly. But in case when there is bramble between I get error like:
input:1: Syntax Error GraphQL request (1:36) Expected Name, found String
So my question is: does bramble support those kind of queries with multipart/form-data content type?
There are some tests that mutate the global log object, using this helper function https://github.com/movio/bramble/blob/main/instrumentation_test.go#L14. Specifically it gives a writer to the log object that is closed at the end of the test. This can cause other tests that run code which logs to print out errors like this: Failed to write to log, io: read/write on closed pipe
.
So the roadmap will or will not support subscriptions ?
so but I found the reasons to be ambiguously describing this
Hello,
I'm having a hard time running the gateway, I did follow the below steps:
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 "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
appreciate your help
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.
Supporting non-nullable boundary accessors adds a significant amount of complexity to the implementation of Bramble's query executor. This is due to GraphQL's bubbling of errors to parents of non-nullable fields. Dropping support for nullable boundary accessor fields would make Bramble quite a bit simpler.
@nmaquet Is there a way to set headers for a service in the services list?
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.
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!
The Meta plugin could be improved in the following ways:
type(id: ID!): BrambleType
to BrambleMetaQuery
service(id: ID!): BrambleService
to BrambleMetaQuery
fields: [BrambleField!]!
to BrambleService
types: [BrambleType!]!
to BrambleService
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!
@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.
Hi there!
Bramble provides a nice way to configure itself. But there has some issue.
json
. And json
is not config language friendly. yaml
, toml
is far better.0.0.0.0
.int
. It should accept the whole listen address (ex: 0.0.0.0:8082
, 192.0.0.120:8083
) as string.Allow to inject configuration JSON block as environment variable.
This will allow us to setup parameters dynamically in manifest.
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.
Services implementing Relay's Global Object Identification Spec (i.e. the Node
interface) should be allowed to be configured as boundary types without the need for an additional boundary accessor field.
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.
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.
There has an open issue in apollo/federation. Is there any ways bramble tries or will try to handle it?
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".
Another crazy one along the lines of #87, and confirms the issue hypothesized in #91.
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
}
}
}
}
}
}
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.
@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.
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\"",
}
}
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.
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.
@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.@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.@boundary
directive are hidden from the gateway; they should be allowed to remain public (#84).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:
@boundary
directive no longer appears on types (we'll just ignore it on types, thus being backwards compatible)@boundary
query somewhere in the graph, with some rules:@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.id
without a @boundary
query is an error (a boundary query is required to access unique data).@boundary(public: true)
will include the query in the gateway schema, and omit the boundary directive.@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.
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.
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?
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
.
This would make federated queries more robust as one error in the resolution of the array wouldn't null-out the whole array. See #51 (comment)
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.
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 ?
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.
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.
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
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 ```
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.
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.