Mastering Domain-Driven Design in Go: Build a Food Recommendation API
This article walks through a complete Go implementation of a food recommendation API using Domain-Driven Design, covering project setup, layer separation, entity and repository definitions, infrastructure configuration, application services, HTTP interfaces, authentication, and how to run the application.
Today we share a Go implementation based on Domain-Driven Design (DDD). DDD is a software development method that connects implementation with evolving models, simplifying the complexity developers face.
Why Use DDD
Provides principles and patterns for solving difficult problems
Structures complex design around a domain model
Facilitates creative collaboration between technical and domain experts to iteratively refine the conceptual model
DDD Layers
Domain : defines business logic and domain entities.
Infrastructure : contains external libraries, database engines, etc.
Application : channel between interface and domain, handling use‑case orchestration.
Interface : handles HTTP requests, web services, batch jobs.
Step 1: Initialize Project
Initialize go.mod in the project root:
go mod init food-appProject Structure
We will use PostgreSQL and Redis for persistence. Create a .env file with connection details.
#Postgres
APP_ENV=local
API_PORT=8888
DB_HOST=127.0.0.1
DB_DRIVER=postgres
ACCESS_SECRET=98hbun98h
REFRESH_SECRET=786dfdbjhsb
DB_USER=steven
DB_PASSWORD=password
DB_NAME=food-app
DB_PORT=5432
#Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=Domain Layer
Define entities such as User and PublicUser, with GORM tags and validation methods.
package entity
import (
"food-app/infrastructure/security"
"github.com/badoux/checkmail"
"html"
"strings"
"time"
)
type User struct {
ID uint64 `gorm:"primary_key;auto_increment" json:"id"`
FirstName string `gorm:"size:100;not null;" json:"first_name"`
LastName string `gorm:"size:100;not null;" json:"last_name"`
Email string `gorm:"size:100;not null;unique" json:"email"`
Password string `gorm:"size:100;not null;" json:"password"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}
// BeforeSave hashes password
func (u *User) BeforeSave() error {
hashPassword, err := security.Hash(u.Password)
if err != nil {
return err
}
u.Password = string(hashPassword)
return nil
}
// Validate checks fields based on action
func (u *User) Validate(action string) map[string]string {
// validation logic omitted for brevity
return nil
}Only the Entity and Repository patterns are used for this simple app.
Repository Layer
Define UserRepository interface and its implementation using GORM.
package repository
type UserRepository interface {
SaveUser(*entity.User) (*entity.User, map[string]string)
GetUser(uint64) (*entity.User, error)
GetUsers() ([]entity.User, error)
GetUserByEmailAndPassword(*entity.User) (*entity.User, map[string]string)
} package persistence
type UserRepo struct {
db *gorm.DB
}
func NewUserRepository(db *gorm.DB) *UserRepo {
return &UserRepo{db}
}
// implementation of SaveUser, GetUser, etc.Infrastructure Layer
Configure database connection and automigrate tables.
package persistence
func NewRepositories(driver, user, password, port, host, name string) (*Repositories, error) {
dsn := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", host, port, user, name, password)
db, err := gorm.Open(driver, dsn)
if err != nil {
return nil, err
}
db.LogMode(true)
return &Repositories{
User: NewUserRepository(db),
Food: NewFoodRepository(db),
db: db,
}, nil
}Application Layer
Implements use‑case services that delegate to the repository.
package application
type userApp struct {
us repository.UserRepository
}
func (u *userApp) SaveUser(user *entity.User) (*entity.User, map[string]string) {
return u.us.SaveUser(user)
}Interface Layer
HTTP handlers for user operations, authentication, and food resources using Gin.
package interfaces
func (s *Users) SaveUser(c *gin.Context) {
var user entity.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusUnprocessableEntity, gin.H{"invalid_json": "invalid json"})
return
}
if errs := user.Validate(""); len(errs) > 0 {
c.JSON(http.StatusUnprocessableEntity, errs)
return
}
newUser, err := s.us.SaveUser(&user)
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusCreated, newUser.PublicUser())
}Authentication middleware validates JWT tokens, and a CORS middleware is provided.
Run the Application
Start the server with go run main.go. The main.go loads environment variables, creates repository and Redis services, registers routes, and runs Gin on the configured port.
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.
360 Zhihui Cloud Developer
360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.
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.
