docs: add mkdocs, update links, add architecture documentation

This commit is contained in:
2025-11-05 07:44:21 +01:00
parent 6a17236474
commit 54a047f5dc
351 changed files with 3482 additions and 10 deletions

620
docs/content/playbook.md Normal file
View File

@@ -0,0 +1,620 @@
# 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! 🚀