Events
gFly’s event system implements the Observer Pattern to decouple business logic from side effects. Your application dispatches events after completing operations, and independent listeners respond — synchronously, asynchronously, or via a persistent queue.
go get -u github.com/gflydev/event@v1.0.0
NoteIMPORTANT!
Event and listener files should be placed in directoryinternal/events/<domain>/
Any struct implementing IEvent represents an occurrence in the system. By convention, event names follow the "domain.action" pattern.
// IEvent is the interface that all events must implement.
type IEvent interface {
EventName() string
}
Listeners are generic — the Handle method receives the concrete event type directly, no type assertion needed.
// IListener with T type is the generic interface for typed event listeners.
type IListener[T IEvent] interface {
Handle(event T) error
}
A subscriber groups multiple listeners for a domain and registers them with the dispatcher.
// ISubscriber groups multiple listeners for one or more events.
type ISubscriber interface {
Subscribe(dispatcher *Dispatcher)
}
| Strategy | Blocks caller | Error propagation | Survives restart | Retry |
|---|---|---|---|---|
Dispatch (sync) |
Yes | Yes (stops on first error) | No | No |
DispatchAsync |
No | Logged only | No | No |
| Queued listener | No | Logged only | Yes (Redis) | Via worker |
Use sync when the result affects the HTTP response (e.g., validation, DB writes). Use async for side effects like emails or notifications. Use queued when persistence and retry are required.
internal/
└── events/
├── init.go # Register all domain subscribers
└── user/
├── user_events.go # Event definitions
├── user_listener.go # Listener implementations
└── user_subscriber.go
Define a plain struct and implement IEvent:
// internal/events/user/user_events.go
package user
const EventUserRegistered = "user.registered"
// UserRegistered fires after a new user account is created.
type UserRegistered struct {
UserID int64
Email string
}
func (e UserRegistered) EventName() string { return EventUserRegistered }
// internal/events/user/user_listener.go
package user
import "github.com/gflydev/core/log"
// SendWelcomeEmailListener sends a welcome email after registration.
type SendWelcomeEmailListener struct{}
func (l *SendWelcomeEmailListener) Handle(event UserRegistered) error {
log.Infof("Sending welcome email to %s", event.Email)
// send email logic here
return nil
}
Wire the listeners to the dispatcher inside a Subscriber:
// internal/events/user/user_subscriber.go
package user
import "github.com/gflydev/event"
type Subscriber struct{}
func (s *Subscriber) Subscribe(d *event.Dispatcher) {
event.ListenOn[UserRegistered](d, &SendWelcomeEmailListener{})
}
Register all domain subscribers in internal/events/init.go:
// internal/events/init.go
package events
import (
"github.com/gflydev/event"
userevents "myapp/internal/events/user"
)
func init() {
event.Subscribe(&userevents.Subscriber{})
}
Then auto-load the package via a blank import in your application entry point:
// cmd/web/main.go
import _ "myapp/internal/events"
Call Dispatch or DispatchAsync from your service layer after business operations complete:
import "github.com/gflydev/event"
// Synchronous — blocks until all listeners finish.
if err := event.Dispatch(user.UserRegistered{UserID: u.ID, Email: u.Email}); err != nil {
return err
}
// Asynchronous — returns immediately, listeners run in background.
event.DispatchAsync(user.UserRegistered{UserID: u.ID, Email: u.Email})
For work that must survive server restarts or be retried on failure, implement ITask and dispatch through the queue instead of running the logic directly in the listener’s Handle method. The queue worker processes the task:
./build/artisan queue:run
See the Queue documentation for details on defining and registering tasks.
Event definition (internal/events/order/order_events.go):
package order
type OrderPlaced struct {
OrderID int64
CustomerID int64
Total float64
}
func (e OrderPlaced) EventName() string { return "order.placed" }
Listener (internal/events/order/order_listener.go):
package order
import "github.com/gflydev/core/log"
type NotifyWarehouseListener struct{}
func (l *NotifyWarehouseListener) Handle(event OrderPlaced) error {
log.Infof("Notifying warehouse for order %d", event.OrderID)
return nil
}
Subscriber (internal/events/order/order_subscriber.go):
package order
import "github.com/gflydev/event"
type Subscriber struct{}
func (s *Subscriber) Subscribe(d *event.Dispatcher) {
event.ListenOn[OrderPlaced](d, &NotifyWarehouseListener{})
}
Registration (internal/events/init.go):
package events
import (
"github.com/gflydev/event"
orderevents "myapp/internal/events/order"
)
func init() {
event.Subscribe(&orderevents.Subscriber{})
}
Dispatch from service:
event.DispatchAsync(order.OrderPlaced{
OrderID: o.ID,
CustomerID: o.CustomerID,
Total: o.Total,
})