使用Golang的事件监听器和调度器实例

640 阅读7分钟

在这个例子中,我们将有两种类型的Golang的事件驱动编程例子。它们彼此非常相似,但两者的主要区别在于通道的使用。第一个例子不使用通道。你永远不会接触到internal/pkg/event 文件夹,所以你可以把它看作是一个外部库包。user 文件夹是你的代码。

这是一个有主见但非常直接的 "甚至驱动 "的例子。事件以某种方式被派发,然而处理的方式由你决定。派遣事件可以用阻塞或非阻塞的方式处理(如goroutine)。它有三个简单的步骤,如下所列:

  1. 创建一个事件调度器的实例。(在应用程序启动时完成)

  2. 注册事件和监听器。(在应用启动阶段完成)

  3. 派遣事件

属性

  • 一个调度器可以注册许多监听器

  • 一个调度器可以为一个特定的听众注册许多事件

  • 一个调度器可以调度许多事件

  • 一个监听器可以监听许多事件

  • 一个事件可以链接到一个事件类型

  • 调度器防止注册重复的事件

  • 调度器防止调度非注册的事件

例子1 - 没有通道

结构

├── cmd
│   └── client
│       └── main.go
└── internal
    ├── pkg
    │   └── event
    │       ├── dispatcher.go
    │       ├── event.go
    │       └── listener.go
    └── user
        ├── created.go
        ├── deleted.go
        ├── listener.go
        └── updated.go

文件

cmd/client/main.go
package main

import (
	"context"
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
	"github.com/inanzzz/client/internal/user"
)

func main() {
	// ----------------------------------------------------------------------------------------
	// Register events with listeners at application boot
	dispatcher := event.NewDispatcher()
	if err := dispatcher.Register(user.Listener{}, user.Created, user.Updated); err != nil {
		log.Fatalln(err)
	}

	// ----------------------------------------------------------------------------------------
	// Dispatch registered events. Valid.
	go func() {
		err := dispatcher.Dispatch(context.Background(), user.Created, user.CreatedEvent{
			Time: time.Now().UTC(),
			ID:   "111",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	go func() {
		err := dispatcher.Dispatch(context.Background(), user.Updated, user.UpdatedEvent{
			Time:     time.Now().UTC(),
			ID:       "222",
			Key:      "name",
			OldValue: "inanzzz",
			NewValue: "zzznani",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	// ----------------------------------------------------------------------------------------
	// Dispatch a valid event type to unregistered event name. Error.
	go func() {
		err := dispatcher.Dispatch(context.Background(), user.Deleted, user.DeletedEvent{
			Time: time.Now().UTC(),
			ID:   "333",
			Who:  "admin",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	// ----------------------------------------------------------------------------------------
	// Dispatch a wrong event type to registered event name. Error.
	go dispatcher.Dispatch(context.Background(), user.Created, nil)
	go dispatcher.Dispatch(context.Background(), user.Updated, "hi")
	go dispatcher.Dispatch(context.Background(), user.Created, 123)
	go dispatcher.Dispatch(context.Background(), user.Updated, struct{}{})
	go dispatcher.Dispatch(context.Background(), user.Created, make(chan int))

	select {}
}
internal/pkg/event/dispatcher.go
package event

import (
	"context"
	"fmt"
)

type Dispatcher struct {
	events map[Name]Listener
}

func NewDispatcher() *Dispatcher {
	return &Dispatcher{
		events: make(map[Name]Listener),
	}
}

func (d *Dispatcher) Register(listener Listener, names ...Name) error {
	for _, name := range names {
		if _, ok := d.events[name]; ok {
			return fmt.Errorf("the '%s' event is already registered", name)
		}

		d.events[name] = listener
	}

	return nil
}

func (d *Dispatcher) Dispatch(ctx context.Context, name Name, event interface{}) error {
	if _, ok := d.events[name]; !ok {
		return fmt.Errorf("the '%s' event is not registered", name)
	}

	d.events[name].Listen(ctx, event)

	return nil
}
internal/pkg/event/event.go
package event

import (
	"context"
)

// All custom events names must be of this type.
type Name string

// All custom event types must satisfy this interface.
type Event interface {
	Handle(ctx context.Context)
}
internal/pkg/event/listener.go
package event

import (
	"context"
)

// All custom event listeners must satisfy this interface.
type Listener interface {
	Listen(ctx context.Context, event interface{})
}
internal/user/created.go
package user

import (
	"context"
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
)

const Created event.Name = "user.created"

type CreatedEvent struct {
	Time time.Time
	ID   string
}

func (e CreatedEvent) Handle(ctx context.Context) {
	log.Printf("creating: %+v\n", e)
}
internal/user/deleted.go
package user

import (
	"context"
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
)

const Deleted event.Name = "user.deleted"

type DeletedEvent struct {
	Time time.Time
	ID   string
	Who  string
}

func (e DeletedEvent) Handle(ctx context.Context) {
	log.Printf("deleting: %+v\n", e)
}
internal/user/updated.go
package user

import (
	"context"
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
)

const Updated event.Name = "user.updated"

type UpdatedEvent struct {
	Time     time.Time
	ID       string
	Key      string
	OldValue string
	NewValue string
}

func (e UpdatedEvent) Handle(ctx context.Context) {
	log.Printf("updating: %+v\n", e)
}
internal/user/listener.go
package user

import (
	"context"
	"log"
)

type Listener struct{}

func (u Listener) Listen(ctx context.Context, event interface{}) {
	switch event := event.(type) {
	case CreatedEvent:
		event.Handle(ctx)
	case UpdatedEvent:
		event.Handle(ctx)
	case DeletedEvent:
		event.Handle(ctx)
	default:
		log.Printf("registered an invalid user event: %T\n", event)
	}
}

测试

2020/05/27 21:08:53 creating: {Time:2020-05-27 20:08:53.272564 +0000 UTC ID:111}
2020/05/27 21:08:53 registered an invalid user event: 
2020/05/27 21:08:53 the 'user.deleted' event is not registered
2020/05/27 21:08:53 registered an invalid user event: string
2020/05/27 21:08:53 updating: {Time:2020-05-27 20:08:53.273203 +0000 UTC ID:222 Key:name OldValue:inanzzz NewValue:zzznani}
2020/05/27 21:08:53 registered an invalid user event: int
2020/05/27 21:08:53 registered an invalid user event: struct {}
2020/05/27 21:08:53 registered an invalid user event: chan int

例子2--有渠道(优先)的例子

如果你想实现通道优先选项,你可以看看我之前写的博文。**注意:**查看下面的第三个例子(后期添加)。

结构

├── cmd
│   └── client
│       └── main.go
└── internal
    ├── pkg
    │   └── event
    │       ├── dispatcher.go
    │       ├── event.go
    │       ├── job.go
    │       └── listener.go
    └── user
        ├── created.go
        ├── deleted.go
        ├── listener.go
        └── updated.go

文件

cmd/client/main.go
package main

import (
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
	"github.com/inanzzz/client/internal/user"
)

func main() {
	// ----------------------------------------------------------------------------------------
	// Register events with listeners at application boot
	dispatcher := event.NewDispatcher()
	if err := dispatcher.Register(user.Listener{}, user.Created, user.Updated); err != nil {
		log.Fatalln(err)
	}

	// ----------------------------------------------------------------------------------------
	// Dispatch registered events. Valid.
	go func() {
		err := dispatcher.Dispatch(user.Created, user.CreatedEvent{
			Time: time.Now().UTC(),
			ID:   "111",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	go func() {
		err := dispatcher.Dispatch(user.Updated, user.UpdatedEvent{
			Time:     time.Now().UTC(),
			ID:       "222",
			Key:      "name",
			OldValue: "inanzzz",
			NewValue: "zzznani",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	// ----------------------------------------------------------------------------------------
	// Dispatch a valid event type to unregistered event name. Error.
	go func() {
		err := dispatcher.Dispatch(user.Deleted, user.DeletedEvent{
			Time: time.Now().UTC(),
			ID:   "333",
			Who:  "admin",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	// ----------------------------------------------------------------------------------------
	// Dispatch a wrong event type to registered event name. Error.
	go dispatcher.Dispatch(user.Created, nil)
	go dispatcher.Dispatch(user.Updated, "hi")
	go dispatcher.Dispatch(user.Created, 123)
	go dispatcher.Dispatch(user.Updated, struct{}{})
	go dispatcher.Dispatch(user.Created, make(chan int))

	select {}
}
internal/pkg/event/dispatcher.go
package event

import (
	"fmt"
)

type Dispatcher struct {
	jobs   chan job
	events map[Name]Listener
}

func NewDispatcher() *Dispatcher {
	d := &Dispatcher{
		jobs:   make(chan job),
		events: make(map[Name]Listener),
	}

	go d.consume()

	return d
}

func (d *Dispatcher) Register(listener Listener, names ...Name) error {
	for _, name := range names {
		if _, ok := d.events[name]; ok {
			return fmt.Errorf("the '%s' event is already registered", name)
		}

		d.events[name] = listener
	}

	return nil
}

func (d *Dispatcher) Dispatch(name Name, event interface{}) error {
	if _, ok := d.events[name]; !ok {
		return fmt.Errorf("the '%s' event is not registered", name)
	}

	d.jobs <- job{eventName: name, eventType: event}

	return nil
}

func (d *Dispatcher) consume() {
	for job := range d.jobs {
		d.events[job.eventName].Listen(job.eventType)
	}
}
internal/pkg/event/event.go
package event

// All custom events names must be of this type.
type Name string

// All custom event types must satisfy this interface.
type Event interface {
	Handle()
}
内部/pkg/event/job.go
package event

// job represents events. When a new event is dispatched, it
// gets tuned into a job and put into `Dispatcher.jobs` channel.
type job struct {
	eventName Name
	eventType interface{}
}
internal/pkg/event/listener.go
package event

// All custom event listeners must satisfy this interface.
type Listener interface {
	Listen(event interface{})
}
internal/user/created.go
package user

import (
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
)

const Created event.Name = "user.created"

type CreatedEvent struct {
	Time time.Time
	ID   string
}

func (e CreatedEvent) Handle() {
	log.Printf("creating: %+v\n", e)
}
internal/user/deleted.go
package user

import (
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
)

const Deleted event.Name = "user.deleted"

type DeletedEvent struct {
	Time time.Time
	ID   string
	Who  string
}

func (e DeletedEvent) Handle() {
	log.Printf("deleting: %+v\n", e)
}
internal/user/updated.go
package user

import (
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
)

const Updated event.Name = "user.updated"

type UpdatedEvent struct {
	Time     time.Time
	ID       string
	Key      string
	OldValue string
	NewValue string
}

func (e UpdatedEvent) Handle() {
	log.Printf("updating: %+v\n", e)
}
internal/user/listener.go
package user

import (
	"log"
)

type Listener struct{}

func (u Listener) Listen(event interface{}) {
	switch event := event.(type) {
	case CreatedEvent:
		event.Handle()
	case UpdatedEvent:
		event.Handle()
	case DeletedEvent:
		event.Handle()
	default:
		log.Printf("registered an invalid user event: %T\n", event)
	}
}

测试

2020/05/27 21:12:01 creating: {Time:2020-05-27 20:12:01.556947 +0000 UTC ID:111}
2020/05/27 21:12:01 the 'user.deleted' event is not registered
2020/05/27 21:12:01 updating: {Time:2020-05-27 20:12:01.557059 +0000 UTC ID:222 Key:name OldValue:inanzzz NewValue:zzznani}
2020/05/27 21:12:01 registered an invalid user event: 
2020/05/27 21:12:01 registered an invalid user event: string
2020/05/27 21:12:01 registered an invalid user event: int
2020/05/27 21:12:01 registered an invalid user event: struct {}
2020/05/27 21:12:01 registered an invalid user event: chan int

例子3--没有通道

只是想让你知道,这是本文章的一个最新补充。与上面两个例子相比,这是一个简化版本。你不需要实现你的监听器。你所要做的就是,创建你的事件并像往常一样注册。这就是全部。如果你在这个例子中加入了通道功能,你可以认为它是首选版本无论怎样,这都是我的首选。

结构

├── cmd
│   └── client
│       └── main.go
├── go.mod
└── internal
    ├── pkg
    │   └── event
    │       ├── dispatcher.go
    │       └── event.go
    └── user
        ├── create.go
        ├── delete.go
        └── update.go

文件

cmd/client/main.go
package main

import (
	"context"
	"log"
	"time"

	"github.com/inanzzz/client/internal/pkg/event"
	"github.com/inanzzz/client/internal/user"
)

func main() {
	// ----------------------------------------------------------------------------------------
	// Register events with dispatcher at application boot
	dispatcher := event.NewDispatcher()
	dispatcher.Register(user.AfterCreate{}, user.BeforeUpdate{}, user.AfterUpdate{})

	// ----------------------------------------------------------------------------------------
	// Dispatch registered events. Valid.
	go func() {
		err := dispatcher.Dispatch(context.Background(), user.AfterCreate{
			Time: time.Now().UTC(),
			ID:   "111",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	go func() {
		err := dispatcher.Dispatch(context.Background(), user.BeforeUpdate{
			Time:     time.Now().UTC(),
			ID:       "222",
			Key:      "name",
			OldValue: "inanzzz",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	go func() {
		err := dispatcher.Dispatch(context.Background(), user.AfterUpdate{
			Time:     time.Now().UTC(),
			ID:       "222",
			Key:      "name",
			NewValue: "zzznani",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	// ----------------------------------------------------------------------------------------
	// Dispatch unregistered event. Error.
	go func() {
		err := dispatcher.Dispatch(context.Background(), user.AfterDelete{
			Time: time.Now().UTC(),
			ID:   "333",
			Who:  "admin",
		})
		if err != nil {
			log.Println(err)
		}
	}()

	select {}
}
internal/pkg/event/dispatcher.go
package event

import (
	"context"
	"fmt"
	"reflect"
)

type Dispatcher struct {
	events []string
}

func NewDispatcher() *Dispatcher {
	return &Dispatcher{}
}

func (d *Dispatcher) Register(events ...Event) {
	for _, v := range events {
		d.events = append(d.events, reflect.TypeOf(v).String())
	}
}

func (d *Dispatcher) Dispatch(ctx context.Context, event Event) error {
	name := reflect.TypeOf(event).String()

	for _, v := range d.events {
		if v == name {
			return event.Handle(ctx)
		}
	}

	return fmt.Errorf("%s is not a registered event", name)
}
internal/pkg/event/event.go
package event

import (
	"context"
)

// All custom events must satisfy this interface.
type Event interface {
	Handle(ctx context.Context) error
}
internal/user/create.go
package user

import (
	"context"
	"log"
	"time"
)

type AfterCreate struct {
	Time time.Time
	ID   string
}

func (event AfterCreate) Handle(ctx context.Context) error {
	log.Printf("after create: %+v\n", event)
	return nil
}
internal/user/delete.go
package user

import (
	"context"
	"log"
	"time"
)

type AfterDelete struct {
	Time time.Time
	ID   string
	Who  string
}

func (event AfterDelete) Handle(ctx context.Context) error {
	log.Printf("after delete: %+v\n", event)
	return nil
}
internal/user/update.go
package user

import (
	"context"
	"log"
	"time"
)

type BeforeUpdate struct {
	Time     time.Time
	ID       string
	Key      string
	OldValue string
}

func (event BeforeUpdate) Handle(ctx context.Context) error {
	log.Printf("before update: %+v\n", event)
	return nil
}

type AfterUpdate struct {
	Time     time.Time
	ID       string
	Key      string
	NewValue string
}

func (event AfterUpdate) Handle(ctx context.Context) error {
	log.Printf("after update: %+v\n", event)
	return nil
}

测试

2020/07/08 15:40:10 after create: {Time:2020-07-08 14:40:10.024471 +0000 UTC ID:111}
2020/07/08 15:40:10 before update: {Time:2020-07-08 14:40:10.024654 +0000 UTC ID:222 Key:name OldValue:inanzzz}
2020/07/08 15:40:10 after update: {Time:2020-07-08 14:40:10.024816 +0000 UTC ID:222 Key:name NewValue:zzznani}
2020/07/08 15:40:10 the 'user.AfterDelete' event is not registered