Skip to content

Commit 5c7a61c

Browse files
authored
Merge pull request #167 from ilramdhan/be/poy-rm-v2-chain-202607
fix(finance): retry RabbitMQ connect at startup and self-heal on drop
2 parents 9eeef74 + f637a53 commit 5c7a61c

9 files changed

Lines changed: 420 additions & 41 deletions

File tree

services/finance/cmd/server/main.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func run() error { //nolint:gocognit,gocyclo // linear service wiring / DI setup
120120
}
121121

122122
// Setup RabbitMQ (optional - graceful degradation for publisher)
123-
rmqAdapter, costJobPub, closeRabbitMQ := setupRabbitMQ(cfg)
123+
rmqAdapter, costJobPub, closeRabbitMQ := setupRabbitMQ(ctx, cfg)
124124
defer closeRabbitMQ()
125125

126126
// Wrap into explicit interface values so that when RabbitMQ is unavailable
@@ -981,13 +981,27 @@ func startServers(ctx context.Context, cfg *config.Config,
981981
// graceful degradation). Returns the job-publisher adapter (oracle sync / RM
982982
// cost), the cost-calc job-trigger publisher (orchestrator hand-off), and a
983983
// close function for graceful shutdown.
984-
func setupRabbitMQ(cfg *config.Config) (*rabbitmq.JobPublisherAdapter, *rabbitmq.CostJobPublisher, func()) {
985-
rmqConn, err := rabbitmq.NewConnection(cfg.RabbitMQ, log.Logger)
984+
func setupRabbitMQ(ctx context.Context, cfg *config.Config) (*rabbitmq.JobPublisherAdapter, *rabbitmq.CostJobPublisher, func()) {
985+
const connectAttempts = 3
986+
987+
rmqConn, err := rabbitmq.NewConnectionWithRetry(cfg.RabbitMQ, log.Logger, connectAttempts)
986988
if err != nil {
987-
log.Warn().Err(err).Msg("Failed to connect to RabbitMQ, sync trigger will fail")
989+
log.Warn().
990+
Err(err).
991+
Str("url", rabbitmq.SanitizeURL(cfg.RabbitMQ.URL)).
992+
Int("attempts", connectAttempts).
993+
Msg("Failed to connect to RabbitMQ after all attempts; the service will still start, " +
994+
"but RM cost recalculate/export, cost sheet export, oracle sync trigger and " +
995+
"multi-product calc scopes will fail until RabbitMQ is reachable and finance is restarted")
988996
return nil, nil, func() {}
989997
}
990998

999+
// Supervise redials on connection loss and swaps the live channel in place,
1000+
// so publishers recover mid-life instead of staying broken for the pod's
1001+
// whole lifetime. Bound to the root ctx: cancelled on shutdown.
1002+
superviseCtx, stopSupervisor := context.WithCancel(ctx)
1003+
go rmqConn.Supervise(superviseCtx)
1004+
9911005
publisher := rabbitmq.NewPublisher(rmqConn, log.Logger)
9921006
adapter := rabbitmq.NewJobPublisherAdapter(publisher, log.Logger)
9931007

@@ -998,6 +1012,7 @@ func setupRabbitMQ(cfg *config.Config) (*rabbitmq.JobPublisherAdapter, *rabbitmq
9981012
}
9991013

10001014
closeFunc := func() {
1015+
stopSupervisor()
10011016
if closeErr := rmqConn.Close(); closeErr != nil {
10021017
log.Warn().Err(closeErr).Msg("Failed to close RabbitMQ connection")
10031018
}

services/finance/cmd/worker/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func run() error { //nolint:gocognit,gocyclo // linear setup function
8080
}
8181

8282
// Setup RabbitMQ.
83-
rmqConn, err := rabbitmq.NewConnection(cfg.RabbitMQ, log.Logger)
83+
rmqConn, err := rabbitmq.NewConnectionWithRetry(cfg.RabbitMQ, log.Logger, 3)
8484
if err != nil {
8585
return err
8686
}

services/finance/internal/application/costsheet/request_export_handler.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import (
1414
"github.com/mutugading/goapps-backend/services/finance/internal/domain/job"
1515
)
1616

17+
// ErrPublisherUnavailable is returned when the finance service has no working
18+
// RabbitMQ publisher, so no export job can be queued.
19+
var ErrPublisherUnavailable = errors.New("message queue unavailable: RabbitMQ not connected " +
20+
"(finance service could not reach the broker at startup; check RabbitMQ health and restart the finance service)")
21+
1722
// maxExportProducts caps how many products a single export job renders.
1823
// Filter resolutions above this no longer truncate — they fan out into a
1924
// parent job plus N ≤maxExportProducts-sized child jobs (see Handle), each
@@ -212,7 +217,7 @@ func chunkIDs(ids []int64, size int) [][]int64 {
212217
// validate checks the fields Handle needs before doing any work.
213218
func (h *RequestExportHandler) validate(cmd RequestExportCommand) error {
214219
if h.publisher == nil {
215-
return fmt.Errorf("message queue unavailable: RabbitMQ not connected")
220+
return ErrPublisherUnavailable
216221
}
217222
if cmd.Period == "" {
218223
return fmt.Errorf("period is required")

services/finance/internal/application/oraclesync/trigger_handler.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ package oraclesync
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"time"
78

89
"github.com/mutugading/goapps-backend/services/finance/internal/domain/job"
910
)
1011

12+
// ErrPublisherUnavailable is returned when the finance service has no working
13+
// RabbitMQ publisher, so no sync job can be queued.
14+
var ErrPublisherUnavailable = errors.New("message queue unavailable: RabbitMQ not connected " +
15+
"(finance service could not reach the broker at startup; check RabbitMQ health and restart the finance service)")
16+
1117
// TriggerCommand holds the input for triggering a sync job.
1218
type TriggerCommand struct {
1319
Period string
@@ -41,7 +47,7 @@ func NewTriggerHandler(jobRepo job.Repository, publisher JobPublisher) *TriggerH
4147
// Handle creates a job execution and publishes it to the queue.
4248
func (h *TriggerHandler) Handle(ctx context.Context, cmd TriggerCommand) (*TriggerResult, error) {
4349
if h.publisher == nil {
44-
return nil, fmt.Errorf("message queue unavailable: RabbitMQ not connected")
50+
return nil, ErrPublisherUnavailable
4551
}
4652

4753
// Resolve period if not provided.

services/finance/internal/application/rmcost/request_export_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func NewRequestExportHandler(jobRepo job.Repository, publisher ExportJobPublishe
4343
// Handle creates a job_execution row and publishes the message to RabbitMQ.
4444
func (h *RequestExportHandler) Handle(ctx context.Context, cmd RequestExportCommand) (*RequestExportResult, error) {
4545
if h.publisher == nil {
46-
return nil, fmt.Errorf("message queue unavailable: RabbitMQ not connected")
46+
return nil, ErrPublisherUnavailable
4747
}
4848
if cmd.Period == "" {
4949
return nil, fmt.Errorf("period is required")

services/finance/internal/application/rmcost/trigger_handler.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package rmcost
33

44
import (
55
"context"
6+
"errors"
67
"fmt"
78
"time"
89

@@ -13,6 +14,12 @@ import (
1314
"github.com/mutugading/goapps-backend/services/finance/internal/domain/rmcost"
1415
)
1516

17+
// ErrPublisherUnavailable is returned when the finance service has no working
18+
// RabbitMQ publisher, so no job can be queued. The "RabbitMQ not connected"
19+
// substring is asserted on by handlers_test.go — keep it.
20+
var ErrPublisherUnavailable = errors.New("message queue unavailable: RabbitMQ not connected " +
21+
"(finance service could not reach the broker at startup; check RabbitMQ health and restart the finance service)")
22+
1623
// TriggerReason identifies why the calculation was requested. Maps to
1724
// rmcost.HistoryTriggerReason on the worker side via the job params JSON.
1825
type TriggerReason string
@@ -70,7 +77,7 @@ func NewTriggerHandler(jobRepo job.Repository, publisher JobPublisher) *TriggerH
7077
// so operators see the error instead of a permanently-QUEUED row.
7178
func (h *TriggerHandler) Handle(ctx context.Context, cmd TriggerCommand) (*TriggerResult, error) {
7279
if h.publisher == nil {
73-
return nil, fmt.Errorf("message queue unavailable: RabbitMQ not connected")
80+
return nil, ErrPublisherUnavailable
7481
}
7582
if cmd.CreatedBy == "" {
7683
return nil, rmcost.ErrEmptyCreatedBy

0 commit comments

Comments
 (0)