sdk

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 0 Imported by: 0

README

Platform Go SDK

Build a service that runs on the platform: it declares what it offers, serves it over REST and gRPC, authenticates its callers, keeps each tenant's data apart, and talks to its peers without hardcoding a single URL.

# This repository is private, so the public module proxy cannot fetch it.
# Tell Go to go straight to git, and give git a credential for github.com.
go env -w GOPRIVATE=github.com/neokarl/*
git config --global url."git@github.com:".insteadOf "https://github.com/"

go get github.com/neokarl/sdk-go

Without GOPRIVATE the failure is a 404 from proxy.golang.org naming the module, which looks like a typo in the import path rather than the access problem it is.

Requires Go 1.25.4 or later.

The smallest service that works

package main

import (
	"context"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/neokarl/sdk-go/contracts"
	"github.com/neokarl/sdk-go/service"
)

func main() {
	svc := service.New(contracts.ServiceManifest{
		ID:              "inventory",
		Name:            "Inventory",
		Version:         "0.1.0",
		PlatformVersion: "^0.1.0",
		Type:            contracts.ServiceTypeAPI,
		APIBaseURL:      "http://localhost:8090",
	}, service.WithoutAuth())

	svc.GET("item.list", "/api/v1/items", func(c service.Context) error {
		return service.OK(c, []string{"widget", "sprocket"})
	})

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()
	if err := svc.Run(ctx, ":8090"); err != nil {
		log.Fatal(err)
	}
}

That is a complete service. It serves your route, /healthz, /readyz, and /service.manifest.json — the endpoint an operator points the platform at to install you. Registering a route also declares it in that manifest, so what you advertise and what you serve cannot drift apart.

Run it: go run ./examples/minimal. For the production-shaped version with auth, a database and peer calls, see examples/full.

What's in the box

One package per concern. Take what you need; nothing here requires anything else except contracts.

Package What it's for
service Serve REST and gRPC with the platform conventions built in
auth Verify who is calling, and whether they may
tenancy Keep tenants' data apart, enforced by the database
events Publish and consume domain events
workflow Run work that has to survive a restart
client Call peer services by name, not by URL
contracts The manifest and wire types
errors The platform error taxonomy
observability Structured logs and OpenTelemetry traces
host Assemble a platform host: boot order, middleware chain, health
boot Process startup: env, database, tracing, signals, teardown

Building the host, not a service

Most of this module is for building a service. Two packages are for the other side: host assembles the platform host itself — the ordered boot graph, the middleware chain, health, graceful shutdown, the gRPC identity bridge — and boot holds the startup primitives a host and a service both need: dotenv, typed env lookups, opening Postgres, installing tracing, signal handling and an ordered teardown stack.

host names no domain concept. It has no idea what a user, a tenant or a plugin registry is; a host's own services and its authentication policy are injected. That is what lets host assembly live in the SDK without the framework depending on anything in a host.

Three things worth knowing early

Authorization is one call, and it fails closed. service.WithAuth(verifier) installs authentication and authorization together. A route declaring service.Requires("inventory.read") on a service that configured neither WithAuth nor WithoutAuth panics at startup — a declared permission that silently checks nothing is worse than no permission at all.

svc := service.New(manifest, service.WithAuth(verifier))
svc.GET("item.get", "/api/v1/items/:id", h.get, service.Requires("inventory.read"))

Tenant isolation is the database's job, not your queries'. tenancy.Setup migrates your models, installs a row-level-security policy per table, and then verifies the result — refusing to start if isolation is not actually in force. A query that forgets to filter returns nothing rather than everything.

if err := tenancy.Setup(ctx, db, &Item{}); err != nil {
	return err
}

// Inside a handler: the policy sees the tenant, so this cannot cross the boundary.
err := tenancy.Scoped(c.Ctx(), db, func(tx *gorm.DB) error {
	return tx.Find(&items).Error
})

You call peers by operation, not by URL. The platform catalog resolves (service, operation) to a method and path, so a peer can move or rename a route without breaking you.

c, err := client.New(ctx, platformURL)
defer c.Close()

item, err := client.InvokeData[Item](ctx, c, client.Call{
	Service: "inventory",
	Op:      "item.get",
	Path:    map[string]string{"id": id},
})

Documentation

Development

go test ./...                              # unit tests
docker compose -f docker-compose.test.yml up -d
TEST_POSTGRES_DSN='postgres://sdk:sdk@localhost:55432/sdk?sslmode=disable' \
TEST_REDIS_ADDR=localhost:56379 \
  go test ./...                            # including the integration tests
golangci-lint run

The tenancy and events integration tests skip without those services. They cover row-level tenant isolation and the durable event bus — the two things most worth covering — so run them before changing either package.

Contributing and security

Report vulnerabilities privately: see SECURITY.md. Changes are recorded in CHANGELOG.md.

License

Apache 2.0 — see LICENSE.

Documentation

Overview

Package sdk is the Go SDK for building services on the platform.

A service is a self-contained backend that declares what it offers in a manifest, serves it over REST and/or gRPC, and is discovered and called by the platform and by other services. This module gives you the pieces to build one, organised one package per concern:

service       serve HTTP and gRPC, with the platform conventions built in
host          assemble a platform host: boot order, middleware chain, health
boot          process startup: env, database, tracing, signals, teardown
auth          authenticate the caller and authorize the operation
tenancy       scope data to a tenant, and make the database enforce it
events        publish and consume domain events
workflow      run work that must survive a restart
client        call other services by (service, operation) rather than URL
contracts     the manifest and wire types that define the contract
errors        the platform error taxonomy
observability structured logging and OpenTelemetry tracing

Getting started

The smallest useful service is a manifest, a route, and Run:

func main() {
    svc := service.New(contracts.ServiceManifest{
        ID:              "inventory",
        Name:            "Inventory",
        Version:         "0.1.0",
        PlatformVersion: "^0.1.0",
        Type:            contracts.ServiceTypeAPI,
        APIBaseURL:      "http://inventory:8090",
    })
    svc.GET("item.list", "/api/v1/items", listItems)

    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()
    if err := svc.Run(ctx, ":8090"); err != nil {
        log.Fatal(err)
    }
}

Each package's own documentation covers its concern in full. Start with github.com/neokarl/sdk-go/service.

Services and hosts

Most of this module is for building a *service*. Two packages are for the other side of the architecture: github.com/neokarl/sdk-go/host assembles the platform host a service registers with, and github.com/neokarl/sdk-go/boot holds the startup primitives both need. Neither knows anything about any host's or service's domain.

Versioning

Version is the version of this module. It is deliberately distinct from github.com/neokarl/sdk-go/contracts.Version, which is the version of the platform *contract* — the manifest shape and the rules a service must satisfy. A service declares which contract it targets via its manifest's PlatformVersion; the SDK version is just which release of this code you build against.

Index

Constants

View Source
const Version = "0.1.0"

Version is the semantic version of this SDK module.

It tracks the git tag this code was released under. It is not the platform contract version — see the package documentation for the distinction.

Variables

This section is empty.

Functions

This section is empty.

Types

This section is empty.

Directories

Path Synopsis
Package auth is the framework's authentication surface: it verifies OIDC/JWT bearer tokens against an issuer's JWKS, for both the HTTP edge (echo middleware) and the service plane (gRPC interceptors).
Package auth is the framework's authentication surface: it verifies OIDC/JWT bearer tokens against an issuer's JWKS, for both the HTTP edge (echo middleware) and the service plane (gRPC interceptors).
Package boot holds the process-level primitives every platform binary needs before it can do anything useful: read configuration from the environment, open the database, install tracing, and shut down cleanly on a signal.
Package boot holds the process-level primitives every platform binary needs before it can do anything useful: read configuration from the environment, open the database, install tracing, and shut down cleanly on a signal.
Package client is how a service calls other services.
Package client is how a service calls other services.
cmd
apidoc command
Command apidoc extracts this module's public API into a JSON document, so a documentation site can render real signatures instead of hand-maintaining a table that drifts.
Command apidoc extracts this module's public API into a JSON document, so a documentation site can render real signatures instead of hand-maintaining a table that drifts.
Package contracts holds the wire-format types the platform exposes to plugins and the frontend.
Package contracts holds the wire-format types the platform exposes to plugins and the frontend.
Package errors defines the platform's standard error taxonomy.
Package errors defines the platform's standard error taxonomy.
Package events is the framework's durable event bus.
Package events is the framework's durable event bus.
examples
full command
Command full is a production-shaped platform service: authentication and authorization, tenant-isolated persistence, tracing, events, and calls to peer services — wired in the order they have to be wired.
Command full is a production-shaped platform service: authentication and authorization, tenant-isolated persistence, tracing, events, and calls to peer services — wired in the order they have to be wired.
minimal command
Command minimal is the smallest complete platform service: a manifest, some routes, and Run.
Command minimal is the smallest complete platform service: a manifest, some routes, and Run.
Package host assembles a platform host — the process that serves the plugin catalog, fronts the browser, and exposes the built-in services other services call.
Package host assembles a platform host — the process that serves the plugin catalog, fronts the browser, and exposes the built-in services other services call.
internal
mtls
Package mtls builds mutual-TLS credentials for the gRPC service plane, so a call is both encrypted and authenticated at the channel: the server verifies the client's certificate and vice-versa.
Package mtls builds mutual-TLS credentials for the gRPC service plane, so a call is both encrypted and authenticated at the channel: the server verifies the client's certificate and vice-versa.
Package middleware bundles the Echo middleware stack used by the API server.
Package middleware bundles the Echo middleware stack used by the API server.
Package observability wires the two things every service needs to be debuggable in production: structured logging and distributed tracing.
Package observability wires the two things every service needs to be debuggable in production: structured logging and distributed tracing.
Package service is the ergonomic entry point for building a platform plugin's Go backend.
Package service is the ergonomic entry point for building a platform plugin's Go backend.
Package tenancy scopes a service's data to one tenant and makes the *database* enforce it, rather than trusting a dozen query paths to remember a WHERE clause.
Package tenancy scopes a service's data to one tenant and makes the *database* enforce it, rather than trusting a dozen query paths to remember a WHERE clause.
Package transport propagates the caller's identity across service-to-service gRPC calls, so a downstream service knows who originated a request rather than seeing it arrive anonymous.
Package transport propagates the caller's identity across service-to-service gRPC calls, so a downstream service knows who originated a request rather than seeing it arrive anonymous.
Package workflow runs work that must survive a restart.
Package workflow runs work that must survive a restart.
temporal
Package temporal runs github.com/neokarl/sdk-go/workflow jobs on Temporal.
Package temporal runs github.com/neokarl/sdk-go/workflow jobs on Temporal.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL