125 lines
2.9 KiB
Go
125 lines
2.9 KiB
Go
package gochannel
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/ThreeDotsLabs/watermill"
|
|
"github.com/ThreeDotsLabs/watermill/message"
|
|
)
|
|
|
|
// FanOut is a component that receives messages from a topic and passes them
|
|
// to all subscribers. In effect, messages are "multiplied".
|
|
//
|
|
// A typical use case for using FanOut is having one external subscription and multiple workers
|
|
// inside the process.
|
|
//
|
|
// You need to call AddSubscription method for all topics that you want to listen to.
|
|
// This needs to be done *before* starting the FanOut.
|
|
//
|
|
// FanOut exposes the standard Subscriber interface.
|
|
type FanOut struct {
|
|
internalPubSub *GoChannel
|
|
internalRouter *message.Router
|
|
|
|
subscriber message.Subscriber
|
|
|
|
logger watermill.LoggerAdapter
|
|
|
|
subscribedTopics map[string]struct{}
|
|
subscribedLock sync.Mutex
|
|
}
|
|
|
|
// NewFanOut creates a new FanOut.
|
|
func NewFanOut(
|
|
subscriber message.Subscriber,
|
|
logger watermill.LoggerAdapter,
|
|
) (*FanOut, error) {
|
|
if subscriber == nil {
|
|
return nil, errors.New("missing subscriber")
|
|
}
|
|
if logger == nil {
|
|
logger = watermill.NopLogger{}
|
|
}
|
|
|
|
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &FanOut{
|
|
internalPubSub: NewGoChannel(Config{}, logger),
|
|
internalRouter: router,
|
|
|
|
subscriber: subscriber,
|
|
|
|
logger: logger,
|
|
|
|
subscribedTopics: map[string]struct{}{},
|
|
}, nil
|
|
}
|
|
|
|
// AddSubscription add an internal subscription for the given topic.
|
|
// You need to call this method with all topics that you want to listen to, before the FanOut is started.
|
|
// AddSubscription is idempotent.
|
|
func (f *FanOut) AddSubscription(topic string) {
|
|
f.subscribedLock.Lock()
|
|
defer f.subscribedLock.Unlock()
|
|
|
|
_, ok := f.subscribedTopics[topic]
|
|
if ok {
|
|
// Subscription already exists
|
|
return
|
|
}
|
|
|
|
f.logger.Trace("Adding fan-out subscription for topic", watermill.LogFields{
|
|
"topic": topic,
|
|
})
|
|
|
|
f.internalRouter.AddHandler(
|
|
fmt.Sprintf("fanout-%s", topic),
|
|
topic,
|
|
f.subscriber,
|
|
topic,
|
|
f.internalPubSub,
|
|
message.PassthroughHandler,
|
|
)
|
|
|
|
f.subscribedTopics[topic] = struct{}{}
|
|
}
|
|
|
|
// Run runs the FanOut.
|
|
func (f *FanOut) Run(ctx context.Context) error {
|
|
return f.internalRouter.Run(ctx)
|
|
}
|
|
|
|
// Running is closed when FanOut is running.
|
|
func (f *FanOut) Running() chan struct{} {
|
|
return f.internalRouter.Running()
|
|
}
|
|
|
|
func (f *FanOut) IsClosed() bool {
|
|
return f.internalRouter.IsClosed()
|
|
}
|
|
|
|
// Subscribe starts subscription to the FanOut's internal Pub/Sub.
|
|
func (f *FanOut) Subscribe(ctx context.Context, topic string) (<-chan *message.Message, error) {
|
|
return f.internalPubSub.Subscribe(ctx, topic)
|
|
}
|
|
|
|
// Close closes the FanOut's internal Pub/Sub.
|
|
func (f *FanOut) Close() error {
|
|
var err error
|
|
|
|
if routerCloseErr := f.internalRouter.Close(); routerCloseErr != nil {
|
|
err = errors.Join(err, routerCloseErr)
|
|
}
|
|
if internalPubSubCloseErr := f.internalPubSub.Close(); internalPubSubCloseErr != nil {
|
|
err = errors.Join(err, internalPubSubCloseErr)
|
|
}
|
|
|
|
return err
|
|
}
|