mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-10-30 14:03:45 +03:00
[hw-6] add notifier service, kafka
This commit is contained in:
7
loms/internal/infra/messaging/kafka/order_event.go
Normal file
7
loms/internal/infra/messaging/kafka/order_event.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package kafka
|
||||
|
||||
type OrderEvent struct {
|
||||
OrderID int64 `json:"order_id"`
|
||||
Status string `json:"status"`
|
||||
Moment string `json:"moment"`
|
||||
}
|
||||
110
loms/internal/infra/messaging/kafka/producer.go
Normal file
110
loms/internal/infra/messaging/kafka/producer.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package kafka
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"route256/loms/internal/domain/entity"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
pb "route256/pkg/api/loms/v1"
|
||||
)
|
||||
|
||||
type statusProducer struct {
|
||||
topic string
|
||||
producer sarama.AsyncProducer
|
||||
}
|
||||
|
||||
func NewStatusProducer(topic string, producer sarama.AsyncProducer) (*statusProducer, error) {
|
||||
p := &statusProducer{topic: topic, producer: producer}
|
||||
go p.runCallbacks()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func mapOrderStatus(pbStatus string) (string, error) {
|
||||
switch pbStatus {
|
||||
case pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String():
|
||||
return "awaiting payment", nil
|
||||
case pb.OrderStatus_ORDER_STATUS_CANCELLED.String():
|
||||
return "cancelled", nil
|
||||
case pb.OrderStatus_ORDER_STATUS_FAILED.String():
|
||||
return "failed", nil
|
||||
case pb.OrderStatus_ORDER_STATUS_NEW.String():
|
||||
return "new", nil
|
||||
case pb.OrderStatus_ORDER_STATUS_PAYED.String():
|
||||
return "payed", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unexpected OrderStatus: %v", pbStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *statusProducer) runCallbacks() {
|
||||
for {
|
||||
select {
|
||||
case err, ok := <-p.producer.Errors():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
log.Error().Err(err).Msgf("kafka publish error")
|
||||
case _, ok := <-p.producer.Successes():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: add msg metrics (latency/partition/offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *statusProducer) Send(ctx context.Context, id entity.ID, status string) error {
|
||||
log.Debug().Msgf("sending event for id: %d; status: %s", id, status)
|
||||
|
||||
newStatus, err := mapOrderStatus(status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
evt := OrderEvent{
|
||||
OrderID: int64(id),
|
||||
Status: newStatus,
|
||||
Moment: time.Now().UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
|
||||
value, err := json.Marshal(evt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal event: %w", err)
|
||||
}
|
||||
|
||||
return p.SendRaw(ctx, id, value)
|
||||
}
|
||||
|
||||
func (p *statusProducer) SendRaw(ctx context.Context, id entity.ID, value []byte) error {
|
||||
if len(value) == 0 {
|
||||
return fmt.Errorf("empty message value")
|
||||
}
|
||||
|
||||
msg := &sarama.ProducerMessage{
|
||||
Topic: p.topic,
|
||||
Key: sarama.StringEncoder(strconv.FormatInt(int64(id), 10)),
|
||||
Value: sarama.ByteEncoder(value),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
select {
|
||||
case p.producer.Input() <- msg:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *statusProducer) Close() error {
|
||||
return p.producer.Close()
|
||||
}
|
||||
54
loms/internal/infra/messaging/kafka_outbox/dispatcher.go
Normal file
54
loms/internal/infra/messaging/kafka_outbox/dispatcher.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package kafkaoutbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"route256/loms/internal/domain/entity"
|
||||
"route256/loms/internal/domain/repository/outbox"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type producer interface {
|
||||
SendRaw(ctx context.Context, id entity.ID, value []byte) error
|
||||
}
|
||||
|
||||
type outboxRepo interface {
|
||||
WithNewEvents(ctx context.Context, limit int, h func(c context.Context, e outbox.Event) error) error
|
||||
}
|
||||
|
||||
type Dispatcher struct {
|
||||
repo outboxRepo
|
||||
producer producer
|
||||
limit int
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
func NewDispatcher(repo outboxRepo, producer producer, batch int, pollEvery time.Duration) *Dispatcher {
|
||||
return &Dispatcher{
|
||||
repo: repo,
|
||||
producer: producer,
|
||||
limit: batch,
|
||||
interval: pollEvery,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) Run(ctx context.Context) {
|
||||
t := time.NewTicker(d.interval)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
if err := d.repo.WithNewEvents(ctx, d.limit, func(c context.Context, e outbox.Event) error {
|
||||
return d.producer.SendRaw(c, e.OrderID, e.Payload)
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("d.repo.WithNewEvents")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user