Skip to main content
gFly v1.18.1
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

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.

Installation

go get -u github.com/gflydev/event@v1.0.0
Note
IMPORTANT!
Event and listener files should be placed in directory internal/events/<domain>/

Core Interfaces

IEvent

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
}

IListener[T]

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
}

ISubscriber

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)
}

Dispatch Strategies

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.

Directory Structure

internal/
└── events/
    ├── init.go              # Register all domain subscribers
    └── user/
        ├── user_events.go   # Event definitions
        ├── user_listener.go # Listener implementations
        └── user_subscriber.go

Creating an Event

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 }

Creating a Listener

// 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
}

Creating a Subscriber

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{})
}

Registering Subscribers

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"

Dispatching 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})

Queued Listeners

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.

Full Example

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,
})