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.
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 utilitiesLayer 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 id3. 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:98765. Start Application
go mod tidy
go run cmd/server/main.go6. Verify Endpoints
curl http://localhost:8080/health
curl http://localhost:8080/api/users
curl http://localhost:8080/api/ordersDeveloping 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 ProcessesKey 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:
XxxHandlerLayering 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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
