138 lines
3.6 KiB
Go
138 lines
3.6 KiB
Go
package eventbus
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
amqp "github.com/rabbitmq/amqp091-go"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Event représente un événement métier dans le système
|
|
// Suit le pattern défini dans ORIGIN_MASTER_ARCHITECTURE.md
|
|
type Event struct {
|
|
EventID string `json:"event_id"`
|
|
EventType string `json:"event_type"` // format: {domain}.{entity}.{action}.{version}
|
|
AggregateID string `json:"aggregate_id"`
|
|
AggregateType string `json:"aggregate_type"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Version int `json:"version"`
|
|
Data map[string]interface{} `json:"data"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
// RabbitMQClient gère la connexion et publication d'événements vers RabbitMQ
|
|
// Implémentation minimale alignée avec ORIGIN pour Phase 1
|
|
type RabbitMQClient struct {
|
|
conn *amqp.Connection
|
|
channel *amqp.Channel
|
|
exchange string
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewRabbitMQClient crée un nouveau client RabbitMQ
|
|
// url format: amqp://user:pass@host:5672/
|
|
func NewRabbitMQClient(url, exchange string, logger *zap.Logger) (*RabbitMQClient, error) {
|
|
conn, err := amqp.Dial(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to connect to RabbitMQ: %w", err)
|
|
}
|
|
|
|
channel, err := conn.Channel()
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, fmt.Errorf("failed to open channel: %w", err)
|
|
}
|
|
|
|
// Déclarer l'exchange (topic type pour routing flexible)
|
|
err = channel.ExchangeDeclare(
|
|
exchange, // name
|
|
"topic", // type
|
|
true, // durable
|
|
false, // auto-deleted
|
|
false, // internal
|
|
false, // no-wait
|
|
nil, // arguments
|
|
)
|
|
if err != nil {
|
|
channel.Close()
|
|
conn.Close()
|
|
return nil, fmt.Errorf("failed to declare exchange: %w", err)
|
|
}
|
|
|
|
logger.Info("RabbitMQ client initialized",
|
|
zap.String("exchange", exchange),
|
|
zap.String("url", url),
|
|
)
|
|
|
|
return &RabbitMQClient{
|
|
conn: conn,
|
|
channel: channel,
|
|
exchange: exchange,
|
|
logger: logger,
|
|
}, nil
|
|
}
|
|
|
|
// PublishEvent publie un événement sur RabbitMQ
|
|
// routingKey format: {domain}.{entity}.{action} (ex: "auth.user.registered")
|
|
func (c *RabbitMQClient) PublishEvent(ctx context.Context, event *Event) error {
|
|
body, err := json.Marshal(event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal event: %w", err)
|
|
}
|
|
|
|
err = c.channel.PublishWithContext(
|
|
ctx,
|
|
c.exchange, // exchange
|
|
event.EventType, // routing key
|
|
false, // mandatory
|
|
false, // immediate
|
|
amqp.Publishing{
|
|
ContentType: "application/json",
|
|
DeliveryMode: amqp.Persistent, // messages persistent
|
|
Timestamp: event.Timestamp,
|
|
MessageId: event.EventID,
|
|
Type: event.EventType,
|
|
Body: body,
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
c.logger.Error("Failed to publish event",
|
|
zap.Error(err),
|
|
zap.String("event_type", event.EventType),
|
|
zap.String("event_id", event.EventID),
|
|
)
|
|
return fmt.Errorf("failed to publish event: %w", err)
|
|
}
|
|
|
|
c.logger.Debug("Event published",
|
|
zap.String("event_type", event.EventType),
|
|
zap.String("event_id", event.EventID),
|
|
zap.String("aggregate_id", event.AggregateID),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close ferme proprement la connexion RabbitMQ
|
|
func (c *RabbitMQClient) Close() error {
|
|
if c.channel != nil {
|
|
c.channel.Close()
|
|
}
|
|
if c.conn != nil {
|
|
c.conn.Close()
|
|
}
|
|
c.logger.Info("RabbitMQ client closed")
|
|
return nil
|
|
}
|
|
|
|
// HealthCheck vérifie si la connexion RabbitMQ est active
|
|
func (c *RabbitMQClient) HealthCheck() error {
|
|
if c.conn == nil || c.conn.IsClosed() {
|
|
return fmt.Errorf("RabbitMQ connection is closed")
|
|
}
|
|
return nil
|
|
}
|