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 的主要區別
代碼組織邏輯
-
MVC 按技術功能分層(Controller/Service/DAO),關注技術實現。
-
DDD 按業務領域劃分模塊(如訂單域、支付域),通過限界上下文隔離核心業務邏輯。
業務邏輯的載體
-
MVC 通常採用貧血模型,將數據(Model)和行爲(Service)分離,導致邏輯分散,維護成本高。
-
DDD 通過聚合根和領域服務實現富模型,將業務邏輯集中在領域層,提高可擴展性。
適用性和成本
-
MVC 開發成本低,適合需求穩定的中小型系統。
-
DDD 需要前期領域建模和統一語言,適合業務複雜、長期演進需求的大型系統,但團隊必須具備領域抽象能力。例如,在電商促銷規則中,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 項目可以遵循以下結構:
-
領域層:核心業務邏輯,例如 domain 目錄下的實體和聚合。
-
應用層:用例和業務流程的編排。
-
基礎設施層:數據庫、緩存、外部 API 等適配器。
-
接口層:提供 HTTP、gRPC 或 CLI 接口。
依賴倒置
領域層不應直接依賴基礎設施層;相反,它依賴接口實現依賴倒置。
注: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 架構
-
層次:三層——Controller/Service/DAO
-
職責:
-
Controller 處理請求,Service 包含邏輯
-
DAO 直接操作數據庫
-
痛點:Service 層變得臃腫,業務邏輯與數據操作耦合
DDD 架構
-
層次:四層——接口層 / 應用層 / 領域層 / 基礎設施層
-
職責:
-
應用層編排流程(如調用領域服務)
-
領域層封裝業務原子操作(如訂單創建規則)
-
基礎設施層實現技術細節(如數據庫訪問)
-
痛點:領域層獨立於技術實現,邏輯與層次結構緊密對應
模塊化和可擴展性
MVC:
-
高耦合:缺乏明確的業務邊界;跨模塊調用(如訂單服務直接依賴賬戶表)使代碼難以維護。
-
擴展性差:添加新功能需要全局修改(如添加風控規則必須侵入訂單服務),容易引起級聯問題。
DDD:
-
限界上下文:模塊按業務能力劃分(如支付域、風控域);使用事件驅動協作(如訂單支付完成事件)實現解耦。
-
獨立演進:每個領域模塊可以獨立升級(如支付邏輯優化不影響訂單服務),降低系統級風險。
適用場景
-
小型到中型系統更適合 MVC:業務簡單(如博客、CMS、管理後臺),需要快速開發,業務規則明確且不頻繁變更。
-
複雜業務更適合 DDD:規則密集型(如金融交易、供應鏈),多領域協作(如電商訂單與庫存聯動),業務需求頻繁變更。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ftIZj7nEfbcDyUdKRhf00Q