Vergleich von MVC- und DDD-Layered-Architekturen in Go: Eine detaillierte Anleitung
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Detaillierter Vergleich von MVC- und DDD-Layered-Architekturen in Go
MVC und DDD sind zwei populäre Konzepte für Layered-Architekturen in der Backend-Entwicklung. MVC (Model-View-Controller) ist ein Designmuster, das hauptsächlich verwendet wird, um Benutzeroberfläche, Geschäftslogik und Datenmodelle zu trennen, um eine einfachere Entkopplung und Schichtung zu erreichen, während DDD (Domain-Driven Design) eine architektonische Methodik ist, die darauf abzielt, Design- und Wartungsschwierigkeiten in komplexen Systemen zu lösen, indem Geschäftsdomänenmodelle erstellt werden.
Im Java-Ökosystem haben viele Systeme schrittweise von MVC zu DDD übergegangen. In Sprachen wie Go, Python und NodeJS – die Einfachheit und Effizienz befürworten – bleibt MVC jedoch die Mainstream-Architektur. Im Folgenden werden wir speziell die Unterschiede in der Verzeichnisstruktur zwischen MVC und DDD basierend auf der Go-Sprache erörtern.
MVC-Diagrammstruktur
+------------------+
| View | Benutzeroberflächenschicht: verantwortlich für die Datenanzeige und Benutzerinteraktion (z. B. HTML-Seiten, API-Antworten)
+------------------+
| Controller | Controller-Schicht: verarbeitet Benutzeranfragen, ruft Service-Logik auf, koordiniert Model und View
+------------------+
| Model | Model-Schicht: enthält Datenobjekte (z. B. Datenbanktabellenstrukturen) und etwas Geschäftslogik (oft in der Service-Schicht verstreut)
+------------------+
DDD-Diagrammstruktur
+--------------------+
| Benutzeroberfläche | Verantwortlich für Benutzerinteraktion und -anzeige (z. B. REST-API, Weboberfläche)
+--------------------+
| Application Layer | Orchestriert Geschäftsprozesse (z. B. Aufrufen von Domänendiensten, Transaktionsmanagement), enthält keine Kerngeschäftsregeln
+--------------------+
| Domain Layer | Kernschicht für Geschäftslogik: enthält Aggregatwurzeln, Entitäten, Wertobjekte, Domänendienste usw., kapselt Geschäftsregeln
+--------------------+
| Infrastructure | Stellt technische Implementierungen bereit (z. B. Datenbankzugriff, Message Queues, externe APIs)
+--------------------+
Hauptunterschiede zwischen MVC und DDD
-
Logik der Codeorganisation
- MVC schichtet nach technischer Funktion (Controller/Service/DAO) und konzentriert sich auf die technische Implementierung.
- DDD unterteilt Module nach Geschäftsdomäne (z. B. Auftragsdomäne, Zahlungsdomäne) und isoliert die Kerngeschäftslogik durch begrenzte Kontexte.
-
Träger der Geschäftslogik
- MVC verwendet normalerweise ein anämisches Modell, das Daten (Model) und Verhalten (Service) trennt, was aufgrund der verteilten Logik zu hohen Wartungskosten führt.
- DDD erreicht ein umfangreiches Modell durch Aggregatwurzeln und Domänendienste, das die Geschäftslogik in der Domänenschicht konzentriert und die Skalierbarkeit verbessert.
-
Anwendbarkeit und Kosten
- MVC hat geringe Entwicklungskosten und eignet sich für kleine bis mittlere Systeme mit stabilen Anforderungen.
- DDD erfordert eine vorgelagerte Domänenmodellierung und eine einheitliche Sprache und eignet sich für große Systeme mit komplexen Geschäfts- und langfristigen Entwicklungsanforderungen, aber das Team muss über Domänenabstraktionsfähigkeiten verfügen. Bei E-Commerce-Aktionsregeln kann DDD beispielsweise verhindern, dass Logik über mehrere Dienste verstreut wird.
Go-Sprache MVC-Verzeichnisstruktur
MVC ist hauptsächlich in drei Schichten unterteilt: View, Controller und Model.
gin-order/
├── cmd
│ └── main.go # Anwendungseinstiegspunkt, startet die Gin-Engine
├── internal
│ ├── controllers # Controller-Schicht (verarbeitet HTTP-Anfragen), auch als Handler bekannt
│ │ └── order
│ │ └── order_controller.go # Controller für das Bestellmodul
│ ├── services # Service-Schicht (verarbeitet Geschäftslogik)
│ │ └── order
│ │ └── order_service.go # Service-Implementierung für das Bestellmodul
│ ├── repository # Datenzugriffsschicht (interagiert mit der Datenbank)
│ │ └── order
│ │ └── order_repository.go # Datenzugriffs-Schnittstelle und -Implementierung für das Bestellmodul
│ ├── models # Model-Schicht (Definitionen der Datenstruktur)
│ │ └── order
│ │ └── order.go # Datenmodell für das Bestellmodul
│ ├── middleware # Middleware (z. B. Authentifizierung, Protokollierung, Anforderungsabfang)
│ │ ├── logging.go # Protokollierungs-Middleware
│ │ └── auth.go # Authentifizierungs-Middleware
│ └── config # Konfigurationsmodul (Datenbank-, Serverkonfigurationen usw.)
│ └── config.go # Anwendungs- und Umgebungskonfigurationen
├── pkg # Allgemeine Utility-Pakete (z. B. Antwort-Wrapper)
│ └── response.go # Utility-Methoden für die Antwortverarbeitung
├── web # Frontend-Ressourcen (Vorlagen und statische Assets)
│ ├── static # Statische Ressourcen (CSS, JS, Bilder)
│ └── templates # Vorlagendateien (HTML-Vorlagen)
│ └── order.tmpl # View-Vorlage für das Bestellmodul (wenn HTML-Rendering erforderlich ist)
├── go.mod # Go-Modulverwaltungsdatei
└── go.sum # Go-Modulabhängigkeitssperrdatei
Go-Sprache DDD-Verzeichnisstruktur
DDD ist hauptsächlich in vier Schichten unterteilt: Schnittstelle, Anwendung, Domäne und Infrastruktur.
go-web/
│── cmd/
│ └── main.go # Anwendungseinstiegspunkt
│── internal/
│ ├── application/ # Anwendungsschicht (koordiniert Domänenlogik, verarbeitet Anwendungsfälle)
│ │ ├── services/ # Service-Schicht, Geschäftslogikverzeichnis
│ │ │ └── order_service.go # Auftragsanwendungsservice, ruft Geschäftslogik der Domänenschicht auf
│ ├── domain/ # Domänenschicht (Kerngeschäftslogik und Schnittstellendefinitionen)
│ │ ├── order/ # Auftragsaggregat
│ │ │ ├── order.go # Auftragsentität (Aggregatwurzel), enthält Kerngeschäftslogik
│ │ ├── repository/ # Allgemeine Repository-Schnittstellen
│ │ │ ├── repository.go # Allgemeine Repository-Schnittstelle (CRUD-Operationen)
│ │ │ └── order_repository.go # Auftragsrepository-Schnittstelle, definiert Operationen für Auftragsdaten
│ ├── infrastructure/ # Infrastrukturschicht (implementiert Schnittstellen, die in der Domänenschicht definiert sind)
│ │ ├── repository/ # Repository-Implementierung
│ │ │ └── order_repository_impl.go # Auftragsrepository-Implementierung, konkrete Auftragsdatenspeicherung
│ └── interfaces/ # Schnittstellenschicht (verarbeitet externe Anfragen, z. B. HTTP-Schnittstellen)
│ │ ├── handlers/ # HTTP-Handler
│ │ │ └── order_handler.go # HTTP-Handler für Aufträge
│ │ └── routes/
│ │ │ ├── router.go # Basis-Router-Utility-Setup
│ │ │ └── order-routes.go # Auftragsroutenkonfiguration
│ │ │ └── order-routes-test.go # Auftragsroutentest
│ └── middleware/ # Middleware (z. B.: Authentifizierung, Abfangen, Autorisierung usw.)
│ │ └── logging.go # Protokollierungs-Middleware
│ ├── config/ # Dienstbezogene Konfiguration
│ │ └── server_config.go # Serverkonfiguration (z. B. Port, Timeout-Einstellungen usw.)
│── pkg/ # Wiederverwendbare öffentliche Bibliotheken
│ └── utils/ # Utility-Klassen (z. B.: Protokollierung, Datumsbearbeitung usw.)
Go-Sprache MVC-Code-Implementierung
Controller (Schnittstellenschicht) → Service (Geschäftslogikschicht) → Repository (Datenzugriffsschicht) → Model (Datenmodell)
Geschichteter Code
Controller-Schicht
// 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) }
Routenkonfiguration
// 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() { // Initialisiere die Datenbank db := database.NewGORM() // Dependency Injection orderRepo := order_repo.NewMySQLOrderRepository(db) orderService := order_service.NewOrderService(orderRepo) orderController := order_controller.NewOrderController(orderService) // Erstelle Router r := gin.Default() // Registriere Middleware r.Use(middleware.Logger()) // Routengruppen apiGroup := r.Group("/api") { orderGroup := apiGroup.Group("/orders") { orderGroup.GET("/:id", orderController.GetOrder) orderGroup.POST("", orderController.CreateOrder) } } // Starte den Service r.Run(":8080") }
Service-Schicht
// 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) }
Datenzugriffsschicht (Repository)
// 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 { return nil, 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 { return nil, err } return orders, nil }
Model-Schicht
// 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-Sprache MVC Best Practices
Interface Segregation Principle
Die Repository-Schicht definiert Schnittstellen, die mehrere Datenbankimplementierungen unterstützen.
// Einfacher Wechsel zu einer Mock-Implementierung type MockOrderRepository struct {} func (m *MockOrderRepository) FindByID(id uint) (*model.Order, error) { return &model.Order{OrderNo: "mock-123"}, nil }
Einheitliches Antwortformat
// pkg/response/response.go func Success(c *gin.Context, data interface{}) { c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": data, }) }
Middleware-Kette
// Globale Middleware r.Use(gin.Logger(), gin.Recovery()) // Routengruppen-Middleware adminGroup := r.Group("/admin", middleware.AuthJWT())
Datenbankmigration
Verwendung von GORM AutoMigrate:
db.AutoMigrate(&model.Order{})
Go-Sprache DDD-Code-Implementierung und Best Practices
Fokus auf das Domänenmodell
DDD betont die Erstellung von Domänenmodellen und die Organisation der Geschäftslogik unter Verwendung von Aggregaten, Entitäten und Wertobjekten.
In Go werden Entitäten und Wertobjekte typischerweise mit Struct definiert:
// Entität type User struct { ID int Name string }
Layered Architecture
DDD verwendet typischerweise eine Layered Architecture. Go-Projekte können dieser Struktur folgen:
- Domänenschicht: Kerngeschäftslogik, z. B. Entitäten und Aggregate unter dem Domänenverzeichnis.
- Anwendungsschicht: Anwendungsfälle und Orchestrierung von Geschäftsprozessen.
- Infrastrukturschicht: Adapter für Datenbank, Caching, externe APIs usw.
- Schnittstellenschicht: Bietet HTTP-, gRPC- oder CLI-Schnittstellen.
Dependency Inversion
Die Domänenschicht sollte nicht direkt von der Infrastrukturschicht abhängen; stattdessen stützt sie sich auf Schnittstellen für Dependency Inversion.
Hinweis: Der Kern der DDD-Architektur ist die Dependency Inversion (DIP). Die Domäne ist der innerste Kern, der nur Geschäftsregeln und Schnittstellenabstraktionen definiert. Andere Schichten hängen von der Domäne zur Implementierung ab, aber die Domäne hängt nicht von externen Implementierungen ab. In der Hexagonal Architecture befindet sich die Domänenschicht im Kern, während andere Schichten (wie Anwendung, Infrastruktur) konkrete technische Details (wie Datenbankoperationen, API-Aufrufe) bereitstellen, indem sie von der Domäne definierte Schnittstellen implementieren, wodurch eine Entkopplung zwischen Domäne und technischer Implementierung erreicht wird.
// Domänenschicht: definiert Schnittstelle type UserRepository interface { GetByID(id int) (*User, error) }
// Infrastrukturschicht: Datenbankimplementierung type userRepositoryImpl struct { db *sql.DB } func (r *userRepositoryImpl) GetByID(id int) (*User, error) { // Datenbankabfragelogik }
Aggregatverwaltung
Die Aggregatwurzel verwaltet den Lebenszyklus des gesamten Aggregats:
type Order struct { ID int Items []OrderItem Status string } func (o *Order) AddItem(item OrderItem) { o.Items = append(o.Items, item) }
Anwendungsservice
Anwendungsservices kapseln Domänenlogik und verhindern, dass externe Schichten Domänenobjekte direkt manipulieren:
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) }
Event-Driven
Domänenereignisse werden zur Entkopplung verwendet. In Go können Sie dies über Kanäle oder Pub/Sub implementieren:
type OrderCreatedEvent struct { OrderID int } func publishEvent(event OrderCreatedEvent) { go func() { eventChannel <- event }() }
Kombination von CQRS (Command Query Responsibility Segregation)
DDD kann mit CQRS kombiniert werden. In Go können Sie Command für Änderungsoperationen und Query für das Lesen von Daten verwenden:
type CreateOrderCommand struct { UserID int Items []OrderItem } func (h *OrderHandler) Handle(cmd CreateOrderCommand) (*Order, error) { return h.service.CreateOrder(cmd.UserID, cmd.Items) }
Zusammenfassung: MVC vs. DDD-Architektur
Kernunterschiede in der Architektur
MVC-Architektur
-
Schichten: Drei Schichten – Controller/Service/DAO
-
Verantwortlichkeiten:
- Der Controller verarbeitet Anfragen, der Service enthält die Logik
- DAO bedient direkt die Datenbank
-
Schwachstellen: Die Service-Schicht ist aufgebläht, und die Geschäftslogik ist mit Datenoperationen gekoppelt
DDD-Architektur
-
Schichten: Vier Schichten – Schnittstellenschicht / Anwendungsschicht / Domänenschicht / Infrastrukturschicht
-
Verantwortlichkeiten:
- Die Anwendungsschicht orchestriert Prozesse (z. B. ruft Domänendienste auf)
- Die Domänenschicht kapselt geschäftliche atomare Operationen (z. B. Regeln zur Auftragserstellung)
- Die Infrastrukturschicht implementiert technische Details (z. B. Datenbankzugriff)
-
Schwachstellen: Die Domänenschicht ist unabhängig von technischen Implementierungen, und die Logik entspricht eng der Schichtstruktur
Modularität und Skalierbarkeit
MVC:
- Hohe Kopplung: Es fehlen klare Geschäftsgrenzen; modulübergreifende Aufrufe (z. B. der Auftragsdienst, der sich direkt auf Kontotabellen stützt) erschweren die Wartung des Codes.
- Schlechte Skalierbarkeit: Das Hinzufügen neuer Funktionen erfordert globale Änderungen (z. B. das Hinzufügen von Risikokontrollregeln muss in den Auftragsdienst eindringen), was leicht zu kaskadierenden Problemen führen kann.
DDD:
- Begrenzter Kontext: Die Module werden nach Geschäftsfunktionen unterteilt (z. B. Zahlungsdomäne, Risikokontrolldomäne); die ereignisgesteuerte Zusammenarbeit (z. B. Auftrag abgeschlossen Zahlungsereignis) wird zur Entkopplung verwendet.
- Unabhängige Entwicklung: Jedes Domänenmodul kann unabhängig aktualisiert werden (z. B. die Optimierung der Zahlungslogik wirkt sich nicht auf den Auftragsdienst aus), wodurch Risiken auf Systemebene reduziert werden.
Anwendbare Szenarien
- Bevorzugen Sie MVC für kleine bis mittlere Systeme: Einfaches Geschäft (z. B. Blogs, CMS, Admin-Backends), das eine schnelle Entwicklung mit klaren Geschäftsregeln und ohne häufige Änderungen erfordert.
- Bevorzugen Sie DDD für komplexe Geschäfte: Regelintensiv (z. B. Finanztransaktionen, Lieferkette), Multi-Domänen-Zusammenarbeit (z. B. E-Commerce-Auftrags- und Bestandsverknüpfung), häufige Änderungen der Geschäftsanforderungen.
Wir sind Leapcell, Ihre erste Wahl für das Hosting von Go-Projekten.
Leapcell ist die Next-Gen Serverless Plattform für Webhosting, asynchrone Aufgaben und Redis:
Multi-Sprachen Unterstützung
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Unbegrenzte Projekte kostenlos bereitstellen
- Zahlen Sie nur für die Nutzung - keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: $25 unterstützt 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Developer Experience
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollständig automatisierte CI/CD-Pipelines und GitOps-Integration.
- Echtzeit-Metriken und -Protokollierung für verwertbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Auto-Skalierung zur einfachen Bewältigung hoher Parallelität.
- Null Betriebsaufwand - konzentrieren Sie sich einfach auf den Aufbau.
Erfahren Sie mehr in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ