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).