Skip to content

Latest commit

 

History

History
424 lines (299 loc) · 10.8 KB

File metadata and controls

424 lines (299 loc) · 10.8 KB

Go DDD Modular Monolith with Hexagonal Architecture

Go Version License

A Golang example project demonstrating Domain-Driven Design (DDD) in a Modular Monolith architecture with Hexagonal (Ports and Adapters) pattern.

Read the original Blogpost here:

Please read the first article, then proceed to the second and this repository to understand the reasoning behind the current structure.

Quick Links:

Table of Contents

Architecture Overview

This project combines three powerful architectural patterns:

Modular Monolith

  • Multiple independent modules (payment, payment-settings) in a single deployable unit
  • Each module has clear boundaries and communicates via well-defined interfaces
  • Modules can be developed, tested, and evolved independently
  • All modules share the same process and database, simplifying deployment and transactions

Hexagonal Architecture (Ports & Adapters)

  • Domain logic (hexagon core) is isolated from external concerns
  • Inbound adapters: REST controllers, cron jobs (drive the application)
  • Outbound adapters: Repositories, external APIs (driven by the application)
  • Ports: Interfaces that define contracts between core and adapters

Domain-Driven Design (DDD)

  • Clear separation between domain logic and infrastructure
  • Each module represents a bounded context
  • Domain entities and business rules are at the center

Project Structure

.
├── application/            # Application entry point
├── cmd/                    # CLI commands (Cobra)
│   ├── rest.go            # REST API server command
│   ├── cron_update_payment.go  # Cron job command
│   └── root.go            # Root command configuration
├── modules/               # Business modules (bounded contexts)
│   ├── payment/
│   │   ├── factory/       # Module factory for dependency injection
│   │   ├── internal/
│   │   │   ├── adapter/   # Inbound/Outbound adapters
│   │   │   │   ├── controller/  # REST controllers (inbound)
│   │   │   │   ├── cron/        # Cron jobs (inbound)
│   │   │   │   └── repository/  # Database repository (outbound)
│   │   │   ├── ports/     # Interface definitions
│   │   │   └── service/   # Business logic
│   │   ├── module.go      # Module registration
│   │   └── payment.go     # Domain entities / Public API for the domain/module
│   └── payment-settings/  # Similar structure
├── pkg/                   # Shared utilities
│   ├── config/            # Configuration management
│   ├── dbutils/           # Database utilities
│   ├── errors/            # Error handling
│   ├── logger/            # Logging utilities
│   ├── middlewares/       # HTTP middlewares
│   └── uniqueid/          # ID generation (ULID)
├── migrations/            # Database migrations
└── docker-compose.yml     # Docker setup

Features

  • Clean Architecture with clear separation of concerns
  • Hexagonal Architecture (Ports and Adapters)
  • Domain-Driven Design principles
  • RESTful API using Echo framework
  • PostgreSQL database with migrations
  • Structured logging with zerolog
  • Comprehensive testing (unit and E2E)
  • Docker support
  • Hot reload development with Air
  • CLI commands with Cobra
  • Mock generation with Mockery
  • Code linting with golangci-lint

Prerequisites

  • Go 1.24.4 or higher
  • Docker and Docker Compose (for database)
  • Make (for running commands)

Quick Start

Option A: One-Command Setup (Recommended for First Time)

If you want everything set up automatically:

# Clone the repository
git clone https://github.com/bxcodec/golang-ddd-modular-monolith-with-hexagonal.git
cd golang-ddd-modular-monolith-with-hexagonal

# Copy environment file
cp .env.example .env

# Install dependencies and start everything
make init
make up

The make up command will:

  1. Start PostgreSQL in Docker
  2. Start the application with hot reload (you'll need to run migrations separately, see below)

After the app starts, open a new terminal and run migrations:

# In a new terminal window
cd golang-ddd-modular-monolith-with-hexagonal
make migrate-up
# Press Enter when prompted to apply all migrations

The API will be available at http://localhost:9090

curl http://localhost:9090/health

Expected response:

{
  "status": "ok",
  "mode": "rest",
  "environment": "development"
}

CLI Commands

The application supports multiple commands via Cobra:

Start REST API Server

go run application/main.go rest

Run Cron Job

go run application/main.go cron-update-payment

Options:

  • --batch-size - Number of payments to process in one batch
  • --dry-run - Run in dry-run mode without making changes

Example:

go run application/main.go cron-update-payment --batch-size 100 --dry-run

Development

Hot Reload with Air

Air is automatically started when you run make up. It watches for file changes and rebuilds the application.

Running Tests

# Run unit tests
make test-unit

# Run E2E tests
make test-e2e

# Run all tests
make test-all

# Run tests with detailed output
make tests-complete

Code Formatting

make fmt

This will format your code using:

  • gofmt
  • gofumpt
  • goimports

Linting

make lint

Generate Mocks

make go-generate

This uses Mockery to generate mocks based on the .mockery.yml configuration.

Database Migrations

Apply Migrations

make migrate-up

Rollback Migrations

make migrate-down

Create New Migration

make migrate-create

Drop Database

make migrate-drop

Docker

Build Docker Image

make image-build

Run with Docker Compose

docker compose up

This will start both PostgreSQL and the application.

Makefile Commands

Run make help to see all available commands:

make help

Key commands:

  • make up - Start development environment
  • make down - Stop Docker containers
  • make destroy - Teardown everything (removes volumes)
  • make build - Build the binary
  • make tests - Run tests
  • make lint - Run linter
  • make fmt - Format code
  • make clean - Clean artifacts

Testing

The project includes both unit tests and E2E tests:

Unit Tests

Located alongside the code, testing individual components in isolation using mocks.

E2E Tests

Use testcontainers to spin up real PostgreSQL instances for integration testing.

Test Coverage

make test-all

Coverage reports are generated in coverage-all.out.

Architecture Benefits

Why Modular Monolith?

  • Simpler than microservices while maintaining modularity
  • Easy refactoring to microservices if needed (modules are already isolated)
  • Shared infrastructure reduces operational complexity
  • No network latency between modules

Why Hexagonal Architecture?

  • Business logic is independent of frameworks and tools
  • Easy to swap implementations (e.g., change database, HTTP framework)
  • Highly testable (mock adapters, test ports)
  • Clear separation between what the application does and how it does it

Why DDD?

  • Focus on core domain and domain logic
  • Ubiquitous language between developers and domain experts
  • Clear bounded contexts
  • Better code organization and maintainability

Module Communication

Modules communicate via well-defined ports (interfaces):

// payment module depends on payment-settings module
type IPaymentSettingsPort interface {
    GetPaymentSetting(id string) (PaymentSetting, error)
    // ...
}

// payment module uses the interface, not the concrete implementation
paymentModule := paymentfactory.NewModule(paymentfactory.ModuleConfig{
    DB:                  db,
    PaymentSettingsPort: paymentSettingsModule.Service, // dependency injection
})

This allows:

  • Clear contracts between modules
  • Easy testing with mocks
  • Potential extraction to microservices

Production Deployment

Build Binary

make build

The binary will be created as engine in the project root.

Run Binary

./engine rest

Docker Deployment

docker build -t payment-app:latest .
docker run -p 9090:9090 \
  -e POSTGRES_HOST=your-db-host \
  -e POSTGRES_PASSWORD=your-db-password \
  payment-app:latest rest

Contributing

Contributions are welcome. Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for new features
  4. Ensure all tests pass
  5. Run linter and formatter
  6. Submit a pull request

License

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

Related Resources

Author

Iman Tumorang

Acknowledgments

This project is inspired by various clean architecture implementations and aims to demonstrate a practical approach to building maintainable Go applications.