Skip to content

Commit

Permalink
Functional options pattern for Engine init. (#62)
Browse files Browse the repository at this point in the history
* randomize listen port. makes the tests pass on macos.

* functional options pattern

* functional options pattern
  • Loading branch information
perbu authored Nov 26, 2023
1 parent c7be50d commit 0a3f7f4
Show file tree
Hide file tree
Showing 7 changed files with 31 additions and 37 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,11 @@ time.Sleep(time.Second)

## Customizing the Engine

We're using the function option pattern. All function options are in the actor package and start their name with
"EngineOpt". So, setting a custom PID separator for the output looks like this:

```go
cfg := actor.Config{
PIDSeparator: "->",
}
e := actor.NewEngine(cfg)
e := actor.NewEngine(actor.EngineOptPidSeparator(""))
```

After configuring the Engine with a custom PID Separator the string representation of PIDS will look like this:
Expand All @@ -164,21 +164,21 @@ The default for Hollywood is, as any good library, not to log anything, but rath
configure logging as it sees fit. However, as a convenience, Hollywood provides a simple logging package that
you can use to gain some insight into what is going on inside the library.

When you create a Hollywood engine, you can provide an optional actor configuration. This gives you the opportunity to
have the log package create a suitable logger. The logger will be based on the standard library's `log/slog` package.
When you create a Hollywood engine, you can pass some configuration options. This gives you the opportunity to
have the log package create a suitable logger. The logger is based on the standard library's `log/slog` package.

If you want Hollywood to log with its defaults, it will provide structured logging with the loglevel being `ÌNFO`.
You'll then initialize the engine as such:

```go
engine := actor.NewEngine(actor.Config{Logger: log.Default()})
engine := actor.NewEngine(actor.EngineOptLogger(log.Default()))
```

If you want more control, say by having having the loglevel be DEBUG and the output format be JSON, you can do so by
If you want more control, say by having the loglevel be DEBUG and the output format be JSON, you can do so by

```go
lh := log.NewHandler(os.Stdout, log.JsonFormat, slog.LevelDebug)
engine := actor.NewEngine(actor.Config{Logger: log.NewLogger("[engine]", lh)})
lh := log.NewHandler(os.Stdout, log.JsonFormat, slog.LevelDebug)
engine := actor.NewEngine(actor.EngineOptLogger(log.NewLogger("[engine]", lh)))
```

This will have the engine itself log with the field "log", prepopulated with the value "[engine]" for the engine itself.
Expand Down
31 changes: 14 additions & 17 deletions actor/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ type Receiver interface {
Receive(*Context)
}

// Config holds configuration for the actor Engine.
type Config struct {
// PIDSeparator separates a process ID when printed out
// in a string representation. The default separator is "/".
// pid := NewPID("127.0.0.1:4000", "foo", "bar")
// 127.0.0.1:4000/foo/bar
PIDSeparator string
Logger log.Logger
}

// Engine represents the actor engine.
type Engine struct {
EventStream *EventStream
Expand All @@ -43,10 +33,11 @@ type Engine struct {
}

// NewEngine returns a new actor Engine.
func NewEngine(cfg ...Config) *Engine {
// You can pass an optional logger through
func NewEngine(opts ...func(*Engine)) *Engine {
e := &Engine{}
if len(cfg) == 1 {
e.configure(cfg[0])
for _, o := range opts {
o(e)
}
e.EventStream = NewEventStream(e.logger)
e.address = LocalLookupAddr
Expand All @@ -56,11 +47,17 @@ func NewEngine(cfg ...Config) *Engine {
return e
}

func (e *Engine) configure(cfg Config) {
if cfg.PIDSeparator != "" {
pidSeparator = cfg.PIDSeparator
func EngineOptLogger(logger log.Logger) func(*Engine) {
return func(e *Engine) {
e.logger = logger
}
}

func EngineOptPidSeparator(sep string) func(*Engine) {
// This looks weird because the separator is a global variable.
return func(e *Engine) {
pidSeparator = sep
}
e.logger = cfg.Logger
}

// WithRemote returns a new actor Engine with the given Remoter,
Expand Down
2 changes: 1 addition & 1 deletion examples/chat/client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func main() {
)
flag.Parse()

e := actor.NewEngine(actor.Config{Logger: log.Default()})
e := actor.NewEngine(actor.EngineOptLogger(log.Default()))
rem := remote.New(e, remote.Config{
ListenAddr: *listenAt,
Logger: log.NewLogger("[remote]", log.NewHandler(os.Stdout, log.TextFormat, slog.LevelDebug)),
Expand Down
2 changes: 1 addition & 1 deletion examples/eventstream/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

func main() {
lh := log.NewHandler(os.Stdout, log.TextFormat, slog.LevelDebug)
e := actor.NewEngine(actor.Config{Logger: log.NewLogger("[engine]", lh)})
e := actor.NewEngine(actor.EngineOptLogger(log.NewLogger("[engine]", lh)))
wg := sync.WaitGroup{}
wg.Add(1)

Expand Down
5 changes: 3 additions & 2 deletions examples/helloworld/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ func (f *foo) Receive(ctx *actor.Context) {
}

func main() {
lh := log.NewHandler(os.Stdout, log.JsonFormat, slog.LevelDebug)
engine := actor.NewEngine(actor.Config{Logger: log.NewLogger("[engine]", lh)})
lh := log.NewHandler(os.Stdout, log.TextFormat, slog.LevelDebug)
engine := actor.NewEngine(actor.EngineOptLogger(log.NewLogger("[engine]", lh)))

pid := engine.Spawn(newFoo, "my_actor")
for i := 0; i < 100; i++ {
engine.Send(pid, &message{data: "hello world!"})
Expand Down
6 changes: 1 addition & 5 deletions examples/middleware/hooks/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,8 @@ func WithHooks() func(actor.ReceiveFunc) actor.ReceiveFunc {
}

func main() {
// Set the process ID separator to something custom.
cfg := actor.Config{
PIDSeparator: "→",
}
// Create a new engine
e := actor.NewEngine(cfg)
e := actor.NewEngine(actor.EngineOptPidSeparator("→"))
// Spawn the a new "foo" receiver with middleware.
pid := e.Spawn(newFoo, "foo", actor.WithMiddleware(WithHooks()))
// Send a message to foo
Expand Down
2 changes: 1 addition & 1 deletion remote/stream_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (s *streamWriter) Invoke(msgs []actor.Envelope) {

if err := s.stream.Send(env); err != nil {
if errors.Is(err, io.EOF) {
s.conn.Close()
_ = s.conn.Close()
return
}
s.logger.Errorw("failed sending message",
Expand Down

0 comments on commit 0a3f7f4

Please sign in to comment.