Files
goplt/docs/content/playbook.md
0x1d b4b918cba8
All checks were successful
CI / Test (pull_request) Successful in 27s
CI / Lint (pull_request) Successful in 20s
CI / Build (pull_request) Successful in 16s
CI / Format Check (pull_request) Successful in 2s
docs: ensure newline before lists across docs for MkDocs rendering
2025-11-06 10:56:50 +01:00

29 KiB
Raw Blame History

GoPlatform Boilerplate Playbook

“Pluginfriendly SaaS/Enterprise Platform Go Edition”

1 ARCHITECTURAL IMPERATIVES (Goflavoured)

Principle Gospecific rationale Enforcement Technique
Hexagonal Architecture Gos packagelevel visibility (internal/) naturally creates a boundary between core and plugins. Keep all domain code in internal/domain, expose only interfaces in pkg/.
Dependency Injection (DI) via Constructors Go avoids reflectionheavy containers; compiletime wiring is preferred. Use ubergo/fx (runtime graph) or ubergo/dig for optional runtime DI. For a lighter weight solution, use plain constructor injection with a small registry.
microMicroservices Architecture Each service is independently deployable from day one. Services communicate via gRPC/HTTP through service clients. Each service has its own entry point (cmd/{service}/), Go module (go.mod), database connection, and deployment. Services discover each other via Consul service registry.
Pluginfirst design Gos plugin package allows runtime loading of compiled .so files (Linux/macOS). Provide an IModule interface and a loader that discovers *.so files (or compiledin modules for CI).
APIFirst (OpenAPI + gin/gorilla) Guarantees languageagnostic contracts. Generate server stubs from an openapi.yaml stored in api/.
SecuritybyDesign Gos static typing makes it easy to keep auth data out of the request flow. Central middleware for JWT verification + contextbased user propagation.
Observability (OpenTelemetry) The Go ecosystem ships firstclass OTEL SDK. Instrument HTTP, DB, queues, and custom events automatically.
ConfigurationasCode Viper + Cobra give hierarchical config and flag parsing. Load defaults → file → env → secret manager (AWS Secrets Manager / Vault).
Testing & CI go test is fast; Testcontainers via testcontainers-go can spin up DB, Redis, Kafka. CI pipeline runs unit, integration, and contract tests on each PR.
Semantic Versioning & Compatibility Go modules already enforce version constraints. Core declares minimal required versions in go.mod and uses replace for local dev.

2 CORE KERNEL (Infrastructure Only)

The core kernel provides foundational infrastructure that all services depend on. It contains no business logic.

Component Public Interfaces (exported from pkg/) Recommended Packages Brief Implementation Sketch
Config type ConfigProvider interface { Get(key string) any; Unmarshal(v any) error } github.com/spf13/viper Load defaults (config/default.yaml), then env overrides, then optional secretstore.
Logger type Logger interface { Debug(msg string, fields ...Field); Info(...); Error(...); With(fields ...Field) Logger } go.uber.org/zap (or zerolog) Global logger is created in each service's cmd/{service}/main.go; exported via pkg/logger.
DI / Service Registry type Container interface { Provide(constructor any) error; Invoke(fn any) error } go.uber.org/fx (for lifecycle) Each service creates its own fx.New() container, registers service-specific services.
Health & Metrics type HealthChecker interface { Check(ctx context.Context) error } github.com/prometheus/client_golang/prometheus, github.com/heptiolabs/healthcheck Each service exposes /healthz, /ready, /metrics.
Error Bus type ErrorPublisher interface { Publish(err error) } Simple channelbased implementation + optional Sentry (github.com/getsentry/sentry-go) Each service registers its own ErrorBus.
Service Registry type ServiceRegistry interface { Register(ctx, service) error; Discover(ctx, name) ([]Service, error) } github.com/hashicorp/consul/api Consul-based service discovery. Services register on startup, clients discover via registry.
Observability type Tracer interface { StartSpan(ctx, name) (Span, context.Context) } go.opentelemetry.io/otel OpenTelemetry integration for distributed tracing across services.
Event Bus type EventBus interface { Publish(ctx context.Context, ev Event) error; Subscribe(topic string, handler EventHandler) } github.com/segmentio/kafka-go Kafka-based event bus for asynchronous cross-service communication.
Scheduler / Background Jobs type Scheduler interface { Cron(spec string, job JobFunc) error; Enqueue(q string, payload any) error } github.com/robfig/cron/v3, github.com/hibiken/asynq (Redisbacked) Shared infrastructure for background jobs.
Notification type Notifier interface { Send(ctx context.Context, n Notification) error } github.com/go-mail/mail (SMTP), github.com/aws/aws-sdk-go-v2/service/ses Shared infrastructure for notifications.
Multitenancy (optional) type TenantResolver interface { Resolve(ctx context.Context) (tenantID string, err error) } Header/ subdomain parser + JWT claim scanner Tenant ID is stored in request context and automatically added to SQL queries via Ent's Client interceptor.

2.1 CORE SERVICES (Independent Microservices)

Core business services are implemented as separate, independently deployable services:

Service Entry Point Responsibilities Service Client Interface
Auth Service cmd/auth-service/ JWT token generation/validation, authentication AuthServiceClient in pkg/services/auth.go
Identity Service cmd/identity-service/ User CRUD, password management, email verification IdentityServiceClient in pkg/services/identity.go
Authz Service cmd/authz-service/ Permission resolution, RBAC/ABAC authorization AuthzServiceClient in pkg/services/authz.go
Audit Service cmd/audit-service/ Audit logging, immutable audit records AuditServiceClient in pkg/services/audit.go
API Gateway cmd/api-gateway/ Request routing, authentication, rate limiting, CORS N/A (entry point)

Each service:

  • Has its own go.mod (or shared workspace)
  • Manages its own database connection pool and schema
  • Exposes gRPC server (and optional HTTP)
  • Registers with Consul service registry
  • Uses service clients for inter-service communication

All public interfaces live under pkg/ so that services can import them without pulling in implementation details. The concrete implementations stay in internal/ (for core kernel) or services/{service}/internal/ (for service implementations) and are registered with the container during service bootstrap.

Note: Business logic services (Auth, Identity, Authz, Audit) are NOT in the core kernel. They are separate services implemented in Epic 2.


3 MODULE (PLUGIN) FRAMEWORK

3.1 Interface that every module must implement

// pkg/module/module.go
package module

import (
    "context"
    "go.uber.org/fx"
)

// IModule is the contract a feature plugin must fulfil.
type IModule interface {
    // Name returns a unique, humanreadable identifier.
    Name() string
    // Init registers the module's services, routes, jobs, and permissions.
    // The fx.Options returned are merged into the app's lifecycle.
    Init() fx.Option
    // Migrations returns a slice of Ent migration functions (or raw SQL) that
    // the core will run when the platform starts.
    Migrations() []func(*ent.Client) error
}

3.2 Registration Mechanics

Two ways to get a module into the platform:

Approach When to use Pros Cons
Static registration each module imports core and calls module.Register(MyBlogModule{}) in its own init() Development, CI (no .so needed) Simpler, works on Windows; compiletime type safety Requires recompiling the binary for new modules
Runtime plugin loading compile each module as a go build -buildmode=plugin -o blog.so ./modules/blog Production SaaS where clients drop new modules, or separate microservice extraction Hotswap without rebuild Only works on Linux/macOS; plugins must be compiled with same Go version & same go.mod replace graph; debugging harder

Static registration example

// internal/registry/registry.go
package registry

import (
    "sync"
    "github.com/yourorg/platform/pkg/module"
)

var (
    mu      sync.Mutex
    modules = make(map[string]module.IModule)
)

func Register(m module.IModule) {
    mu.Lock()
    defer mu.Unlock()
    if _, ok := modules[m.Name()]; ok {
        panic("module already registered: " + m.Name())
    }
    modules[m.Name()] = m
}

func All() []module.IModule {
    mu.Lock()
    defer mu.Unlock()
    out := make([]module.IModule, 0, len(modules))
    for _, m := range modules {
        out = append(out, m)
    }
    return out
}

Plugin loader skeleton

// internal/pluginloader/loader.go
package pluginloader

import (
    "plugin"
    "github.com/yourorg/platform/pkg/module"
)

func Load(path string) (module.IModule, error) {
    p, err := plugin.Open(path)
    if err != nil {
        return nil, err
    }
    sym, err := p.Lookup("Module")
    if err != nil {
        return nil, err
    }
    mod, ok := sym.(module.IModule)
    if !ok {
        return nil, fmt.Errorf("invalid module type")
    }
    return mod, nil
}

Tip: Ship a tiny CLI (platformctl modules list) that scans ./plugins/*.so, loads each via Load, and prints Name(). This is a great sanity check for ops.

3.3 Permissions DSL (compiletime safety)

// pkg/perm/perm.go
package perm

type Permission string

func (p Permission) String() string { return string(p) }

var (
    // Core permissions
    SystemHealthCheck Permission = "system.health.check"

    // Blog module  generated by a small go:generate script
    BlogPostCreate Permission = "blog.post.create"
    BlogPostRead   Permission = "blog.post.read"
    BlogPostUpdate Permission = "blog.post.update"
    BlogPostDelete Permission = "blog.post.delete"
)

A codegen tool (go generate ./...) can scan each modules module.yaml for declared actions and emit a single perm.go file, guaranteeing no duplicate strings.


4 SAMPLE FEATURE SERVICE Blog Service

Each feature module is implemented as an independent service:

cmd/
└─ blog-service/
   └─ main.go              # Service entry point

services/
└─ blog/
   ├─ go.mod               # Service dependencies
   ├─ module.yaml          # Service manifest
   ├─ api/
   │   └─ blog.proto       # gRPC service definition
   ├─ internal/
   │   ├─ api/
   │   │   └─ handler.go
   │   ├─ domain/
   │   │   ├─ post.go
   │   │   └─ post_repo.go
   │   └─ service/
   │       └─ post_service.go
   └─ pkg/
       └─ module.go

4.1 module.yaml

name: blog
version: 0.1.0
dependencies:
  - core >= 1.3.0
permissions:
  - blog.post.create
  - blog.post.read
  - blog.post.update
  - blog.post.delete
routes:
  - method: POST
    path: /api/v1/blog/posts
    permission: blog.post.create
  - method: GET
    path: /api/v1/blog/posts/:id
    permission: blog.post.read

4.2 Service Entry Point

// cmd/blog-service/main.go
package main

import (
    "context"
    "github.com/yourorg/platform/internal/config"
    "github.com/yourorg/platform/internal/di"
    "github.com/yourorg/platform/services/blog/internal/api"
    "github.com/yourorg/platform/services/blog/internal/service"
    "go.uber.org/fx"
)

func main() {
    cfg := config.Load()
    
    fx.New(
        // Core kernel services
        di.CoreModule(cfg),
        
        // Blog service implementation
        fx.Provide(service.NewPostService),
        fx.Provide(service.NewPostRepo),
        
        // gRPC server
        fx.Provide(api.NewGRPCServer),
        
        // Service registry
        fx.Provide(di.ProvideServiceRegistry),
        
        // Start service
        fx.Invoke(startService),
    ).Run()
}

func startService(lc fx.Lifecycle, server *api.GRPCServer, registry registry.ServiceRegistry) {
    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            // Register with Consul
            registry.Register(ctx, &registry.ServiceInstance{
                ID:      "blog-service-1",
                Name:    "blog-service",
                Address: "localhost",
                Port:    8091,
            })
            
            // Start gRPC server
            return server.Start()
        },
        OnStop: func(ctx context.Context) error {
            registry.Deregister(ctx, "blog-service-1")
            return server.Stop()
        },
    })
}

4.3 Service Implementation

// services/blog/internal/service/post_service.go
package service

import (
    "context"
    "github.com/yourorg/platform/pkg/services"
    "github.com/yourorg/platform/services/blog/internal/domain"
)

type PostService struct {
    repo          *domain.PostRepo
    authzClient   services.AuthzServiceClient
    identityClient services.IdentityServiceClient
    auditClient   services.AuditServiceClient
}

func NewPostService(
    repo *domain.PostRepo,
    authzClient services.AuthzServiceClient,
    identityClient services.IdentityServiceClient,
    auditClient services.AuditServiceClient,
) *PostService {
    return &PostService{
        repo:          repo,
        authzClient:   authzClient,
        identityClient: identityClient,
        auditClient:   auditClient,
    }
}

func (s *PostService) CreatePost(ctx context.Context, req *CreatePostRequest) (*Post, error) {
    // Check permission via Authz Service
    if err := s.authzClient.Authorize(ctx, "blog.post.create"); err != nil {
        return nil, err
    }
    
    // Get user info via Identity Service
    user, err := s.identityClient.GetUser(ctx, req.AuthorID)
    if err != nil {
        return nil, err
    }
    
    // Create post
    post, err := s.repo.Create(ctx, &domain.Post{
        Title:    req.Title,
        Content:  req.Content,
        AuthorID: user.ID,
    })
    if err != nil {
        return nil, err
    }
    
    // Audit log via Audit Service
    s.auditClient.Record(ctx, &services.AuditAction{
        ActorID:  user.ID,
        Action:   "blog.post.create",
        TargetID: post.ID,
    })
    
    return post, nil
}

4.4 gRPC Handler

// services/blog/internal/api/handler.go
package api

import (
    "context"
    "github.com/yourorg/platform/services/blog/api/pb"
    "github.com/yourorg/platform/services/blog/internal/service"
)

type BlogServer struct {
    pb.UnimplementedBlogServiceServer
    service *service.PostService
}

func (s *BlogServer) CreatePost(ctx context.Context, req *pb.CreatePostRequest) (*pb.CreatePostResponse, error) {
    post, err := s.service.CreatePost(ctx, &service.CreatePostRequest{
        Title:    req.Title,
        Content:  req.Content,
        AuthorID: req.AuthorId,
    })
    if err != nil {
        return nil, err
    }
    
    return &pb.CreatePostResponse{
        Post: &pb.Post{
            Id:      post.ID,
            Title:   post.Title,
            Content: post.Content,
        },
    }, nil
}

Repository using Ent

// internal/domain/post_repo.go
package domain

import (
    "context"
    "github.com/yourorg/platform/internal/ent"
    "github.com/yourorg/platform/internal/ent/post"
)

type PostRepo struct{ client *ent.Client }

func NewPostRepo(client *ent.Client) *PostRepo { return &PostRepo{client} }

func (r *PostRepo) Create(ctx context.Context, p *Post) (*Post, error) {
    entPost, err := r.client.Post.
        Create().
        SetTitle(p.Title).
        SetContent(p.Content).
        SetAuthorID(p.AuthorID).
        Save(ctx)
    if err != nil {
        return nil, err
    }
    return fromEnt(entPost), nil
}

Result: Adding a new feature is just a matter of creating a new folder under modules/, implementing module.IModule, registering routes, permissions and migrations. The core automatically wires everything together.


5 INFRASTRUCTURE ADAPTERS (swapable, perenvironment)

Concern Implementation (Go) Where it lives
Database entgo.io/ent (codegen) internal/infra/ent/
Cache github.com/go-redis/redis/v9 internal/infra/cache/
Message Queue github.com/segmentio/kafka-go (Kafka) or github.com/hibiken/asynq (Redis) internal/infra/bus/
Blob Storage github.com/aws/aws-sdk-go-v2/service/s3 (or GCS) internal/infra/blob/
Email github.com/go-mail/mail (SMTP) internal/infra/email/
SMS / Push Twilio SDK, Firebase Cloud Messaging internal/infra/notify/
Secret Store AWS Secrets Manager (aws-sdk-go-v2) or HashiCorp Vault (github.com/hashicorp/vault/api) internal/infra/secret/

All adapters expose an interface in pkg/infra/… and are registered in the DI container as singletons.


6 OBSERVABILITY STACK

Layer Library What it does
Tracing go.opentelemetry.io/otel, go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp Autoinstrument HTTP, DB (Ent plugin), Kafka, Redis
Metrics github.com/prometheus/client_golang/prometheus Counter/Histogram per request, DB latency, job execution
Logging go.uber.org/zap (structured JSON) Global logger, requestscoped fields (request_id, user_id, tenant_id)
Error Reporting github.com/getsentry/sentry-go (optional) Capture panics & errors, link to trace ID
Dashboard Grafana + Prometheus + Loki (logs) Provide readymade dashboards in ops/ folder

Instrumentation example (HTTP)

import (
    "net/http"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

func main() {
    r := gin.New()
    // Wrap the entire router with OTEL middleware
    wrapped := otelhttp.NewHandler(r, "http-server")
    http.ListenAndServe(":8080", wrapped)
}

Metrics middleware

func PromMetrics() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        duration := time.Since(start).Seconds()
        method := c.Request.Method
        path := c.FullPath()
        status := fmt.Sprintf("%d", c.Writer.Status())
        requestDuration.WithLabelValues(method, path, status).Observe(duration)
    }
}

7 CONFIGURATION & ENVIRONMENT

config/
├─ default.yaml          # baseline values
├─ development.yaml
├─ production.yaml
└─ secrets/              # gitignored, loaded via secret manager

Bootstrap

func LoadConfig() (*Config, error) {
    v := viper.New()
    v.SetConfigName("default")
    v.AddConfigPath("./config")
    if err := v.ReadInConfig(); err != nil { return nil, err }

    env := v.GetString("environment") // dev / prod / test
    v.SetConfigName(env)
    v.MergeInConfig() // overrides defaults

    v.AutomaticEnv() // env vars win
    // optional: secret manager overlay
    return &Config{v}, nil
}

All services receive a *Config via DI.


8 CI / CD PIPELINE (GitHub Actions)

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      GO111MODULE: on
    steps:
      - uses: actions/checkout@v4
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - name: Cache Go modules
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
      - name: Install Tools
        run: |
          go install github.com/vektra/mockery/v2@latest
          go install github.com/golang/mock/mockgen@latest
      - name: Lint
        run: |
          go install golang.org/x/lint/golint@latest
          golint ./...
      - name: Unit Tests
        run: |
          go test ./... -cover -race -short
      - name: Integration Tests (Docker Compose)
        run: |
          docker compose -f ./docker-compose.test.yml up -d
          go test ./... -tags=integration -count=1
          docker compose -f ./docker-compose.test.yml down
      - name: Build Binaries
        run: |
          go build -ldflags="-X main.version=${{ github.sha }}" -o bin/platform ./cmd/platform
      - name: Publish Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.ref == 'refs/heads/main' }}
          tags: ghcr.io/yourorg/platform:${{ github.sha }}

Key points

  • go test ./... -tags=integration runs tests that spin up Postgres, Redis, Kafka via Testcontainers (github.com/testcontainers/testcontainers-go).
  • Linting via golint or staticcheck.
  • Docker image built from the compiled binary (multistage: golang:1.22-alpinescratch or distroless).
  • Semanticrelease can be added on top (semantic-release action) to tag releases automatically.

9 TESTING STRATEGY

Test type Tools Typical coverage
Unit testing, github.com/stretchr/testify, github.com/golang/mock Individual services, repositories (use inmemory DB or mocks).
Integration testcontainers-go for Postgres, Redis, Kafka; real Ent client Endtoend request → DB → event bus flow.
Contract pact-go or OpenAPI validator middleware (github.com/getkin/kin-openapi) Guarantees that modules do not break the published API.
Load / Stress k6 or vegeta scripts in perf/ Verify that auth middleware adds < 2ms latency per request.
Security gosec, zap for secret detection, OWASP ZAP for API scan Detect hardcoded secrets, SQL injection risk.

Example integration test skeleton

func TestCreatePost_Integration(t *testing.T) {
    ctx := context.Background()
    // Spin up a PostgreSQL container
    pg, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: testcontainers.ContainerRequest{
            Image:        "postgres:15-alpine",
            Env:          map[string]string{"POSTGRES_PASSWORD": "secret", "POSTGRES_DB": "test"},
            ExposedPorts: []string{"5432/tcp"},
            WaitingFor:   wait.ForListeningPort("5432/tcp"),
        },
        Started: true,
    })
    require.NoError(t, err)
    defer pg.Terminate(ctx)

    // Build DSN from container host/port
    host, _ := pg.Endpoint(ctx)
    dsn := fmt.Sprintf("postgres://postgres:secret@%s/test?sslmode=disable", host)

    // Initialize Ent client against that DSN
    client, err := ent.Open("postgres", dsn)
    require.NoError(t, err)
    defer client.Close()
    // Run schema migration
    err = client.Schema.Create(ctx)
    require.NoError(t, err)

    // Build the whole app using fx, injecting the test client
    app := fx.New(
        core.ProvideAll(),
        fx.Provide(func() *ent.Client { return client }),
        blog.Module.Init(),
        // ...
    )
    // Start the app, issue a HTTP POST request through httptest.Server,
    // assert 201 and DB row existence.
}

10 COMMON PITFALLS & SOLUTIONS (Gocentric)

Pitfall Symptom Remedy
Circular imports (core ↔ module) import cycle not allowed build error Keep interfaces only in pkg/. Core implements, modules depend on the interface only.
Toobig binary (all modules compiled in) Long build times, memory pressure on CI Use Go plugins for truly optional modules; keep core binary minimal.
Version mismatch with plugins Panic: plugin was built with a different version of package X Enforce a single replace directive for core in each modules go.mod (e.g., replace github.com/yourorg/platform => ../../platform).
Context leakage (request data not passed) Logs missing user_id, permission checks use zerovalue user Always store user/tenant info in context.Context via middleware; provide helper auth.FromContext(ctx).
Ent migrations outofsync Startup fails with “column does not exist” Run go generate ./... (ent codegen) and ent/migrate automatically at boot after all module migrations are collected.
Hardcoded permission strings Typos go unnoticed → 403 bugs Use the generated perm package (see §4.1) and reference constants everywhere.
Blocking I/O in request path 500ms latency spikes Offload longrunning work to asynq jobs or Kafka consumers; keep request handlers thin.
Overexposed HTTP handlers Missing auth middleware → open endpoint Wrap the router with a global security middleware that checks a whitelist and enforces Authorizer for everything else.
Memory leaks with goroutine workers Leak after many module reloads Use fx.Lifecycle to start/stop background workers; always cancel contexts on Stop.
Testing with real DB slows CI Pipeline > 10min Use testcontainers in parallel jobs, cache Docker images, or use inmemory SQLite for unit tests and only run DBheavy tests in a dedicated job.

11 QUICKSTART STEPS (What to code first)

  1. Bootstrap repo
    mkdir platform && cd platform
    go mod init github.com/yourorg/platform
    mkdir cmd internal pkg config modules
    touch cmd/main.go
    
  2. Create core container (internal/di/container.go) using dig. Register Config, Logger, DB, EventBus, Authenticator, Authorizer.
  3. Add Auth middleware (pkg/auth/middleware.go) that extracts JWT, validates claims, injects User into context.
  4. Implement Permission DSL (pkg/perm/perm.go) and a simple inmemory resolver (pkg/perm/resolver.go).
  5. Write module interface (pkg/module/module.go) and a static registry (internal/registry/registry.go).
  6. Add first plugin copy the Blog module skeleton from §4.
    cd modules/blog && go mod init github.com/yourorg/blog && go run ../.. (build & test).
  7. Wire everything in cmd/main.go using fx.New(...):
    app := fx.New(
        core.Module,               // registers core services
        fx.Invoke(registry.RegisterAllModules), // loads static modules
        fx.Invoke(func(lc fx.Lifecycle, r *gin.Engine) {
            lc.Append(fx.Hook{
                OnStart: func(context.Context) error {
                    go r.Run(":8080")
                    return nil
                },
                OnStop: func(ctx context.Context) error { return nil },
            })
        }),
    )
    app.Run()
    
  8. Run integration test (go test ./... -tags=integration) to sanitycheck DB + routes.
  9. Add CI workflow (copy the .github/workflows/ci.yml from §8) and push.
  10. Publish first Docker image (docker build -t ghcr.io/yourorg/platform:dev .).

After step10 you have a complete, productiongrade scaffolding that:

  • authenticates users via JWT (with optional OIDC),
  • authorizes actions through a compiletimechecked permission DSL,
  • logs/metrics/traces out of the box,
  • lets any team drop a new *.so or static module under modules/ to add resources,
  • ships with a working CI pipeline and a readytorun Docker image.

12 REFERENCE IMPLEMENTATION (public)

If you prefer to start from a real opensource baseline, check out the following community projects that already adopt most of the ideas above:

Repo Highlights
github.com/gomicroservices/cleanarch Cleanarchitecture skeleton, fx DI, Ent ORM, JWT auth
github.com/ory/hydra Full OIDC provider (good for auth reference)
github.com/segmentio/kafka-go examples Eventbus integration
github.com/ThreeDotsLabs/watermill Pub/sub abstraction usable as bus wrapper
github.com/hibiken/asynq Background job framework (Redis based)

Fork one, strip the business logic, and rename the packages to match your github.com/yourorg/platform namespace.


13 FINAL CHECKLIST (before you ship)

  • Core modules compiled & registered in internal/di.
  • module.IModule interface and static registry in place.
  • JWT auth + middleware + User context helper.
  • Permission constants generated & compiled in pkg/perm.
  • Ent schema + migration runner that aggregates all module migrations.
  • OpenTelemetry tracer/provider wired at process start.
  • Prometheus metrics endpoint (/metrics).
  • Health endpoints (/healthz, /ready).
  • Dockerfile (multistage) and docker-compose.yml for dev.
  • GitHub Actions pipeline (CI) passes all tests.
  • Sample plugin (Blog) builds, loads, registers routes, and passes integration test.
  • Documentation: README.md, docs/architecture.md, docs/extension-points.md.

Congratulations! You now have a robust, extensible Go platform boilerplate that can be the foundation for any SaaS, internal toolset, or microservice ecosystem you wish to build. Happy coding!