# Table of Contents

1. [Overview](#overview)
2. [Factor 1: Codebase](#factor-1-codebase)
3. [Factor 2: Dependencies](#factor-2-dependencies)
4. [Factor 3: Config](#factor-3-config)
5. [Factor 4: Backing Services](#factor-4-backing-services)
6. [Factor 5: Build, Release, Run](#factor-5-build-release-run)
7. [Factor 6: Processes](#factor-6-processes)
8. [Factor 7: Port Binding](#factor-7-port-binding)
9. [Factor 8: Concurrency](#factor-8-concurrency)
10. [Factor 9: Disposability](#factor-9-disposability)
11. [Factor 10: Dev/Prod Parity](#factor-10-devprod-parity)
12. [Factor 11: Logs](#factor-11-logs)
13. [Factor 12: Admin Processes](#factor-12-admin-processes)

---

## Overview

This template follows [12-Factor App](https://12factor.net/) methodology for production-ready deployments.

---

## Factor 1: Codebase

One codebase tracked in version control, multiple deploys.

- Git repository with main branch
- Environment-specific configs via environment variables
- Same codebase deploys to local, staging, production

```bash
git clone https://gitlab.example.com/backend.git
cd backend
git checkout main
```

---

## Factor 2: Dependencies

Explicitly declare and isolate dependencies.

```go
// go.mod
require (
    github.com/gin-gonic/gin v1.12.0
    github.com/jackc/pgx/v5 v5.5.0
    github.com/MicahParks/keyfunc/v3 v3.0.0
)
```

Use `go mod vendor` for full isolation:

```bash
go mod vendor
```

Never rely on system-wide packages.

---

## Factor 3: Config

Store config in environment variables.

**Configuration Loading:**

```go
import "github.com/golobby/env/v2"

func Load() (*Config, error) {
    env := os.Getenv("APP_ENV")
    if env == "local" {
        dotenv.Load()
    }
    var cfg Config
    if err := env.Parse(&cfg); err != nil {
        return nil, err
    }
    return &cfg, nil
}
```

**Local:** Use `.env` file via golobby/dotenv  
**Staging/Prod:** Use OpenBao v2 KV secrets

See [CONFIGURATION](CONFIGURATION.md) for details.

---

## Factor 4: Backing Services

Treat backing services as attached resources via config.

```go
// Connection strings via config, not hardcoded
dsn := cfg.Persistence.DSN          // PostgreSQL
jwksURL := cfg.Keycloak.JWKSURL     // Keycloak
tempoEndpoint := cfg.Tempo.Endpoint // OpenTelemetry
```

**Health checks:**

```go
r.GET("/healthz", func(c *gin.Context) {
    checks := map[string]bool{
        "database": pingDB(),
        "cache":    pingCache(),
    }
    for name, ok := range checks {
        if !ok {
            c.JSON(503, gin.H{"status": "unhealthy", "checks": checks})
            return
        }
    }
    c.JSON(200, gin.H{"status": "ok"})
})
```

---

## Factor 5: Build, Release, Run

Strict separation of build, release, and run stages.

**GitLab CI Pipeline:**

```yaml
stages:
  - build
  - test
  - release
  - deploy

build:
  stage: build
  script:
    - go build -ldflags "-X main.version=$CI_COMMIT_SHA" -o bin/server ./cmd/server

test:
  stage: test
  script:
    - go test -race -coverprofile=coverage.out ./...
  coverage: '/total:\s+\(statements\)\s+(\d+\.\d+)%/'

release:
  stage: release
  script:
    - docker build -t $IMAGE_NAME:$CI_COMMIT_SHA .
    - docker push $IMAGE_NAME:$CI_COMMIT_SHA
  rules:
    - main

deploy:
  stage: deploy
  script:
    - kubectl set image deployment/server server=$IMAGE_NAME:$CI_COMMIT_SHA
  environment:
    name: production
```

See [DEPLOYMENT](DEPLOYMENT.md) for CI/CD details.

---

## Factor 6: Processes

Stateless processes with no shared state.

```go
// Share-nothing architecture
// User sessions stored in PostgreSQL, not memory
// File uploads to object storage, not local disk
// Cache in PostgreSQL unlogged tables, not process memory
```

All persistent data stored in backing services (PostgreSQL).

---

## Factor 7: Port Binding

Self-contained HTTP service.

```go
port := cfg.App.Port
if !strings.HasPrefix(port, ":") {
    port = ":" + port
}
log.Fatal(http.ListenAndServe(port, r.Handler()))
```

Export HTTP via port binding. No reverse proxy required, but compatible.

---

## Factor 8: Concurrency

Scale via process model.

```go
type WorkerPool struct {
    workers int
    jobs    chan Job
    wg      sync.WaitGroup
}

func (wp *WorkerPool) Start(ctx context.Context) {
    for i := 0; i < wp.workers; i++ {
        wp.wg.Add(1)
        go wp.worker(ctx, i)
    }
}

func (wp *WorkerPool) Submit(job Job) {
    wp.jobs <- job
}
```

Scale horizontally by running multiple instances.

---

## Factor 9: Disposability

Fast startup and graceful shutdown.

```go
func gracefulShutdown(sig os.Signal, server *http.Server) {
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    log.Printf("Received %s, shutting down gracefully...", sig)
    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server shutdown failed: %v", err)
    }
}

// SIGTERM handling
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<-sigChan
gracefulShutdown(<-sigChan, server)
```

**Startup:** Connect to DB, load config, initialize services  
**Shutdown:** Close connections, finish in-flight requests, release resources

---

## Factor 10: Dev/Prod Parity

Use Docker Compose for local development.

```yaml
version: '3.8'
services:
  app:
    build: .
    env_file: .env
    depends_on:
      - postgres
    ports:
      - "8080:8080"

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: messaging
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
```

Same PostgreSQL version in all environments.

---

## Factor 11: Logs

Structured logging to stdout only.

```go
import "log/slog"

log := slog.New(slog.NewJSONHandler(os.Stdout, nil))
log.Info("server started", "port", 8080, "env", "production")
```

No log files in container. Aggregate via external services (Loki, CloudWatch).

---

## Factor 12: Admin Processes

One-off admin tasks as CLI commands.

```bash
./messaging-be migrate up
./messaging-be migrate down 1
./messaging-be user list
./messaging-be health
```

See [DEPLOYMENT](DEPLOYMENT.md) for admin CLI details.
