Golang MVC 和 DDD 分層架構的詳細比較

MVC 和 DDD 是後端開發中兩種流行的分層架構概念。MVC(Model-View-Controller)是一種設計模式,主要用於分離用戶界面、業務邏輯和數據模型,以便更容易解耦和分層;而 DDD(Domain-Driven Design,領域驅動設計)是一種架構方法論,旨在通過構建業務領域模型來解決複雜系統中的設計和維護難題。

在 Java 生態系統中,許多系統已經逐漸從 MVC 過渡到 DDD。但在像 Go、Python 和 NodeJS 這樣提倡簡單性和效率的語言中,MVC 仍然是主流架構。下面,我們將基於 Go 語言,具體討論 MVC 和 DDD 在目錄結構上的差異。

MVC 圖解結構

+------------------+
|      View        | 用戶界面層:負責數據展示和用戶交互(如 HTML 頁面、API 響應)
+------------------+
|   Controller     | 控制器層:處理用戶請求,調用 Service 邏輯,協調 Model 和 View
+------------------+
|      Model       | 模型層:包含數據對象(如數據庫表結構)和一些業務邏輯(通常分散在 Service 層)
+------------------+

DDD 圖解結構

+--------------------+
|   User Interface   | 負責用戶交互和展示(如 REST API、Web 界面)
+--------------------+
| Application Layer  | 編排業務流程(如調用領域服務、事務管理),不包含核心業務規則
+--------------------+
|   Domain Layer     | 核心業務邏輯層:包含聚合根、實體、值對象、領域服務等,封裝業務規則
+--------------------+
| Infrastructure     | 提供技術實現(如數據庫訪問、消息隊列、外部 API)
+--------------------+

MVC 和 DDD 的主要區別

代碼組織邏輯

業務邏輯的載體

適用性和成本

Go 語言 MVC 目錄結構

MVC 主要分爲三層:視圖、控制器和模型。

gin-order/
├── cmd
│   └── main.go                  # 應用入口點,啓動 Gin 引擎
├── internal
│   ├── controllers              # 控制器層(處理 HTTP 請求),也稱爲 handlers
│   │   └── order
│   │       └── order_controller.go  # Order 模塊的控制器
│   ├── services                 # 服務層(處理業務邏輯)
│   │   └── order
│   │       └── order_service.go       # Order 模塊的服務實現
│   ├── repository               # 數據訪問層(與數據庫交互)
│   │   └── order
│   │       └── order_repository.go    # Order 模塊的數據訪問接口和實現
│   ├── models                   # 模型層(數據結構定義)
│   │   └── order
│   │       └── order.go               # Order 模塊的數據模型
│   ├── middleware               # 中間件(如認證、日誌、請求攔截)
│   │   ├── logging.go             # 日誌中間件
│   │   └── auth.go                # 認證中間件
│   └── config                   # 配置模塊(數據庫、服務器配置等)
│       └── config.go                # 應用和環境配置
├── pkg                          # 通用實用工具包(如響應包裝器)
│   └── response.go              # 響應處理實用方法
├── web                          # 前端資源(模板和靜態資產)
│   ├── static                   # 靜態資源(CSS、JS、圖片)
│   └── templates                # 模板文件(HTML 模板)
│       └── order.tmpl           # Order 模塊的視圖模板(如果需要渲染 HTML)
├── go.mod                       # Go 模塊管理文件
└── go.sum                       # Go 模塊依賴鎖定文件

Go 語言 DDD 目錄結構

DDD 主要分爲四層:接口、應用、領域和基礎設施。

go-web/
│── cmd/
│   └── main.go               # 應用入口點
│── internal/
│   ├── application/          # 應用層(協調領域邏輯,處理用例)
│   │   ├── services/         # 服務層,業務邏輯目錄
│   │   │   └── order_service.go # 訂單應用服務,調用領域層業務邏輯
│   ├── domain/               # 領域層(核心業務邏輯和接口定義)
│   │   ├── order/            # 訂單聚合
│   │   │   ├── order.go      # 訂單實體(聚合根),包含核心業務邏輯
│   │   ├── repository/       # 通用倉儲接口
│   │   │   ├── repository.go # 通用倉儲接口(CRUD 操作)
│   │   │   └── order_repository.go # 訂單倉儲接口,定義訂單數據操作
│   ├── infrastructure/       # 基礎設施層(實現領域層定義的接口)
│   │   ├── repository/       # 倉儲實現
│   │   │   └── order_repository_impl.go  # 訂單倉儲實現,具體訂單數據存儲
│   └── interfaces/           # 接口層(處理外部請求,如 HTTP 接口)
│   │   ├── handlers/         # HTTP 處理器
│   │   │  └── order_handler.go # 訂單的 HTTP 處理器
│   │   └── routes/
│   │   │   ├── router.go     # 基礎路由工具設置
│   │   │   └── order-routes.go # 訂單路由配置
│   │   │   └── order-routes-test.go # 訂單路由測試
│   └── middleware/           # 中間件(例如:認證、攔截、授權等)
│   │   └── logging.go        # 日誌中間件
│   ├── config/               # 服務相關配置
│   │   └── server_config.go  # 服務器配置(例如:端口、超時設置等)
│── pkg/                      # 可重用的公共庫
│   └── utils/                # 實用工具類(例如:日誌、日期處理等)

Go 語言 MVC 代碼實現

控制器(接口層)→ 服務(業務邏輯層)→ 倉儲(數據訪問層)→ 模型(數據模型)

分層代碼

控制器層

// internal/controller/order/order.go
package order

import (
    "net/http"
    "strconv"
    "github.com/gin-gonic/gin"
    "github.com/gin-order/internal/model"
    "github.com/gin-order/internal/service/order"
    "github.com/gin-order/internal/pkg/response"
)

type OrderController struct {
    service *order.OrderService
}

func NewOrderController(service *order.OrderService) *OrderController {
    return &OrderController{service: service}
}

func (c *OrderController) GetOrder(ctx *gin.Context) {
    idStr := ctx.Param("id")
    id, _ := strconv.ParseUint(idStr, 10, 64)

    order, err := c.service.GetOrderByID(uint(id))
    if err != nil {
        response.Error(ctx, http.StatusNotFound, "Order not found")
        return
    }

    response.Success(ctx, order)
}

func (c *OrderController) CreateOrder(ctx *gin.Context) {
    var req model.Order
    if err := ctx.ShouldBindJSON(&req); err != nil {
        response.Error(ctx, http.StatusBadRequest, "Invalid request")
        return
    }

    if err := c.service.CreateOrder(&req); err != nil {
        response.Error(ctx, http.StatusInternalServerError, "Create failed")
        return
    }

    response.Success(ctx, req)
}

路由配置

// cmd/server/main.go
package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-order/internal/controller/order"
    "github.com/gin-order/internal/pkg/database"
    "github.com/gin-order/internal/repository/order"
    "github.com/gin-order/internal/service/order"
)

func main() {
    // 初始化數據庫
    db := database.NewGORM()

    // 依賴注入
    orderRepo := order_repo.NewMySQLOrderRepository(db)
    orderService := order_service.NewOrderService(orderRepo)
    orderController := order_controller.NewOrderController(orderService)

    // 創建路由
    r := gin.Default()

    // 註冊中間件
    r.Use(middleware.Logger())

    // 路由組
    apiGroup := r.Group("/api")
    {
        orderGroup := apiGroup.Group("/orders")
        {
            orderGroup.GET("/:id", orderController.GetOrder)
            orderGroup.POST("", orderController.CreateOrder)
        }
    }

    // 啓動服務
    r.Run(":8080")
}

服務層

// internal/service/order/service.go
package order

import (
    "github.com/gin-order/internal/model"
    "github.com/gin-order/internal/repository/order"
)

type OrderService struct {
    repo order.OrderRepository
}

func NewOrderService(repo order.OrderRepository) *OrderService {
    return &OrderService{repo: repo}
}

func (s *OrderService) GetOrderByID(id uint) (*model.Order, error) {
    return s.repo.FindByID(id)
}

func (s *OrderService) CreateOrder(order *model.Order) error {
    return s.repo.Create(order)
}

數據訪問層(倉儲)

// internal/repository/order/interface.go
package order

import"github.com/gin-order/internal/model"

type OrderRepository interface {
    FindByID(id uint) (*model.Order, error)
    Create(order *model.Order) error
    FindByStatus(status string) ([]model.Order, error)
}

// internal/repository/order/mysql.go
package order

import (
    "gorm.io/gorm"
    "github.com/gin-order/internal/model"
)

type MySQLOrderRepository struct {
    db *gorm.DB
}

func NewMySQLOrderRepository(db *gorm.DB) OrderRepository {
    return &MySQLOrderRepository{db: db}
}

func (r *MySQLOrderRepository) FindByID(id uint) (*model.Order, error) {
    var order model.Order
    if err := r.db.First(&order, id).Error; err != nil {
        returnnil, err
    }
    return &order, nil
}

func (r *MySQLOrderRepository) Create(order *model.Order) error {
    return r.db.Create(order).Error
}

func (r *MySQLOrderRepository) FindByStatus(status string) ([]model.Order, error) {
    var orders []model.Order
    if err := r.db.Where("status = ?", status).Find(&orders).Error; err != nil {
        returnnil, err
    }
    return orders, nil
}

模型層

// internal/model/order.go
package model

import"time"

type Order struct {
    OrderID     uint      `gorm:"primaryKey;column:order_id"`
    OrderNo     string    `gorm:"uniqueIndex;column:order_no"`
    UserID      uint      `gorm:"index;column:user_id"`
    OrderName   string    `gorm:"column:order_name"`
    Amount      float64   `gorm:"type:decimal(10,2);column:amount"`
    Status      string    `gorm:"column:status"`
    CreatedAt   time.Time `gorm:"column:created_at"`
    UpdatedAt   time.Time `gorm:"column:updated_at"`
}

func (Order) TableName() string {
    return"orders"
}

Go 語言 MVC 最佳實踐

接口隔離原則

倉儲層定義接口,支持多種數據庫實現。

// 輕鬆切換到 Mock 實現
type MockOrderRepository struct {}
func (m *MockOrderRepository) FindByID(id uint) (*model.Order, error) {
    return &model.Order{OrderNo: "mock-123"}, nil
}

統一響應格式

// pkg/response/response.go
func Success(c *gin.Context, data interface{}) {
    c.JSON(http.StatusOK, gin.H{
        "code":    0,
        "message""success",
        "data":    data,
    })
}

中間件鏈

// 全局中間件
r.Use(gin.Logger(), gin.Recovery())

// 路由組中間件
adminGroup := r.Group("/admin", middleware.AuthJWT())

數據庫遷移

使用 GORM 自動遷移:

db.AutoMigrate(&model.Order{})

Go 語言 DDD 代碼實現和最佳實踐

專注於領域模型

DDD 強調領域模型的構建,使用聚合、實體和值對象組織業務邏輯。

在 Go 中,實體和值對象通常使用結構體定義:

// 實體
type User struct {
    ID   int
    Name string
}

分層架構

DDD 通常採用分層架構。Go 項目可以遵循以下結構:

依賴倒置

領域層不應直接依賴基礎設施層;相反,它依賴接口實現依賴倒置。

注:DDD 架構的核心是依賴倒置原則(DIP)。Domain 是最內層核心,只定義業務規則和接口抽象。其他層依賴 Domain 進行實現,但 Domain 不依賴任何外部實現。在六邊形架構中,領域層位於核心,而其他層(如應用層、基礎設施層)通過實現領域定義的接口來提供具體技術細節(如數據庫操作、API 調用),實現領域與技術實現的解耦。

// 領域層:定義接口
type UserRepository interface {
    GetByID(id int) (*User, error)
}
// 基礎設施層:數據庫實現
type userRepositoryImpl struct {
    db *sql.DB
}

func (r *userRepositoryImpl) GetByID(id int) (*User, error) {
    // 數據庫查詢邏輯
}

聚合管理

聚合根管理整個聚合的生命週期:

type Order struct {
    ID      int
    Items   []OrderItem
    Status  string
}

func (o *Order) AddItem(item OrderItem) {
    o.Items = append(o.Items, item)
}

應用服務

應用服務封裝領域邏輯,防止外部層直接操作領域對象:

type OrderService struct {
    repo OrderRepository
}

func (s *OrderService) CreateOrder(userID int, items []OrderItem) (*Order, error) {
    order := Order{UserID: userID, Items: items, Status: "Pending"}
    return s.repo.Save(order)
}

事件驅動

領域事件用於解耦。在 Go 中,你可以通過通道或發佈 / 訂閱實現:

type OrderCreatedEvent struct {
    OrderID int
}

func publishEvent(event OrderCreatedEvent) {
    go func() {
        eventChannel <- event
    }()
}

結合 CQRS(命令查詢責任分離)

DDD 可以與 CQRS 結合。在 Go 中,你可以使用 Command 進行更改操作,Query 進行數據讀取:

type CreateOrderCommand struct {
    UserID int
    Items  []OrderItem
}

func (h *OrderHandler) Handle(cmd CreateOrderCommand) (*Order, error) {
    return h.service.CreateOrder(cmd.UserID, cmd.Items)
}

總結:MVC vs. DDD 架構

架構核心差異

MVC 架構

DDD 架構

模塊化和可擴展性

MVC:

DDD:

適用場景

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ftIZj7nEfbcDyUdKRhf00Q