Refactor delivery targets to a Target interface (closes #77)
All checks were successful
check / check (push) Successful in 5s
All checks were successful
check / check (push) Successful in 5s
Each target TYPE is now an implementation of a Target interface, dispatched from a registry in processDelivery instead of a type switch on TargetType. Every target owns its full delivery, including durable retries. - Target.Deliver receives the context, the per-webhook DB, the Delivery, the attempt Task, and a Scheduler for durable re-enqueue (the existing timer + retry queue). The target makes one attempt, records the DeliveryResult, updates DeliveryStatus, and — for retry targets — decides whether to retry, computes its own backoff, gates with its own circuit breaker, and reschedules via the Scheduler. - httpTarget and slackTarget share a retry core (retry, backoff, circuit breaker). database and log targets are fire-and-forget. - Slack retry/breaker is gated on MaxRetries: 0 stays fire-and-forget (existing Slack targets unchanged), >0 gets retry + backoff + breaker on the shared core. - The engine keeps only the worker pool, queue/channels, restart recovery/sweep, the recordResult/updateDeliveryStatus helpers, and ScheduleRetry. Recovery/sweep hand each orphaned retrying delivery back to its target to recompute the backoff. - The log target logs the entire inbound webhook: full body and headers, method, content type, and the webhook and entrypoint ids (supersedes the smaller log-summary work). - Task gains EntrypointID, populated in the webhook handler, the recovery-task builder, and buildEventFromTask. Behaviour is preserved: existing delivery tests pass with their export_test wrappers re-pointed at the new targets; new pure Deliver tests cover the log full-content output and the gated Slack retry path.
This commit is contained in:
@@ -39,37 +39,50 @@ func ExportTruncate(s string, maxLen int) string {
|
||||
return truncate(s, maxLen)
|
||||
}
|
||||
|
||||
// ExportDeliverHTTP exposes deliverHTTP for testing.
|
||||
// ExportDeliverHTTP delivers via the http target for testing.
|
||||
func (e *Engine) ExportDeliverHTTP(
|
||||
ctx context.Context,
|
||||
webhookDB *gorm.DB,
|
||||
d *database.Delivery,
|
||||
task *Task,
|
||||
) {
|
||||
e.deliverHTTP(ctx, webhookDB, d, task)
|
||||
e.httpTarget.Deliver(ctx, webhookDB, d, task, e)
|
||||
}
|
||||
|
||||
// ExportDeliverDatabase exposes deliverDatabase.
|
||||
// ExportDeliverDatabase delivers via the database target.
|
||||
func (e *Engine) ExportDeliverDatabase(
|
||||
webhookDB *gorm.DB, d *database.Delivery,
|
||||
) {
|
||||
e.deliverDatabase(webhookDB, d)
|
||||
e.targets[database.TargetTypeDatabase].Deliver(
|
||||
context.Background(), webhookDB, d, &Task{}, e,
|
||||
)
|
||||
}
|
||||
|
||||
// ExportDeliverLog exposes deliverLog for testing.
|
||||
// ExportDeliverLog delivers via the log target for testing.
|
||||
func (e *Engine) ExportDeliverLog(
|
||||
webhookDB *gorm.DB, d *database.Delivery,
|
||||
) {
|
||||
e.deliverLog(webhookDB, d)
|
||||
e.targets[database.TargetTypeLog].Deliver(
|
||||
context.Background(), webhookDB, d, &Task{}, e,
|
||||
)
|
||||
}
|
||||
|
||||
// ExportDeliverSlack exposes deliverSlack for testing.
|
||||
// ExportDeliverSlack delivers via the slack target for
|
||||
// testing.
|
||||
func (e *Engine) ExportDeliverSlack(
|
||||
ctx context.Context,
|
||||
webhookDB *gorm.DB,
|
||||
d *database.Delivery,
|
||||
) {
|
||||
e.deliverSlack(ctx, webhookDB, d)
|
||||
task := &Task{
|
||||
DeliveryID: d.ID,
|
||||
TargetID: d.TargetID,
|
||||
AttemptNum: 1,
|
||||
}
|
||||
|
||||
e.targets[database.TargetTypeSlack].Deliver(
|
||||
ctx, webhookDB, d, task, e,
|
||||
)
|
||||
}
|
||||
|
||||
// ExportProcessNewTask exposes processNewTask.
|
||||
@@ -96,53 +109,56 @@ func (e *Engine) ExportProcessDelivery(
|
||||
e.processDelivery(ctx, webhookDB, d, task)
|
||||
}
|
||||
|
||||
// ExportGetCircuitBreaker exposes getCircuitBreaker.
|
||||
// ExportGetCircuitBreaker exposes the http target's
|
||||
// getCircuitBreaker.
|
||||
func (e *Engine) ExportGetCircuitBreaker(
|
||||
targetID string,
|
||||
) *CircuitBreaker {
|
||||
return e.getCircuitBreaker(targetID)
|
||||
return e.httpTarget.getCircuitBreaker(targetID)
|
||||
}
|
||||
|
||||
// ExportParseHTTPConfig exposes parseHTTPConfig.
|
||||
func (e *Engine) ExportParseHTTPConfig(
|
||||
configJSON string,
|
||||
) (*HTTPTargetConfig, error) {
|
||||
return e.parseHTTPConfig(configJSON)
|
||||
return parseHTTPConfig(configJSON)
|
||||
}
|
||||
|
||||
// ExportParseSlackConfig exposes parseSlackConfig.
|
||||
func (e *Engine) ExportParseSlackConfig(
|
||||
configJSON string,
|
||||
) (*SlackTargetConfig, error) {
|
||||
return e.parseSlackConfig(configJSON)
|
||||
return parseSlackConfig(configJSON)
|
||||
}
|
||||
|
||||
// ExportDoHTTPRequest exposes doHTTPRequest.
|
||||
// ExportDoHTTPRequest exposes the http target's
|
||||
// doHTTPRequest.
|
||||
func (e *Engine) ExportDoHTTPRequest(
|
||||
ctx context.Context,
|
||||
cfg *HTTPTargetConfig,
|
||||
event *database.Event,
|
||||
) (int, string, int64, error) {
|
||||
return e.doHTTPRequest(ctx, cfg, event)
|
||||
return e.httpTarget.doHTTPRequest(ctx, cfg, event)
|
||||
}
|
||||
|
||||
// ExportClientForConfig exposes clientForConfig.
|
||||
// ExportClientForConfig exposes the http target's
|
||||
// clientForConfig.
|
||||
func (e *Engine) ExportClientForConfig(
|
||||
cfg *HTTPTargetConfig,
|
||||
) *http.Client {
|
||||
return e.clientForConfig(cfg)
|
||||
return e.httpTarget.clientForConfig(cfg)
|
||||
}
|
||||
|
||||
// ExportClient returns the engine's shared HTTP client.
|
||||
// ExportClient returns the http target's shared HTTP client.
|
||||
func (e *Engine) ExportClient() *http.Client {
|
||||
return e.client
|
||||
return e.httpTarget.client
|
||||
}
|
||||
|
||||
// ExportScheduleRetry exposes scheduleRetry.
|
||||
// ExportScheduleRetry exposes ScheduleRetry.
|
||||
func (e *Engine) ExportScheduleRetry(
|
||||
task Task, delay time.Duration,
|
||||
) {
|
||||
e.scheduleRetry(task, delay)
|
||||
e.ScheduleRetry(task, delay)
|
||||
}
|
||||
|
||||
// ExportRecoverPendingDeliveries exposes
|
||||
@@ -199,13 +215,15 @@ func NewTestEngine(
|
||||
client *http.Client,
|
||||
workers int,
|
||||
) *Engine {
|
||||
return &Engine{
|
||||
e := &Engine{
|
||||
log: log,
|
||||
client: client,
|
||||
deliveryCh: make(chan Task, deliveryChannelSize),
|
||||
retryCh: make(chan Task, retryChannelSize),
|
||||
workers: workers,
|
||||
}
|
||||
e.initTargets(client)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// NewTestEngineSmallRetry creates an Engine with a tiny
|
||||
@@ -213,10 +231,13 @@ func NewTestEngine(
|
||||
func NewTestEngineSmallRetry(
|
||||
log *slog.Logger,
|
||||
) *Engine {
|
||||
return &Engine{
|
||||
e := &Engine{
|
||||
log: log,
|
||||
retryCh: make(chan Task, 1),
|
||||
}
|
||||
e.initTargets(nil)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// NewTestEngineWithDB creates an Engine with a real
|
||||
@@ -228,15 +249,17 @@ func NewTestEngineWithDB(
|
||||
client *http.Client,
|
||||
workers int,
|
||||
) *Engine {
|
||||
return &Engine{
|
||||
e := &Engine{
|
||||
database: db,
|
||||
dbManager: dbMgr,
|
||||
log: log,
|
||||
client: client,
|
||||
deliveryCh: make(chan Task, deliveryChannelSize),
|
||||
retryCh: make(chan Task, retryChannelSize),
|
||||
workers: workers,
|
||||
}
|
||||
e.initTargets(client)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// NewTestCircuitBreaker creates a CircuitBreaker with
|
||||
|
||||
Reference in New Issue
Block a user