Шина событий для Go приложений
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Alexander Neonxp Kiryukhin 6c8e4800cc
feature: новая типизированная реализация шины событий
- Переписывание шины на generic-модель с маршрутизацией по типу события
- Удаление trie-структуры, путей, wildcard и опций конструктора
- Подписка возвращает типизированный канал Listener[E]
- Отписка через контекст с закрытием канала слушателя
- Non-blocking публикация с буфером в одно событие
- Нулевое значение Bus готово к использованию без конструктора
- Переход на Go 1.27 с использованием generic methods
- Добавление тестов с проверкой конкурентности под race detector
- Обновление README под новый API
2026-08-29 21:07:26 +03:00
.golangci.yml v1.0.0 2026-01-24 17:46:54 +03:00
bus.go feature: новая типизированная реализация шины событий 2026-08-29 21:07:26 +03:00
bus_test.go feature: новая типизированная реализация шины событий 2026-08-29 21:07:26 +03:00
go.mod feature: новая типизированная реализация шины событий 2026-08-29 21:07:26 +03:00
LICENSE v1.0.0 2026-01-24 17:46:54 +03:00
README.md feature: новая типизированная реализация шины событий 2026-08-29 21:07:26 +03:00

eventbus - Асинхронная шина событий для Go

License: GPL
v3

English version below

eventbus - это реализация асинхронной шины событий для языка программирования Go. Библиотека предоставляет механизм публикации и подписки на типизированные события: подписка выполняется по типу события, доставка осуществляется в каналы подписчиков.

Особенности

  • Типобезопасность: Подписка и доставка строго типизированы - слушатель Listener[E] получает только события типа E
  • Любые типы событий: Событием может быть любой тип - структура, указатель, строка, число; типы T и *T - это разные события
  • Zero-value ready: Шина не требует конструктора - нулевое значение eventbus.Bus готово к использованию
  • Асинхронная доставка: События доставляются в каналы подписчиков без блокировки издателя: если буфер слушателя (1 событие) заполнен, событие отбрасывается
  • Отписка через контекст: При отмене контекста подписка удаляется, канал слушателя закрывается
  • Потокобезопасность: Все операции синхронизированы с использованием sync.RWMutex
  • Generic methods: Реализована с использованием generic-методов (Go 1.27+)

Установка

go get go.neonxp.ru/eventbus

Требуется Go 1.27 или новее.

Использование

package main

import (
	"context"
	"fmt"

	"go.neonxp.ru/eventbus"
)

type UserLogin struct {
	User string
}

type UserLogout struct {
	User string
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var bus eventbus.Bus

	// Подписываемся на события по типу
	logins := bus.Subscribe[UserLogin](ctx)
	logouts := bus.Subscribe[UserLogout](ctx)

	go func() {
		for ev := range logins {
			fmt.Printf("User login: %s\n", ev.User)
		}
	}()

	go func() {
		for ev := range logouts {
			fmt.Printf("User logout: %s\n", ev.User)
		}
	}()

	// Публикуем события - тип события определяется автоматически
	bus.Publish(UserLogin{User: "user123"})
	bus.Publish(UserLogout{User: "user123"})
}

Отписка выполняется отменой контекста подписки - канал слушателя закроется:

ctx, cancel := context.WithCancel(context.Background())
ch := bus.Subscribe[UserLogin](ctx)

// ...

cancel()
for range ch {} // канал закроется, цикл завершится

API

func (b *Bus) Subscribe[E any](ctx context.Context) Listener[E]

Подписывается на события типа E. Возвращает канал-слушатель для получения событий. При отмене контекста подписка удаляется, а канал закрывается.

func (b *Bus) Publish(ev any)

Отправляет событие всем подписчикам, подписанным на тип события. Доставка не блокирует издателя: если буфер слушателя заполнен (не прочитано предыдущее событие), событие отбрасывается.

type Listener[E any] chan E

Типизированный канал слушателя.

Нюансы

  • Типы T и *T - это разные события: подписчик Subscribe[T] не получит Publish(&value{}) и наоборот
  • Буфер слушателя - 1 событие. Если подписчик не успевает читать, новые события отбрасываются. Для гарантированной доставки увеличивайте скорость чтения или используйте собственную буферизацию
  • Publish(nil) безопасен: событие не доставляется никому

Лицензия

Этот проект лицензирован в соответствии с GNU General Public License версии 3 (GPLv3). Подробности смотрите в файле LICENSE.

                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2026 Alexander NeonXP Kiryukhin <i@neonxp.ru>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

Ссылки

English Version

eventbus is an implementation of an asynchronous event bus for the Go programming language. The library provides a publish-subscribe mechanism for typed events: subscriptions are made by event type, and events are delivered to the subscribers' channels.

Features

  • Type safety: Subscribing and delivery are strictly typed - a Listener[E] receives only events of type E
  • Any event types: Any type can be an event - struct, pointer, string, number; types T and *T are different events
  • Zero-value ready: The bus does not require a constructor - the zero value of eventbus.Bus is ready to use
  • Asynchronous delivery: Events are delivered to the subscribers' channels without blocking the publisher: if a listener's buffer (1 event) is full, the event is dropped
  • Unsubscribe via context: When the context is canceled, the subscription is removed and the listener channel is closed
  • Thread-safe: All operations are synchronized using sync.RWMutex
  • Generic methods: Implemented using generic methods (Go 1.27+)

Installation

go get go.neonxp.ru/eventbus

Requires Go 1.27 or newer.

Usage

package main

import (
	"context"
	"fmt"

	"go.neonxp.ru/eventbus"
)

type UserLogin struct {
	User string
}

type UserLogout struct {
	User string
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	var bus eventbus.Bus

	// Subscribe to events by type
	logins := bus.Subscribe[UserLogin](ctx)
	logouts := bus.Subscribe[UserLogout](ctx)

	go func() {
		for ev := range logins {
			fmt.Printf("User login: %s\n", ev.User)
		}
	}()

	go func() {
		for ev := range logouts {
			fmt.Printf("User logout: %s\n", ev.User)
		}
	}()

	// Publish events - the event type is inferred automatically
	bus.Publish(UserLogin{User: "user123"})
	bus.Publish(UserLogout{User: "user123"})
}

Unsubscribe by canceling the subscription context - the listener channel will be closed:

ctx, cancel := context.WithCancel(context.Background())
ch := bus.Subscribe[UserLogin](ctx)

// ...

cancel()
for range ch {} // the channel will be closed, the loop will end

API

func (b *Bus) Subscribe[E any](ctx context.Context) Listener[E]

Subscribes to events of type E. Returns a listener channel for receiving events. When the context is canceled, the subscription is removed and the channel is closed.

func (b *Bus) Publish(ev any)

Publishes an event to all subscribers of the event type. Delivery does not block the publisher: if a listener's buffer is full (the previous event has not been read), the event is dropped.

type Listener[E any] chan E

A typed listener channel.

Caveats

  • Types T and *T are different events: a Subscribe[T] subscriber will not receive Publish(&value{}) and vice versa
  • The listener buffer is 1 event. If a subscriber does not keep up with reading, new events are dropped. For guaranteed delivery, read faster or use your own buffering
  • Publish(nil) is safe: no one receives the event

License

This project is licensed under the GNU General Public License version 3 (GPLv3). See the LICENSE file for details.

                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2026 Alexander NeonXP Kiryukhin <i@neonxp.ru>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.