ayer: Echo + Huma
Huma wraps Echo to provide declarative, type-safe API definitions. This eliminates manual OpenAPI documentation and ensures the API contract matches the implementation.
package main
import (
"context"
"net/http"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humaecho"
"github.com/labstack/echo/v4"
)
// CatalogItem defines the response structure for inventory lookups.
type CatalogItem struct {
Body struct {
SKU string `json:"sku" doc:"Stock Keeping Unit"`
Price float64 `json:"price" doc:"Current price in USD"`
InStock bool `json:"in_stock" doc:"Availability status"`
}
}
func main() {
e := echo.New()
// Initialize Huma with Echo adapter and OpenAPI 3.1 config.
api := humaecho.New(e, huma.DefaultConfig("Inventory Service", "2.0.0"))
// Register endpoint with automatic validation and documentation.
huma.Register(api, huma.Operation{
Method: http.MethodGet,
Path: "/inventory/{sku}",
Summary: "Retrieve inventory details for a specific SKU",
}, func(ctx context.Context, input *struct {
SKU string `path:"sku" doc:"Product SKU"`
}) (*CatalogItem, error) {
// Business logic: Fetch item (mocked here).
item := &CatalogItem{}
item.Body.SKU = input.SKU
item.Body.Price = 29.99
item.Body.InStock = true
return item, nil
})
e.Logger.Fatal(e.Start(":8080"))
}
Rationale: Huma enforces strict typing on request/response structures. If the code compiles, the OpenAPI spec is accurate. This prevents the common pitfall of documentation drift and enables client SDK generation directly from the service.
2. Data Layer: Ent ORM
Ent replaces reflection-heavy ORMs with code generation. Schemas are defined in Go, and queries are type-safe, providing IDE autocompletion and compile-time error detection.
// Generated code usage example for type-safe queries.
// Assumes Ent schema for 'Invoice' with fields 'Status' and 'DueDate'.
func FetchOverdueInvoices(ctx context.Context, client *ent.Client) ([]*ent.Invoice, error) {
return client.Invoice.
Query().
Where(invoice.StatusEQ("unpaid")).
Where(invoice.DueDateLT(time.Now())).
Order(ent.Asc(invoice.FieldDueDate)).
All(ctx)
}
Rationale: Ent's code generation ensures that schema changes are caught at compile time. The fluent API reduces boilerplate and eliminates runtime panics associated with string-based queries or reflection mismatches.
3. Observability: slog + OpenTelemetry eBPF
Structured logging with slog provides a standard format for log aggregation, while eBPF-based OpenTelemetry instrumentation captures traces without modifying business logic.
package main
import (
"log/slog"
"os"
"time"
)
func main() {
// Configure global structured logger with JSON output.
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
// Log structured event with typed attributes.
logger.Info("Cache lookup completed",
slog.String("cache_key", "session:user_123"),
slog.Bool("hit", true),
slog.Duration("latency", 2*time.Millisecond),
)
}
Rationale: slog is part of the standard library, ensuring consistency across dependencies. eBPF instrumentation captures distributed traces automatically, providing full visibility into latency and errors without the overhead of manual span creation.
4. Configuration: Koanf
Koanf manages configuration from multiple sources (YAML, environment variables, remote providers) with a unified interface.
package main
import (
"strings"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
)
var config = koanf.New(".")
func LoadConfig() error {
// Load base configuration from YAML file.
if err := config.Load(file.Provider("config.yaml"), yaml.Parser()); err != nil {
return err
}
// Override with environment variables prefixed with "SYS_".
return config.Load(env.Provider("SYS_", ".", func(s string) string {
return strings.ToLower(strings.TrimPrefix(s, "SYS_"))
}), nil)
}
Rationale: Koanf's layered loading strategy allows environment variables to override file-based configuration, supporting cloud-native deployment patterns. The tiny footprint and flexible providers make it ideal for dynamic environments.
5. Security: Sigstore
Sigstore ensures binary integrity by signing artifacts during the build process and verifying them before deployment.
package main
import (
"github.com/sigstore/sigstore-go/pkg/verify"
)
// ValidateReleaseArtifact verifies the signature of a binary against a policy.
func ValidateReleaseArtifact(binaryPath string, signature []byte) error {
// Define policy requiring a specific builder identity.
policy := verify.NewPolicy(
verify.SubjectAlternativeName("builder@ci.internal"),
)
_, err := verify.VerifyArtifact(binaryPath, signature, policy)
return err
}
Rationale: Sigstore integrates into the CI/CD pipeline to sign binaries, preventing supply chain attacks. Verification at deployment ensures only trusted artifacts run in production.
6. Orchestration: Temporal
Temporal provides durable execution for complex workflows, persisting state and handling retries automatically.
package main
import (
"time"
"go.temporal.io/sdk/workflow"
)
// OnboardingWorkflow orchestrates user provisioning with retries.
func OnboardingWorkflow(ctx workflow.Context, userID string) error {
retryPolicy := &workflow.RetryPolicy{
InitialInterval: 1 * time.Second,
MaximumInterval: 10 * time.Second,
MaximumAttempts: 5,
}
activityOpts := workflow.ActivityOptions{
StartToCloseTimeout: 30 * time.Second,
RetryPolicy: retryPolicy,
}
ctx = workflow.WithActivityOptions(ctx, activityOpts)
// Execute provisioning activity; state is persisted across retries.
return workflow.ExecuteActivity(ctx, ProvisionAccount, userID).Get(ctx, nil)
}
Rationale: Temporal eliminates the need for custom state machines and retry logic. Workflows survive crashes and network partitions, ensuring exactly-once execution semantics for critical business processes.
Pitfall Guide
-
Huma Struct Tag Errors
- Explanation: Missing or incorrect struct tags in Huma definitions can cause OpenAPI generation failures or runtime validation errors.
- Fix: Use linters to enforce tag presence and validate structs against the OpenAPI spec during CI.
-
Ent Schema Regeneration Neglect
- Explanation: Modifying Ent schemas without running
go generate leads to compile errors or outdated query methods.
- Fix: Integrate
go generate into Makefile hooks or pre-commit scripts to ensure generated code stays in sync.
-
Temporal Non-Determinism
- Explanation: Using non-deterministic functions like
time.Now() or random number generators inside Temporal workflows can cause replay failures.
- Fix: Use
workflow.Now() and workflow.GetRandomValue() to ensure deterministic replay behavior.
-
slog Context Loss
- Explanation: Failing to propagate the logger instance through context can result in missing log attributes or inconsistent formatting.
- Fix: Use
slog.NewContext to attach the logger to the context and retrieve it in downstream handlers.
-
Koanf Key Collisions
- Explanation: Environment variables may unintentionally override YAML configuration if prefixes are not managed carefully.
- Fix: Define strict prefixes for environment variables and document the override hierarchy in configuration guides.
-
Echo Middleware Order
- Explanation: Incorrect middleware ordering can cause authentication checks to run before logging, obscuring request details.
- Fix: Establish a standard middleware chain: Recovery β Logging β RequestID β Auth β Business Logic.
-
Sigstore Policy Over-Restriction
- Explanation: Overly strict Sigstore policies may block valid builds from trusted builders, causing deployment failures.
- Fix: Apply least-privilege policies and regularly audit builder identities to balance security and operational flexibility.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| High-throughput API | Echo + Huma | Type safety and low latency with auto-docs. | Low; reduces manual doc maintenance. |
| Complex Data Relations | Ent | Compile-time checks and fluent queries. | Medium; requires code generation step. |
| Distributed Workflows | Temporal | Durable execution and crash recovery. | High; requires Temporal server infrastructure. |
| Zero-Code Observability | OTel eBPF | Full trace coverage without code changes. | Low; minimal overhead, high ROI. |
| Multi-Source Config | Koanf | Flexible providers and layered overrides. | Low; lightweight and easy to integrate. |
Configuration Template
# config.yaml
server:
port: 8080
read_timeout: 5s
write_timeout: 10s
database:
host: "localhost"
port: 5432
name: "inventory_db"
max_connections: 25
logging:
level: "info"
format: "json"
temporal:
address: "localhost:7233"
namespace: "default"
Quick Start Guide
- Initialize Project: Run
go mod init github.com/yourorg/resilient-service and add dependencies (echo, huma, ent, koanf, temporal, sigstore).
- Generate Ent Code: Define schemas in
ent/schema/ and run go generate ./ent to produce query code.
- Configure Server: Set up Echo with Huma adapter, load configuration via Koanf, and initialize
slog.
- Start Services: Run the Go binary with the OpenTelemetry eBPF agent and start the Temporal worker for workflow execution.
- Verify: Access the auto-generated OpenAPI docs at
/docs and test endpoints to confirm type safety and observability integration.