Go Gin DDD Scaffold: Ready-to-Use Template with RocketMQ & Dual Databases

This article introduces a production-ready Go Gin DDD scaffold featuring four-layer architecture, RocketMQ event-driven messaging, dual MySQL/PostgreSQL databases, unified responses, and step-by-step guides for setup and new module development.

Go Development Architecture Practice
Go Development Architecture Practice
Go Development Architecture Practice
Go Gin DDD Scaffold: Ready-to-Use Template with RocketMQ & Dual Databases

Gin-Framework-DDD is an out-of-the-box Domain-Driven Design (DDD) scaffold for Go, built on the Gin web framework and RocketMQ message queue. It provides a complete project structure that follows strict DDD layering, enabling teams to quickly bootstrap web services that adhere to DDD principles.

Why Use DDD?

The author argues that DDD adoption depends on business complexity, not language choice. For projects with dozens of modules and hundreds of endpoints, DDD's explicit separation of concerns — domain logic, application orchestration, infrastructure details, and interface handling — improves maintainability and control. For smaller projects, simple layered architecture suffices.

Core Features

Strict DDD four-layer architecture: Domain, Application, Infrastructure, Interface

Gin framework for high-performance HTTP services

Event-driven design: domain events with RocketMQ producer/consumer

Dual database support: user database (MySQL) and order database (PostgreSQL) independently configurable

Unified response format with centralized error codes

Global middleware: logging, recovery, CORS

Optional email notifications triggered by order creation events via SMTP

Technology Stack

Go 1.21+ — Language version

Gin 1.9+ — HTTP framework

RocketMQ 5.3+ — Event message queue

MySQL 8.0+ — User database default

PostgreSQL 14+ — Order database default

YAML — Configuration file format

Project Structure

gin-ddd/
├── cmd/server/main.go          # Entry point, assembles layers, starts HTTP + MQ
├── config/config.yaml          # Application configuration
├── docs/init.sql               # MySQL initialization script (example)
├── internal/
│   ├── domain/                 # Domain layer
│   │   ├── model/
│   │   │   ├── order/order.go  # Order aggregate root
│   │   │   └── user/user.go    # User aggregate root
│   │   ├── repository/         # Repository interfaces
│   │   │   ├── order/order_repository.go
│   │   │   └── user/user_repository.go
│   │   ├── event/              # Domain events
│   │   │   ├── domain_event.go
│   │   │   ├── order_event.go
│   │   │   ├── user_event.go
│   │   │   └── event_publisher.go
│   │   ├── notification/mail_service.go  # Notification domain interface (email)
│   │   └── service/            # Domain services (reserved)
│   ├── application/            # Application layer
│   │   ├── dto/
│   │   │   ├── order/order_dto.go
│   │   │   └── user/user_dto.go
│   │   └── service/
│   │       ├── order/order_service.go  # Order application service (publishes events)
│   │       └── user/user_service.go    # User application service
│   ├── infrastructure/         # Infrastructure layer
│   │   ├── config/             # Configuration & DB initialization
│   │   ├── persistence/        # Repository implementations
│   │   │   ├── order/order_repository_impl.go
│   │   │   └── user/user_repository_impl.go
│   │   ├── mq/                 # RocketMQ implementation
│   │   │   ├── rocketmq_producer.go
│   │   │   └── rocketmq_consumer.go
│   │   ├── mail/               # SMTP email implementation
│   │   ├── middleware/         # Gin middleware
│   │   ├── common/response.go  # Unified response
│   │   └── constants/error_code.go  # Error codes
│   └── interfaces/             # Interface layer
│       ├── handler/            # HTTP handlers
│       ├── router/             # Route configuration
│       └── vo/                 # Request/response objects
└── pkg/utils/                  # Logging and utilities

Layer Responsibilities

Domain — internal/domain/ — Domain models, rules, events — No framework dependencies, business logic cohesion

Application — internal/application/ — Orchestrates domain objects, transaction boundaries — Thin and clear, does not implement business rules

Infrastructure — internal/infrastructure/ — DB, MQ, email, technical details — Provides implementations upward, details sink downward

Interface — internal/interfaces/ — HTTP request/response, routing — Handles external interaction, no business rules

Quick Start

1. Environment Preparation

Go 1.21+

MySQL 8.0+ and PostgreSQL 14+ (or choose one)

RocketMQ 5.3+ (optional)

2. Initialize Databases

Default configuration uses dual databases:

User DB: MySQL

Order DB: PostgreSQL

MySQL user database example (use docs/init.sql as starting point):

CREATE DATABASE IF NOT EXISTS gin_ddd CHARACTER SET utf8mb4;
USE gin_ddd;
CREATE TABLE IF NOT EXISTS users (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL UNIQUE,
  email VARCHAR(100) NOT NULL UNIQUE,
  phone VARCHAR(20),
  created_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

PostgreSQL order database example (matches current order repository fields):

CREATE DATABASE seed;
\c seed;
CREATE TABLE IF NOT EXISTS orders (
  id BIGSERIAL PRIMARY KEY,
  order_no VARCHAR(50) NOT NULL UNIQUE,
  user_id BIGINT NOT NULL,
  total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
  status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Database adaptation notes:

If order DB changes to MySQL, SQL placeholders must change from $1 to ? If user DB changes to PostgreSQL, insert ID retrieval must use

RETURNING id

3. Configure Application

Edit config/config.yaml, at minimum configure databases and RocketMQ:

server:
  host: "0.0.0.0"
  port: 8080
  mode: "debug"

database:
  user:
    driver: "mysql"
    host: "localhost"
    port: 3306
    username: "root"
    password: "your_password"
    database: "gin_ddd"
  order:
    driver: "postgres"
    host: "localhost"
    port: 5432
    username: "postgres"
    password: "your_password"
    database: "seed"

rocketmq:
  enabled: true
  nameserver: "localhost:9876"
  group_name: "gin-ddd-group"
  instance_name: "gin-ddd-instance"
  topics:
    order_event: "order-event-topic"

Notes: rocketmq.enabled: true initializes producer and consumer

Order event topic is hardcoded as order-event-topic in code; must match config

4. Start RocketMQ (Optional)

sh bin/mqnamesrv
sh bin/mqbroker -n localhost:9876

5. Start Application

go mod tidy
go run cmd/server/main.go

6. Verify Endpoints

curl http://localhost:8080/health
curl http://localhost:8080/api/users
curl http://localhost:8080/api/orders

Developing New Features on the Scaffold

Example: Adding a "Product Management" module.

Step 1: Domain Model internal/domain/model/product/product.go

package product

import "time"

type Product struct {
	ID        int64
	Name      string
	Price     float64
	Stock     int
	CreatedAt time.Time
	UpdatedAt time.Time
}

Step 2: Repository Interface internal/domain/repository/product/product_repository.go

package product

import (
	"context"
	"gin-ddd/internal/domain/model/product"
)

type ProductRepository interface {
	Create(ctx context.Context, p *product.Product) error
	Update(ctx context.Context, p *product.Product) error
	FindByID(ctx context.Context, id int64) (*product.Product, error)
	FindAll(ctx context.Context) ([]*product.Product, error)
}

Step 3: Repository Implementation internal/infrastructure/persistence/product/product_repository_impl.go

package product

import (
	"context"
	"database/sql"
	"gin-ddd/internal/domain/model/product"
)

type ProductRepositoryImpl struct {
	db *sql.DB
}

func NewProductRepository(db *sql.DB) *ProductRepositoryImpl {
	return &ProductRepositoryImpl{db: db}
}

func (r *ProductRepositoryImpl) Create(ctx context.Context, p *product.Product) error {
	_, err := r.db.ExecContext(ctx,
		`INSERT INTO products (name, price, stock, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`,
		p.Name, p.Price, p.Stock, p.CreatedAt, p.UpdatedAt,
	)
	return err
}

Step 4: Application Service internal/application/service/product/product_service.go

package product

import (
	"context"
	"time"
	"gin-ddd/internal/domain/model/product"
	productDomain "gin-ddd/internal/domain/repository/product"
)

type ProductService struct {
	repo productDomain.ProductRepository
}

func NewProductService(repo productDomain.ProductRepository) *ProductService {
	return &ProductService{repo: repo}
}

func (s *ProductService) Create(ctx context.Context, name string, price float64, stock int) error {
	p := &product.Product{
		Name:      name,
		Price:     price,
		Stock:     stock,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	return s.repo.Create(ctx, p)
}

Step 5: HTTP Handler and Router

Place request/response objects in internal/interfaces/vo/product/, handler in internal/interfaces/handler/product/, and register routes in internal/interfaces/router/router.go.

Step 6: Database Table

CREATE TABLE IF NOT EXISTS products (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10,2) NOT NULL,
  stock INT NOT NULL DEFAULT 0,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Event-Driven with RocketMQ

Event Types

Order events:

order.created

order.paid

order.shipped

order.delivered

order.cancelled

order.refunded

User events:

user.created

user.activated

user.deactivated

user.blocked

user.deleted

Message Flow

HTTP Request -> Application Service -> Domain Model ->
Publish DomainEvent -> RocketMQ Producer ->
RocketMQ Broker -> Consumer ->
Event Handling -> Send Email / Trigger Subsequent Processes

Key Points on Event Publishing and Consumption

Order creation publishes order.created event; publish failure does not affect main flow

Consumer parses messages by Tag into OrderEvent or UserEvent Order email notification triggers only on order.created when email is enabled

Email Configuration (QQ Mail)

Enable in config/config.yaml:

mail:
  enabled: true
  host: "smtp.qq.com"
  port: 465
  username: "[email protected]"
  password: "your_smtp_auth_code"
  from_email: "[email protected]"
  from_name: "Order System"

Notes:

Must use SMTP auth code, not QQ login password

Port 465 uses TLS; port 587 uses STARTTLS

Recipient taken from user table's email field

Common Troubleshooting

Log shows "event publisher not initialized": RocketMQ not enabled or initialization failed

Order event sent but email not received: verify user email field and SMTP auth code

Consumer not receiving messages: confirm Topic and Tag correctness, Broker running

Development Standards

Naming Conventions

Domain models: nouns, e.g., Order, User Application services: XxxService Repository interfaces: XxxRepository Repository implementations: XxxRepositoryImpl Handlers:

XxxHandler

Layering Principles

Domain layer must not depend on infrastructure

Application layer only orchestrates and coordinates transactions

Infrastructure provides technical implementations

Interface layer handles only HTTP interaction

Common Commands

go mod tidy
go test ./...

Source Code

https://github.com/microwind/design-patterns/tree/main/practice-projects/gin-ddd

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GoMySQLRocketMQDDDPostgreSQLEvent-DrivenGinScaffold
Go Development Architecture Practice
Written by

Go Development Architecture Practice

Daily sharing of Golang-related technical articles, practical resources, language news, tutorials, real-world projects, and more. Looking forward to growing together. Let's go!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.