Files
goplt/docs/content/playbook.md

620 lines
26 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# GoPlatform Boilerplate Playbook
**“Pluginfriendly SaaS/Enterprise Platform Go Edition”**
## 1⃣ ARCHITECTURAL IMPERATIVES (Goflavoured)
| Principle | Gospecific rationale | Enforcement Technique |
|-----------|-----------------------|------------------------|
| **Clean / 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**. |
| **Modular Monolith → Microserviceready** | A single binary is cheap in Go; later you can extract modules into separate services without breaking APIs. | Each module lives in its own Go **module** (`go.mod`) under `./modules/*`. The core loads them via the **Go plugin** system *or* static registration (preferred for CI stability). |
| **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 (What every Goplatform must ship)
| Module | 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 `cmd/main.go`; exported via `pkg/logger`. |
| **DI / Service Registry** | `type Container interface { Provide(constructor any) error; Invoke(fn any) error }` | `go.uber.org/dig` (or `fx` for lifecycle) | Core creates a `dig.New()` container, registers core services, then calls `container.Invoke(app.Start)`. |
| **Health & Metrics** | `type HealthChecker interface { Check(ctx context.Context) error }` | `github.com/prometheus/client_golang/prometheus`, `github.com/heptiolabs/healthcheck` | Expose `/healthz`, `/ready`, `/metrics`. |
| **Error Bus** | `type ErrorPublisher interface { Publish(err error) }` | Simple channelbased implementation + optional Sentry (`github.com/getsentry/sentry-go`) | Core registers a singleton `ErrorBus`. |
| **Auth (JWT + OIDC)** | `type Authenticator interface { GenerateToken(userID string, roles []string) (string, error); VerifyToken(token string) (*TokenClaims, error) }` | `github.com/golang-jwt/jwt/v5`, `github.com/coreos/go-oidc` | Token claims embed `sub`, `roles`, `tenant_id`. Middleware adds `User` to `context.Context`. |
| **Authorization (RBAC/ABAC)** | `type Authorizer interface { Authorize(ctx context.Context, perm Permission) error }` | Custom DSL, `github.com/casbin/casbin/v2` (optional) | Permission format: `"module.resource.action"`; core ships a simple inmemory resolver and a `casbin` adapter. |
| **Audit** | `type Auditor interface { Record(ctx context.Context, act AuditAction) error }` | Write to appendonly table (Postgres) or Elastic via `olivere/elastic` | Audits include `actorID`, `action`, `targetID`, `metadata`. |
| **Event Bus** | `type EventBus interface { Publish(ctx context.Context, ev Event) error; Subscribe(topic string, handler EventHandler) }` | `github.com/segmentio/kafka-go` (for production) + inprocess fallback | Core ships an **inprocess bus** used by tests and a **Kafka bus** for real deployments. |
| **Persistence (Repository)** | `type UserRepo interface { FindByID(id string) (*User, error); Create(u *User) error; … }` | `entgo.io/ent` (codegen ORM) **or** `gorm.io/gorm` | Core provides an `EntClient` wrapper that implements all core repos. |
| **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) | Expose a `JobRegistry` where modules can register periodic 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`, `github.com/IBM/sarama` (for push) | Core supplies an `EmailNotifier` and a `WebhookNotifier`. |
| **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 Ents `Client` interceptor. |
All *public* interfaces live under `pkg/` so that plugins can import them without pulling in implementation details. The concrete implementations stay in `internal/` (or separate go.mod modules) and are **registered with the container** during bootstrap.
---
## 3⃣ MODULE (PLUGIN) FRAMEWORK
### 3.1 Interface that every module must implement
```go
// 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**
```go
// 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**
```go
// 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)
```go
// 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 MODULE **Blog**
```
modules/
└─ blog/
├─ go.mod # (module github.com/yourorg/blog)
├─ module.yaml
├─ internal/
│ ├─ api/
│ │ └─ handler.go
│ ├─ domain/
│ │ ├─ post.go
│ │ └─ post_repo.go
│ └─ service/
│ └─ post_service.go
└─ pkg/
└─ module.go
```
### 4.1 `module.yaml`
```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 Go implementation
```go
// pkg/module.go
package blog
import (
"github.com/yourorg/platform/pkg/module"
"go.uber.org/fx"
)
type BlogModule struct{}
func (b BlogModule) Name() string { return "blog" }
func (b BlogModule) Init() fx.Option {
return fx.Options(
// Register repository implementation
fx.Provide(NewPostRepo),
// Register service layer
fx.Provide(NewPostService),
// Register HTTP handlers (using Gin)
fx.Invoke(RegisterHandlers),
// Register permissions (optional just for documentation)
fx.Invoke(RegisterPermissions),
)
}
func (b BlogModule) Migrations() []func(*ent.Client) error {
// Ent migration generated in internal/ent/migrate
return []func(*ent.Client) error{
func(c *ent.Client) error { return c.Schema.Create(context.Background()) },
}
}
// Export a variable for the plugin loader
var Module BlogModule
```
**Handler registration (Gin example)**
```go
// internal/api/handler.go
package api
import (
"github.com/gin-gonic/gin"
"github.com/yourorg/blog/internal/service"
"github.com/yourorg/platform/pkg/perm"
"github.com/yourorg/platform/pkg/auth"
)
func RegisterHandlers(r *gin.Engine, svc *service.PostService, authz auth.Authorizer) {
grp := r.Group("/api/v1/blog")
grp.Use(auth.AuthMiddleware()) // verifies JWT, injects user in context
// POST /posts
grp.POST("/posts", func(c *gin.Context) {
if err := authz.Authorize(c.Request.Context(), perm.BlogPostCreate); err != nil {
c.JSON(403, gin.H{"error": "forbidden"})
return
}
// decode request, call svc.Create, return 201…
})
// GET /posts/:id (similar)
}
```
**Repository using Ent**
```go
// 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)**
```go
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**
```go
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**
```go
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)
```yaml
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-alpine``scratch` 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**
```go
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**
```bash
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(...)`:
```go
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! 🚀