bramble

package module
v1.4.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2024 License: MIT Imports: 48 Imported by: 4

README

Bramble

Go Reference Go Report Card codecov

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, 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
  • Shared unions, interfaces, scalars, enums or inputs across services

Check FAQ for details: https://movio.github.io/bramble/#/federation?id=restriction-on-subscription

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.17 or newer

To build the bramble command:

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

To run the tests:

go test ./...

Running locally

There is a docker-compose file that will run bramble and three example services.

docker-compose up

The gateway will then be hosted on http://localhost:8082/query, be sure to point a GraphQL client to this address.

{
  randomFoo {
    nodejs
    graphGophers
    gqlgen
  }
}

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 was initially similar, Bramble now uses a different approach and supports 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.

Documentation

Index

Constants

View Source
const DebugKey contextKey = "debug"

DebugKey is used to request debug info from the context

Variables

View Source
var IdFieldName = "id"
View Source
var Version = "dev"

Functions

func AddField

func AddField(ctx context.Context, name string, value interface{})

AddField adds the given field to the event contained in the context (if any)

func AddFields

func AddFields(ctx context.Context, fields EventFields)

AddFields adds the given fields to the event contained in the context (if any)

func AddOutgoingRequestsHeaderToContext

func AddOutgoingRequestsHeaderToContext(ctx context.Context, key, value string) context.Context

AddOutgoingRequestsHeaderToContext adds a header to all outgoings requests for the current query

func AddPermissionsToContext

func AddPermissionsToContext(ctx context.Context, perms OperationPermissions) context.Context

AddPermissionsToContext adds permissions to the request context. If permissions are set the execution will check them against the query.

func GenerateUserAgent added in v1.1.8

func GenerateUserAgent(operation string) string

func GetOutgoingRequestHeadersFromContext

func GetOutgoingRequestHeadersFromContext(ctx context.Context) http.Header

GetOutgoingRequestHeadersFromContext get the headers that should be added to outgoing requests

func InitTelemetry added in v1.4.13

func InitTelemetry(ctx context.Context, cfg TelemetryConfig) (func(context.Context) error, error)

InitializesTelemetry initializes OpenTelemetry tracing and metrics. It returns a shutdown function that should be called when the application terminates.

func Main

func Main()

Main runs the gateway. This function is exported so that it can be reused when building Bramble with custom plugins.

func MergeSchemas

func MergeSchemas(schemas ...*ast.Schema) (*ast.Schema, error)

MergeSchemas merges the provided schemas together

func NewMetricsHandler

func NewMetricsHandler() http.Handler

NewMetricsHandler returns a new Prometheus metrics handler.

func RegisterMetrics

func RegisterMetrics()

RegisterMetrics register the prometheus metrics.

func RegisterPlugin

func RegisterPlugin(p Plugin)

RegisterPlugin register a plugin so that it can be enabled via the configuration.

func RegisteredPlugins

func RegisteredPlugins() map[string]Plugin

RegisteredPlugins returned the list of registered plugins.

func ValidateSchema

func ValidateSchema(schema *ast.Schema) error

ValidateSchema validates that the schema respects the Bramble specs

Types

type AllowedFields

type AllowedFields struct {
	AllowAll         bool
	AllowedSubfields map[string]AllowedFields
}

AllowedFields is a recursive set of allowed fields.

func MergeAllowedFields added in v1.1.3

func MergeAllowedFields(allowedFields ...AllowedFields) AllowedFields

MergeAllowedFields merges the given AllowedFields. The result is the union of all the allowed fields.

func (AllowedFields) IsAllowed

func (a AllowedFields) IsAllowed(fieldName string) (bool, AllowedFields)

IsAllowed returns whether the sub field is allowed along with the permissions for its own subfields

func (AllowedFields) MarshalJSON

func (a AllowedFields) MarshalJSON() ([]byte, error)

MarshalJSON marshals to a JSON representation.

func (AllowedFields) String added in v1.4.13

func (a AllowedFields) String() string

func (*AllowedFields) UnmarshalJSON

func (a *AllowedFields) UnmarshalJSON(input []byte) error

UnmarshalJSON unmarshals from a JSON representation.

type BasePlugin

type BasePlugin struct{}

BasePlugin is an empty plugin. It can be embedded by any plugin as a way to avoid declaring unnecessary methods.

func (*BasePlugin) ApplyMiddlewarePrivateMux

func (p *BasePlugin) ApplyMiddlewarePrivateMux(h http.Handler) http.Handler

ApplyMiddlewarePrivateMux ...

func (*BasePlugin) ApplyMiddlewarePublicMux

func (p *BasePlugin) ApplyMiddlewarePublicMux(h http.Handler) http.Handler

ApplyMiddlewarePublicMux ...

func (*BasePlugin) Configure

func (p *BasePlugin) Configure(*Config, json.RawMessage) error

Configure ...

func (*BasePlugin) GraphqlQueryPath

func (p *BasePlugin) GraphqlQueryPath() (bool, string)

GraphqlQueryPath ...

func (*BasePlugin) Init

func (p *BasePlugin) Init(s *ExecutableSchema)

Init ...

func (*BasePlugin) InterceptRequest added in v1.4.0

func (p *BasePlugin) InterceptRequest(ctx context.Context, operationName, rawQuery string, variables map[string]interface{})

InterceptRequest is called before bramble starts executing a request. It can be used to inspect the unmarshalled GraphQL request bramble receives.

func (*BasePlugin) InterceptResponse added in v1.4.8

func (p *BasePlugin) InterceptResponse(ctx context.Context, operationName, rawQuery string, variables map[string]interface{}, response *graphql.Response) *graphql.Response

InterceptResponse is called after bramble has finished executing a request. It can be used to inspect and/or modify the response bramble will return.

func (*BasePlugin) SetupPrivateMux

func (p *BasePlugin) SetupPrivateMux(mux *http.ServeMux)

SetupPrivateMux ...

func (*BasePlugin) SetupPublicMux

func (p *BasePlugin) SetupPublicMux(mux *http.ServeMux)

SetupPublicMux ...

func (*BasePlugin) WrapGraphQLClientTransport added in v1.4.0

func (p *BasePlugin) WrapGraphQLClientTransport(transport http.RoundTripper) http.RoundTripper

WrapGraphQLClientTransport wraps the http.RoundTripper used for GraphQL requests.

type BoundaryField added in v1.3.0

type BoundaryField struct {
	Field string
	// Name of the received id argument
	Argument string
	// Whether the query is in the array format
	Array bool
}

BoundaryField contains the name and format for a boundary query

type BoundaryFieldsMap added in v1.3.0

type BoundaryFieldsMap map[string]map[string]BoundaryField

BoundaryFieldsMap is a mapping service -> type -> boundary query

func (BoundaryFieldsMap) Field added in v1.3.0

func (m BoundaryFieldsMap) Field(serviceURL, typeName string) (BoundaryField, error)

Query returns the boundary field for the given service and type

func (BoundaryFieldsMap) RegisterField added in v1.3.0

func (m BoundaryFieldsMap) RegisterField(serviceURL, typeName string, field string, argument string, array bool)

RegisterField registers a boundary field

type ClientOpt added in v1.1.0

type ClientOpt func(*GraphQLClient)

ClientOpt is a function used to set a GraphQL client option

func WithHTTPClient added in v1.3.4

func WithHTTPClient(client *http.Client) ClientOpt

WithHTTPClient sets a custom HTTP client to be used when making downstream queries.

func WithMaxResponseSize added in v1.1.0

func WithMaxResponseSize(maxResponseSize int64) ClientOpt

WithMaxResponseSize sets the max allowed response size. The client will only read up to maxResponseSize and that size is exceeded an an error will be returned.

func WithUserAgent added in v1.1.7

func WithUserAgent(userAgent string) ClientOpt

WithUserAgent set the user agent used by the client.

type Config

type Config struct {
	IdFieldName            string        `json:"id-field-name"`
	GatewayListenAddress   string        `json:"gateway-address"`
	DisableIntrospection   bool          `json:"disable-introspection"`
	MetricsListenAddress   string        `json:"metrics-address"`
	PrivateListenAddress   string        `json:"private-address"`
	GatewayPort            int           `json:"gateway-port"`
	MetricsPort            int           `json:"metrics-port"`
	PrivatePort            int           `json:"private-port"`
	DefaultTimeouts        TimeoutConfig `json:"default-timeouts"`
	GatewayTimeouts        TimeoutConfig `json:"gateway-timeouts"`
	PrivateTimeouts        TimeoutConfig `json:"private-timeouts"`
	Services               []string      `json:"services"`
	LogLevel               log.Level     `json:"loglevel"`
	PollInterval           string        `json:"poll-interval"`
	PollIntervalDuration   time.Duration
	MaxRequestsPerQuery    int64           `json:"max-requests-per-query"`
	MaxServiceResponseSize int64           `json:"max-service-response-size"`
	Telemetry              TelemetryConfig `json:"telemetry"`
	Plugins                []PluginConfig
	// Config extensions that can be shared among plugins
	Extensions map[string]json.RawMessage
	// HTTP client to customize for downstream services query
	QueryHTTPClient *http.Client
	// contains filtered or unexported fields
}

Config contains the gateway configuration

func GetConfig

func GetConfig(configFiles []string) (*Config, error)

GetConfig returns operational config for the gateway

func (*Config) ConfigurePlugins

func (c *Config) ConfigurePlugins() []Plugin

ConfigurePlugins calls the Configure method on each plugin.

func (*Config) GatewayAddress

func (c *Config) GatewayAddress() string

GatewayAddress returns the host:port string of the gateway

func (*Config) Init

func (c *Config) Init() error

Init initializes the config and does an initial fetch of the services.

func (*Config) Load

func (c *Config) Load() error

Load loads or reloads all the config files.

func (*Config) MetricAddress

func (c *Config) MetricAddress() string

MetricAddress returns the address for the metric port

func (*Config) PrivateAddress

func (c *Config) PrivateAddress() string

PrivateAddress returns the address for private port

func (*Config) PrivateHttpAddress added in v1.3.0

func (c *Config) PrivateHttpAddress(path string) string

func (*Config) Watch

func (c *Config) Watch()

Watch starts watching the config files for change.

type DebugInfo

type DebugInfo struct {
	Variables bool
	Query     bool
	Plan      bool
	Timing    bool
	TraceID   bool
}

DebugInfo contains the requested debug info for a query

type EventFields

type EventFields map[string]interface{}

EventFields contains fields to be logged for the event

type ExecutableSchema

type ExecutableSchema struct {
	MergedSchema        *ast.Schema
	Locations           FieldURLMap
	IsBoundary          map[string]bool
	Services            map[string]*Service
	BoundaryQueries     BoundaryFieldsMap
	GraphqlClient       *GraphQLClient
	MaxRequestsPerQuery int64
	// contains filtered or unexported fields
}

ExecutableSchema contains all the necessary information to execute queries

func NewExecutableSchema added in v1.3.5

func NewExecutableSchema(plugins []Plugin, maxRequestsPerQuery int64, client *GraphQLClient, services ...*Service) *ExecutableSchema

func (*ExecutableSchema) Complexity

func (s *ExecutableSchema) Complexity(typeName, fieldName string, childComplexity int, args map[string]interface{}) (int, bool)

Complexity returns the query complexity (unimplemented)

func (*ExecutableSchema) Exec

Exec returns the query execution handler

func (*ExecutableSchema) ExecuteQuery

func (s *ExecutableSchema) ExecuteQuery(ctx context.Context) *graphql.Response

func (*ExecutableSchema) Schema

func (s *ExecutableSchema) Schema() *ast.Schema

Schema returns the merged schema

func (*ExecutableSchema) UpdateSchema

func (s *ExecutableSchema) UpdateSchema(ctx context.Context, forceRebuild bool) error

UpdateSchema updates the schema from every service and then update the merged schema.

func (*ExecutableSchema) UpdateServiceList

func (s *ExecutableSchema) UpdateServiceList(ctx context.Context, services []string) error

UpdateServiceList replaces the list of services with the provided one and update the schema.

type FieldURLMap

type FieldURLMap map[string]string

FieldURLMap maps fields to service URLs

func (FieldURLMap) RegisterURL

func (m FieldURLMap) RegisterURL(parent string, field string, location string)

RegisterURL registers the location for the given field

func (FieldURLMap) URLFor added in v1.2.0

func (m FieldURLMap) URLFor(parent, parentLocation, field string) (string, error)

URLFor returns the URL for the given field

type Gateway

type Gateway struct {
	ExecutableSchema *ExecutableSchema
	// contains filtered or unexported fields
}

Gateway contains the public and private routers

func NewGateway

func NewGateway(executableSchema *ExecutableSchema, plugins []Plugin) *Gateway

NewGateway returns the graphql gateway server mux

func (*Gateway) PrivateRouter

func (g *Gateway) PrivateRouter() http.Handler

PrivateRouter returns the private http handler

func (*Gateway) Router

func (g *Gateway) Router(cfg *Config) http.Handler

Router returns the public http handler

func (*Gateway) UpdateSchemas

func (g *Gateway) UpdateSchemas(interval time.Duration)

UpdateSchemas periodically updates the execute schema

type GraphQLClient

type GraphQLClient struct {
	HTTPClient      *http.Client
	MaxResponseSize int64
	UserAgent       string
	// contains filtered or unexported fields
}

GraphQLClient is a GraphQL client.

func NewClient

func NewClient(opts ...ClientOpt) *GraphQLClient

NewClient creates a new GraphQLClient from the given options.

func NewClientWithPlugins added in v1.4.0

func NewClientWithPlugins(plugins []Plugin, opts ...ClientOpt) *GraphQLClient

func NewClientWithoutKeepAlive added in v1.3.6

func NewClientWithoutKeepAlive(opts ...ClientOpt) *GraphQLClient

func (*GraphQLClient) Request

func (c *GraphQLClient) Request(ctx context.Context, url string, request *Request, out interface{}) error

Request executes a GraphQL request.

type GraphqlError

type GraphqlError struct {
	Message    string                 `json:"message"`
	Path       ast.Path               `json:"path,omitempty"`
	Extensions map[string]interface{} `json:"extensions"`
}

GraphqlError is a single GraphQL error

type GraphqlErrors

type GraphqlErrors []GraphqlError

GraphqlErrors represents a list of GraphQL errors, as returned in a GraphQL response.

func (GraphqlErrors) Error

func (e GraphqlErrors) Error() string

Error returns a string representation of the error list

type OperationPermissions

type OperationPermissions struct {
	AllowedRootQueryFields        AllowedFields `json:"query"`
	AllowedRootMutationFields     AllowedFields `json:"mutation"`
	AllowedRootSubscriptionFields AllowedFields `json:"subscription"`
}

OperationPermissions represents the top level permissions for all operation types

func GetPermissionsFromContext

func GetPermissionsFromContext(ctx context.Context) (OperationPermissions, bool)

GetPermissionsFromContext returns the permissions stored in the context

func MergePermissions added in v1.1.3

func MergePermissions(perms ...OperationPermissions) OperationPermissions

MergePermissions merges the given permissions. The result permissions are the union of the given permissions (allow everything that is allowed in any of the given permissions).

func (*OperationPermissions) FilterAuthorizedFields

func (o *OperationPermissions) FilterAuthorizedFields(op *ast.OperationDefinition) gqlerror.List

FilterAuthorizedFields filters the operation's selection set and removes all fields that are not explicitly authorized. Every unauthorized field is returned as an error.

func (*OperationPermissions) FilterSchema

func (o *OperationPermissions) FilterSchema(schema *ast.Schema) *ast.Schema

FilterSchema returns a copy of the given schema stripped of any unauthorized fields and types

func (OperationPermissions) MarshalJSON

func (o OperationPermissions) MarshalJSON() ([]byte, error)

MarshalJSON marshals to a JSON representation.

type PlanningContext

type PlanningContext struct {
	Operation  *ast.OperationDefinition
	Schema     *ast.Schema
	Locations  FieldURLMap
	IsBoundary map[string]bool
	Services   map[string]*Service
}

PlanningContext contains the necessary information used to plan a query.

type Plugin

type Plugin interface {
	// ID must return the plugin identifier (name). This is the id used to match
	// the plugin in the configuration.
	ID() string
	// Configure is called during initialization and every time the config is modified.
	// The pluginCfg argument is the raw json contained in the "config" key for that plugin.
	Configure(cfg *Config, pluginCfg json.RawMessage) error
	// Init is called once on initialization
	Init(schema *ExecutableSchema)
	SetupPublicMux(mux *http.ServeMux)
	SetupPrivateMux(mux *http.ServeMux)
	// Should return true and the query path if the plugin is a service that
	// should be federated by Bramble
	GraphqlQueryPath() (bool, string)
	ApplyMiddlewarePublicMux(http.Handler) http.Handler
	ApplyMiddlewarePrivateMux(http.Handler) http.Handler
	WrapGraphQLClientTransport(http.RoundTripper) http.RoundTripper

	InterceptRequest(ctx context.Context, operationName, rawQuery string, variables map[string]interface{})
	InterceptResponse(ctx context.Context, operationName, rawQuery string, variables map[string]interface{}, response *graphql.Response) *graphql.Response
}

Plugin is a Bramble plugin. Plugins can be used to extend base Bramble functionalities.

type PluginConfig

type PluginConfig struct {
	Name   string
	Config json.RawMessage
}

PluginConfig contains the configuration for the named plugin

type QueryPlan added in v1.2.0

type QueryPlan struct {
	RootSteps []*QueryPlanStep
}

QueryPlan is a query execution plan

func Plan

func Plan(ctx *PlanningContext) (*QueryPlan, error)

Plan returns a query plan from the given planning context

type QueryPlanStep added in v1.2.0

type QueryPlanStep struct {
	ServiceURL     string
	ServiceName    string
	ParentType     string
	SelectionSet   ast.SelectionSet
	InsertionPoint []string
	Then           []*QueryPlanStep
}

QueryPlanStep is a single execution step

func (*QueryPlanStep) MarshalJSON added in v1.2.0

func (s *QueryPlanStep) MarshalJSON() ([]byte, error)

MarshalJSON marshals the step the JSON

type Request

type Request struct {
	OperationType string                 `json:"operationType,omitempty"`
	Query         string                 `json:"query"`
	OperationName string                 `json:"operationName,omitempty"`
	Variables     map[string]interface{} `json:"variables,omitempty"`
	Headers       http.Header            `json:"-"`
}

Request is a GraphQL request.

func NewRequest

func NewRequest(query string) *Request

NewRequest creates a new GraphQL requests from the provided body.

func (*Request) WithHeaders added in v1.3.0

func (r *Request) WithHeaders(headers http.Header) *Request

func (*Request) WithOperationName added in v1.4.4

func (r *Request) WithOperationName(operationName string) *Request

func (*Request) WithOperationType added in v1.4.13

func (r *Request) WithOperationType(operation string) *Request

func (*Request) WithVariables added in v1.4.4

func (r *Request) WithVariables(variables map[string]interface{}) *Request

type Response

type Response struct {
	Errors GraphqlErrors `json:"errors"`
	Data   interface{}
}

Response is a GraphQL response

type Service

type Service struct {
	ServiceURL   string
	Name         string
	Version      string
	SchemaSource string
	Schema       *ast.Schema
	Status       string
	// contains filtered or unexported fields
}

Service is a federated service.

func NewService

func NewService(serviceURL string, opts ...ClientOpt) *Service

NewService returns a new Service.

func (*Service) Update

func (s *Service) Update(ctx context.Context) (bool, error)

Update queries the service's schema, name and version and updates its status.

type TelemetryConfig added in v1.4.13

type TelemetryConfig struct {
	Enabled     bool   `json:"enabled"`      // Enabled enables OpenTelemetry tracing and metrics.
	Insecure    bool   `json:"insecure"`     // Insecure enables insecure communication with the OpenTelemetry collector.
	Endpoint    string `json:"endpoint"`     // Endpoint is the OpenTelemetry collector endpoint.
	ServiceName string `json:"service_name"` // ServiceName is the name of the service.
}

TelemetryConfig is the configuration for OpenTelemetry tracing and metrics.

type TelemetryErrHandler added in v1.4.13

type TelemetryErrHandler struct {
	// contains filtered or unexported fields
}

TelemetryErrHandler is an error handler that logs errors.

func (*TelemetryErrHandler) Handle added in v1.4.13

func (e *TelemetryErrHandler) Handle(err error)

Handle implements otel.ErrorHandler.

type TimeoutConfig added in v1.4.12

type TimeoutConfig struct {
	ReadTimeout          string        `json:"read"`
	ReadTimeoutDuration  time.Duration `json:"-"`
	WriteTimeout         string        `json:"write"`
	WriteTimeoutDuration time.Duration `json:"-"`
	IdleTimeout          string        `json:"idle"`
	IdleTimeoutDuration  time.Duration `json:"-"`
}

Directories

Path Synopsis
cmd
examples
slow-service Module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL