rpcplatform

package module
v1.7.0 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

README

RPCPlatform

Build PkgGoDev GoReportCard CodeCov Mentioned in Awesome Go

An easy-to-use platform for building microservices without complex infrastructure dependencies. Only etcd is required. Out of the box, you get service discovery, distributed tracing, and other useful features. gRPC is used for communication between services.

etcd is required

If you don't have etcd in your infrastructure, you can run it via Docker for testing:

docker run -d --name etcd \
	-p 2379:2379 -p 2380:2380 \
	-e ETCD_NAME=etcd -e ETCD_INITIAL_CLUSTER=etcd=http://127.0.0.1:2380 \
	-e ETCD_INITIAL_ADVERTISE_PEER_URLS=http://127.0.0.1:2380 -e ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 \
	-e ETCD_ADVERTISE_CLIENT_URLS=http://127.0.0.1:2379 -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \
	gcr.io/etcd-development/etcd:v3.6.5

Of course, you can use Docker in production or install etcd using your favorite package manager. Just remember that the example above is for testing purposes!

How does it work?

All you need to do is assign a name to your server. When it starts, it automatically selects an available port and listens on it (unless you specify otherwise). All clients will connect to this server by its name. If there are multiple server instances with the same name, the load is automatically distributed among them.

The following code examples use a pre-built proto.

First, let's create a new rpcplatform instance and a new server named myServerName, register the implementation of our Sum service, and run it on localhost:

package main

import (
	"context"

	"github.com/nexcode/rpcplatform"
	"github.com/nexcode/rpcplatform/examples/quickstart/proto"
	etcd "go.etcd.io/etcd/client/v3"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

type sumServer struct {
	proto.UnimplementedSumServer
}

func (s *sumServer) Sum(_ context.Context, request *proto.SumRequest) (*proto.SumResponse, error) {
	return proto.SumResponse_builder{
		Sum: new(request.GetA() + request.GetB()),
	}.Build(), nil
}

func main() {
	etcdClient, err := etcd.New(etcd.Config{
		Endpoints: []string{"localhost:2379"},
	})

	if err != nil {
		panic(err)
	}

	rpcp, err := rpcplatform.New("rpcplatform", etcdClient,
		rpcplatform.PlatformOptions.ClientOptions(
			rpcplatform.ClientOptions.GRPCOptions(grpc.WithTransportCredentials(insecure.NewCredentials())),
		),
	)

	if err != nil {
		panic(err)
	}

	server, err := rpcp.NewServer("myServerName", "localhost:")
	if err != nil {
		panic(err)
	}

	proto.RegisterSumServer(server.Server(), &sumServer{})

	if err = server.Serve(context.Background()); err != nil {
		panic(err)
	}
}

For the client, we also create a new rpcplatform instance and a new client named myServerName:

package main

import (
	"context"
	"fmt"
	"math/rand"
	"time"

	"github.com/nexcode/rpcplatform"
	"github.com/nexcode/rpcplatform/examples/quickstart/proto"
	etcd "go.etcd.io/etcd/client/v3"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	etcdClient, err := etcd.New(etcd.Config{
		Endpoints: []string{"localhost:2379"},
	})

	if err != nil {
		panic(err)
	}

	rpcp, err := rpcplatform.New("rpcplatform", etcdClient,
		rpcplatform.PlatformOptions.ClientOptions(
			rpcplatform.ClientOptions.GRPCOptions(grpc.WithTransportCredentials(insecure.NewCredentials())),
		),
	)

	if err != nil {
		panic(err)
	}

	client, err := rpcp.NewClient(context.Background(), "myServerName")
	if err != nil {
		panic(err)
	}

	sumClient := proto.NewSumClient(client.Client())

	for {
		time.Sleep(time.Second)

		sumRequest := proto.SumRequest_builder{
			A: new(int64(rand.Intn(10))),
			B: new(int64(rand.Intn(10))),
		}.Build()

		sumResponse, err := sumClient.Sum(context.Background(), sumRequest)
		if err != nil {
			fmt.Println(err)
			continue
		}

		fmt.Println(sumRequest.GetA(), "+", sumRequest.GetB(), "=", sumResponse.GetSum())
	}
}

That's all you need: add or remove server instances dynamically and create clients at any time — rpcplatform automatically handles service discovery and load balancing.

OpenTelemetry

To visualize our service graph and get telemetry for all gRPC methods, we need to run containers with telemetry services and enable telemetry in rpcplatform.

Let's run containers with Zipkin and Jaeger:

docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin
docker run -d --name jaeger -p 16686:16686 -p 4317:4317 jaegertracing/all-in-one

Now let's create the necessary collectors and add the OpenTelemetry option to the rpcplatform instance:

otlpExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint("localhost:4317"), otlptracegrpc.WithInsecure())
if err != nil {
	panic(err)
}

zipkinExporter, err := zipkin.New("http://localhost:9411/api/v2/spans")
if err != nil {
	panic(err)
}

rpcp, err := rpcplatform.New("rpcplatform", etcdClient,
	rpcplatform.PlatformOptions.OpenTelemetry("myServiceName", 1, otlpExporter, zipkinExporter),
	// other options...
)

if err != nil {
	panic(err)
}

The tracing dashboards are available at:

Zipkin (http://localhost:9411) Jaeger (http://localhost:16686)
Zipkin Jaeger

Usage examples

  • QuickStart: contains the simplest example without additional features
  • OpenTelemetry: example integrating distributed tracing systems
  • Attributes: example using additional settings for client and server

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidEtcdPrefix = errors.New("invalid etcd prefix")
	ErrInvalidTargetName = errors.New("invalid target name")
	ErrInvalidServerName = errors.New("invalid server name")
)
View Source
var (
	// PlatformOptions provides functional options for configuring RPCPlatform.
	PlatformOptions = options.Platform{}

	// ClientOptions provides functional options for configuring Client.
	ClientOptions = options.Client{}

	// ServerOptions provides functional options for configuring Server.
	ServerOptions = options.Server{}
)

Functions

This section is empty.

Types

type Attributes

type Attributes = attributes.Attributes

Attributes contains server attribute values.

func NewAttributes added in v1.5.0

func NewAttributes() *Attributes

NewAttributes returns new Attributes with default values.

type Client

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

func (*Client) Client

func (c *Client) Client() *grpc.ClientConn

Client returns the underlying gRPC ClientConn.

func (*Client) ID added in v1.5.0

func (c *Client) ID() string

ID returns the client identifier.

type ClientOption added in v1.5.0

type ClientOption = func(*config.Client)

ClientOption is a functional option that configures a Client.

type PlatformOption added in v1.5.0

type PlatformOption = func(*config.Platform)

PlatformOption is a functional option that configures an RPCPlatform.

type RPCPlatform

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

func New

func New(etcdPrefix string, etcdClient *etcd.Client, options ...PlatformOption) (*RPCPlatform, error)

New creates a new RPCPlatform for creating clients and servers. You can create one RPCPlatform instance and reuse it throughout your program. All RPCPlatform methods are thread-safe.

func (*RPCPlatform) Lookup added in v1.4.0

func (p *RPCPlatform) Lookup(ctx context.Context, target string, watch bool) (<-chan map[string]*ServerInfo, error)

Lookup returns information about available servers with the given name. If watch is true, the returned channel sends updates whenever servers change. If watch is false, the channel closes after the first update. The returned map keys are server IDs.

func (*RPCPlatform) NewClient

func (p *RPCPlatform) NewClient(ctx context.Context, target string, options ...ClientOption) (*Client, error)

NewClient creates a new client connecting to the specified server name.

func (*RPCPlatform) NewServer

func (p *RPCPlatform) NewServer(name, addr string, options ...ServerOption) (*Server, error)

NewServer creates a new server with the given name listening on addr. If addr is empty, the server listens on all available interfaces. If the port is 0, a random available port is automatically assigned.

type Server

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

func (*Server) ID added in v1.4.0

func (s *Server) ID() string

ID returns the server identifier.

func (*Server) Serve

func (s *Server) Serve(ctx context.Context) error

Serve starts the gRPC server and blocks until it exits or an error occurs.

func (*Server) Server

func (s *Server) Server() *grpc.Server

Server returns the underlying gRPC server.

type ServerInfo added in v1.5.0

type ServerInfo struct {
	Address    string
	Attributes *Attributes
}

ServerInfo contains information about a server stored in etcd.

type ServerOption added in v1.5.0

type ServerOption = func(*config.Server)

ServerOption is a functional option that configures a Server.

Directories

Path Synopsis
examples
internal

Jump to

Keyboard shortcuts

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