a Framework for creating microservices using technologies and design patterns of Erlang/OTP in Golang

Overview

Ergo Framework

GitHub release Go Report Card GoDoc MIT license Build Status

Technologies and design patterns of Erlang/OTP have been proven over the years. Now in Golang. Up to x5 times faster than original Erlang/OTP in terms of network messaging. The easiest drop-in replacement for your hot Erlang-nodes in the cluster.

https://ergo.services

Purpose

The goal of this project is to leverage Erlang/OTP experience with Golang performance. Ergo Framework implements DIST protocol, ETF data format and OTP design patterns (GenServer/Supervisor/Application) which makes you able to create high performance and reliable microservice solutions having native integration with Erlang infrastructure

Features

image

  • Erlang node (run single/multinode)
  • embedded EPMD (in order to get rid of erlang' dependencies)
  • Spawn Erlang-like processes
  • Register/unregister processes with simple atom
  • GenServer behaviour support (with atomic state)
  • Supervisor behaviour support (with all known restart strategies support)
  • Application behaviour support
  • GenStage behaviour support (originated from Elixir's GenStage)
  • Connect to (accept connection from) any Erlang node within a cluster (or clusters, if running as multinode)
  • Making sync request process.Call, async - process.Cast or process.Send in fashion of gen_server:call, gen_server:cast, erlang:send accordingly
  • Monitor processes/nodes
    • local -> local
    • local -> remote
    • remote -> local
  • Link processes
    • local <-> local
    • local <-> remote
    • remote <-> local
  • RPC callbacks support
  • Experimental observer support
  • Unmarshalling terms into the struct using etf.TermIntoStruct, etf.TermMapIntoStruct or etf.TermProplistIntoStruct
  • Support Erlang 22. (including fragmentation feature)
  • Encryption (TLS 1.3) support (including autogenerating self-signed certificates)
  • Tested and confirmed support Windows, Darwin (MacOS), Linux

Requirements

  • Go 1.15.x and above

Changelog

Here are the changes of latest release. For more details see the ChangeLog

1.2.0 - 2021-04-07

  • Added TLS support. Introduced new option TLSmode in ergo.NodeOptions with the following values:

    • ergo.TLSmodeDisabled default value. encryption is disabled
    • ergo.TLSmodeAuto enables encryption with autogenerated and self-signed certificate
    • ergo.TLSmodeStrict enables encryption with specified server/client certificates and keys

    there is example of usage examples/nodetls/tlsGenServer.go

  • Introduced GenStage behaviour implementation (originated from Elixir world). GenStage is an abstraction built on top of GenServer to provide a simple way to create a distributed Producer/Consumer architecture, while automatically managing the concept of backpressure. This implementation is fully compatible with Elixir's GenStage. Example here examples/genstage or just run it go run ./examples/genstage to see it in action

  • Introduced new methods AddStaticRoute/RemoveStaticRoute for Node. This feature allows you to keep EPMD service behind a firewall.

  • Introduced SetTrapExit/GetTrapExit methods for Process in order to control the trapping {'EXIT', from, reason} message

  • Introduced TermMapIntoStruct and TermProplistIntoStruct functions. It should be easy now to transform etf.Map or []eft.ProplistElement into the given struct. See documentation for the details.

  • Improved DIST implementation in order to support KeepAlive messages and get rid of platform-dependent syscall usage

  • Fixed TermIntoStruct function. There was a problem with Tuple value transforming into the given struct

  • Fixed incorrect decoding atoms true, false into the booleans

  • Fixed race condition and freeze of connection serving in corner case #21

  • Fixed problem with monitoring process by the registered name (local and remote)

  • Fixed issue with termination linked processes

  • Fixed platform-dependent issues. Now Ergo Framework has tested and confirmed support of Linux, MacOS, Windows.

Benchmarks

Here is simple EndToEnd test demonstrates performance of messaging subsystem

Hardware: laptop with Intel(R) Core(TM) i5-8265U (4 cores. 8 with HT)

Sequential GenServer.Call using two processes running on single and two nodes

❯❯❯❯ go test -bench=NodeSequential -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/halturin/ergo
BenchmarkNodeSequential/number-8 	  256108	     48578 ns/op
BenchmarkNodeSequential/string-8 	  266906	     51531 ns/op
BenchmarkNodeSequential/tuple_(PID)-8         	  233700	     58192 ns/op
BenchmarkNodeSequential/binary_1MB-8          	    5617	   2092495 ns/op
BenchmarkNodeSequentialSingleNode/number-8         	 2527580	      4857 ns/op
BenchmarkNodeSequentialSingleNode/string-8         	 2519410	      4760 ns/op
BenchmarkNodeSequentialSingleNode/tuple_(PID)-8    	 2524701	      4757 ns/op
BenchmarkNodeSequentialSingleNode/binary_1MB-8     	 2521370	      4758 ns/op
PASS
ok  	github.com/halturin/ergo	120.720s

it means Ergo Framework provides around 25.000 sync requests per second via localhost for simple data and around 4Gbit/sec for 1MB messages

Parallel GenServer.Call using 120 pairs of processes running on a single and two nodes

❯❯❯❯ go test -bench=NodeParallel -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/halturin/ergo
BenchmarkNodeParallel-8        	         2652494	      5246 ns/op
BenchmarkNodeParallelSingleNode-8   	 6100352	      2226 ns/op
PASS
ok  	github.com/halturin/ergo	34.145s

these numbers show around 260.000 sync requests per second via localhost using simple data for messaging

vs original Erlang/OTP

benchmarks

sources of these benchmarks are here

EPMD

Ergo Framework has embedded EPMD implementation in order to run your node without external epmd process needs. By default, it works as a client with erlang' epmd daemon or others ergo's nodes either.

The one thing that makes embedded EPMD different is the behaviour of handling connection hangs - if ergo' node is running as an EPMD client and lost connection, it tries either to run its own embedded EPMD service or to restore the lost connection.

As an extra option, we provide EPMD service as a standalone application. There is a simple drop-in replacement of the original Erlang' epmd daemon.

go get -u github.com/halturin/ergo/cmd/epmd

Multinode

This feature allows to create two or more nodes within a single running instance. The only need is to specify the different set of options for creating nodes (such as: node name, empd port number, secret cookie). You may also want to use this feature to create 'proxy'-node between some clusters. See Examples for more details

Observer

It allows you to see the most metrics/information using standard tool of Erlang distribution. The example below shows this feature in action using one of the examples:

observer demo

Examples

Code below is a simple implementation of GenServer pattern examples/simple/GenServer.go

package main

import (
	"fmt"
	"time"

	"github.com/halturin/ergo"
	"github.com/halturin/ergo/etf"
)

type ExampleGenServer struct {
	ergo.GenServer
	process *ergo.Process
}

type State struct {
	value int
}

func (egs *ExampleGenServer) Init(p *ergo.Process, args ...interface{}) (state interface{}) {
	fmt.Printf("Init: args %v \n", args)
	egs.process = p
	InitialState := &State{
		value: args[0].(int), // 100
	}
	return InitialState
}

func (egs *ExampleGenServer) HandleCast(message etf.Term, state interface{}) (string, interface{}) {
	fmt.Printf("HandleCast: %#v (state value %d) \n", message, state.(*State).value)
	time.Sleep(1 * time.Second)
	state.(*State).value++

	if state.(*State).value > 103 {
		egs.process.Send(egs.process.Self(), "hello")
	} else {
		egs.process.Cast(egs.process.Self(), "hi")
	}

	return "noreply", state
}

func (egs *ExampleGenServer) HandleCall(from etf.Tuple, message etf.Term, state interface{}) (string, etf.Term, interface{}) {
	fmt.Printf("HandleCall: %#v, From: %#v\n", message, from)
	return "reply", message, state
}

func (egs *ExampleGenServer) HandleInfo(message etf.Term, state interface{}) (string, interface{}) {
	fmt.Printf("HandleInfo: %#v (state value %d) \n", message, state.(*State).value)
	time.Sleep(1 * time.Second)
	state.(*State).value++
	if state.(*State).value > 106 {
		return "stop", "normal"
	} else {
		egs.process.Send(egs.process.Self(), "hello")
	}
	return "noreply", state
}

func (egs *ExampleGenServer) Terminate(reason string, state interface{}) {
	fmt.Printf("Terminate: %#v \n", reason)
}

func main() {
	node := ergo.CreateNode("[email protected]", "cookies", ergo.NodeOptions{})

	gs1 := &ExampleGenServer{}
	process, _ := node.Spawn("gs1", ergo.ProcessOptions{}, gs1, 100)
	process.Cast(process.Self(), "hey")

	process.Wait()
	fmt.Println("exited")
}

here is output of this code

$ go run ./examples/simple/GenServer.go
Init: args [100]
HandleCast: "hey" (state value 100)
HandleCast: "hi" (state value 101)
HandleCast: "hi" (state value 102)
HandleCast: "hi" (state value 103)
HandleInfo: "hello" (state value 104)
HandleInfo: "hello" (state value 105)
HandleInfo: "hello" (state value 106)
Terminate: "normal"
exited

See examples/ for more details

Elixir Phoenix Users

Users of the Elixir Phoenix framework might encounter timeouts when trying to connect a Phoenix node to an ergo node. The reason is that, in addition to global_name_server and net_kernel, Phoenix attempts to broadcast messages to the pg2 PubSub handler

To work with Phoenix nodes, you must create and register a dedicated pg2 GenServer, and spawn it inside your node. Take inspiration from the global_name_server.go for the rest of the GenServer methods, but the Spawn must have "pg2" as a process name:

type Pg2GenServer struct {
    ergo.GenServer
}

func main() {
    // ...
    pg2 := &Pg2GenServer{}
    node1 := ergo.CreateNode("[email protected]", "cookies", ergo.NodeOptions{})
    process, _ := node1.Spawn("pg2", ergo.ProcessOptions{}, pg2, nil)
    // ...
}

Development and debugging

There is a couple of options are already defined that you might want to use

  • -trace.node
  • -trace.dist

To enable Golang profiler just add --tags debug in your go run or go build like this:

go run --tags debug ./examples/genserver/demoGenServer.go

Now golang' profiler is available at http://localhost:9009/debug/pprof

Companies are using Ergo Framework

Kaspersky RingCentral LilithGames

is your company using Ergo? add your company logo/name here

Commercial support

please, visit https://ergo.services for more information

Comments
Releases(v1.999.220)
  • v1.999.220(Oct 18, 2022)

    • Introduced gen.Web behavior. It implements Web API Gateway pattern is also sometimes known as the "Backend For Frontend" (BFF). See example examples/genweb
    • Introduced gen.TCP behavior - socket acceptor pool for TCP protocols. It provides everything you need to accept TCP connections and process packets with a small code base and low latency. Here is simple example examples/gentcp
    • Introduced gen.UDP - the same as gen.TCP, but for UDP protocols. Example is here examples/genudp
    • Introduced Events. This is a simple pub/sub feature within a node - any gen.Process can become a producer by registering a new event gen.Event using method gen.Process.RegisterEvent, while the others can subscribe to these events using gen.Process.MonitorEvent. Subscriber process will also receive gen.MessageEventDown if a producer process went down (terminated). This feature behaves in a monitor manner but only works within a node. You may also want to subscribe to a system event - node.EventNetwork to receive event notification on connect/disconnect any peers.
    • Introduced Cloud Client - allows connecting to the cloud platform https://ergo.sevices. You may want to register your email there, and we will inform you about the platform launch day
    • Introduced type registration for the ETF encoding/decoding. This feature allows you to get rid of manually decoding with etf.TermIntoStruct for the receiving messages. Register your type using etf.RegisterType(...), and you will be receiving messages in a native type
    • Predefined set of errors has moved to the lib package
    • Updated gen.ServerBehavior.HandleDirect method (got extra argument etf.Ref to distinguish the requests). This change allows you to handle these requests asynchronously using method gen.ServerProcess.Reply(...)
    • Updated node.Options. Now it has field Listeners (type node.Listener). It allows you to start any number of listeners with custom options - Port, TLS settings, or custom Handshake/Proto interfaces
    • Fixed build on 32-bit arch
    • Fixed freezing on ARM arch #102
    • Fixed problem with encoding negative int8
    • Fixed #103 (there was an issue on interop with Elixir's GenStage)
    • Fixed node stuck on start if it uses the name which is already taken in EPMD
    • Fixed incorrect gen.ProcessOptions.Context handling
    Source code(tar.gz)
    Source code(zip)
  • v1.999.211(Jun 15, 2022)

    This release includes fixes:

    • Fixed problem with encoding negative int8 - critical issue
    • Fixed #103 (there was an issue on interop with Elixir's GenStage)
    • Fixed node stuck on start if it uses the name which is already taken in EPMD
    Source code(tar.gz)
    Source code(zip)
  • v1.999.210(Apr 19, 2022)

    • Introduced compression feature support. Here are new methods and options to manage this feature:
      • gen.Process:
        • SetCompression(enable bool), Compression() bool
        • SetCompressionLevel(level int), CompressionLevel() int
        • SetCompressionThreshold(threshold int), CompressionThreshold() int messages smaller than the threshold will be sent with no compression. The default compression threshold is 1024 bytes.
      • node.Options:
        • Compression these settings are used as defaults for the spawning processes
      • this feature will be ignored if the receiver is running on either the Erlang or Elixir node
    • Introduced proxy feature support with end-to-end encryption.
      • node.Node new methods:
        • AddProxyRoute(...), RemoveProxyRoute(...)
        • ProxyRoute(...), ProxyRoutes()
        • NodesIndirect() returns list of connected nodes via proxy connection
      • node.Options:
        • Proxy for configuring proxy settings
      • includes support (over the proxy connection): compression, fragmentation, link/monitor process, monitor node
      • example examples/proxy.
      • this feature is not available for the Erlang/Elixir nodes
    • Introduced behavior gen.Raft. It's improved implementation of Raft consensus algorithm. The key improvement is using quorum under the hood to manage the leader election process and make the Raft cluster more reliable. This implementation supports quorums of 3, 5, 7, 9, or 11 quorum members. Here is an example of this feature examples/raft.
    • Introduced interfaces to customize network layer
      • Resolver to replace EPMD routines with your solution (e.g., ZooKeeper or any other service registrar)
      • Handshake allows customizing authorization/authentication process
      • Proto provides the way to implement proprietary protocols (e.g., for IoT area)
    • Other new features:
      • gen.Process new methods:
        • NodeUptime(), NodeName(), NodeStop()
      • gen.ServerProcess new method:
        • MessageCounter() shows how many messages have been handled by the gen.Server callbacks
      • gen.ProcessOptions new option:
        • ProcessFallback allows forward messages to the fallback process if the process mailbox is full. Forwarded messages are wrapped into gen.MessageFallback struct. Related to issue #96.
      • gen.SupervisorChildSpec and gen.ApplicationChildSpec got option gen.ProcessOptions to customize options for the spawning child processes.
    • Improved sending messages by etf.Pid or etf.Alias: methods gen.Process.Send, gen.ServerProcess.Cast, gen.ServerProcess.Call now return node.ErrProcessIncarnation if a message is sending to the remote process of the previous incarnation (remote node has been restarted).
    • Introduced type gen.EnvKey for the environment variables
    • All spawned processes now have the node.EnvKeyNode variable to get access to the node.Node value.
    • Improved performance of local messaging (up to 8 times for some cases)
    • Important node.Options has changed. Make sure to adjust your code.
    • Fixed issue #89 (incorrect handling of Call requests)
    • Fixed issues #87, #88 and #93 (closing network socket)
    • Fixed issue #96 (silently drops message if process mailbox is full)
    • Updated minimal requirement of Golang version to 1.17 (go.mod)
    • We still keep the rule Zero Dependencies
    Source code(tar.gz)
    Source code(zip)
  • v1.999.202(Jan 4, 2022)

    This release includes fixes:

    • critical issue #89 (incorrect handling errors during a Call request)
    • issue #87 (incorrect link termination with EPMD).
    Source code(tar.gz)
    Source code(zip)
  • v1.999.201(Nov 22, 2021)

  • v1.999.200(Oct 14, 2021)

    We have to make another release of v2.0.0 due to https://go.dev/blog/v2-go-modules. More details here https://github.com/ergo-services/ergo#versioning

    • Added support of Erlang/OTP 24 (including Alias feature and Remote Spawn introduced in Erlang/OTP 23)
    • Important: This release includes refined API (without backward compatibility) for a more convenient way to create OTP-designed microservices. Make sure to update your code.
    • Important: Project repository has been moved to https://github.com/ergo-services/ergo. It is still available on the old URL https://github.com/halturin/ergo and GitHub will redirect all requests to the new one (thanks to GitHub for this feature).
    • Introduced new behavior gen.Saga. It implements Saga design pattern - a sequence of transactions that updates each service state and publishes the result (or cancels the transaction or triggers the next transaction step). gen.Saga also provides a feature of interim results (can be used as transaction progress or as a part of pipeline processing), time deadline (to limit transaction lifespan), two-phase commit (to make distributed transaction atomic). Here is example examples/gensaga.
    • Introduced new methods Process.Direct and Process.DirectWithTimeout to make direct request to the actor (gen.Server or inherited object). If an actor has no implementation of HandleDirect callback it returns ErrUnsupportedRequest as a error.
    • Introduced new callback HandleDirect in the gen.Server interface as a handler for requests made by Process.Direct or Process.DirectWithTimeout. It should be easy to interact with actors from outside.
    • Introduced new types intended to be used to interact with Erlang/Elixir
      • etf.ListImproper to support improper lists like [a|b] (a cons cell).
      • etf.String (an alias for the Golang string) encodes as a binary in order to support Elixir string type (which is binary() type)
      • etf.Charlist (an alias for the Golang string) encodes as a list of chars []rune in order to support Erlang string type (which is charlist() type)
    • Introduced new methods Node.ProvideRemoteSpawn, Node.RevokeRemoteSpawn, Process.RemoteSpawn.
    • Introduced new interfaces Marshaler (method MarshalETF) and Unmarshaler (method UnmarshalETF) for the custom encoding/decoding data.
    • Improved performance for the local messaging (up to 3 times for some cases)
    • Added example examples/http to demonsrate how HTTP server can be integrated into the Ergo node.
    • Added example examples/gendemo - how to create a custom behavior (design pattern) on top of the gen.Server. Take inspiration from the gen/stage.go or gen/saga.go design patterns.
    • Added support FreeBSD, OpenBSD, NetBSD, DragonFly.
    • Fixed RPC issue #45
    • Fixed internal timer issue #48
    • Fixed memory leaks #53
    • Fixed double panic issue #52
    • Fixed Atom Cache race conditioned issue #54
    • Fixed ETF encoder issues #64 #66
    Source code(tar.gz)
    Source code(zip)
  • v1.2.6(Aug 9, 2021)

  • v1.2.5(Jul 29, 2021)

  • v1.2.4(Jun 30, 2021)

  • v1.2.2(May 11, 2021)

  • v1.2.1(Apr 29, 2021)

  • v1.2.0(Apr 6, 2021)

  • v1.1.0(Apr 22, 2020)

  • 1.0.0(Mar 2, 2020)

Owner
Taras Halturin
Developer | Blender of #golang and #erlang
Taras Halturin
A standard library for microservices.

Go kit Go kit is a programming toolkit for building microservices (or elegant monoliths) in Go. We solve common problems in distributed systems and ap

Go kit 24.4k Jan 2, 2023
A Distributed Content Licensing Framework (DCLF) using Hyperledger Fabric permissioned blockchain.

A Distributed Content Licensing Framework (DCLF) using Hyperledger Fabric permissioned blockchain.

Tal Derei 4 Nov 4, 2022
Go Micro is a framework for distributed systems development

Go Micro Go Micro is a framework for distributed systems development. Overview Go Micro provides the core requirements for distributed systems develop

Asim Aslam 19.9k Jan 8, 2023
Skynet is a framework for distributed services in Go.

##Introduction Skynet is a communication protocol for building massively distributed apps in Go. It is not constrained to Go, so it will lend itself n

null 2k Nov 18, 2022
Cross-platform grid-based user interface framework.

Gruid The gruid module provides packages for easily building grid-based applications in Go. The library abstracts rendering and input for different pl

null 69 Nov 23, 2022
a dynamic configuration framework used in distributed system

go-archaius This is a light weight configuration management framework which helps to manage configurations in distributed system The main objective of

null 205 Dec 9, 2022
Go Micro is a standalone framework for distributed systems development

Go Micro Go Micro is a framework for distributed systems development. Overview Go Micro provides the core requirements for distributed systems develop

Asim Aslam 19.9k Dec 31, 2022
An actor framework for Go

gosiris is an actor framework for Golang. Features Manage a hierarchy of actors (each actor has its own: state, behavior, mailbox, child actors) Deplo

Teiva Harsanyi 246 Dec 28, 2022
Tarmac is a unique framework designed for the next generation of distributed systems

Framework for building distributed services with Web Assembly

Benjamin Cane 225 Dec 31, 2022
Dynatomic is a library for using dynamodb as an atomic counter

Dynatomic Dynatomic is a library for using dynamodb as an atomic counter Dynatomic Motivation Usage Development Contributing Motivation The dynatomic

Tyler Finethy 15 Sep 26, 2022
Simplified distributed locking implementation using Redis

redislock Simplified distributed locking implementation using Redis. For more information, please see examples. Examples import ( "fmt" "time"

Black Square Media 879 Dec 24, 2022
A distributed lock service in Go using etcd

locker A distributed lock service client for etcd. What? Why? A distributed lock service is somewhat self-explanatory. Locking (mutexes) as a service

James Gregory 49 Sep 27, 2022
Lockgate is a cross-platform locking library for Go with distributed locks using Kubernetes or lockgate HTTP lock server as well as the OS file locks support.

Lockgate Lockgate is a locking library for Go. Classical interface: 2 types of locks: shared and exclusive; 2 modes of locking: blocking and non-block

werf 232 Dec 16, 2022
Rink is a "distributed sticky ranked ring" using etcd.

Rink is a "distributed sticky ranked ring" using etcd. A rink provides role scheduling across distributed processes, with each role only assigned

Luno 8 Dec 5, 2022
This is a comprehensive system that simulate multiple servers’ consensus behavior at local machine using multi-process deployment.

Raft simulator with Golang This project is a simulator for the Raft consensus protocol. It uses HTTP for inter-server communication, and a job schedul

Yujie Zhang 1 Jan 30, 2022
Golang client library for adding support for interacting and monitoring Celery workers, tasks and events.

Celeriac Golang client library for adding support for interacting and monitoring Celery workers and tasks. It provides functionality to place tasks on

Stefan von Cavallar 73 Oct 28, 2022
AppsFlyer 505 Dec 27, 2022
Simple, fast and scalable golang rpc library for high load

gorpc Simple, fast and scalable golang RPC library for high load and microservices. Gorpc provides the following features useful for highly loaded pro

Aliaksandr Valialkin 667 Dec 19, 2022
dht is used by anacrolix/torrent, and is intended for use as a library in other projects both torrent related and otherwise

dht Installation Install the library package with go get github.com/anacrolix/dht, or the provided cmds with go get github.com/anacrolix/dht/cmd/....

Matt Joiner 254 Dec 28, 2022