go-cleanstack

command module
v0.0.0-...-fd3df41 Latest Latest
Warning

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

Go to latest
Published: Jan 12, 2026 License: GPL-3.0 Imports: 8 Imported by: 0

README

GoCleanstack

THIS IS A WORK IN PROGRESS !

DO NOT USE IN PRODUCTION !

A production-ready Go multi-module workspace demonstrating Clean Architecture principles with Domain-Driven Design and Hexagonal Architecture, governed by a unified CLI project manager.

Go Workspace Architecture

This project uses Go workspaces (go.work) to manage multiple independent modules:

Module Path Purpose
Root / CLI orchestrator, aggregates sub-applications
Common /internal/common Shared platform utilities (logging, config, errors)
User /internal/app/user User management application with CRUD operations

Benefits:

  • Independent versioning and testing per module
  • Clear boundaries between applications
  • Shared utilities without code duplication
  • Easy to add new applications (app2, app3, etc.)

Features

  • Clean Architecture: Strict separation of concerns with domain, application, and infrastructure layers
  • Hexagonal Architecture: Ports and adapters pattern for dependency inversion
  • Connect RPC: Modern RPC framework built on protobuf and HTTP/2
  • CLI Interface: Cobra-based command-line interface
  • Database: PostgreSQL with sqlx and Goose v3 migrations
  • Configuration: Viper-based configuration with environment-specific TOML files
  • Testing: Full test pyramid (unit, integration, e2e) with testcontainers
  • Docker: Production-ready Docker setup with docker-compose
  • Dev Tools: justfile, golangci-lint, pre-commit hooks

Architecture

This project follows Hexagonal Architecture principles with clear boundaries, organized as a Go workspace:

/                           - Root module (CLI orchestrator)
├── main.go                 - Entry point, aggregates sub-applications

/internal/common/           - Common module (shared utilities)
├── platform/
│   ├── logging/           - Logger abstraction (zap-based)
│   ├── config/            - Generic configuration loader
│   ├── apperr/            - Application error types
│   ├── clierr/            - CLI error handling
│   └── reqid/             - Request ID utilities
└── transport/
    └── connectx/          - Connect RPC interceptors and error handling

/internal/app/user/         - User module (user management application)
├── domain/                - Pure business logic (no external dependencies)
│   ├── entity/            - Domain entities (User, Role) with business rules
│   └── ports/             - Interfaces defining what domain needs
├── service/               - Use cases and business workflows
├── adapters/              - Bridges between domain and infrastructure
├── config/                - App-specific configuration
├── cmd/                   - App CLI commands (serve, version)
├── api/                   - Connect RPC API layer
│   ├── proto/             - Protobuf definitions
│   ├── gen/               - Generated code
│   ├── handler/           - Request handlers
│   └── server.go          - HTTP server
└── infra/                 - External concerns and frameworks
    └── persistence/       - Database access with DTOs and migrations
Dependency Rules
  • Domain has no dependencies on any other layer
  • Application depends only on domain (via ports)
  • Infrastructure provides implementations of domain ports via adapters
  • Infrastructure never imports domain directly (uses DTOs instead)

Project Structure

.
├── go.work                       # Go workspace definition
├── go.mod                        # Root module
├── main.go                       # Entry point (CLI orchestrator)
│
├── internal/
│   ├── common/                   # Common module (go.mod)
│   │   ├── platform/            # Cross-cutting utilities
│   │   │   ├── logging/         # Logger abstraction
│   │   │   ├── config/          # Generic config loader
│   │   │   ├── apperr/          # Application errors
│   │   │   ├── clierr/          # CLI error handling
│   │   │   └── reqid/           # Request ID utilities
│   │   └── transport/           # Transport utilities
│   │       └── connectx/        # Connect RPC interceptors
│   │
│   └── app/
│       └── user/                 # User module (go.mod)
│           ├── main.go          # App entry point (standalone use)
│           ├── cmd/             # App CLI commands
│           │   ├── root.go      # Root command with logger init
│           │   ├── serve.go     # HTTP server command
│           │   └── version.go   # Version command
│           ├── config/          # App-specific configuration
│           │   └── config.go
│           ├── domain/          # Core business logic
│           │   ├── entity/      # Domain entities
│           │   │   ├── user.go  # User entity with validation
│           │   │   └── role.go  # Role enum with business rules
│           │   └── ports/       # Port interfaces
│           │       └── repository.go
│           ├── service/         # Use cases
│           │   └── user_service.go
│           ├── adapters/        # Domain-to-infra adapters
│           │   └── user_repo_adapter.go
│           ├── api/             # Connect RPC API
│           │   ├── proto/       # Protobuf definitions
│           │   ├── gen/         # Generated code
│           │   ├── handler/     # Request handlers
│           │   ├── interceptor/ # Custom interceptors
│           │   └── server.go
│           ├── infra/           # Infrastructure layer
│           │   └── persistence/ # Database access
│           │       ├── db.go
│           │       ├── models.go
│           │       ├── user_repo.go
│           │       └── migrations/
│           └── tests/           # App-specific tests
│               ├── integration/ # Integration tests
│               ├── e2e/         # End-to-end tests
│               └── testutil/    # Test utilities
│
├── config_default.toml           # Default configuration
├── docker-compose.yml            # Docker orchestration
├── Dockerfile                    # Production container
├── justfile                      # Task runner
└── .golangci.yml                 # Linter configuration

Prerequisites

  • Go 1.25 or higher (optional if the app is built from Docker)
  • PostgreSQL 16 or higher
  • Docker and docker-compose (for testing and local development)
  • Node.js/npm (for local development)
  • Python 3 (for pre-commit hooks and local development)

If the installation of Python 3 is system-wide, you must install pre-commit yourself.

Getting Started

1. Install Dependencies

Simply run this command :

./configure

This command can be launch as many times as you want.

2. Configuration

The application uses environment-based configuration with two files:

  1. config_default.toml - Base configuration (always loaded)
  2. config_<env>.toml - Environment-specific overrides (merged with default)

Important: Set APP_ENV environment variable to choose configuration (REQUIRED - no default):

export APP_ENV=development  # or production

Or use [direnv](https://direnv.net/).

3. Start Database

Using Docker:

just up or docker-compose up -d or manually start PostgreSQL and update the database URL in your config file.

4. Run Migrations

just migrate-up or ./bin/cleanstack migrate up or go run . migrate up

5. Run the Server

just dev or go run . serve

The API will be available at http://localhost:4224.

Development Commands

This project uses just as a task runner. Available commands follow.

Development
just generate-api # Generate code from protobuf definitions
just dev          # Run development server (requires APP_ENV env var)
Testing
just test         # Run unit tests
just test-int     # Run integration tests (requires Docker)
just test-e2e     # Run end-to-end tests (requires Docker)
just test-all     # Run all tests
just test-cover   # Generate test coverage report
Database
just migrate-up   # Run database migrations
just migrate-down # Rollback database migrations
Code Quality
just lint         # Run linter
just lint-fix     # Run linter with auto-fix
Build
just build        # Build binary to bin/cleanstack
Docker
just up           # Start services with docker-compose
just down         # Stop services
just logs         # View service logs
Cleanup
just clean        # Remove build artifacts and coverage files

Testing

This project implements a comprehensive test pyramid:

Unit Tests

Run unit tests that don't require external dependencies:

just test
# or
go test ./...

Unit tests are co-located with the code they test:

  • internal/app/user/domain/entity/user_test.go - Domain entity tests
  • internal/app/user/domain/entity/role_test.go - Role enum tests
  • internal/app/user/service/user_service_test.go - Service layer tests
  • internal/common/platform/config/config_test.go - Configuration tests
Integration Tests

Run integration tests that use testcontainers for real PostgreSQL:

just test-int
# or
go test -tags=integration ./tests/integration/...

Integration tests verify:

  • Database operations with real PostgreSQL
  • Repository implementations
  • Data persistence and retrieval
E2E Tests

Run end-to-end tests that test the full application stack:

just test-e2e
# or
go test -tags=e2e ./tests/e2e/...

E2E tests verify:

  • Complete API flows
  • Request/response handling
  • Full dependency wiring
Test Coverage

Generate a test coverage report:

just test-cover
open coverage.html

API Usage

The application exposes a Connect RPC API. You can interact with it using any HTTP client or Connect-compatible client.

Example: Create a User
curl -X POST http://localhost:4224/user.v1.UserService/CreateUser \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "securepassword123",
    "role": "ROLE_USER"
  }'
Example: List Users
curl -X POST http://localhost:4224/user.v1.UserService/ListUsers \
  -H "Content-Type: application/json" \
  -d '{"limit": 10, "offset": 0}'
Example: Get User by ID
curl -X POST http://localhost:4224/user.v1.UserService/GetUser \
  -H "Content-Type: application/json" \
  -d '{"id": 1}'

Docker Deployment

Build and Run
# Build the image
docker build -t go-cleanstack .

# Run with docker-compose
docker-compose up

The docker-compose setup includes:

  • Application container (port varies by APP_ENV: 4224/4225/4226)
  • PostgreSQL database (port varies by APP_ENV: 5435/5436/5437)
  • Health checks and automatic migrations
Environment Variables

Configure the application using environment variables in docker-compose.yml:

environment:
  - APP_ENV=production

Code Generation

Protocol Buffer definitions are in internal/app/user/api/proto/user/v1/. To regenerate code:

just generate-api

This uses buf to generate:

  • Go structs from protobuf messages
  • Connect RPC service interfaces and handlers
  • Generated code is output to internal/app/user/api/gen/

Linting

This project uses golangci-lint with a comprehensive configuration:

just lint        # Check for issues
just lint-fix    # Auto-fix issues

Pre-commit hooks are configured to run linting automatically.

Contributing

This is a template/skeleton project demonstrating architecture patterns. To use it as a starting point:

  1. Fork or clone the repository
  2. Update module names in go.mod, go.work, and all sub-module go.mod files
  3. Update replace directives in all go.mod files to match your module path
  4. Update protobuf package names in internal/app/user/api/proto/
  5. Implement your domain entities and business logic
  6. Add corresponding service methods and API endpoints
  7. To add a new application, create a new module under internal/app/ (see CLAUDE.md for detailed steps)

Architecture Highlights

Hexagonal Architecture Benefits
  1. Testability: Domain logic can be tested without databases or HTTP
  2. Flexibility: Easy to swap infrastructure implementations
  3. Independence: Domain layer is completely isolated from frameworks
  4. Maintainability: Clear boundaries reduce cognitive load
The Adapter Pattern

The UserRepositoryAdapter demonstrates the adapter pattern:

// Domain defines what it needs (port)
type UserRepository interface {
    Create(ctx context.Context, user *entity.User) (*entity.User, error)
    GetByID(ctx context.Context, id int64) (*entity.User, error)
    List(ctx context.Context, offset, limit int) ([]*entity.User, int64, error)
}

// Infrastructure provides DTOs (no domain import)
type UserRepo struct {}
func (r *UserRepo) Create(ctx context.Context, dto *UserDTO) (*UserDTO, error)

// Adapter bridges domain and infrastructure
type UserRepositoryAdapter struct {
    infraRepo *persistence.UserRepo
}
func (a *UserRepositoryAdapter) Create(ctx context.Context, user *entity.User) (*entity.User, error) {
    dto := toDTO(user)           // Convert entity to DTO
    result, err := a.infraRepo.Create(ctx, dto)
    return toEntity(result), err // Convert DTO back to entity
}

This ensures infrastructure never imports domain, maintaining clean boundaries.

Resources

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
app/app1 module
app/user module
common module
pkg
file
From https://github.com/sevlyar/go-daemon
From https://github.com/sevlyar/go-daemon

Jump to

Keyboard shortcuts

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