github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/README.md (about)

     1  [![Go Report Card](https://goreportcard.com/badge/github.com/asynkron/protoactor-go)](https://goreportcard.com/report/github.com/asynkron/protoactor-go)
     2  [![GoDoc](https://godoc.org/github.com/asynkron/protoactor-go?status.svg)](https://godoc.org/github.com/asynkron/protoactor-go)
     3  [![checks](https://github.com/asynkron/protoactor-go/actions/workflows/checks.yml/badge.svg)](https://github.com/asynkron/protoactor-go/actions/workflows/checks.yml)
     4  [![Sourcegraph](https://sourcegraph.com/github.com/asynkron/protoactor-go/-/badge.svg)](https://sourcegraph.com/github.com/asynkron/protoactor-go?badge)
     5  
     6  ### [Join our Slack channel](https://join.slack.com/t/asynkron/shared_invite/zt-ko824601-yGN1d3GHF9jzZX2VtONodQ)
     7  
     8  # Cross platform actors
     9  
    10  Introducing cross platform actor support between Go and C#.
    11  
    12  Can I use this?
    13  The Go implementation is still in beta, there are users using Proto Actor for Go in production already.
    14  But be aware that the API might change over time until 1.0.
    15  
    16  ## Sourcecode - Go
    17  
    18  This is the Go repository for Proto Actor.
    19  
    20  The C# implementation can be found
    21  here [https://github.com/asynkron/protoactor-dotnet](https://github.com/asynkron/protoactor-dotnet)
    22  
    23  ## Design principles:
    24  
    25  **Minimalistic API** -
    26  The API should be small and easy to use.
    27  Avoid enterprisey JVM like containers and configurations.
    28  
    29  **Build on existing technologies** - There are already a lot of great tech for e.g. networking and clustering, build on
    30  those.
    31  e.g. gRPC streams for networking, Consul.IO for clustering.
    32  
    33  **Pass data, not objects** - Serialization is an explicit concern, don't try to hide it.
    34  Protobuf all the way.
    35  
    36  **Be fast** - Do not trade performance for magic API trickery.
    37  
    38  Ultra fast remoting, Proto Actor currently manages to pass over **two million messages per second** between nodes using
    39  only two actors, while still preserving message order!
    40  This is six times more the new super advanced UDP based Artery transport for Scala Akka, and 30 times faster than
    41  Akka.NET.
    42  
    43  ```text
    44  :> node1.exe
    45  Started EndpointManager
    46  Started Activator
    47  Starting Proto.Actor server address="127.0.0.1:8081"
    48  Started EndpointWatcher address="127.0.0.1:8080"
    49  Started EndpointWriter address="127.0.0.1:8080"
    50  EndpointWriter connecting address="127.0.0.1:8080"
    51  EndpointWriter connected address="127.0.0.1:8080"
    52  2020/06/22 10:45:20 Starting to send
    53  2020/06/22 10:45:20 50000
    54  2020/06/22 10:45:20 100000
    55  2020/06/22 10:45:20 150000
    56  ... snip ...
    57  2020/06/22 10:45:21 900000
    58  2020/06/22 10:45:21 950000
    59  2020/06/22 10:45:21 1000000
    60  2020/06/22 10:45:21 Elapsed 732.9921ms
    61  2020/06/22 10:45:21 Msg per sec 2728542 <--
    62  
    63  ```
    64  
    65  ## History
    66  
    67  As the creator of the Akka.NET project, I have come to some distinct conclusions while being involved in that project.
    68  In Akka.NET we created our own thread pool, our own networking layer, our own serialization support, our own
    69  configuration support etc. etc.
    70  This was all fun and challenging, it is however now my firm opinion that this is the wrong way to go about things.
    71  
    72  **If possible, software should be composed, not built**, only add code to glue existing pieces together.
    73  This yields a much better time to market, and allows us to focus on solving the actual problem at hand, in this case
    74  concurrency and distributed programming.
    75  
    76  Proto Actor builds on existing technologies, Protobuf for serialization, gRPC streams for network transport.
    77  This ensures cross platform compatibility, network protocol version tolerance and battle proven stability.
    78  
    79  Another extremely important factor here is business agility and having an exit strategy.
    80  By being cross platform, your organization is no longer tied into a specific platform, if you are migrating from .NET to
    81  Go,
    82  This can be done while still allowing actor based services to communicate between platforms.
    83  
    84  Reinvent by not reinventing.
    85  
    86  ---
    87  
    88  ## Why Actors
    89  
    90  ![batman](/resources/batman.jpg)
    91  
    92  - Decoupled Concurrency
    93  - Distributed by default
    94  - Fault tolerance
    95  
    96  For a more indepth description of the differences, see this
    97  thread [Actors vs. CSP](https://www.quora.com/Go-programming-language-How-are-Akka-actors-are-different-than-Goroutines-and-Channels)
    98  
    99  ## Building
   100  
   101  You need to ensure that your `$GOPATH` variable is properly set.
   102  
   103  Next, install the [standard protocol buffer implementation](https://github.com/google/protobuf) and run the following
   104  commands to get all the necessary tooling:
   105  
   106  ```
   107  go get github.com/asynkron/protoactor-go/...
   108  cd $GOPATH/src/github.com/asynkron/protoactor-go
   109  go get ./...
   110  make
   111  ```
   112  
   113  After invoking last command you will have generated protobuf definitions and built the project.
   114  
   115  Windows users can use Cygwin to run make: [www.cygwin.com](https://www.cygwin.com/)
   116  
   117  ## Testing
   118  
   119  This command exectutes all tests in the repository except for consul integration tests (you need consul for running
   120  those tests). We also skip directories that don't contain any tests.
   121  
   122  ```
   123  go test `go list ./... | grep -v "/examples/" | grep -v "/persistence" | grep -v "/scheduler"`
   124  ```
   125  ## Hello world
   126  
   127  ```go
   128  type Hello struct{ Who string }
   129  type HelloActor struct{}
   130  
   131  func (state *HelloActor) Receive(context actor.Context) {
   132      switch msg := context.Message().(type) {
   133      case Hello:
   134          fmt.Printf("Hello %v\n", msg.Who)
   135      }
   136  }
   137  
   138  func main() {
   139      context := actor.EmptyRootContext
   140      props := actor.PropsFromProducer(func() actor.Actor { return &HelloActor{} })
   141      pid, err := context.Spawn(props)
   142      if err != nil {
   143          panic(err)
   144      }
   145      context.Send(pid, Hello{Who: "Roger"})
   146      console.ReadLine()
   147  }
   148  ```
   149  
   150  ## State machines / SetBehavior, PushBehavior and PopBehavior
   151  
   152  ```go
   153  type Hello struct{ Who string }
   154  type SetBehaviorActor struct{}
   155  
   156  func (state *SetBehaviorActor) Receive(context actor.Context) {
   157      switch msg := context.Message().(type) {
   158      case Hello:
   159          fmt.Printf("Hello %v\n", msg.Who)
   160          context.SetBehavior(state.Other)
   161      }
   162  }
   163  
   164  func (state *SetBehaviorActor) Other(context actor.Context) {
   165      switch msg := context.Message().(type) {
   166      case Hello:
   167          fmt.Printf("%v, ey we are now handling messages in another behavior", msg.Who)
   168      }
   169  }
   170  
   171  func NewSetBehaviorActor() actor.Actor {
   172      return &SetBehaviorActor{}
   173  }
   174  
   175  func main() {
   176      context := actor.EmptyRootContext
   177      props := actor.PropsFromProducer(NewSetBehaviorActor)
   178      pid, err := context.Spawn(props)
   179      if err != nil {
   180          panic(err)
   181      }
   182      context.Send(pid, Hello{Who: "Roger"})
   183      context.Send(pid, Hello{Who: "Roger"})
   184      console.ReadLine()
   185  }
   186  ```
   187  
   188  ## Lifecycle events
   189  
   190  Unlike Akka, Proto Actor uses messages for lifecycle events instead of OOP method overrides
   191  
   192  ```go
   193  type Hello struct{ Who string }
   194  type HelloActor struct{}
   195  
   196  func (state *HelloActor) Receive(context actor.Context) {
   197      switch msg := context.Message().(type) {
   198      case *actor.Started:
   199          fmt.Println("Started, initialize actor here")
   200      case *actor.Stopping:
   201          fmt.Println("Stopping, actor is about shut down")
   202      case *actor.Stopped:
   203          fmt.Println("Stopped, actor and its children are stopped")
   204      case *actor.Restarting:
   205          fmt.Println("Restarting, actor is about restart")
   206      case Hello:
   207          fmt.Printf("Hello %v\n", msg.Who)
   208      }
   209  }
   210  
   211  func main() {
   212      context := actor.EmptyRootContext
   213      props := actor.PropsFromProducer(func() actor.Actor { return &HelloActor{} })
   214      pid, err := context.Spawn(props)
   215      if err != nil {
   216          panic(err)
   217      }
   218      context.Send(pid, Hello{Who: "Roger"})
   219  
   220      // why wait?
   221      // Stop is a system message and is not processed through the user message mailbox
   222      // thus, it will be handled _before_ any user message
   223      // we only do this to show the correct order of events in the console
   224      time.Sleep(1 * time.Second)
   225      context.Stop(pid)
   226  
   227      console.ReadLine()
   228  }
   229  ```
   230  
   231  ## Supervision
   232  
   233  Root actors are supervised by the `actor.DefaultSupervisionStrategy()`, which always issues a `actor.RestartDirective`
   234  for failing actors
   235  Child actors are supervised by their parents.
   236  Parents can customize their child supervisor strategy using `Proto Actor.Props`
   237  
   238  ### Example
   239  
   240  ```go
   241  type Hello struct{ Who string }
   242  type ParentActor struct{}
   243  
   244  func (state *ParentActor) Receive(context actor.Context) {
   245      switch msg := context.Message().(type) {
   246      case Hello:
   247          props := actor.PropsFromProducer(NewChildActor)
   248          child := context.Spawn(props)
   249          context.Send(child, msg)
   250      }
   251  }
   252  
   253  func NewParentActor() actor.Actor {
   254      return &ParentActor{}
   255  }
   256  
   257  type ChildActor struct{}
   258  
   259  func (state *ChildActor) Receive(context actor.Context) {
   260      switch msg := context.Message().(type) {
   261      case *actor.Started:
   262          fmt.Println("Starting, initialize actor here")
   263      case *actor.Stopping:
   264          fmt.Println("Stopping, actor is about shut down")
   265      case *actor.Stopped:
   266          fmt.Println("Stopped, actor and its children are stopped")
   267      case *actor.Restarting:
   268          fmt.Println("Restarting, actor is about restart")
   269      case Hello:
   270          fmt.Printf("Hello %v\n", msg.Who)
   271          panic("Ouch")
   272      }
   273  }
   274  
   275  func NewChildActor() actor.Actor {
   276      return &ChildActor{}
   277  }
   278  
   279  func main() {
   280  	decider := func(reason interface{}) actor.Directive {
   281  		log.Printf("handling failure for child. reason:%v", reason)
   282  
   283  		// return actor.StopDirective
   284  		return actor.RestartDirective
   285  	}
   286  	supervisor := actor.NewOneForOneStrategy(10, 1000, decider)
   287  
   288  	ctx := actor.NewActorSystem().Root
   289  	props := actor.PropsFromProducer(NewParentActor).WithSupervisor(supervisor)
   290  
   291  	pid := ctx.Spawn(props)
   292  	ctx.Send(pid, Hello{Who: "Roger"})
   293  
   294  	console.ReadLine()
   295  }
   296  ```
   297  
   298  ## Networking / Remoting
   299  
   300  Proto Actor's networking layer is built as a thin wrapper ontop of gRPC and message serialization is built on Protocol
   301  Buffers<br/>
   302  
   303  ### Example
   304  
   305  #### Node 1
   306  
   307  ```go
   308  type MyActor struct {
   309      count int
   310  }
   311  
   312  func (state *MyActor) Receive(context actor.Context) {
   313      switch context.Message().(type) {
   314      case *messages.Response:
   315          state.count++
   316          fmt.Println(state.count)
   317      }
   318  }
   319  
   320  func main() {
   321      remote.Start("localhost:8090")
   322  
   323      context := actor.EmptyRootContext
   324      props := actor.PropsFromProducer(func() actor.Actor { return &MyActor{} })
   325      pid, _ := context.Spawn(props)
   326      message := &messages.Echo{Message: "hej", Sender: pid}
   327  
   328      // this is to spawn remote actor we want to communicate with
   329      spawnResponse, _ := remote.SpawnNamed("localhost:8091", "myactor", "hello", time.Second)
   330  
   331      // get spawned PID
   332      spawnedPID := spawnResponse.Pid
   333      for i := 0; i < 10; i++ {
   334          context.Send(spawnedPID, message)
   335      }
   336  
   337      console.ReadLine()
   338  }
   339  ```
   340  
   341  #### Node 2
   342  
   343  ```go
   344  type MyActor struct{}
   345  
   346  func (*MyActor) Receive(context actor.Context) {
   347      switch msg := context.Message().(type) {
   348      case *messages.Echo:
   349          context.Send(msg.Sender, &messages.Response{
   350              SomeValue: "result",
   351          })
   352      }
   353  }
   354  
   355  func main() {
   356      remote.Start("localhost:8091")
   357  
   358      // register a name for our local actor so that it can be spawned remotely
   359      remote.Register("hello", actor.PropsFromProducer(func() actor.Actor { return &MyActor{} }))
   360      console.ReadLine()
   361  }
   362  ```
   363  
   364  ### Message Contracts
   365  
   366  ```proto
   367  syntax = "proto3";
   368  package messages;
   369  import "actor.proto"; // we need to import actor.proto, so our messages can include PID's
   370  
   371  // this is the message the actor on node 1 will send to the remote actor on node 2
   372  message Echo {
   373    actor.PID Sender = 1; // this is the PID the remote actor should reply to
   374    string Message = 2;
   375  }
   376  
   377  // this is the message the remote actor should reply with
   378  message Response {
   379    string SomeValue = 1;
   380  }
   381  ```
   382  
   383  Notice: always use "gogoslick_out" instead of "go_out" when generating proto code. "gogoslick_out" will create type
   384  names which will be used during serialization.
   385  
   386  For more examples, see the example folder in this repository.
   387  
   388  ## Contributors
   389  
   390  <a href="https://github.com/asynkron/protoactor-go/graphs/contributors">
   391    <img src="https://contributors-img.web.app/image?repo=asynkron/protoactor-go" />
   392  </a>
   393  
   394  Made with [contributors-img](https://contributors-img.web.app).