go-bricks

module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2025 License: MIT

README ΒΆ

GoBricks

CI Go Report Card codecov Go Reference

Enterprise-grade building blocks for Go microservices

GoBricks is an open-source, enterprise-grade Go framework for building robust microservices with modular, reusable components. It provides a complete foundation for developing production-ready applications with built-in support for HTTP servers, AMQP messaging, multi-database connectivity, and clean architecture patterns.

πŸš€ Features

  • πŸ—οΈ Modular Architecture: Component-based design with dependency injection
  • 🌐 HTTP Server: Echo-based server with comprehensive middleware ecosystem
  • πŸ—„οΈ Multi-Database Support: PostgreSQL and Oracle with unified interface
  • πŸ“¨ AMQP Messaging: RabbitMQ integration with automatic reconnection
  • βš™οΈ Configuration Management: Koanf-based config with multiple sources (YAML, env vars)
  • πŸ“Š Built-in Observability: Request tracking, performance metrics, structured logging
  • πŸ”§ Database Migrations: Flyway integration for schema management
  • 🧩 Plugin System: Easy module registration and lifecycle management
  • πŸš€ Production Ready: Used in enterprise environments

πŸ“¦ Installation

go mod init your-service
go get github.com/gaborage/go-bricks@latest

πŸƒ Quick Start

1. Create Your Application
// cmd/main.go
package main

import (
    "log"
    "github.com/gaborage/go-bricks/app"
)

func main() {
    framework, err := app.New()
    if err != nil {
        log.Fatal(err)
    }
    
    // Register your modules here
    
    if err := framework.Run(); err != nil {
        log.Fatal(err)
    }
}
2. Create Configuration
# config.yaml
app:
  name: "my-service"
  version: "v1.0.0"
  env: "development"

server:
  port: 8080

database:
  type: "postgresql"
  host: "localhost"
  port: 5432
  database: "mydb"
  username: "postgres"
  password: "password"

log:
  level: "info"
  pretty: true
3. Create a Module
// internal/modules/example/module.go
package example

import (
    "github.com/labstack/echo/v4"
    "github.com/gaborage/go-bricks/app"
    "github.com/gaborage/go-bricks/server"
)

type Module struct {
    deps *app.ModuleDeps
}

func (m *Module) Name() string {
    return "example"
}

func (m *Module) Init(deps *app.ModuleDeps) error {
    m.deps = deps
    return nil
}

func (m *Module) RegisterRoutes(hr *server.HandlerRegistry, e *echo.Echo) {
    server.GET(hr, e, "/hello", m.hello)
}

func (m *Module) Shutdown() error {
    return nil
}

// Enhanced handler signature: focuses on business logic
type HelloReq struct {
    Name string `query:"name" validate:"required"`
}

type HelloResp struct {
    Message string `json:"message"`
}

func (m *Module) hello(req HelloReq, _ server.HandlerContext) (HelloResp, server.IAPIError) {
    return HelloResp{Message: "Hello " + req.Name}, nil
}
4. Register and Run
// cmd/main.go (updated)
func main() {
    framework, err := app.New()
    if err != nil {
        log.Fatal(err)
    }
    
    // Register modules
    framework.RegisterModule(&example.Module{})
    
    if err := framework.Run(); err != nil {
        log.Fatal(err)
    }
}

πŸ—οΈ Architecture

GoBricks follows a modular architecture where each business domain is implemented as a module:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               GoBricks App              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Module A  β”‚  Module B  β”‚  Module C     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  HTTP Server β”‚ Database β”‚ Messaging     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Logger β”‚ Config β”‚ Migrations β”‚ Utils   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Module System

Each module implements the Module interface:

type Module interface {
    Name() string
    Init(deps *ModuleDeps) error
    RegisterRoutes(hr *server.HandlerRegistry, e *echo.Echo)
    Shutdown() error
}

Modules have access to shared dependencies:

type ModuleDeps struct {
    DB        database.Interface  // Database connection
    Logger    logger.Logger       // Structured logger
    Messaging messaging.Client    // AMQP client
}

### Enhanced Handlers: Result and Envelope

Handlers use a type-safe signature and return either a data type or a Result wrapper to control status codes and headers:

```go
// Create returns 201 Created
func (m *Module) createUser(req CreateReq, _ server.HandlerContext) (server.Result[User], server.IAPIError) {
    user := User{ /* ... */ }
    return server.Created(user), nil
}

// Update returns 202 Accepted
func (m *Module) updateUser(req UpdateReq, _ server.HandlerContext) (server.Result[User], server.IAPIError) {
    updated := User{ /* ... */ }
    return server.Accepted(updated), nil
}

// Delete returns 204 No Content (no body)
func (m *Module) deleteUser(req DeleteReq, _ server.HandlerContext) (server.NoContentResult, server.IAPIError) {
    return server.NoContent(), nil
}

All responses are wrapped in a standard envelope on success:

{
  "data": { /* your payload */ },
  "meta": {
    "timestamp": "2025-01-12T12:34:56Z",
    "traceId": "uuid-or-request-id"
  }
}

Errors are returned in a consistent envelope with codes and optional details (details in dev):

{
  "error": {
    "code": "NOT_FOUND",
    "message": "User not found",
    "details": { /* dev only */ }
  },
  "meta": {
    "timestamp": "2025-01-12T12:34:56Z",
    "traceId": "uuid-or-request-id"
  }
}
Migration: echo.Context to Enhanced Handlers

Minimal steps to migrate an endpoint from raw Echo to the enhanced pattern:

  1. Change Module interface and route registration

Before:

func (m *Module) RegisterRoutes(e *echo.Echo) error {
    e.GET("/users/:id", m.getUser)
    return nil
}

func (m *Module) getUser(c echo.Context) error {
    id := c.Param("id")
    // ...parse, validate, fetch
    return c.JSON(http.StatusOK, user)
}

After:

type GetUserReq struct {
    ID int `param:"id" validate:"min=1"`
}

type UserResp struct { /* fields */ }

func (m *Module) RegisterRoutes(hr *server.HandlerRegistry, e *echo.Echo) {
    server.GET(hr, e, "/users/:id", m.getUser)
}

func (m *Module) getUser(req GetUserReq, _ server.HandlerContext) (UserResp, server.IAPIError) {
    // ...business logic only
    // on missing: return UserResp{}, server.NewNotFoundError("User")
    return UserResp{/* ... */}, nil
}
  1. Use struct tags for binding and validation
  • param:"id", query:"q", header:"Authorization", and JSON body tags.
  • Add validate:"..." using validator/v10 rules.
  1. Set custom statuses with Result helpers when needed
  • 201: return server.Created(resp), nil
  • 202: return server.Accepted(resp), nil
  • 204: return server.NoContent(), nil (no body)

Notes

  • The framework auto-wraps responses into { data, meta } and errors into { error, meta }.
  • Existing Echo middleware remains compatible; tracing uses X-Request-ID (generated if missing).

Common Pitfalls

  • Ensure Echo’s validator is set. Using app.New() wires server.NewValidator() automatically. If you build Echo manually, set e.Validator = server.NewValidator().
  • 204 responses must not include a body. Use server.NoContent().
  • To set headers (e.g., Location on 201), return a server.Result[T]{ Data: t, Status: http.StatusCreated, Headers: http.Header{"Location": {"/users/123"}} }.
  • Binding precedence: JSON body β†’ path params β†’ query params β†’ headers. Later sources overwrite earlier values.
  • []string binding: use repeated query params (?names=a&names=b) or comma-separated headers (X-Items: a, b).
  • Error details appear only in development; production hides internal details for 5xx.
  • Handlers get server.HandlerContext with optional Echo access for advanced cases; most handlers won’t need it.

Old β†’ New Mapping (quick reference)

  • c.Param("id") β†’ ID int \\param:"id"\`` in request struct
  • c.QueryParam("q") β†’ Q string \\query:"q"\``
  • Repeated query values β†’ Tags []string \\query:"tags"\`` with ?tags=a&tags=b
  • Header read β†’ Auth string \\header:"Authorization"\``
  • Manual bind/validate β†’ Framework binds and calls c.Validate() for you (add validate:"..." tags)
  • return c.JSON(200, data) β†’ return data, nil
  • return c.JSON(201, data) β†’ return server.Created(data), nil
  • return c.NoContent(204) β†’ return server.NoContent(), nil
  • Errors: echo.NewHTTPError(404) β†’ server.NewNotFoundError("Resource"); 400 β†’ server.NewBadRequestError("..."); 409 β†’ server.NewConflictError("...")

## πŸ—„οΈ Database Support

GoBricks supports multiple databases through a unified interface:

### PostgreSQL
```yaml
database:
  type: "postgresql"
  host: "localhost"
  port: 5432
  database: "myapp"
  username: "postgres"
  password: "password"
  ssl_mode: "disable"
Oracle
database:
  type: "oracle"
  host: "localhost"
  port: 1521
  service_name: "ORCL"
  username: "system"
  password: "password"
Oracle Tips
  • Reserved identifiers: Oracle reserves many keywords like NUMBER. If your table has a column named number, you must quote it in SQL (e.g., "NUMBER" or "number", matching how it was created). GoBricks' query builder helps with this.
  • Placeholders: Oracle uses :1, :2, ... (or named binds like :id), not $1.

Best practice: avoid reserved identifiers in schema. Prefer descriptive names like account_number instead of number. If you need to rename:

-- Use the exact quoted identifier if it was created quoted
ALTER TABLE accounts RENAME COLUMN "NUMBER" TO account_number;

Example using the query builder to safely insert into a table with a reserved column:

import (
    brdb "github.com/gaborage/go-bricks/database"
    "github.com/Masterminds/squirrel"
)

qb := brdb.NewQueryBuilder(brdb.Oracle)

// Will quote the reserved column name on Oracle automatically
insert := qb.
    InsertWithColumns("accounts", "id", "name", "number", "balance", "created_at").
    Values(1, "John", "12345", 100.50, squirrel.Expr(qb.BuildCurrentTimestamp()))

sql, args, _ := insert.ToSql()
// sql: INSERT INTO accounts (id,name,"number",balance,created_at) VALUES (:1,:2,:3,:4,:5)
// args: [1 "John" "12345" 100.5]

πŸ“¨ Messaging

AMQP messaging with RabbitMQ:

messaging:
  broker_url: "amqp://guest:guest@localhost:5672/"
  exchange: "my-service"
  routing_key: "events"
  virtual_host: "/"

βš™οΈ Configuration

Configuration management with multiple sources (priority order):

  1. Environment Variables (highest priority)
  2. Environment-specific YAML (config.production.yaml)
  3. Base YAML (config.yaml)
  4. Default Values (lowest priority)
Environment Variables
export APP_NAME="my-service"
export DATABASE_HOST="prod-db.company.com"
export DATABASE_PASSWORD="secure_password"
export LOG_LEVEL="info"

πŸ“Š Observability

Structured Logging
deps.Logger.Info().
    Str("user_id", userID).
    Int("count", items).
    Msg("Processing user items")
Request Tracking

Automatic tracking of:

  • HTTP request/response metrics
  • Database query performance
  • AMQP message statistics
  • Request correlation IDs
Health Checks

Built-in endpoints:

  • /health - Basic health check
  • /ready - Readiness probe with database connectivity

πŸš€ Production Features

  • Graceful Shutdown: Coordinated shutdown of all components
  • Connection Pooling: Optimized database connection management
  • Rate Limiting: Built-in request rate limiting
  • CORS Support: Configurable cross-origin resource sharing
  • Request Validation: Automatic request validation with go-playground/validator
  • Security Headers: Essential security headers included
  • Recovery Middleware: Panic recovery with logging

πŸ“š Documentation

πŸ› οΈ Examples

See the examples/ directory for complete examples:

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

GoBricks was extracted from production enterprise applications and battle-tested in real-world microservice architectures.


Built with ❀️ for the Go community

🌐 HTTP Client

GoBricks includes a small HTTP client with a fluent builder, request/response interceptors, default headers and basic auth, plus a retry mechanism with exponential backoff and jitter.

  • Create a client with defaults:
package main

import (
    "context"

    brhttp "github.com/gaborage/go-bricks/http"
    "github.com/gaborage/go-bricks/logger"
)

func main() {
    log := logger.New("info", false)
    client := brhttp.NewClient(log)

    ctx := context.Background()
    resp, err := client.Get(ctx, &brhttp.Request{URL: "https://example.com"})
    if err != nil {
        log.Error().Err(err).Msg("request failed")
        return
    }
    log.Info().Int("status", resp.StatusCode).Msg("ok")
}
  • Configure via builder, including retries and interceptors:
package main

import (
    "context"
    nethttp "net/http"
    "time"

    brhttp "github.com/gaborage/go-bricks/http"
    "github.com/gaborage/go-bricks/logger"
)

func main() {
    log := logger.New("info", false)
    client := brhttp.NewBuilder(log).
        WithTimeout(5 * time.Second).
        WithDefaultHeader("Accept", "application/json").
        WithBasicAuth("user", "pass").
        WithRequestInterceptor(func(ctx context.Context, r *nethttp.Request) error {
            r.Header.Set("X-Correlation-ID", "demo-123")
            return nil
        }).
        WithResponseInterceptor(func(ctx context.Context, r *nethttp.Request, resp *nethttp.Response) error {
            if resp.StatusCode >= 500 {
                log.Warn().Int("status", resp.StatusCode).Msg("server error")
            }
            return nil
        }).
        // Retries: exponential backoff with full jitter
        // delay per attempt = baseDelay * 2^attempt, jittered in [0, delay), capped at 30s
        WithRetries(3, 200*time.Millisecond).
        Build()

    ctx := context.Background()
    req := &brhttp.Request{URL: "https://example.com/api"}
    resp, err := client.Get(ctx, req)
    if err != nil {
        log.Error().Err(err).Msg("request failed")
        return
    }
    log.Info().Int("status", resp.StatusCode).Msg("ok")
}

Retry/backoff details:

  • Retries on: transport/network errors, timeouts, and 5xx responses.
  • No retries on 4xx.
  • Backoff: exponential (base Γ— 2^attempt), full jitter, max 30s.
  • Interceptor errors are not retried and are returned immediately.

Directories ΒΆ

Path Synopsis
Package app provides the core application framework for the MDW API Platform.
Package app provides the core application framework for the MDW API Platform.
Package database provides cross-database query building utilities
Package database provides cross-database query building utilities
examples
enhanced-handlers command
Package main demonstrates the enhanced handler system usage.
Package main demonstrates the enhanced handler system usage.
http command
oracle command
params command
trace-propagation command
Package main demonstrates the HTTP client's automatic trace ID propagation
Package main demonstrates the HTTP client's automatic trace ID propagation
Package http provides a small, composable HTTP client with request/response interceptors, default headers, basic auth, and a retry mechanism with exponential backoff and jitter.
Package http provides a small, composable HTTP client with request/response interceptors, default headers, basic auth, and a retry mechanism with exponential backoff and jitter.
internal
Package logger provides logging functionality with zerolog adapter
Package logger provides logging functionality with zerolog adapter
Package messaging provides a unified interface for message queue operations.
Package messaging provides a unified interface for message queue operations.
Package migration provides integration with Flyway for database migrations
Package migration provides integration with Flyway for database migrations
Package server provides enhanced HTTP handler functionality with type-safe request/response handling.
Package server provides enhanced HTTP handler functionality with type-safe request/response handling.

Jump to

Keyboard shortcuts

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