Bramble is a production-ready GraphQL federation gateway.

Bramble

Full documentation

Bramble is a production-ready GraphQL federation gateway. It is built to be a simple, reliable and scalable way to aggregate GraphQL services together.

overview

Features

Bramble supports:

  • Shared types across services
  • Namespaces
  • Field-level permissions
  • Plugins:
    • JWT, Open tracing, CORS, ...
    • Or add your own
  • Hot reloading of configuration

It is also stateless and scales very easily.

Future work/not currently supported

There is currently no support for subscriptions.

Contributing

Contributions are always welcome!

If you wish to contribute please open a pull request. Please make sure to:

  • include a brief description or link to the relevant issue
  • (if applicable) add tests for the behaviour you're adding/modifying
  • commit messages are descriptive

Before making a significant change we recommend opening an issue to discuss the issue you're facing and the proposed solution.

Building and testing

Prerequisite: Go 1.15 or newer

To build the bramble command:

go build -o bramble ./cmd
./bramble -conf config.json

To run the tests:

go test ./...

Comparison with other projects

  • Apollo Server

    While Apollo Server is a popular tool we felt is was not the right tool for us as:

    • the federation specification is more complex than necessary
    • it is written in NodeJS where we favour Go
  • Nautilus

    Nautilus provided a lot of inspiration for Bramble.

    Although the approach to federation is similar Bramble has support for a few more things: fine-grained permissions, namespaces, easy plugin configuration, configuration hot-reloading...

    Bramble is also a central piece of software for Movio products and thus is actively maintained and developed.

Comments
  • Question: variables get inlined... where?

    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.

  • Add Fedaration gateway example

    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 compressed response from services to gateway

    Allow compressed response from services to gateway

    Currently, bramble was not able to handle the compressed response (ie., gzip format response) from services. So, added the switch case in the code which handles the compressed response as well from the services

  • Does Bramble support resolving one field from multiple federated services?

    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.

  • Fragment not mapping fields for interface types

    Fragment not mapping fields 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. When we do it by using the Fragments it is not mapping fields inside it when multiple type of interface implemenations are being returned

    In the following PR https://github.com/movio/bramble/pull/153/files in the test case If we fetch either Gadjet Or Gizmo it works. If we want to fetch a list of object where object could be either Gadject or Gizmo it would not work

    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 
    }
    
    fragment MagazineFragment on Magazine {
          publisher 
    }
    
    query GetBooks {
      books {
        __typename
        title
        ... TextBookFragment
        ... MagazineFragment
      }
    }
    

    Response

     {
        books : [
           {
                 ___typename : "TextBook"
           }
          {
                 ___typename : "Magazine"
           }
       ]
    }
    

    Problem:

    The other fields are not mapped

  • Proposed fix: lock down aliases that may break queries

    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 https://github.com/movio/bramble/pull/89 goes out.

  • Enabling/Disabling schema introspection

    Enabling/Disabling schema introspection

    Hi Team we are currently looking for a way to disable introspection in bramble application. We want users to prevent from doing the following queries

    {
      __schema {
        types {
          name
          kind
        }
      }
    }
    

    Something similar to disable introspection in gql-gen https://github.com/99designs/gqlgen/blob/7435403cf94ce8147fdd9d473a5469d63e7e5b38/graphql/context_operation.go#L20

    Can one help me on how can we achieve this using bramble

  • Fix: user-defined field aliases may break federation

    Fix: user-defined field aliases may break federation

    This adds _bramble_id as a universal ID hint, which allows id to behave as a standard user-defined selection. This fixes the numerous ways to break federation with user-defined field aliases describe in https://github.com/movio/bramble/issues/90 and https://github.com/movio/bramble/issues/93.

    Apologies for the large PR – the vast majority of it is test fixtures.

    The Problem

    As described in https://github.com/movio/bramble/issues/90, there are many creative ways for user-defined queries to break Bramble federation ids using field aliases, for example:

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

    While dynamic adjustments can be made to the query planner to account for such aliases, a user-aliased id still ends up breaking in the execution resolver, per https://github.com/movio/bramble/issues/93.

    A general observation: query planning currently optimizes for vanity queries at the cost of tricky logic holes and extra looping. The process becomes simpler and more reliable when the query planner always adds consistent implementation fields for itself, even if they are redundant with the contents of the user-defined query. Not making id do double-duty as both a user selection and an implementation detail avoids contention between the planning and resolution steps.

    The fix(es)

    • Makes the planner always add a _bramble_id: id selection to boundary scopes. This becomes a universal implementation key, eliminating _id and leaving id untouched as a basic user selection.
    • Validates _bramble_id and __typename aliases. A user selection will not be allowed to hijack these fields.
    • Foregoes vanity queries in favor of eliminating childstep recursion complexity and selectionSetHasFieldNamed looping. Queries are now more verbose, but they are extremely consistent and avoid suspected logic holes.

    Duplicated selections are ugly

    If there’s human sensitivity to duplicated field selections (machines don’t care), then I’d propose adding a single-pass loop at the end of extractSelectionSet that imposes uniqueness once based on field alias falling back to field name. At present, “selectionSetHasFieldNamed” runs for each de-duplicated field, and doesn’t take into account field aliases.

    Resolves https://github.com/movio/bramble/issues/90. Resolves https://github.com/movio/bramble/issues/93.

  • Hide fields from public federated gateway

    Hide fields from public federated gateway

  • Fix: broken namespace aliases and mutable selections

    Fix: broken namespace aliases and mutable selections

    Fixes some problems with namespace operations in the query planner, and simplifies the overall logic in the process.

    Problems + Fixes

    query {
      boo: myNamespace {
        product {
          name
        }
        manufacturer {
          name
        }
      }
    }
    
    • The namespace handler condition was demonstrably broken by field aliases: it composed its insertion point using field Name rather than Alias. This issue is now fixed.

    • The namespace condition could also corrupt the user's original selection because it modified fields directly rather than making copies. This was problematic because it modified the selection used to filter the final result, as described in https://github.com/movio/bramble/issues/106. This is also fixed.

    • With the above adjustments made, the namespace condition ended up basically identical to the normal composite field handler. So, this simplifies the logic so that a failed location lookup now just flows into the normal field handlers; this new pattern has the added advantage of handing both leaf values and composite fields.

    Resolves https://github.com/movio/bramble/issues/106 by happy accident.

  • Fix: fully traverse nested field selections while planning

    Fix: fully traverse nested field selections while planning

    As described in https://github.com/movio/bramble/issues/73, deeply-nested selection paths that transitioned between services were broken. This was a fairly significant issue.

    The problem

    While extracting selection sets, the mergedWithExistingStep check was treating all consolidated fields as leaf values. That means that when a composite field was encountered with its own nested selections, that entire selection set was merged into the current planning step without traversing its children to delegate them properly. This resulted in fields belonging to foreign services getting lumped into the present service selection, and the query failing with a GraphQL validation message:

    Cannot query field <remoteField> on type <LocalType>
    

    The fix

    This adjusts the flow of extractSelectionSet so that all remote fields are collected up front and then create steps once together later in the process. This eliminates the need for the previous step-merging pattern, and is computationally simpler to create steps all at once rather than creating them incrementally for each remote step in sequence.

    Note that this refactor looks bigger than it really is: this simplification was able to remove some conditional nesting and thus decrease the indent on a few existing code blocks.

    Tests

    Tests have been added. There's one update to an existing test where a benign array order has changed due to the revised sequencing of extracting remote fields.

    Resolves https://github.com/movio/bramble/issues/73.

  • Differentiate log level depending on

    Differentiate log level depending on "response.status"

    https://github.com/movio/bramble/pull/179

    Currently, all requests are being logged as Info (v1.4.3/instrumentation.go:56) and it really results in lots and lots of logs, hence increases costs for our applications.

    As per our application's requirements, we don't want to see logs for HTTP200 responses.

    What we would like to achieve is to log depending on the response.status value like below.

    However, if you come up with a better idea, we are welcome to use it.

    func (e *event) finish() {
    	e.writeLock.Do(func() {
    
    		var logEntry = log.WithFields(log.Fields{
    			"timestamp": e.timestamp.Format(time.RFC3339Nano),
    			"duration":  time.Since(e.timestamp).String(),
    		}).WithFields(log.Fields(e.fields))
    
    		responseStatusCandidate := e.fields["response.status"]
    
    		if responseStatusCandidate == nil{
    			logEntry.Info(e.name)
    			return
    		}
    
    		responseStatusCode, ok := responseStatusCandidate.(int)
    
    		if !ok{
    			logEntry.Info(e.name)
    			return
    		}
    
    		if (responseStatusCode >= 100 && responseStatusCode <= 199){
    			// informational responses
    			logEntry.Debug(e.name)
    		} else if (responseStatusCode >= 200 && responseStatusCode <= 299){
    			// successful responses
    			logEntry.Debug(e.name)
    		} else if (responseStatusCode >= 300 && responseStatusCode <= 399){
    			// redirection messages
    			logEntry.Debug(e.name)
    		} else if (responseStatusCode >= 400 && responseStatusCode <= 499){
    			// client error responses
    			logEntry.Error(e.name)
    		} else if (responseStatusCode >= 500 && responseStatusCode <= 599){
    			// server error responses
    			logEntry.Error(e.name)
    		} else {
    			logEntry.Info(e.name)
    		}
    	})
    }
    
  • Plugin` configuration doesnt depend on config changes

    Plugin` configuration doesnt depend on config changes

    Once plugin configuration was changed in bramble.json file, file watcher handles event and reload config in case of CREATE or CHANGE. But, for example auth plugin doesn`t depend on this reload process, as this object is constructed at the moment of starting Bramble (i tried to change Role section, bramble triggered event changes, but auth plugin was working under old role model)

  • Types not used in any query are not getting into compiled schema?

    Types not used in any query are not getting into compiled schema?

    I'm defining types which are not used in any query. I could see them in the downstream service, but not in the compiled schema.

    I'm expecting them to be in the compiled schema so frontend could see those types through gateway.

    Is it a bug or is it inеended?

  • should bramble complain about built-in directives declared in spec?

    should bramble complain about built-in directives declared in spec?

    I've recently encountered a bug when was trying to federate a service which contained specifiedBy directive. It is in latest gql spec, but i've got an error:

    Undefined directive specifiedBy.
    

    Should I declare such directives in downstream services? Or it is a bug?

  •  persisted queries

    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?

Vektor - Build production-grade web services quickly
Vektor - Build production-grade web services quickly

Vektor enables development of modern web services in Go. Vektor is designed to simplify the development of web APIs by eliminating boilerplate, using secure defaults, providing plug-in points, and offering common pieces needed for web apps. Vektor is fairly opinionated, but aims to provide flexibility in the right places.

Dec 15, 2022
Couper is a lightweight API gateway designed to support developers in building and operating API-driven Web projects
Couper is a lightweight API gateway designed to support developers in building and operating API-driven Web projects

Couper Couper is a lightweight API gateway designed to support developers in building and operating API-driven Web projects. Getting started The quick

Nov 18, 2022
GoPrisma - A Go wrapper for prisma to turn databases into GraphQL APIs using Go.

GoPrisma - a Go wrapper for the Prisma Engines What's this? Introspect a database and use it as a GraphQL API using Go. Supported Databases: SQLite Po

Dec 20, 2022
Apollo Federation Gateway v1 implementations by Go

fedeway Apollo Federation Gateway v1 implementations by Go. ⚠️ This product is under development. don't use in production. ⚠️ TODO implements validati

Jan 6, 2023
protoc-gen-grpc-gateway-ts is a Typescript client generator for the grpc-gateway project. It generates idiomatic Typescript clients that connect the web frontend and golang backend fronted by grpc-gateway.

protoc-gen-grpc-gateway-ts protoc-gen-grpc-gateway-ts is a Typescript client generator for the grpc-gateway project. It generates idiomatic Typescript

Dec 19, 2022
GRONG is a DNS (Domain Name System) authoritative name server.It is more a research project than a production-ready program.

GRONG (Gross and ROugh Nameserver written in Go) is a DNS (Domain Name System) authoritative name server. It is intended as a research project and is

Oct 17, 2020
An production-ready microservice using Go and a few lightweight libraries
An production-ready microservice using Go and a few lightweight libraries

Go Micro Example This small sample project was created as a collection of the various things I've learned about best practices building microservices

Dec 26, 2022
Create production ready microservices mono repo pattern wired with Neo4j. Microservices for other languages and front end repos to be added as well in future.
Create production ready microservices mono repo pattern wired with Neo4j. Microservices for other languages and front end repos to be added as well in future.

Create Microservices MonoRepo in GO/Python Create a new production-ready project with backend (Golang), (Python) by running one CLI command. Focus on

Oct 26, 2022
go/template is a tool for jumpstarting production-ready Golang projects quickly.
go/template is a tool for jumpstarting production-ready Golang projects quickly.

go/template go/template provides a blueprint for production-ready Go project layouts. Credit to Renée French for the Go Gopher logo Credit to Go Autho

Dec 24, 2022
✨ Create a new production-ready project with backend, frontend and deploy automation by running one CLI command!
✨ Create a new production-ready project with backend, frontend and deploy automation by running one CLI command!

✨ Create a new production-ready project with backend, frontend and deploy automation by running one CLI command!

Dec 31, 2022
sample-go-test-app-vaibhav is a simple example of a production ready RPC service in Go

sample-go-test-app-vaibhav sample-go-test-app-vaibhav is a simple example of a production ready RPC service in Go. Instead of attempting to abstract a

Dec 2, 2021
Production Ready GO - Development Workspace

dev_ProdGO Production Ready GO - Development Workspace Install and Check Version MacOS $brew install go/golang $go version $mkdir -p $HOME/go/{bin,sr

Jan 6, 2022
Mauliasproxy - a simple room alias proxy that can respond to the federation alias query endpoint

Mauliasproxy - a simple room alias proxy that can respond to the federation alias query endpoint

Dec 22, 2022
Authenticating using Workload Identity Federation to Cloud Run, Cloud Functions
Authenticating using Workload Identity Federation to Cloud Run, Cloud Functions

Authenticating using Workload Identity Federation to Cloud Run, Cloud Functions This tutorial and code samples cover how customers that use Workload i

Dec 3, 2022
The Durudex gateway combines all durudex services so that it can be used through a single gateway.

The Durudex gateway combines all durudex services so that it can be used through a single gateway.

Dec 13, 2022
Ruuvi-go-gateway - Software replica of the Ruuvi Gateway

ruuvi-go-gateway ruuvi-go-gateway is a software that tries to replicate Ruuvi Ga

Dec 21, 2022
Grpc-gateway-map-null - gRPC Gateway test using nullable values in map

Demonstrate gRPC gateway behavior with nullable values in maps Using grpc-gatewa

Jan 6, 2022
GraphJin - Build APIs in 5 minutes with GraphQL. An instant GraphQL to SQL compiler.
GraphJin - Build APIs in 5 minutes with GraphQL. An instant GraphQL to SQL compiler.

GraphJin - Build APIs in 5 minutes GraphJin gives you a high performance GraphQL API without you having to write any code. GraphQL is automagically co

Jan 4, 2023
GraphJin - Build APIs in 5 minutes with GraphQL. An instant GraphQL to SQL compiler.
GraphJin - Build APIs in 5 minutes with GraphQL. An instant GraphQL to SQL compiler.

GraphJin - Build APIs in 5 minutes GraphJin gives you a high performance GraphQL API without you having to write any code. GraphQL is automagically co

Jan 1, 2023
GraphJin - Build APIs in 5 minutes with GraphQL. An instant GraphQL to SQL compiler.
GraphJin - Build APIs in 5 minutes with GraphQL. An instant GraphQL to SQL compiler.

GraphJin gives you a high performance GraphQL API without you having to write any code. GraphQL is automagically compiled into an efficient SQL query. Use it either as a library or a standalone service.

Dec 29, 2022