Integration Patterns (SDK-First)
This guide explains how to integrate Tripswitch into a production Go service using the official SDK. It focuses on where Tripswitch fits in your code and what responsibilities remain yours. While examples use Go, the integration pattern applies across all Tripswitch SDKs.
The Integration Loop
Every interaction with a circuit breaker follows four steps:
- Check state — Ask whether the breaker is Open, Closed, or Half-Open.
- Decide — Based on that state, choose whether to proceed with the call.
- Execute — If proceeding, call the dependency.
- Report outcome — Tell Tripswitch whether the call succeeded or failed.
These steps separate cleanly between the SDK and your service:
- The SDK handles state lookup and outcome reporting.
- Your service handles the decision and the execution.
This separation matters. Breaker state lives on the Tripswitch server and is synchronized to your service via the SDK. But the SDK never calls your dependencies for you. You remain in control of what happens when a breaker is Open, how you handle failures, and when to retry.
What the SDK Guarantees vs. What the Service Owns
SDK Responsibilities
| Responsibility | What it means |
|---|---|
| Efficient state lookup | State checks are local (no network round-trip per call). The SDK maintains a local cache synchronized from the server. |
| Safe defaults | Unknown breakers fail open. Lost server connectivity doesn’t block your traffic. (See below.) |
| Consistent request shaping | Half-Open throttling uses the same probabilistic model across all SDK instances. |
| Outcome batching | Samples are buffered and sent efficiently. Your hot path isn’t blocked by telemetry. |
| Clear key distinction | Project keys are for runtime operations. Admin keys are for management. These never mix. |
Service Responsibilities
| Responsibility | What it means |
|---|---|
| Execution control | You call the dependency. The SDK provides state, not execution. |
| Retry logic | The SDK doesn’t retry. If you want retries, you implement them. |
| Fallback behavior | When a breaker is Open, the SDK returns an error. What you do with that error is up to you. |
| Concurrency limits | The SDK doesn’t limit how many requests you send. If you need concurrency control, implement it yourself. |
| Degradation strategy | Cached responses, reduced functionality, error pages — these are your decisions. |
Fail-open by default
Tripswitch defaults to fail-open on uncertainty: when state is unknown or stale, traffic proceeds. This avoids cascading failures caused by control-plane outages. If this is unacceptable for your system, configure WithFailOpen(false) explicitly.
Canonical Go Example
This example shows the full integration loop: checking state, executing a dependency call, and reporting the outcome.
package main
import (
"context"
"errors"
"log"
"net/http"
"github.com/tripswitch-dev/tripswitch-go"
)
func main() {
// Initialize the client with project credentials
ts := tripswitch.NewClient("proj_abc123",
tripswitch.WithAPIKey("eb_pk_live_..."),
tripswitch.WithIngestSecret("your-64-char-hex-secret"),
)
defer ts.Close(context.Background())
// Block until initial breaker state is loaded.
// Without this, early requests may see unknown state and fail open.
ctx := context.Background()
if err := ts.Ready(ctx); err != nil {
log.Fatalf("Failed to connect to Tripswitch: %v", err)
}
// Define which breaker protects this dependency
breaker := tripswitch.Breaker{
Name: "payment-gateway",
RouterID: "rtr_uuid_from_dashboard",
Metric: "error_rate",
}
// Execute a protected call
resp, err := tripswitch.Execute(ts, ctx, breaker, func() (*http.Response, error) {
return http.Get("https://payments.example.com/charge")
})
// Handle the result
if err != nil {
if tripswitch.IsBreakerError(err) {
// Breaker is Open — the call was never attempted
log.Println("Payment gateway unavailable, using fallback")
return
}
// The call was attempted but failed
log.Printf("Payment failed: %v", err)
return
}
// Success
defer resp.Body.Close()
log.Printf("Payment succeeded: %d", resp.StatusCode)
}
What happens in each state
| State | Behavior |
|---|---|
| Closed | The task executes. The outcome is reported. |
| Open |
The task does not execute. Execute returns tripswitch.ErrOpen immediately. |
| Half-Open |
Some calls are allowed through (based on the configured allow rate). Rejected calls return tripswitch.ErrOpen. |
The SDK’s Execute function handles this loop for you, but the pattern remains explicit: check, decide, execute, report. Execute is a convenience, not a requirement. Advanced integrations may perform explicit state checks and report outcomes manually, but the underlying pattern remains the same.
Half-Open Behavior
Half-Open is the most dangerous state to get wrong. Understanding it prevents cascading failures during recovery.
Why Half-Open exists
When a breaker trips Open, it stops all traffic to a failing dependency. But eventually you need to test whether the dependency has recovered. Half-Open allows a controlled number of probe requests through.
How the SDK handles Half-Open
In Half-Open state, the server specifies an allow rate (e.g., 0.1 means 10% of requests). The SDK applies this probabilistically:
- Each call has a random chance of being allowed through.
-
Rejected calls return
tripswitch.ErrOpenwithout executing. - Allowed calls execute and report their outcome.
This happens automatically inside Execute. You don’t need to implement probe logic yourself.
What you must still enforce
The SDK handles per-request throttling, but it doesn’t know your system’s broader context.
This surprises teams: Allow rates apply per instance, not globally. A 10% allow rate across 50 instances means 50 concurrent probe requests — not “gentle” recovery traffic.
| Your responsibility | Why |
|---|---|
| Global probe limits | If you have 100 instances each allowing 10% through, you’re sending 10 requests concurrently. Consider whether your recovering dependency can handle that. |
| Timeout enforcement | Probe requests should have tight timeouts. A slow probe ties up resources and delays recovery detection. |
| Outcome accuracy |
A successful HTTP 200 with bad data is still a success to the breaker unless you tell it otherwise. Use WithErrorEvaluator if you need custom failure detection. |
Example: Custom failure detection for probes
resp, err := tripswitch.Execute(ts, ctx, breaker, callPaymentAPI,
tripswitch.WithErrorEvaluator(func(err error) bool {
// Don't count client errors as breaker failures
var httpErr *HTTPError
if errors.As(err, &httpErr) {
return httpErr.StatusCode >= 500
}
return err != nil
}),
)
Half-Open is not magic. The SDK gives you safe defaults, but you remain responsible for ensuring probe traffic doesn’t overwhelm a recovering service.
Polling vs. Subscription
The SDK needs to know the current breaker state. There are two approaches: polling the server periodically, or subscribing to a stream of state changes.
The SDK uses subscription (SSE)
The Go SDK maintains a persistent Server-Sent Events connection to Tripswitch. When breaker state changes, the server pushes an update immediately.
| Aspect | Subscription (SSE) |
|---|---|
| Latency | State changes arrive in milliseconds. |
| Efficiency | No repeated requests. One connection per client. |
| Failure mode | If the connection drops, state becomes stale. |
If you build your own client or operate in restricted environments where persistent connections aren’t viable, polling remains a valid fallback.
What happens when the connection fails
The SDK reconnects automatically with exponential backoff. During disconnection:
- Default behavior (fail-open): Unknown or stale state allows traffic through. Your dependency handles the load.
-
Fail-closed option: If you configure
WithFailOpen(false), lost connectivity blocks all traffic until reconnection.
Fail-open is the default because blocking all traffic due to a Tripswitch outage is usually worse than allowing some potentially-failing requests through.
Checking connection health
stats := ts.Stats()
if !stats.SSEConnected {
log.Warn("Tripswitch state sync disconnected, operating on stale data")
}
Common Mistakes
These patterns cause problems in production. Each is covered in detail in the full Common Mistakes guide.
Ignoring Open state
// Wrong: Calling the dependency anyway
resp, err := tripswitch.Execute(ts, ctx, breaker, callDependency)
if err != nil {
resp, err = callDependency() // Defeats the purpose
}
When the breaker is Open, it’s Open for a reason. Calling the dependency anyway undermines circuit breaker protection.
Flooding during Half-Open
If every instance allows 10% through and you have 50 instances, you’re sending 5x more probe traffic than intended. Coordinate probe limits at the system level, not just the SDK level.
Reporting outcomes inconsistently
// Wrong: Success reported even when response is unusable
resp, _ := tripswitch.Execute(ts, ctx, breaker, func() (*Response, error) {
r, err := callAPI()
if err != nil {
return nil, err
}
return r, nil // Reported as success even if r.Status == 500
})
The SDK reports based on whether your function returns an error. If a 500 response is a failure, return an error.
Treating Tripswitch like a wrapper
Tripswitch doesn’t wrap your dependencies. It doesn’t retry for you, doesn’t cache responses, and doesn’t transform errors. It provides breaker state and collects outcomes. Everything else is your code.
Summary
Integrating Tripswitch means understanding the division of responsibility:
- Tripswitch tracks breaker state remotely and synchronizes it to your service.
- The SDK provides efficient local state checks and batched outcome reporting.
- Your service decides what to do with that state and executes all dependency calls.
The SDK makes the common case easy. But the safety guarantees come from understanding what the SDK does — and what it deliberately does not do.