lastbackend / toolkit

Toolkit is a framework for distributed systems
Apache License 2.0
28 stars 3 forks source link

Last.Backend Toolkit

License GoDev Go Report Card Sourcegraph

lastbackend:toolkit is a programming toolkit for building microservices, services (or elegant monoliths) in Go. We provide you base application modular bolierpate with plugins so you can focus on delivering business value.

Communication

Overview

Last.Backend provides the modular boilerplate with plugins and package management, developed for distributed systems development including RPC and Event driven communication. We provide basics to get you started as quickliy as possible.

Features

Toolkit abstracts away the details of distributed systems. Here are the main features.

Preparation

Run go mod tidy to resolve the versions. Install all dependencies by running Configure

Download annotations and place them to: $GOPATH/grpc/annotations

Annotations: toolkit

Getting Started

Start with define your apis/.proto file:

syntax = "proto3";

package lastbackend.example;

option go_package = "github.com/lastbackend/toolkit/examples/service/gen;servicepb";
import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto";

message HelloWorldRequest {
  string name = 1;
}

message HelloWorldResponse {
  string id = 1;
}

service Example {
  //  Example methods
  rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse) {}
};

Generate toolkit service with command

protoc \
    -I. \
    -I$GOPATH/src \
    -I$GOPATH/grpc/annotations \
    -I. \
    --go_out=:$GOPATH/src \
    --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \
    ./apis/<service name>.proto

Import generated serive in main.go

import servicepb "<app path>/gen"

Define your application

  app, err := servicepb.NewExampleService("example")
  if err != nil {
    fmt.Println(err)
  }

And now you can start it:

  if err := app.Start(context.Background()); err != nil {
    app.Log().Errorf("could not run the service %v", err)
    os.Exit(1)
    return
  }

  // <- code goes here after application stops

Start application with:

go run main.go

Get help

go run main.go -h

Now you can add options/servers/plugins and packages. After modifing .proto file you need to re-run code generation command from step above.

Add some servers:

only GRPC

  option (toolkit.runtime) = {
    servers: [GRPC]
  };

only HTTP

  option (toolkit.runtime) = {
    servers: [HTTP]
  };

only GRPC, HTTP

  option (toolkit.runtime) = {
    servers: [GRPC, HTTP]
  };

Add plugin:

option (toolkit.plugins) = {
  prefix: "pgsql"    // prefix used if you need to add multiple same plugin instances
  plugin: "postgres" // check available plugins in plugins directory
};

Add anoter service client:

option (toolkit.services) = {
  service: "example",
  package: "<package path>"
};

Register your config

  app, err := servicepb.NewExampleService("example")
  if err != nil {
    fmt.Println(err)
  }

  // Config management
  cfg := config.New()

  if err := app.RegisterConfig(cfg); err != nil {
    app.Log().Error(err)
    return
  }

Register custom package

  app, err := servicepb.NewExampleService("example")
  if err != nil {
    fmt.Println(err)
  }

  // Config management
  cfg := config.New()

  if err := app.RegisterConfig(cfg); err != nil {
    app.Log().Error(err)
    return
  }

  // Add packages
  app.RegisterPackage(repository.NewRepository, controller.NewController)

Define GRPC service descriptor

// exmaple services
type Handlers struct {
  servicepb.ExampleRpcServer

  app  toolkit.Service
  cfg  *config.Config
  repo *repository.Repository
}

func (h Handlers) HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest) (*typespb.HelloWorldResponse, error) {
  h.app.Log().Info("ExamplseRpcServer: HelloWorld: call")

  md, ok := metadata.FromIncomingContext(ctx)
  if !ok {
    return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
  }

  demo := h.repo.Get(ctx)

  resp := typespb.HelloWorldResponse{
    Id:   fmt.Sprintf("%d", demo.Id),
    Name: fmt.Sprintf("%s: %d", req.Name, demo.Count),
    Type: req.Type,
  }

  if len(md["x-req-id"]) > 0 {
    header := metadata.New(map[string]string{"x-response-id": md["x-req-id"][0]})
    grpc.SendHeader(ctx, header)
  }

  return &resp, nil
}

func NewServer(app toolkit.Service, cfg *config.Config, repo *repository.Repository) servicepb.ExampleRpcServer {
  return &Handlers{
    repo: repo,
    app:  app,
    cfg:  cfg,
  }
}
// main.go
  app, err := servicepb.NewExampleService("example")
  if err != nil {
    fmt.Println(err)
  }

  // Config management
  cfg := config.New()

  if err := app.RegisterConfig(cfg); err != nil {
    app.Log().Error(err)
    return
  }

  // Add packages
  app.RegisterPackage(repository.NewRepository, controller.NewController)

  // GRPC service descriptor
  app.Server().GRPC().SetService(server.NewServer)

More examples

See here See examples for more examples.

Changelog

See CHANGELOG for release history.

Contributing

Want to help develop? Check out our contributing documentation.

License

Last.Backend Toolkit is Apache 2.0 licensed.