No description
  • Go 98%
  • Nix 2%
Find a file
hayshin 072466dfcf
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
refactor(devenv): split reusable Go profiles
- move shared Go tooling and defaults into devenv/base.nix
- separate package and microservice commands into dedicated profiles
- document remote microservice profile consumption
2026-07-15 21:48:19 +05:00
.forgejo/workflows ci: adopt shared Go workflows 2026-07-14 11:03:05 +05:00
appinfo refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
client refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
config refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
devenv refactor(devenv): split reusable Go profiles 2026-07-15 21:48:19 +05:00
docs refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
errors refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
idempotency refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
integration refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
internal/cmd/checklinks refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
logging refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
metadata refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
observability refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
outbox refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
pagination refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
ptr refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
retry refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
routeinfo refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
runtime refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
server refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
testdata/minio refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
testflag refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
transport refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
tx refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
.gitignore refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
devenv.lock chore: upd devenv 2026-07-15 21:06:58 +05:00
devenv.nix refactor(devenv): split reusable Go profiles 2026-07-15 21:48:19 +05:00
devenv.yaml feat(platform): add shared service platform packages (#1) 2026-07-10 12:41:08 +05:00
docker-compose.yaml refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
go.mod refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
go.sum refactor: simplify package layout and harden runtime behavior (#2) 2026-07-11 20:19:46 +05:00
README.md refactor(devenv): split reusable Go profiles 2026-07-15 21:48:19 +05:00

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.nix provides Go, common tools and infrastructure clients, shared local environment defaults, formatting, linting, unit testing, development, cleanup, and git hooks.
  • package.nix extends the base profile with package integration tests, Docker Compose lifecycle, package builds, checks, and package help. This repository imports it from devenv.nix.
  • microservice.nix extends 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:

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
}