Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 39 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ advertising brokers, trading engines, etc... It can handle **10 million messages
The Actor Model is a computational model used to build highly concurrent and distributed systems. It was introduced by
Carl Hewitt in 1973 as a way to handle complex systems in a more scalable and fault-tolerant manner.

In the Actor Model, the basic building block is an actor, sometimes referred to as a receiver in Hollywood,
which is an independent unit of computation that communicates with other actors by exchanging messages.
In the Actor Model, the basic building block is an actor, sometimes referred to as a receiver in Hollywood,
which is an independent unit of computation that communicates with other actors by exchanging messages.
Each actor has its own state and behavior, and
can only communicate with other actors by sending messages. This message-passing paradigm allows for a highly
decentralized and fault-tolerant system, as actors can continue to operate independently even if other actors fail or
Expand All @@ -36,7 +36,7 @@ Compiles to WASM! Both GOOS=js and GOOS=wasm32
- High performance dRPC as the transport layer
- Optimized proto buffers without reflection
- Lightweight and highly customizable
- Cluster support for writing distributed self discovering actors
- Cluster support for writing distributed self discovering actors

# Benchmarks

Expand Down Expand Up @@ -72,27 +72,27 @@ compiler.

## Hello world.

Let's go through a Hello world message. The complete example is available in the
Let's go through a Hello world message. The complete example is available in the
[hello world](examples/helloworld) folder. Let's start in main:
```go
engine, err := actor.NewEngine(actor.NewEngineConfig())
```
This creates a new engine. The engine is the core of Hollywood. It's responsible for spawning actors, sending messages
and handling the lifecycle of actors. If Hollywood fails to create the engine it'll return an error. For development
and handling the lifecycle of actors. If Hollywood fails to create the engine it'll return an error. For development
you shouldn't use to pass any options to the engine so you can pass nil. We'll look at the options later.

Next we'll need to create an actor. These are some times referred to as `Receivers` after the interface they must
implement. Let's create a new actor that will print a message when it receives a message.
Next we'll need to create an actor. These are some times referred to as `Receivers` after the interface they must
implement. Let's create a new actor that will print a message when it receives a message.

```go
pid := engine.Spawn(newHelloer, "hello")
```
This will cause the engine to spawn an actor with the ID "hello". The actor will be created by the provided
This will cause the engine to spawn an actor with the ID "hello". The actor will be created by the provided
function `newHelloer`. Ids must be unique. It will return a pointer to a PID. A PID is a process identifier. It's a unique identifier for the actor. Most of
the time you'll use the PID to send messages to the actor. Against remote systems you'll use the ID to send messages,
the time you'll use the PID to send messages to the actor. Against remote systems you'll use the ID to send messages,
but on local systems you'll mostly use the PID.

Let's look at the `newHelloer` function and the actor it returns.
Let's look at the `newHelloer` function and the actor it returns.

```go
type helloer struct{}
Expand Down Expand Up @@ -125,15 +125,15 @@ func (h *helloer) Receive(ctx *actor.Context) {
```

You can see we define a message struct. This is the message we'll send to the actor later. The Receive method
also handles a few other messages. These lifecycle messages are sent by the engine to the actor, you'll use these to
also handles a few other messages. These lifecycle messages are sent by the engine to the actor, you'll use these to
initialize your actor

The engine passes an actor.Context to the `Receive` method. This context contains the message, the PID of the sender and
some other dependencies that you can use.

Now, lets send a message to the actor. We'll send a `message`, but you can send any type of message you want. The only
requirement is that the actor must be able to handle the message. For messages to be able to cross the wire
they must be serializable. For protobuf to be able to serialize the message it must be a pointer.
requirement is that the actor must be able to handle the message. For messages to be able to cross the wire
they must be serializable. For protobuf to be able to serialize the message it must be a pointer.
Local messages can be of any type.

Finally, lets send a message to the actor.
Expand Down Expand Up @@ -198,7 +198,7 @@ The options should be pretty self explanatory. You can set the maximum number of
how many times the given actor should be restarted in case of panic, the size of the inbox, which sets a limit on how
and unprocessed messages the inbox can hold before it will start to block.

### As a stateless function
### As a stateless function
Actors without state can be spawned as a function, because its quick and simple.
```go
e.SpawnFunc(func(c *actor.Context) {
Expand All @@ -211,7 +211,7 @@ e.SpawnFunc(func(c *actor.Context) {
```

## Remote actors
Actors can communicate with each other over the network with the Remote package.
Actors can communicate with each other over the network with the Remote package.
This works the same as local actors but "over the wire". Hollywood supports serialization with protobuf.

### Configuration
Expand All @@ -238,37 +238,37 @@ In a production system thing will eventually go wrong. Actors will crash, machin
the deadletter queue. You can build software that can handle these events in a graceful and predictable way by using
the event stream.

The Eventstream is a powerful abstraction that allows you to build flexible and pluggable systems without dependencies.
The Eventstream is a powerful abstraction that allows you to build flexible and pluggable systems without dependencies.

1. Subscribe any actor to a various list of system events
2. Broadcast your custom events to all subscribers
2. Broadcast your custom events to all subscribers

Note that events that are not handled by any actor will be dropped. You should have an actor subscribed to the event
stream in order to receive events. As a bare minimum, you'll want to handle `DeadLetterEvent`. If Hollywood fails to
deliver a message to an actor it will send a `DeadLetterEvent` to the event stream.
Note that events that are not handled by any actor will be dropped. You should have an actor subscribed to the event
stream in order to receive events. As a bare minimum, you'll want to handle `DeadLetterEvent`. If Hollywood fails to
deliver a message to an actor it will send a `DeadLetterEvent` to the event stream.

Any event that fulfills the `actor.LogEvent` interface will be logged to the default logger, with the severity level,
Any event that fulfills the `actor.LogEvent` interface will be logged to the default logger, with the severity level,
message and the attributes of the event set by the `actor.LogEvent` `log()` method.

### List of internal system events
### List of internal system events
* `actor.ActorInitializedEvent`, an actor has been initialized but did not processed its `actor.Started message`
* `actor.ActorStartedEvent`, an actor has started
* `actor.ActorStoppedEvent`, an actor has stopped
* `actor.DeadLetterEvent`, a message was not delivered to an actor
* `actor.ActorRestartedEvent`, an actor has restarted after a crash/panic.
* `actor.RemoteUnreachableEvent`, sending a message over the wire to a remote that is not reachable.
* `cluster.MemberJoinEvent`, a new member joins the cluster
* `cluster.MemberLeaveEvent`, a new member left the cluster
* `cluster.ActivationEvent`, a new actor is activated on the cluster
* `cluster.DeactivationEvent`, an actor is deactivated on the cluster
* `cluster.MemberJoinEvent`, a new member joins the cluster
* `cluster.MemberLeaveEvent`, a new member left the cluster
* `cluster.ActivationEvent`, a new actor is activated on the cluster
* `cluster.DeactivationEvent`, an actor is deactivated on the cluster

### Eventstream example

There is a [eventstream monitoring example](examples/eventstream-monitor) which shows you how to use the event stream.
It features two actors, one is unstable and will crash every second. The other actor is subscribed to the event stream
and maintains a few counters for different events such as crashes, etc.
and maintains a few counters for different events such as crashes, etc.

The application will run for a few seconds and the poison the unstable actor. It'll then query the monitor with a
The application will run for a few seconds and the poison the unstable actor. It'll then query the monitor with a
request. As actors are floating around inside the engine, this is the way you interact with them. main will then print
the result of the query and the application will exit.

Expand All @@ -285,15 +285,22 @@ addr is a string with the format "host:port".

## Middleware

You can add custom middleware to your Receivers. This can be useful for storing metrics, saving and loading data for
your Receivers on `actor.Started` and `actor.Stopped`.
You can add custom middleware to your Recievers to extend their behavior. Middleware wraps around the Reciever's message handling function and allows you to run code during important lifecycle events such as `actor.Initialized`, `actor.Started` and `actor.Stopped`.

For examples on how to implement custom middleware, check out the middleware folder in the ***[examples](examples/middleware)***
A common use case for middleware is to implement hooks that run custom logic on these lifecycle events, like logging, metrics collection, or state management.

For example, the middleware in the ***[examples](examples/middleware)*** demonstrates a `WithHooks` function. This middlware intercepts lifecycle messages and calls corresponding hook methods (`OnInit`, `OnStart`, `OnStop`) defined on Recievers that implements the Hooker interface.

To use this middleware, you can pass it when spawning a Reciever.

``` e.Spawn(newFoo, "foo", actor.WithMiddleware(WithHooks()))```

This setup ensures the middleware calls your custom lifecycle hooks automatically as the Receiver’s state changes.

## Logging

Hollywood has some built in logging. It will use the default logger from the `log/slog` package. You can configure the
logger to your liking by setting the default logger using `slog.SetDefaultLogger()`. This will allow you to customize
logger to your liking by setting the default logger using `slog.SetDefaultLogger()`. This will allow you to customize
the log level, format and output. Please see the `slog` package for more information.

Note that some events might be logged to the default logger, such as `DeadLetterEvent` and `ActorStartedEvent` as these
Expand Down