142 lines
3.7 KiB
Go
142 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.k6n.net/mats/platform/rabbit"
|
|
amqp "github.com/rabbitmq/amqp091-go"
|
|
)
|
|
|
|
// rabbitPublisher is the broker-delivery side used by the outbox relay. It dials
|
|
// lazily and reconnects on failure, so the relay can be started even when the
|
|
// broker is down at boot: messages accumulate durably in the outbox and drain
|
|
// once the broker is reachable.
|
|
type rabbitPublisher struct {
|
|
url string
|
|
mu sync.Mutex
|
|
conn *amqp.Connection
|
|
ch *amqp.Channel
|
|
}
|
|
|
|
func newRabbitPublisher(url string) *rabbitPublisher {
|
|
return &rabbitPublisher{url: url}
|
|
}
|
|
|
|
func (p *rabbitPublisher) connect() error {
|
|
conn, err := rabbit.Connect(p.url, "cart-order-publisher")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ch, err := conn.Channel()
|
|
if err != nil {
|
|
_ = conn.Close()
|
|
return err
|
|
}
|
|
p.conn, p.ch = conn, ch
|
|
return nil
|
|
}
|
|
|
|
func (p *rabbitPublisher) reset() {
|
|
if p.ch != nil {
|
|
_ = p.ch.Close()
|
|
}
|
|
if p.conn != nil {
|
|
_ = p.conn.Close()
|
|
}
|
|
p.ch, p.conn = nil, nil
|
|
}
|
|
|
|
func (p *rabbitPublisher) Publish(exchange, routingKey string, body []byte) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
if p.ch == nil {
|
|
if err := p.connect(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Bound the publish so a half-open/black-holed connection can't wedge the
|
|
// outbox relay indefinitely; on timeout the error path resets and the relay
|
|
// retries on its next tick.
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
err := p.ch.PublishWithContext(ctx, exchange, routingKey, false, false,
|
|
amqp.Publishing{ContentType: "application/json", Body: body})
|
|
if err != nil {
|
|
p.reset() // force a reconnect on the next attempt
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ingestOrderFromQueue records one order from the queue. It uses the exact same
|
|
// idempotent business logic as the HTTP endpoint.
|
|
func ingestOrderFromQueue(ctx context.Context, s *server, body []byte) error {
|
|
var req fromCheckoutReq
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
return err
|
|
}
|
|
if len(req.Lines) == 0 {
|
|
return nil // nothing actionable
|
|
}
|
|
|
|
_, _, _, _, runErr, err := s.createOrderFromCheckoutInternal(ctx, &req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if runErr != nil {
|
|
return runErr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// startOrderIngest connects to AMQP and consumes "order-queue" until ctx is
|
|
// done. The connection is reconnecting — on broker blips the caller re-declares
|
|
// the queue and re-consumes automatically.
|
|
func startOrderIngest(ctx context.Context, amqpURL string, s *server) {
|
|
conn, err := rabbit.Dial(amqpURL, "cart-order-ingest")
|
|
if err != nil {
|
|
s.logger.Warn("order ingest disabled: amqp dial failed", "err", err)
|
|
return
|
|
}
|
|
// Reconnecting consumer: re-declare queue and re-consume on reconnect.
|
|
conn.NotifyOnReconnect(func() {
|
|
ch, err := conn.Channel()
|
|
if err != nil {
|
|
s.logger.Warn("order ingest: channel on reconnect", "err", err)
|
|
return
|
|
}
|
|
q, err := ch.QueueDeclare("order-queue", false, false, false, false, nil)
|
|
if err != nil {
|
|
s.logger.Warn("order ingest: queue declare on reconnect", "err", err)
|
|
_ = ch.Close()
|
|
return
|
|
}
|
|
msgs, err := ch.Consume(q.Name, "order-service", true, false, false, false, nil)
|
|
if err != nil {
|
|
s.logger.Warn("order ingest: consume on reconnect", "err", err)
|
|
_ = ch.Close()
|
|
return
|
|
}
|
|
s.logger.Info("order ingest: consuming from order-queue (reconnect)")
|
|
go func() {
|
|
defer ch.Close()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case d, ok := <-msgs:
|
|
if !ok {
|
|
s.logger.Warn("order ingest: channel closed (will reconnect)")
|
|
return
|
|
}
|
|
if err := ingestOrderFromQueue(ctx, s, d.Body); err != nil {
|
|
s.logger.Error("order queue ingest failed", "err", err)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
})
|
|
}
|