Implement notifications service with shared messaging and email drivers (#36)
This commit is contained in:
1 parent
36277b6c45
commit
5c2d36281b
54 files changed
+3746
-6
No files matched your search
@@ -0,0 +1,108 @@
|
||||
// Package bus connects the notification outbox to the shared messaging runtime.
|
||||
package bus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/notifications/model"
|
||||
merrors "git.sendico.io/tech/sxchnge/shared/errors"
|
||||
mlogger "git.sendico.io/tech/sxchnge/shared/logging"
|
||||
"git.sendico.io/tech/sxchnge/shared/messaging/reliable"
|
||||
)
|
||||
|
||||
const subject = "sxchnge.notifications.email"
|
||||
|
||||
type Outbox interface {
|
||||
Due(context.Context, time.Time) ([]model.Notification, error)
|
||||
Recover(context.Context, time.Time) error
|
||||
}
|
||||
type Handler interface {
|
||||
Dispatch(context.Context, string) (bool, error)
|
||||
}
|
||||
type Bus struct {
|
||||
transport reliable.Transport
|
||||
subscription reliable.Subscription
|
||||
outbox Outbox
|
||||
handler Handler
|
||||
logger mlogger.Logger
|
||||
}
|
||||
|
||||
func New(ctx context.Context, transport reliable.Transport, admin reliable.Admin, outbox Outbox, handler Handler, logger mlogger.Logger) (*Bus, error) {
|
||||
settings := reliable.DurableSettings{Enabled: true, Stream: reliable.DurableStreamSettings{Name: "SXCHNGE_NOTIFICATIONS", Subjects: []string{subject}, Storage: "file", MaxAgeHours: 48, Replicas: 1}, Provisioning: reliable.DurableProvisioningSettings{Mode: reliable.DurableProvisioningApp, Environment: reliable.DurableEnvironmentNonProduction}}
|
||||
consumer := reliable.DurableConsumerSettings{Durable: "email", Subject: subject, AckWaitSeconds: 60, MaxDeliver: -1, DeliverPolicy: reliable.DurableDeliverAll}
|
||||
if err := admin.EnsureConsumerResources(ctx, settings, consumer); err != nil {
|
||||
return nil, merrors.OpaqueWrap(merrors.ErrServiceUnavailable, err, "configure notification messaging")
|
||||
}
|
||||
subscription, err := transport.OpenConsumer(ctx, settings, consumer)
|
||||
if err != nil {
|
||||
return nil, merrors.OpaqueWrap(merrors.ErrServiceUnavailable, err, "subscribe notification worker")
|
||||
}
|
||||
return &Bus{transport, subscription, outbox, handler, mlogger.Named(logger, "notifications", "bus")}, nil
|
||||
}
|
||||
func (b *Bus) Close() error { return b.subscription.Close() }
|
||||
func (b *Bus) PublishDue(ctx context.Context) error {
|
||||
if err := b.outbox.Recover(ctx, time.Now().UTC()); err != nil {
|
||||
return err
|
||||
}
|
||||
jobs, err := b.outbox.Due(ctx, time.Now().UTC())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, job := range jobs {
|
||||
err = b.transport.Publish(ctx, reliable.Record{Subject: subject, Payload: []byte(job.ID.Hex()), MessageID: job.ID.Hex() + "-" + strconv.FormatUint(uint64(job.Attempts), 10)})
|
||||
if err != nil {
|
||||
return merrors.OpaqueWrap(merrors.ErrServiceUnavailable, err, "publish notification job")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (b *Bus) RunPublisher(ctx context.Context) {
|
||||
for ctx.Err() == nil {
|
||||
op, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
err := b.PublishDue(op)
|
||||
cancel()
|
||||
if err != nil && ctx.Err() == nil {
|
||||
b.logger.Warn("Notification outbox publication failed")
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
func (b *Bus) RunWorker(ctx context.Context) {
|
||||
for ctx.Err() == nil {
|
||||
deliveries, err := b.subscription.Fetch(ctx, 1, time.Second)
|
||||
if err != nil {
|
||||
if ctx.Err() == nil {
|
||||
b.logger.Warn("Notification job fetch failed")
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, delivery := range deliveries {
|
||||
payload := delivery.Record().Payload
|
||||
if len(payload) == 24 {
|
||||
if _, err = b.handler.Dispatch(ctx, string(payload)); err != nil {
|
||||
b.logger.Warn("Notification job processing failed")
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
b.logger.Warn("Malformed notification job discarded")
|
||||
}
|
||||
ackCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
|
||||
err = delivery.Ack(ackCtx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
b.logger.Warn("Notification acknowledgement failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user