Implementierung von CQRS in Go: Ein praktischer Leitfaden für skalierbare Architektur
CQRS in Go implementieren, ohne unnötigen Ballast
CQRS ist eines dieser Patterns, das überbeworben, überkompliziert und gelegentlich fälschlicherweise als Heilmittel gegen die langweilige CRUD-Alltagsarbeit dargestellt wird.
Die nützliche Version ist viel einfacher: Trennen Sie den Code, der den Zustand ändert, von dem Code, der den Zustand liest, und lassen Sie jede Seite für ihre eigene Aufgabe weiterentwickeln. Martin Fowler beschreibt CQRS als die Verwendung eines anderen Modells zur Aktualisierung von Informationen als des Modells, das zum Lesen verwendet wird, warnt jedoch davor, dass dies für die meisten Systeme riskante Komplexität hinzufügt. Microsoft bringt denselben Kernpunkt in operativeren Begriffen zum Ausdruck: Trennen Sie Lese- und Schreibmodelle, sodass jedes unabhängig optimiert werden kann.

Wenn Sie mit Go arbeiten, entspricht diese Idee der Sprache ungewöhnlich gut. Go ist gut in expliziten Grenzen, kleinen Interfaces, langweiligen Datentypen und an Anwendungsfällen orientierten Paketen. Das macht grundlegendes CQRS in Go viel weniger theatralisch, als es oft in Konferenz-Präsentationen wirkt. Sie benötigen kein Event Sourcing, kein Kafka und nicht drei Datenbanken, um zu beginnen. Sowohl Microsofts CQRS-Leitlinien als auch die Go-Beispiele von Three Dots Labs zeigen, dass eine einfache Implementierung denselben zugrunde liegenden Speicher verwenden kann, wobei zuerst separate Befehls- und Abfragehandler hinzugefügt werden und ausgefeiltere Infrastruktur nur dann eingeführt wird, wenn das Problem es tatsächlich erfordert.
Was CQRS tatsächlich bedeutet
Im Kern zieht CQRS eine harte Linie zwischen Befehlen und Abfragen. Eine Abfrage liest Daten und sollte den Zustand des Systems nicht ändern. Ein Befehl ändert den Zustand und sollte keine Domänendaten als Hauptergebnis zurückgeben. Three Dots Labs formulieren dies in praktischen Go-Begriffen: Abfragen geben Daten zurück und Befehle führen Änderungen durch, wobei Fehler ein normales Befehlsresultat sind. Das ist der grundlegende Schritt. Alles andere ist optional.
Ein häufiges Missverständnis ist, dass CQRS automatisch separate Datenbanken, asynchrone Projektionen oder Event Sourcing bedeutet. Das ist nicht wahr. Microsofts Musterguide behandelt separate Datenspeicher explizit als die fortgeschrittenere Form, nicht als Standard, und Three Dots Labs zeigen eine Go-Implementierung, bei der Abfragen aus derselben Datenbank lesen wie Schreibvorgänge, weil dies für das jeweilige System ausreichend ist. Wenn Ihr Artikel nur eine Sache klar vermitteln soll, dann diese: CQRS ist primär eine Entscheidung für Modellierung und Anwendungsstruktur, kein obligatorisches Paket für verteilte Systeme.
Das andere wichtige Detail ist die Benennung. Befehle sollten die geschäftliche Absicht modellieren, nicht Speichermutationen. Microsofts Beispiel kontrastiert „Hotelzimmer buchen“ mit „Reservierungsstatus auf Reserviert setzen“, und Three Dots Labs empfehlen Namen, die nahe an der Sprache der Domänenexperten liegen, wie „TrainingPlanen“ oder „TrainingStornieren“, anstatt generische Verben wie „Erstellen“ und „Löschen“. In Go zahlt sich diese Disziplin bei der Benennung aus, da Befehlsnamen oft zu Typnamen, Handlernamen und Paketgrenzen werden.
Warum Teams darauf zurückgreifen
CQRS wird attraktiv, wenn ein einzelnes CRUD-Modell beginnt, zu viele Aufgaben schlecht zu erledigen. Microsofts Leitlinien listen die üblichen Druckpunkte auf: Die Lese- und Schreibdarstellungen derselben Daten weichen voneinander ab, gleichzeitige Updates verursachen Lock-Konkurrenz, die Leseleistung leidet unter Abfragekomplexität und geteilte Entitäten verwandeln Sicherheitsregeln in ein Gewirr. Mit anderen Worten, das Problem ist nicht, dass CRUD moralisch falsch ist. Das Problem ist, dass ein Modell gezwungen wird, unverträgliche Anforderungen gleichzeitig zu erfüllen.
Das ist bei technischen Produkten besonders häufig der Fall. Schreibvorgänge kümmern sich tendenziell um Validierung, Invarianten, Transaktionen und Geschäftsregeln. Lesevorgänge kümmern sich tendenziell um Filter, Joins, Aggregation, Caching, Sortierung und das Bereitstellen genau der Struktur, die eine Seite oder API benötigt. CQRS ermöglicht es der Schreibseite, streng und domänenorientiert zu bleiben, während die Leseseite pragmatisch und DTO-orientiert bleibt. Microsoft empfiehlt explizit ein Schreibmodell, das sich auf Validierung und Konsistenz konzentriert, und ein Lesemodell, das sich auf DTOs oder Projektionen konzentriert, die für Präsentation und Antwortzeit optimiert sind.
Es gibt auch einen Vorteil auf Team-Ebene. Three Dots Labs argumentieren, dass die Trennung von Befehlen und Abfragen die Entkopplung verbessert, den Ausführungsfluss klarer macht und die Einarbeitung beschleunigt, da Entwickler eine kleine Liste verfügbarer Befehle und Abfragen inspizieren können, anstatt Logik durch zufällige Service-Schichten zu verfolgen. Microsoft stellt similarly fest, dass CQRS besonders in kollaborativen Umgebungen nützlich ist, in denen mehrere Benutzer dieselben Daten aktualisieren und Befehle genug Granularität benötigen, um Konflikte zu verhindern oder aufzulösen.
Meine leicht opinionierte Ansicht ist folgende: Die meisten Teams adoptieren CQRS zu spät, nachdem ein „Service“ bereits zu einem Monolithen mit weichem Kern geworden ist. Aber viele Teams adoptieren es auch zu früh, hauptsächlich weil das Architekturdagramm teuer aussah und daher ernsthaft wirkte. Der richtige Moment ist, wenn Lese- und Schreibvorgänge in Bezug auf Struktur, Geschwindigkeit oder Regeln klar voneinander driftieren, nicht wenn Ihre Todo-App Ambitionen hat.
Die Vorteile und die Rechnung
Grundlegendes CQRS hat echte Vorteile, noch bevor Sie Messaging oder separate Stores hinzufügen. Es gibt Ihnen kleinere Befehlsmodelle, kleinere Abfragemodelle, klarere Use Cases und offensichtlichere Stellen, um Querschnittsbelange wie Protokollierung und Instrumentierung anzuwenden. Three Dots Labs rufen explizit bessere Code-Organisation, Entkopplung und einfachere Modelle als unmittelbare Gewinne hervor, während Microservices.io einfachere Befehls- und Abfragemodelle sowie Unterstützung für denormalisierte, skalierbare Leseansichten hervorhebt.
Sobald das Problem es rechtfertigt, öffnet CQRS auch die Tür zu stärkerer Optimierung der Lese-Seite. Microsofts Leitlinien stellen fest, dass separate Lesemodelle DTOs, Projektionen, schreibgeschützte Replikate oder sogar eine völlig andere Speichertechnologie verwenden können. Es weist auch auf Materialized Views als Möglichkeit hin, schwere Joins und ORM-lastige Abfragepfade zu vermeiden. Wenn Sie evaluieren, welche Datenschicht Sie auf der Schreibseite verwenden sollen, deckt Vergleich von Go ORMs für PostgreSQL die Kompromisse zwischen GORM, Ent, Bun und sqlc in praktischen Begriffen auf. Dort beginnt CQRS operativ, nicht nur strukturell, auszuzahlen.
Die Kosten sind ebenso real. Fowlers Warnung ist immer noch der richtige Ausgangspunkt: Für die meisten Systeme fügt CQRS riskante Komplexität hinzu. Microsoft listet erhöhte Komplexität und Endgültigkeit der Konsistenz (Eventual Consistency) als Kernüberlegungen auf, während Microservices.io potenzielle Code-Duplizierung und Replikationsverzögerung in Leseansichten hinzufügt. Wenn Sie Stores trennen, erben Sie auch die Aufgabe, sie synchron zu halten, normalerweise durch Events, ohne sich auf eine ordentliche verteilte Transaktion zwischen Ihrer Datenbank und Ihrem Broker zu verlassen.
Event Sourcing entfernt diese Rechnung nicht; es ändert ihre Form. Microsofts CQRS-Leitlinien sagen, dass Event Sourcing den Event Store zur einzigen Quelle der Wahrheit machen kann und Ihnen ermöglicht, Materialized Views durch Abspielen der Historie neu aufzubauen, während Event Horizon auf Nachverfolgbarkeit und Audit-Logging als Hauptvorteile hinweist. Microsoft warnt jedoch auch, dass View-Generierung, Replay und Event-Handling mehr Designkomplexität hinzufügen, und schlägt Snapshots vor, um Replay-Kosten zu reduzieren. Deshalb erkläre ich Event Sourcing lieber als „CQRS plus eine zweite schwierige Entscheidung“, nicht als Eintrittskarte.
Eine nützliche Faustregel, die man im Kopf behalten sollte, ist, dass grundlegendes CQRS günstig ist, während verteiltes CQRS teuer ist, und das Vermischen dieser beiden Gespräche einer der häufigsten Wege ist, wie Teams auf viel mehr Komplexität enden, als das Problem je erfordert hat.
Eine einfache CQRS-Implementierung in Go
Ein vernünftiger erster Schritt in Go ist, eine Datenbank zu behalten und nur die Anwendungsschicht zu trennen. Befehle besitzen Geschäftsregeln und Persistenz. Abfragen geben Lesemodelle zurück, die für Aufrufer geformt sind. Dies ist genau die Art von grundlegendem CQRS, die Three Dots Labs empfehlen, bevor sie nach asynchronen Bussen oder separaten Lese-Speichern greifen.
Beginnen Sie mit Befehlen
package blog
import (
"context"
"errors"
"time"
)
type PublishPostCommand struct {
Title string
Slug string
BodyMD string
Author string
}
type PostRepository interface {
NextID(ctx context.Context) (string, error)
Save(ctx context.Context, post Post) error
}
type Post struct {
ID string
Title string
Slug string
BodyMD string
Author string
PublishedAt time.Time
}
type PublishPostHandler struct {
Repo PostRepository
Now func() time.Time
}
func (h PublishPostHandler) Handle(ctx context.Context, cmd PublishPostCommand) error {
if cmd.Title == "" || cmd.Slug == "" || cmd.BodyMD == "" {
return errors.New("title, slug, and body are required")
}
id, err := h.Repo.NextID(ctx)
if err != nil {
return err
}
post := Post{
ID: id,
Title: cmd.Title,
Slug: cmd.Slug,
BodyMD: cmd.BodyMD,
Author: cmd.Author,
PublishedAt: h.Now(),
}
return h.Repo.Save(ctx, post)
}
Dieser Handler versucht nicht, eine Seite zu bedienen, eine Listenantwort zu formen oder SQL für ein Card-Grid zu optimieren. Es erzwingt nur die Absicht und persistiert ein gültiges Aggregate. Das ist die Befehlsseite, die eine Aufgabe gut erledigt.
Fügen Sie Abfragen hinzu
package blog
import "context"
type PostView struct {
ID string
Title string
Slug string
Author string
PublishedAt string
Excerpt string
}
type LatestPostsQuery struct {
Limit int
}
type PostReadModel interface {
Latest(ctx context.Context, limit int) ([]PostView, error)
BySlug(ctx context.Context, slug string) (PostView, error)
}
type LatestPostsHandler struct {
ReadModel PostReadModel
}
func (h LatestPostsHandler) Handle(ctx context.Context, q LatestPostsQuery) ([]PostView, error) {
limit := q.Limit
if limit <= 0 {
limit = 10
}
return h.ReadModel.Latest(ctx, limit)
}
type GetPostBySlugQuery struct {
Slug string
}
type GetPostBySlugHandler struct {
ReadModel PostReadModel
}
func (h GetPostBySlugHandler) Handle(ctx context.Context, q GetPostBySlugQuery) (PostView, error) {
return h.ReadModel.BySlug(ctx, q.Slug)
}
Beachten Sie, dass die Lese-Seite ein PostView zurückgibt, nicht das Schreibmodell. Das spiegelt Microsofts Empfehlung wider, dass das Lesemodell für DTOs und Präsentation optimiert sein soll, während das Schreibmodell auf transaktionale Integrität und Domänenregeln abgestimmt ist.
Verdrahten Sie es wie eine Go-Anwendung, nicht wie eine Schrein
package app
import "your/module/internal/blog"
type Application struct {
Commands Commands
Queries Queries
}
type Commands struct {
PublishPost blog.PublishPostHandler
}
type Queries struct {
LatestPosts blog.LatestPostsHandler
GetPostBySlug blog.GetPostBySlugHandler
}
Diese Struktur ist nicht zufällig. Three Dots Labs verwenden ein sehr ähnliches Muster in Wild Workouts: Ein Application-Typ, der Commands und Queries exposes, mit konkreten Handlern, die aus separaten app/command- und app/query-Paketen verdrahtet werden. Ihr Service-Compositions-Code importiert diese Pakete separat und konstruiert daraus ein einzelnes Application-Objekt. Es ist eine saubere, go-ähnliche Art, die Grenze offensichtlich zu machen, ohne Framework-Drama. Wenn Ihr Abhängigkeitsgraph komplex wird, während die Handler multiplizieren, deckt Dependency Injection in Go Wire, Dig und Constructor-Injection-Patterns ab, die sich natürlich mit dieser handler-basierten Struktur kombinieren.
Wenn Sie später asynchrone Befehle, cross-service Events oder einen denormalisierten Suchindex benötigen, können Sie sie von dieser Basislinie aus hinzufügen. Three Dots Labs präsentieren asynchrone Command-Busse und separate Query-Datenbanken explizit als spätere Optimierungen, nicht als Startpunkt.
Go-Bibliotheken, die man kennen sollte
Das Go-CQRS-Ökosystem ist schmaler als das .NET-Ökosystem, was ehrlich gesagt ein Segen ist. Sie können die echten Optionen in einem Nachmittag überblicken und vermeiden, drei Abstraktionen zu adoptieren, die Sie nicht benötigen.
Watermill
Watermill ist die klarste moderne Wahl, wenn Sie CQRS plus Messaging möchten. Sein CQRS-Komponente ist eine High-Level-API, die Ihnen ermöglicht, mit Go-Structs statt mit rohen Nachrichten zu arbeiten, und ihre Bausteine umfassen einen EventBus, EventProcessor, CommandBus und CommandProcessor. Die Docs decken auch Event-Handler-Gruppen für geordnete Verarbeitung auf geteilten Topics, ein Read-Model-Beispiel und benutzerdefinierte Marshaling-Metadaten ab. Außerhalb der CQRS-Schicht unterstützt Watermill eine breite Palette von Pub/Sub-Backends, einschließlich RabbitMQ, Kafka, NATS Jetstream, Redis Streams, Google Cloud Pub/Sub, SQL, HTTP und anderen. Pkg.go.dev markiert Watermill als produktionsreif mit einer stabilen öffentlichen API seit v1.0.0, und die aktuelle veröffentlichte Modulversion ist v1.5.2, mit GitHub, das diese Release am 13. Mai auflistet.
commandBus, err := cqrs.NewCommandBusWithConfig(pub, cfg)
eventBus, err := cqrs.NewEventBusWithConfig(pub, cfg)
commandProcessor, err := cqrs.NewCommandProcessorWithConfig(router, cfg)
eventProcessor, err := cqrs.NewEventProcessorWithConfig(router, cfg)
Verwenden Sie Watermill, wenn Befehle und Events Prozessgrenzen überschreiten müssen, wenn Sie möchten, dass Retry- und Redelivery-Semantik erstklassig sind, oder wenn Sie wissen, dass Ihr „einfacher“ Service bereits halb auf event-getriebene Realität zugeht. Der Nachteil ist, dass Sie nun Gespräche über Broker, Topics, Ordnung und Idempotenz führen, ob Sie wollten oder nicht. Das ist kein Mangel an Watermill. Das ist die Kosten des Problemraums.
Event Horizon
Event Horizon ist ein CQRS- und Event-Sourcing-Toolkit für Go. Seine Maintainer beschreiben es als in Produktionssystemen verwendet, weisen aber auch darauf hin, dass die API nicht endgültig ist. Das Toolkit bietet Aggregate-, Befehls- und Event-Registrierungshilfen, offizielle Event-Store-Implementierungen für Speicher- und MongoDB-Varianten, Projektions- und Repository-Unterstützung und Beispiele, die eine Outbox-Pattern-basierte Anwendung einschließen. Der Release-Stream ist noch aktiv, mit GitHub, das v0.17.0 am 16. Juni zeigt und frühere Releases Features wie Snapshots, wiederholbare Projektionen, persistente Befehlsplanung und das Outbox-Pattern hinzufügen.
eh.RegisterAggregate(func(id uuid.UUID) eh.Aggregate {
return &InvoiceAggregate{ID: id}
})
eh.RegisterCommand(func() eh.Command {
return &CreateInvoiceCommand{}
})
Event Horizon macht am meisten Sinn, wenn Event Sourcing der Punkt ist, nicht eine optionale zukünftige Erweiterung. Wenn Sie audit-freundliche Streams, wiederholbare Historie, Projektionen und ein event-store-zentrisches Modell möchten, ist es eine ernsthafte Option. Wenn Sie nur sauberere Anwendungsservices in einem Monolithen möchten, ist es wahrscheinlich mehr Maschinerie, als Sie benötigen. Der Hinweis „API ist nicht endgültig“ bedeutet auch, dass Sie etwas mehr Anpassung über die Zeit budgetieren sollten als mit Watermill.
Go-MediatR
Go-MediatR ist kein vollständiges CQRS-Framework, aber es ist nützlich für in-process CQRS. Sein README beschreibt es als Mediator-Pattern-Implementierung, die mit CQRS verwendet wird, mit Request/Response-Dispatch für Befehle und Abfragen, Notification-Dispatch für Events und Pipeline-Verhalten für Querschnittsbelange. Das Projekt hat auch getaggte Releases, mit GitHub, das v1.4.0 als neueste Release auflistet und thread-sichere Handler-Registrierung und konurrenzbezogene Verbesserungen hervorhebt.
resp, err := mediatr.Send[*CreateProductCommand, *CreateProductResponse](ctx, cmd)
post, err := mediatr.Send[*GetPostBySlugQuery, *PostView](ctx, query)
Das ist ein guter Fit, wenn Sie handler-basierte Befehle und Abfragen möchten, aber keinen Broker, Projektionsmotor oder Event Store. Es ist besonders freundlich für Teams, die von MediatR in .NET kommen. Der Kompromiss ist ebenso klar: Sie müssen immer noch Ihre eigene Persistenz, Lesemodell-Aktualisierungsstrategie und Out-of-Process-Integrationsgeschichte designen. Mit anderen Worten, es gibt Ihnen die Anwendungsgrenze, nicht die ganze Architektur.
Ältere Frameworks und Referenzmaterial
Es gibt ältere Go-CQRS-Bibliotheken, die immer noch lehrreich sind, aber ich würde sie als Referenzmaterial behandeln, bevor ich sie als Greenfield-Standard behandle.
jetbasrawi/go.cqrs beschreibt sich selbst als Go-CQRS-Referenzimplementierung mit Beispielanwendungen basierend auf Greg Youngs Prinzipien. Pkg.go.dev zeigt jedoch kein gültiges go.mod, keine getaggte Version und keine stabile Version, während GitHub keine Releases zeigt und die Paketmetadaten vor 7,4 Jahren veröffentlicht wurden. Das ist nützliche Geschichte, kein starkes Signal für eine frische Produktionsadoption in 2026.
andrewwebber/cqrs ist ähnlich: Es bietet Event Sourcing, Befehlsausstellung und -verarbeitung, Event-Veröffentlichung und Lesemodell-Generierung aus veröffentlichten Events, aber die Paketmetadaten wurden ebenfalls vor 7,4 Jahren veröffentlicht. Ich würde es absolut lesen, wenn Sie verstehen möchten, wie frühere Go-CQRS-Bibliotheken das Problem angegangen sind. Ich wäre vorsichtig, es zur Grundlage einer neuen Codebasis zu machen, es sei denn, Sie sind glücklich, Teilzeit-Maintainer Ihrer eigenen Architekturstack zu werden.
Eine praktische Go-Projektstruktur
Eine typische Go-CQRS-Struktur sollte Use Cases offensichtlich machen, nicht sie unter generischen Abstraktionen begraben. Wild Workouts ist ein guter Referenzpunkt hier. Das Repository trennt begrenzte Kontexte unter internal, hält Befehle und Abfragen in distinkten Anwendungspaketen und verdrahtet sie in einen Application-Typ, der Commands und Queries exposes. Service-Composition zieht Adapter, Handler und Abhängigkeiten explizit zusammen. Die hier beschriebenen Patterns stimmen mit den breiteren Leitlinien in Go-Projektstruktur: Praktiken & Patterns überein, die den breiteren Satz von Layout-Entscheidungen abdecken, mit denen Teams konfrontiert sind, wenn Go-Codebasen wachsen.
Eine pragmatische Struktur sieht so aus:
internal/
blog/
app/
app.go
command/
publish_post.go
unpublish_post.go
query/
get_post_by_slug.go
latest_posts.go
domain/
post.go
slug.go
adapters/
postgres/
post_repository.go
post_read_model.go
ports/
http/
handler.go
service/
application.go
Diese Struktur hat einige Vorteile.
Erstens leben Befehls- und Abfragehandler nah an den Use Cases, die sie implementieren. Das macht es schwieriger, Geschäftsverhalten in Repositories oder Handlern zu verstecken, die nach Transportschichten benannt sind. Three Dots Labs tun dies direkt in Wild Workouts, wo app/command und app/query separate Pakete sind und der Top-Level-Application Handler nach Verantwortung gruppiert.
Zweitens kann das Domain-Paket fokussiert auf Invarianten und Verhalten bleiben, während die Abfrage-Seite frei ist, DTOs und Projektionen zurückzugeben. Das stimmt mit Microsofts Schreibmodell- und Lesemodell-Leitlinien überein und vermeidet das gemeinsame CQRS-Anti-Pattern, bei dem die Abfrage-Seite aus ideologischer Reinheit zurück durch Domänenobjekte gezwungen wird.
Drittens skaliert diese Struktur vom kleinsten nützlichen CQRS zu schwereren Varianten. Sie können heute eine PostgreSQL-Datenbank und zwei Repository-Implementierungen behalten, dann einen Suchindex oder event-getriebene Lese-Projektionen später hinzufügen, ohne die gesamte Anwendungsstruktur neu schreiben zu müssen. Three Dots Labs beschreiben diese Progression von grundlegendem CQRS zu asynchronen Command-Bussen und separaten Query-Stores explizit nur, wenn das System sie benötigt.
Wann CQRS passt und wann nicht
CQRS macht Sinn, wenn Lese- und Schreibvorgänge wirklich unterschiedliche Probleme sind. Microsoft empfiehlt es für Workloads, bei denen Lese- und Schreibmodelle unabhängige Optimierung benötigen, bei denen mehrere Benutzer an denselben Daten kollaborieren und bei denen klare Trennung bei Leistung, Skalierbarkeit und Sicherheit hilft. Microservices.io fügt einen klassischen Fit hinzu: Denormalisierte, Hochleistungs-Ansichten, die aus Domänen-Events oder materialisierten Projektionen aufgebaut sind. Three Dots Labs weisen auch auf komplexe Geschäftslogik, Wartbarkeit und zukünftige Erweiterung zu asynchronen Befehlen oder spezialisierten Lese-Speichern als starke Gründe hin, es in Go zu adoptieren.
In der Praxis bedeutet das oft Systeme mit reichen Domänenregeln, teuren Lesemodellen, Reporting-Ansichten, die sich nicht sauber auf Aggregate abbilden lassen, oder Microservices, die Events veröffentlichen und Projektionen anderswo aufbauen. In diesen Kontexten erscheint das Saga-Pattern für verteilte Transaktionen oft zusammen mit CQRS als Koordinationsmechanismus für mehrstufige Geschäftsoperationen, die Service-Grenzen überschreiten. Es passt auch zu Produkten, bei denen die Schreibseite streng und auditierbar sein muss, während die Lese-Seite schnell sein und für UI- oder API-Verbrauch geformt sein muss. Wenn Sie bereits über Projektionen, Replikate oder das NeuAufbauen von Ansichten aus Events sprechen, befinden Sie sich wahrscheinlich im CQRS-Territorium, ob Sie das Label verwenden oder nicht.
CQRS macht keinen Sinn, wenn Ihr Service ein straightforwarder Dateneditor ist. Fowler sagt offen, dass CQRS für die meisten Systeme riskante Komplexität hinzufügt, und Three Dots Labs sagen, dass einfache CRUD-Services, die im Wesentlichen dieselben Daten empfangen und zurückgeben, kein guter Fit sind. In ihrem eigenen Wild Workouts-Beispiel verwendet ein einfacherer Users-Service keine Clean Architecture und CQRS, weil die Patterns dort nicht ihre Miete zahlen würden.
Das ist der Teil, den es in einem technischen Blog klar zu sagen gilt: CQRS ist kein Reifheitsabzeichen, sondern ein bewusster Kompromiss, und es macht nur Sinn, wenn Sie tatsächlich das brauchen, was es Ihnen gibt. Wenn Ihr Admin-Panel Zeilen schreibt und dieselben Zeilen zurückliest, trennen Sie das Modell nicht nur, weil Sie können. Wenn Ihre Befehls-Handler hauptsächlich „Setze Feld X auf Record Y“ sind, haben Sie kein CQRS-Problem. Sie haben eine normale Anwendung, und das ist absolut respektabler Software.
Abschließende Gedanken
Der beste Weg, CQRS in Go zu implementieren, ist, mit der langweiligen Version zu beginnen. Trennen Sie Befehls-Handler von Abfrage-Handlern. Lassen Sie Befehle die geschäftliche Absicht modellieren. Lassen Sie Abfragen Lesemodelle zurückgeben. Behalten Sie dieselbe Datenbank, wenn das alles ist, was Sie brauchen. Fügen Sie dann erst dann asynchrone Busse, Projektionen, separate Stores oder Event Sourcing hinzu, wenn das System Ihre Hand zwingt. Diese Progression ist konsistent mit Fowlers Warnung vor Komplexität, Microsofts gestaffelten CQRS-Leitlinien und den pragmatischen Go-Beispielen von Three Dots Labs.
Wenn Sie eine Bibliothek benötigen, ist Watermill die stärkste Allzweckwahl für message-getriebenes CQRS in Go, Event Horizon ist überzeugend, wenn Event Sourcing das Zentrum der Schwerkraft ist, und Go-MediatR ist eine gute leichte Berührung, wenn Sie nur in-process Befehls- und Abfrage-Dispatch benötigen. Alles andere sollte seinen Platz sehr sorgfältig verdienen. Für eine breitere Karte von Code-Struktur, Integration und Datenzugriffspatterns in produktionsreifen Go-Systemen ist der App Architecture Guide ein nützlicher Begleiter.
Das ist am Ende die am meisten go-ähnliche Antwort auf CQRS: Verwenden Sie das Pattern, nicht das Kostüm.