From 0a3f7f44341c0cb3ccfd24cd3d1e354e860f460b Mon Sep 17 00:00:00 2001 From: Per Buer Date: Sun, 26 Nov 2023 09:28:08 +0100 Subject: [PATCH] Functional options pattern for Engine init. (#62) * randomize listen port. makes the tests pass on macos. * functional options pattern * functional options pattern --- README.md | 20 ++++++++++---------- actor/engine.go | 31 ++++++++++++++----------------- examples/chat/client/main.go | 2 +- examples/eventstream/main.go | 2 +- examples/helloworld/main.go | 5 +++-- examples/middleware/hooks/main.go | 6 +----- remote/stream_writer.go | 2 +- 7 files changed, 31 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 6959079..d0a00df 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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. diff --git a/actor/engine.go b/actor/engine.go index 107923a..18c804c 100644 --- a/actor/engine.go +++ b/actor/engine.go @@ -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 @@ -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 @@ -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, diff --git a/examples/chat/client/main.go b/examples/chat/client/main.go index 2a96dc9..d49f727 100644 --- a/examples/chat/client/main.go +++ b/examples/chat/client/main.go @@ -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)), diff --git a/examples/eventstream/main.go b/examples/eventstream/main.go index 99b5ed4..4916196 100644 --- a/examples/eventstream/main.go +++ b/examples/eventstream/main.go @@ -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) diff --git a/examples/helloworld/main.go b/examples/helloworld/main.go index c351cc6..1deba9a 100644 --- a/examples/helloworld/main.go +++ b/examples/helloworld/main.go @@ -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!"}) diff --git a/examples/middleware/hooks/main.go b/examples/middleware/hooks/main.go index fb3b00a..da2802f 100644 --- a/examples/middleware/hooks/main.go +++ b/examples/middleware/hooks/main.go @@ -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 diff --git a/remote/stream_writer.go b/remote/stream_writer.go index bdf58ed..1d05624 100644 --- a/remote/stream_writer.go +++ b/remote/stream_writer.go @@ -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",