dgraph-lambda-go

command module
v0.8.24 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2021 License: GPL-3.0 Imports: 1 Imported by: 0

README

dgraph-lambda-go

Go Library written to build Dgraph Lambda servers as an alternative to the Dgraph JS Lambda Server

It is currently in development! Please create an issue if something is not working correctly.

If you would like to support me please visit my

Getting started

  • Create project go mod init
  • To install dgraph-lambda-go run the command go get -d github.com/schartey/dgraph-lambda-go in your project directory.
  • Then initialize the project by running go run github.com/schartey/dgraph-lambda-go init.
  • Set path to your graphql schema in lambda.yaml
  • Generate types and resolvers go run github.com/schartey/dgraph-lambda-go generate
  • Implement your lambda resolvers
  • Run your server go run server.go

Configuration

When first initializing the lambda server it will generate a basic lambda.yaml file with the following configuration:

schema:
  - ../trendgraph/dgraph/*.graphql

exec:
  filename: lambda/generated/generated.go
  package: generated

model:
  filename: lambda/model/models_gen.go
  package: model

force:
  - "Home"

autobind:
  - "github.com/schartey/dgraph-lambda-go/examples/models"

resolver:
  dir: lambda/resolvers
  package: resolvers
  filename_template: "{resolver}.resolver.go" # also allow "{name}.resolvers.go"

server:
  standalone: true
Schema

A list of graphql schema files using glob. This is probably only one file when using DGraph.

Exec

This option allows you to select a file path where generated code should go that should NOT be edited.

Model

This option allows you to define a file path where the generated models should be placed.

Force

Force generation of specific models

Autobind

You might have some predefined models already. Here you can define a list of packages in which models can be found that should be used instead of generating them. Models you add within the generated model folder will be autobound automatically.

Resolver

Define a folder and package name for the generated resolvers. The filename_template can currently only be {resolver}.resolver.go, but I want to allow resolver generation based on name as well in the future. Using {resolver}.resolver.go will generate a fieldResolver.go, queryResolver.go, mutationResolver.go, webhookResolver.go and middlewareResolver.go file where each type of resolver will reside in.

Server

On initialization a server.go file is generated from which you can start the server. With standalone set to false you can add custom routes to the http server.

Generating resolvers

This framework is able to generate field, query, mutation and webhook resolvers. These will automatically be detected in the graphql schema file. To generate middleware you have to use comments within the schema. For example:

Type Fields:
type User @lambdaOnMutate(add: true, update: true, delete: true) {
    id: ID!
    username: String!
    """
    @middleware(["auth"])
    """
    secret: string @lambda
}
Queries
type Query {
    """
    @middleware(["auth"])
    """
    randomUser(seed: String): User @lambda
}
Mutations
type Mutation {
    """
    @middleware(["auth"])
    """
    createUser(input: CreateUserInput!): User @lambda
}

Implementing resolvers

Here are implementations from the above mentioned schema examples.

Field Resolver
func (f *FieldResolver) User_secret(ctx context.Context, parents []string, authHeader api.AuthHeader) ([]string, error) { 
	var secrets []string
    for _, userParent := range userParents {
        secrets = append(secrets, fmt.Sprintf("Secret - %s", userParent.Id))
    }
    return secrets, nil
}
Query Resolver
func (q *QueryResolver) Query_randomUser(ctx context.Context, seed string, authHeader api.AuthHeader) (*model.User, error) { 
	nameGenerator := namegenerator.NewNameGenerator(seed)
    name := nameGenerator.Generate()

    user := &model.User{
        Id:       "0x1",
        Username: name,
    }
    return user, nil
}
Mutation Resolver
func (q *MutationResolver) Mutation_createUser(ctx context.Context, input *model.CreateUserInput, authHeader api.AuthHeader) (*model.User, error) {
    user := &User{
        Id:       "0x1",
        Username: createUserInput.Username,
    }
	return user, nil
}
Webhook Resolver
func (w *WebhookResolver) Webhook_User(ctx context.Context, event api.Event) error {
    // Send Email
	return nil
}
Middleware Resolver
func (m *MiddlewareResolver) Middleware_auth(md *api.MiddlewareData) error {
    // Check Token
    valid := true //false
    if valid {
    	md.Ctx = context.WithValue(md.Ctx, "logged_in", "true")
        return nil
    } else {
        return errors.New("Token invalid!")
    }
}

Inject custom dependencies

Typically you want to at least inject a graphql/dql client into your resolvers. To do so just add your client to the Resolver struct

// Add objects to your desire
type Resolver struct {
    Dql *dgo.Dgraph
}

and pass the client to the executor in your generated server.go file

dql := NewDqlClient()
resolver := &resolvers.Resolver{ Dql: dql}
executer := generated.NewExecuter(resolver)

Then you can access the client in your resolvers like this

func (q *QueryResolver) Query_randomUser(ctx context.Context, seed string, authHeader api.AuthHeader) (*model.User, error) {
    // Oversimplified
    vars := map[string]string{"$uid": uid}
	query := `
		query findUser($uid: string) {
			findUser(func: uid($uid)) {
				id: uid
			}
		}`

	res, err := s.dql.NewReadOnlyTxn().QueryWithVars(ctx, query, vars)
	if err != nil {
		return nil, err
	}

    // You can use the provided dql json unmarshaller
	var findUserResult struct {
        FindUser []model.User `dql:"findUser"`
    }
	dson.Unmarshal(res.GetJson(), &findUserResult)

	return &findUserResult.FindUser[0], nil
}

Notes

  • Working with Cyclic types If you have types in your schema with @hasInverse and one of them should not be generated, but you provide your own (see Autobind), then you should put that type declaration into the generation folder with the models_gen.go file. Otherwise the imports will be cyclic and Golang does not like that. See example under examples/lambda/model/cyclic_model.go

  • When using graphql to generate the dgraph schema, type fields are prefixed with the type (Type.field). dgraph-lambda-go provides a json parser that uses the tag "dql" and is able to convert dql query results into the generated models using jsoniter. Example:

Schema

type User {
    id: ID!
    username: String!
}

Model

type User struct {
	Id       string `json:"id" dql:"uid"`
	Username string `json:"username" dql:"User.username"`
}

Code

    vars := map[string]string{"$uid": uid}
	query := `
		query findUser($uid: string) {
			findUser(func: uid($uid)) {
				id: uid
			}
		}`

	res, err := s.dql.NewReadOnlyTxn().QueryWithVars(ctx, query, vars)
	if err != nil {
		return nil, err
	}

// ======== Here we use the dson unmarshaller ======
	var findUserResult struct {
        FindUser []model.User `dql:"findUser"`
    }
	dson.Unmarshal(res.GetJson(), &findUserResult)
// =================================================

	return &findUserResult.FindUser[0], nil

Known Issues

  • In DGraph it is allowed to skip fields in types that are already implemented in the interface. The GraphQl parser used for this project is very strict on the GraphQl specs and does not allow this, so you have to copy all fields you are using in the interface to your type.

Examples

Additional examples will be provided in the examples module

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

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