Ответ
В Go нет встроенной поддержки триггеров как в SQL, но их можно эмулировать с помощью методов-наблюдателей или паттерна "Наблюдатель" (Observer), который позволяет объектам уведомлять другие объекты об изменениях своего состояния.
Пример (базовая реализация паттерна Observer):
package main
import "fmt"
// Trigger определяет тип функции-наблюдателя, которая будет вызываться при изменении.
// Она принимает старое и новое значение.
type Trigger func(old, new interface{})
// Observable представляет объект, за которым можно наблюдать.
type Observable struct {
value interface{}
triggers []Trigger
}
// Set устанавливает новое значение и уведомляет всех наблюдателей.
func (o *Observable) Set(value interface{}) {
old := o.value
o.value = value
// Вызываем все зарегистрированные триггеры
for _, trigger := range o.triggers {
trigger(old, value)
}
}
// AddTrigger добавляет новую функцию-наблюдатель к списку.
func (o *Observable) AddTrigger(t Trigger) {
o.triggers = append(o.triggers, t)
}
func main() {
obj := Observable{}
// Добавляем первый наблюдатель
obj.AddTrigger(func(old, new interface{}) {
fmt.Printf("Наблюдатель 1: Значение изменилось с %v на %vn", old, new)
})
// Добавляем второй наблюдатель
obj.AddTrigger(func(old, new interface{}) {
fmt.Printf("Наблюдатель 2: Обновление! Новое значение: %vn", new)
})
fmt.Println("Устанавливаем значение 42...")
obj.Set(42) // Вызовет оба триггера
fmt.Println("nУстанавливаем значение 'hello'...")
obj.Set("hello") // Вызовет оба триггера
fmt.Println("nУстанавливаем значение 123.45...")
obj.Set(123.45) // Вызовет оба триггера
}
Пояснения и улучшения:
- Эта базовая реализация использует
interface{}, что позволяет передавать любые типы данных. Однако это требует ручной проверки типов (с помощью утверждений типаvalue.(int)) при их использовании внутри функцийTrigger. - В Go 1.18+ для более типобезопасных решений можно использовать дженерики (generics), чтобы определить
ObservableиTriggerдля конкретного типа данных, избегая необходимости вinterface{}и утверждениях типа. - Для более сложных сценариев, таких как асинхронная обработка событий, отписка от событий, или когда требуется более сложная маршрутизация событий, можно использовать:
- Event Bus: Специализированные библиотеки, такие как
github.com/asaskevich/EventBus, предоставляют более мощные механизмы для публикации и подписки на события. - Системы очередей сообщений: Для распределенных систем или когда требуется надежная доставка и персистентность событий, используются внешние брокеры сообщений, такие как Kafka, RabbitMQ или NATS.
- Event Bus: Специализированные библиотеки, такие как
Ответ 18+ 🔞
Ах ты ж ёпта, ну и тема подъехала! Слушай, в Го, блядь, нету этих ваших SQL-ных триггеров, которые прям так и норовят в таблицу влезть. Но наши русские программисты не лыком шиты — можно накрутить наблюдателей, по-нашему, по-пацански.
Вот смотри, как это примерно выглядит, если на пальцах:
package main
import "fmt"
// Trigger — это типа наш стукач, который докладывает, когда что-то поменялось.
type Trigger func(old, new interface{})
// Observable — это объект, за которым можно следить, как за соседом через забор.
type Observable struct {
value interface{}
triggers []Trigger
}
// Set — устанавливаем новое значение и сразу же начинаем стучать всем наблюдателям.
func (o *Observable) Set(value interface{}) {
old := o.value
o.value = value
// А теперь, сука, оповещаем всех, кто подписался!
for _, trigger := range o.triggers {
trigger(old, value)
}
}
// AddTrigger — подписываем нового папарацци на наши изменения.
func (o *Observable) AddTrigger(t Trigger) {
o.triggers = append(o.triggers, t)
}
func main() {
obj := Observable{}
// Первый наблюдатель — просто любопытный.
obj.AddTrigger(func(old, new interface{}) {
fmt.Printf("Наблюдатель 1: Значение изменилось с %v на %vn", old, new)
})
// Второй — уже с претензией.
obj.AddTrigger(func(old, new interface{}) {
fmt.Printf("Наблюдатель 2: Обновление! Новое значение: %vn", new)
})
fmt.Println("Устанавливаем значение 42...")
obj.Set(42) // Оба триггера сработают, как штык
fmt.Println("nУстанавливаем значение 'hello'...")
obj.Set("hello") // Опять эти два халявщика получат уведомление
fmt.Println("nУстанавливаем значение 123.45...")
obj.Set(123.45) // Ну вы поняли, пиздец
}
А теперь, блядь, пояснения для тех, кто в танке:
- Эта реализация использует
interface{}, что, конечно, даёт гибкость, но потом придётся в коде разбираться, что за тип пришёл — как в тёмной комнате искать чёрную кошку. Можно, конечно, делать проверки типов, но это уже как повезёт. - Если ты на Go 1.18+, то, ёпта, есть дженерики — можно сделать всё типобезопасно, без этих вот интерфейсных плясок с бубном.
- А если тебе нужно что-то посерьёзнее — асинхронность, отписка, чтобы не засирали память — то тут уже можно прикрутить:
- Event Bus — готовые библиотеки, вроде
github.com/asaskevich/EventBus, там уже всё разложено по полочкам. - Очереди сообщений — если система распределённая и нужно, чтобы сообщения точно дошли, даже если сервер упал. Тут уже подключаем Kafka, RabbitMQ или NATS — это как выбрать оружие для апокалипсиса.
- Event Bus — готовые библиотеки, вроде
Короче, варианты есть, главное — не проёбываться с архитектурой, а то потом будет, как в том анекдоте: «А кто у вас всё время серит на крыше?».