Skip to content

feat: per-endpoint circuit breaker for /api/comments downstream calls (#613) - #724

Merged
greatest0fallt1me merged 3 commits into
Predictify-org:mainfrom
thelmaoffiong:feature/comments-circuit-breaker-613
Jul 29, 2026
Merged

feat: per-endpoint circuit breaker for /api/comments downstream calls (#613)#724
greatest0fallt1me merged 3 commits into
Predictify-org:mainfrom
thelmaoffiong:feature/comments-circuit-breaker-613

Conversation

@thelmaoffiong

Copy link
Copy Markdown
Contributor

Summary

Implements a per-endpoint circuit breaker around the two downstream calls made by /api/comments, as required by issue #613. When a breaker trips, the route responds with HTTP 503 immediately rather than queuing requests behind a failing dependency.

New file: src/lib/circuitBreaker.ts

A generic, reusable CircuitBreaker class with the classic three-state machine:

State Behaviour
CLOSED All calls pass through. Failures are counted in a configurable rolling window. Opens when failureThreshold failures accumulate within windowMs.
OPEN All calls fast-fail with CircuitOpenError. After resetTimeoutMs elapses the breaker moves to HALF_OPEN.
HALF_OPEN A single probe call is allowed. Success → CLOSED (failure count reset). Failure → OPEN (timeout restarts). Concurrent callers during the probe are also fast-failed.

Also exports CircuitOpenError — callers check instanceof CircuitOpenError to distinguish a tripped breaker from a genuine downstream error.

Default configuration:

  • failureThreshold = 5
  • windowMs = 60 000 ms (1 min rolling window)
  • resetTimeoutMs = 30 000 ms (probe after 30 s)

Updated: src/routes/comments.ts

Two module-level breaker singletons (exported for tests):

export const commentsDbBreaker = new CircuitBreaker(comments-db, { ... });
export const commentsOutboundBreaker = new CircuitBreaker(comments-outbound, { ... });

Both downstream calls are now wrapped:

Call Breaker
listMarketComments() — Postgres via Drizzle commentsDbBreaker
fetchWithCorrelationId() — optional outbound HTTP commentsOutboundBreaker

When either breaker is OPEN the route returns:

HTTP 503
{ "error": { "code": "service_unavailable", "message": "Service temporarily unavailable. Please retry later." } }

Tests: tests/commentsCircuitBreaker.test.ts (31 tests)

CircuitBreaker state machine (15 tests)

  • CLOSED pass-through, error re-throw, threshold not met
  • CLOSED → OPEN on threshold, success resets failure count
  • OPEN fast-fail (CircuitOpenError), callable not invoked
  • OPEN → HALF_OPEN after reset timeout
  • HALF_OPEN → CLOSED on probe success
  • HALF_OPEN → OPEN on probe failure
  • Concurrent callers rejected while probe in flight
  • Rolling window discards old failures
  • reset() restores CLOSED state

Route integration (12 tests)

  • GET returns 200 when breaker CLOSED and service succeeds
  • GET returns 503 when commentsDbBreaker is OPEN
  • GET does not call listMarketComments when breaker is OPEN
  • DB errors from the service trip the breaker
  • POST returns 201 when outbound breaker CLOSED and fetch succeeds
  • POST returns 503 when commentsOutboundBreaker is OPEN
  • POST does not call fetchWithCorrelationId when breaker is OPEN
  • POST returns 201 with no outbound when outboundUrl is absent
  • Non-circuit fetch errors are logged, comment still created
  • Outbound fetch errors trip the outbound breaker

CircuitOpenError class (4 tests)

Tests: 31 passed, 31 total

All 13 existing market-comments tests continue to pass (no regressions).

Closes #613

…alls

Implements a CLOSED/OPEN/HALF_OPEN circuit breaker in src/lib/circuitBreaker.ts
and integrates two named breaker instances into src/routes/comments.ts:

  commentsDbBreaker      — guards listMarketComments (Postgres via Drizzle)
  commentsOutboundBreaker — guards fetchWithCorrelationId (outbound HTTP)

Behaviour:
- CLOSED: calls pass through; failures are counted in a 60-second rolling window
- OPEN:   all calls fast-fail with HTTP 503 { error: { code: 'service_unavailable' } }
- HALF_OPEN: a single probe is allowed after 30 s; success → CLOSED, failure → OPEN
- Both breakers are exported from comments.ts for test-time manipulation

Adds 31 focused tests in tests/commentsCircuitBreaker.test.ts:
- State machine tests: CLOSED/OPEN/HALF_OPEN transitions, rolling window,
  concurrent probe guard, reset()
- Route integration tests: 503 on open DB breaker, DB errors trip the breaker,
  503 on open outbound breaker, fetch errors trip the breaker

No regressions: all 13 existing market-comments tests continue to pass.

Closes Predictify-org#613
@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@thelmaoffiong Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@greatest0fallt1me

Copy link
Copy Markdown
Contributor

Merged into main via admin resolver (-X theirs).

@greatest0fallt1me
greatest0fallt1me merged commit 45952e6 into Predictify-org:main Jul 29, 2026
1 check passed
@greatest0fallt1me

Copy link
Copy Markdown
Contributor

All checks green — merging. Nice job! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add per-endpoint circuit breaker for downstream calls on /api/comments [b#039]

2 participants