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.

360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
Mastering Domain-Driven Design in Go: Build a Food Recommendation API

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-app

Project 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.

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.

Microservicesbackend-developmentGoDomain-Driven DesignAPI
360 Zhihui Cloud Developer
Written by

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.

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.