- Go 98%
- Nix 2%
|
All checks were successful
CI / format (push) Successful in 7s
CI / vet (push) Successful in 5m40s
CI / build (push) Successful in 6m15s
CI / test (push) Successful in 6m30s
CI / links (push) Successful in 3m21s
CI / ci (push) Successful in 0s
Release / release-1 (push) Successful in 15s
Release / release (push) Successful in 0s
- move shared Go tooling and defaults into devenv/base.nix - separate package and microservice commands into dedicated profiles - document remote microservice profile consumption |
||
|---|---|---|
| .forgejo/workflows | ||
| appinfo | ||
| client | ||
| config | ||
| devenv | ||
| docs | ||
| errors | ||
| idempotency | ||
| integration | ||
| internal/cmd/checklinks | ||
| logging | ||
| metadata | ||
| observability | ||
| outbox | ||
| pagination | ||
| ptr | ||
| retry | ||
| routeinfo | ||
| runtime | ||
| server | ||
| testdata/minio | ||
| testflag | ||
| transport | ||
| tx | ||
| .gitignore | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| docker-compose.yaml | ||
| go.mod | ||
| go.sum | ||
| README.md | ||
gopkg
Shared Go infrastructure packages for platform services.
Infrastructure-only repository: no service-specific business logic or domain models.
Module: git.hayshin.dev/platform/go-pkg.
Single module keeps package releases atomic and avoids cross-module version coordination. Dependency-heavy clients remain isolated by package, so consumers compile only imports they use.
Development environments
Reusable devenv profiles live under devenv/:
base.nixprovides Go, common tools and infrastructure clients, shared local environment defaults, formatting, linting, unit testing, development, cleanup, and git hooks.package.nixextends the base profile with package integration tests, Docker Compose lifecycle, package builds, checks, and package help. This repository imports it fromdevenv.nix.microservice.nixextends the base profile with service environment defaults, protobuf/sqlc/mock generation, migrations, service build/run, integration infrastructure, rename, checks, and service help.
Go microservices should consume the microservice profile as a remote, non-flake input:
inputs:
go-pkg:
url: git+ssh://forgejo@ssh.hayshin.dev/platform/go-pkg.git
flake: false
git-hooks:
url: github:cachix/git-hooks.nix
inputs:
nixpkgs:
follows: nixpkgs
imports:
- go-pkg/devenv/microservice.nix
Keep service-local devenv.nix files limited to identity or exceptional overrides. Shared defaults use lib.mkDefault, so a service can normally set only its name:
{ ... }:
{
env.APP_NAME = "catalog";
}
Do not copy shared scripts into services or use cross-repository relative imports.
Documentation
Start here, then package-local READMEs for API details.
| Guide | Purpose |
|---|---|
docs/service-bootstrap.md |
Recommended service main/run wiring with srv.Group. |
docs/observability.md |
Logging, metadata, metrics, tracing, and probes. |
docs/outbox.md |
Transactional outbox architecture and setup. |
docs/idempotency.md |
HTTP/gRPC idempotency flow and configuration. |
docs/testing.md |
Docker Compose, PostgreSQL migrations, and integration test helpers. |
Package-local docs:
transport/httptransport/grpcclient/pgclient/pg/outboxclient/nats/natsoutboxoutboxmetadataruntime/telemetryloggingobservability/tracing
Package map
Core utilities
| Package | Purpose |
|---|---|
appinfo |
App name/version/build metadata. |
config |
Typed environment config loader using caarlos0/env tags. |
logging |
Zap contextual logger with OpenTelemetry trace/span enrichment. |
metadata |
Request identity, rich metadata, and context helpers. |
metadata/encoding |
Metadata wire encoding helpers. |
pagination |
Offset pagination helpers. |
retry |
Small retry helper for transient operations. |
routeinfo |
Matched-route metadata helpers. |
testflag |
Test-only context/transport flags. |
tx |
Generic transaction abstractions. |
ptr |
Pointer helpers. |
Clients
| Package | Purpose |
|---|---|
client |
Generic client lifecycle holders and grouped dial/close helpers. |
client/pg |
PostgreSQL pgxpool config and construction helper. |
client/pg/transaction |
Recommended explicit PostgreSQL transaction manager/repository helpers. |
client/pg/conv |
pgx pgtype, time, numeric, and page-token conversion helpers. |
client/pg/outbox |
PostgreSQL transactional outbox repository, migrations, and relay. |
client/redis |
Redis go-redis client construction helper. |
client/redis/idempotent |
Redis-backed idempotency store. |
client/nats |
NATS connection construction helper. |
client/nats/jetstreamutil |
JetStream stream utility helpers. |
client/nats/natsoutbox |
NATS JetStream publisher for PostgreSQL outbox events. |
client/s3 |
AWS SDK v2 S3/S3-compatible client construction helper. |
client/s3/s3bucket |
Convenience wrapper for common bucket object operations. |
client/s3/s3helpers |
S3 helper utilities such as public object URL resolution. |
client/smtp |
Small SMTP email client. |
client/k8s |
Kubernetes client config and construction helper. |
client/vault |
Vault client config and auth helper. |
Errors and validation
| Package | Purpose |
|---|---|
errors/apperr |
Coded errors for consistent service error handling. |
errors/grpcerr |
gRPC status mapping plus unary/stream interceptors for app errors and panic recovery. |
errors/httperr |
HTTP status/message mapping plus standard JSON error bodies. |
errors/validation |
Field-level validation errors with HTTP fields and gRPC BadRequest details support. |
errors/recoverer |
Panic-to-error helpers for workers/lifecycle code. |
Events, idempotency, and outbox
| Package | Purpose |
|---|---|
idempotency |
Store interface, memory store, and shared reserve/run/complete/release state machine. |
outbox |
Domain-facing transactional outbox port. |
client/pg/outboxadapter |
Adapter from domain outbox port to pgoutbox. |
Observability
| Package | Purpose |
|---|---|
observability/tracing |
OpenTelemetry tracing setup with OTLP exporters. |
observability/metrics |
Shared metric helpers. |
observability/httpmetrics |
HTTP RED metrics helpers. |
observability/grpcmetrics |
gRPC RED metrics helpers. |
runtime/telemetry |
Auxiliary metrics/probes/tracing lifecycle server. |
runtime/telemetry/probes |
PostgreSQL, Redis, and NATS probe constructors. |
Lifecycle and transport
| Package | Purpose |
|---|---|
server |
Service entrypoint, serve groups, and graceful shutdown helpers. |
server/loop |
Worker loop helper. |
transport/http/server |
Runnable chi HTTP server. |
transport/http/stack |
Declarative HTTP server middleware stack. |
transport/http/modules/* |
HTTP server modules for recovery, metadata, metrics, tracing, idempotency, and related concerns. |
transport/http/client |
Outbound HTTP client builder with metadata, metrics, retry, and tracing. |
transport/http/middleware |
HTTP middleware helpers for error-returning handlers. |
transport/grpc/server |
Runnable gRPC server. |
transport/grpc/client |
gRPC client connection builder. |
transport/grpc/stack |
Declarative gRPC server module stack. |
transport/grpc/clientstack |
Declarative gRPC client module stack. |
transport/grpc/modules/* |
gRPC server/client modules for error mapping, metadata, tracing, metrics, logging, idempotency, retries, health, and reflection. |
transport/grpc/middleware/* |
Low-level gRPC interceptor helpers. |
Migrations and tests
| Package | Purpose |
|---|---|
runtime/pg/migrate |
PostgreSQL goose migration runner for filesystem or embedded migrations. |
runtime/pg/testutil |
Test helper: apply migrations once per schema and open a pgxpool for integration tests. |
runtime/integrationtest |
Test helper: start shared Docker Compose dependencies for integration tests. |
Common patterns
Service bootstrap
func main() {
info := appinfo.Info{Name: "catalog", Version: version}
if err := srv.Run(info, run); err != nil {
os.Exit(1)
}
}
func run(ctx context.Context) error {
telemetry := telemetrysrv.New(telemetrysrv.Config{Port: 8080})
grpcServer := grpcsrv.New(grpcsrv.Args{Port: 9090}, grpcstack.CoreConfig().Server())
g, _ := srv.WithGroup(ctx)
g.Go(telemetry)
g.Go(grpcServer)
return g.Wait()
}
See docs/service-bootstrap.md.
Config loading
Load typed config from environment variables with config:
pgcfg, err := config.Load[pg.Config]()
if err != nil {
return err
}
Config structs use github.com/caarlos0/env/v11 tags: env:"NAME", envDefault:"value", and env:"NAME,notEmpty" for required non-empty values.
PostgreSQL transactions
Prefer client/pg/transaction for new code. Build explicit repository set implementing WithTransaction(actor tx.Actor), then run service operations through pgtx.NewManager(pool, repos).DoTx(ctx, fn). Callback receives transaction-bound repositories, making transaction scope visible at service boundaries.
Context-injected PostgreSQL repositories were removed; use explicit client/pg/transaction transaction boundaries.
PostgreSQL migrations
Run goose migrations with runtime/pg/migrate:
pgcfg, err := pg.LoadConfig()
if err != nil {
return err
}
migcfg, err := pgmigrate.LoadConfig()
if err != nil {
return err
}
runner, err := pgmigrate.New(ctx, pgcfg, migcfg)
if err != nil {
return err
}
defer runner.Close()
_, err = runner.Up(ctx)
return err
Schema handling follows pg.Config.Schema: pgmigrate creates schema when needed and opens goose connections with search_path set to it, so unqualified migration SQL and goose version table live under service schema. For embedded migrations, pass an fs.FS rooted at migration files with pgmigrate.WithFS.
Integration tests
Use runtime/integrationtest to start shared Docker Compose dependencies and runtime/pg/testutil to apply migrations once per schema per process. See docs/testing.md.
Idempotency
HTTP and gRPC idempotency modules share orchestration in idempotency.Run: reserve key, replay completed responses, reject in-progress calls, run handler, complete cacheable successes, release failures/non-cacheable responses. Transport packages handle key extraction, response serialization/replay, and HTTP/gRPC status mapping. See docs/idempotency.md.
Error handling
HTTP handlers can return shared JSON error shape:
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(httperr.Status(err))
_ = json.NewEncoder(w).Encode(httperr.Body(err))
return
}
Validation can accumulate safely; empty validation returns nil:
var v validation.Builder
v.AddIf(req.Email == "", "email", "is required")
v.AddIf(req.Name == "", "name", "is required")
if err := v.Err(); err != nil {
return err
}