Tier: Adapter · Status: REST Full; SOAP / gRPC / WS scaffolds · Java original:
firefly-service-client· .NET project:FireflyFramework.Client
client is the framework's outbound HTTP client builder — a fluent
RESTBuilder that composes timeouts, retries, default headers, and
correlation-id propagation into a RESTClient with a single Do(ctx, method, path, body, out) method. Non-2xx responses are decoded into
*kernel.FireflyError (RFC 7807-aware), so every consumer of every
external service sees the same error shape.
SOAP / gRPC / WebSocket builders share the same New*(baseURL) shape
and currently return ErrTransportNotRegistered — production
adapters land in dedicated modules in v26.06.
The Java firefly-service-client integrates Resilience4j + service
discovery + OAuth2 token caching + GraphQL helper. ASP.NET defers
much of this to IHttpClientFactory plus Polly. Both worlds settle
on the same shape: a typed builder that yields a typed client. This
module is the Go equivalent — small, stdlib-based, composable with
resilience.Chain.
type RESTBuilder struct{ ... }
func NewREST(baseURL string) *RESTBuilder
func (*RESTBuilder) WithHeader(k, v string) *RESTBuilder
func (*RESTBuilder) WithTimeout(time.Duration) *RESTBuilder
func (*RESTBuilder) WithHTTPClient(*http.Client) *RESTBuilder
func (*RESTBuilder) WithRetries(n int) *RESTBuilder
func (*RESTBuilder) Build() *RESTClient
type RESTClient struct{ ... }
func (*RESTClient) Do(ctx, method, path string, body, out any) error
// Non-REST placeholders (return ErrTransportNotRegistered until wired)
func NewSOAP(string) error
func NewGRPC(string) error
func NewWebSocket(string) error
var ErrTransportNotRegistered = errors.New("…transport adapter not registered")Do automatically:
- JSON-encodes
body(when non-nil) and setsContent-Type: application/json. - Forwards the correlation id from the context as
X-Correlation-Id. - Retries on network errors and 429 / 5xx status codes (exponential backoff capped at 2 s).
- Decodes RFC 7807
application/problem+jsonbodies into a typed*kernel.FireflyErrorpopulated withCode,Title,Status,Detail, andFields.
import (
"context"
"net/http"
"time"
"github.com/fireflyframework/fireflyframework-go/client"
"github.com/fireflyframework/fireflyframework-go/kernel"
)
c := client.NewREST("https://api.example.com").
WithHeader("X-Tenant", "acme").
WithTimeout(5 * time.Second).
WithRetries(3).
Build()
type Order struct{ ID, Customer string }
var out Order
if err := c.Do(ctx, http.MethodPost, "/orders", req, &out); err != nil {
var fe *kernel.FireflyError
if errors.As(err, &fe) {
log.Printf("upstream %d: %s", fe.Status, fe.Detail)
}
}guarded := resilience.Chain(
resilience.AsDecorator(resilience.NewTimeout(5*time.Second)),
resilience.AsDecorator(circuitBreaker),
)
err := guarded(ctx, func() error { return c.Do(ctx, "POST", "/charge", req, &out) })cd client
go test ./...Covers happy-path JSON round-trip, ProblemDetail decoding into a
*kernel.FireflyError, retry on 5xx (3 attempts), and the SOAP /
gRPC / WS sentinel returns.