Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
The simplest complete CQRS implementation doesn't need message queues or event sourcing — it just needs a commit hook that updates the read store in the same request. This synchronous two-database pattern is underrated because it is immediately deployable, easy to reason about, and handles the majority of real-world CQRS use cases. The tradeoff is that every write now has two latencies: the PostgreSQL commit and the Redis update. For most applications that latency is acceptable, and the read-path speedup more than compensates. Understanding synchronous CQRS deeply also makes the jump to eventual consistency easier, because you know exactly what you are giving up and why.
A PostService that writes to PostgreSQL then synchronously updates a Redis projection shows the full two-database flow in one request.
package main
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
_ "github.com/lib/pq"
)
var ctx = context.Background()
type Post struct {
ID string
AuthorID string
AuthorName string
Title string
PublishedAt time.Time
}
type PostService struct {
pg *sql.DB
rdb *redis.Client
}
// CreatePost writes to PostgreSQL and synchronously updates the Redis read projection.
func (s *PostService) CreatePost(p Post) error {
// 1. Durable write — PostgreSQL is the source of truth.
_, err := s.pg.ExecContext(ctx,
"INSERT INTO posts(id, author_id, title, published_at) VALUES($1,$2,$3,$4)",
p.ID, p.AuthorID, p.Title, p.PublishedAt)
if err != nil {
return fmt.Errorf("pg write: %w", err)
}
// 2. Read projection update — Redis for the feed query.
// If this fails, the write already committed; the projection is stale.
// A background reconciler can re-build from PostgreSQL if needed.
feedItem, _ := json.Marshal(map[string]any{
"postId": p.ID,
"authorName": p.AuthorName,
"title": p.Title,
"publishedAt": p.PublishedAt.Unix(),
})
key := fmt.Sprintf("feed:%s", p.AuthorID)
if err := s.rdb.ZAdd(ctx, key, redis.Z{
Score: float64(p.PublishedAt.Unix()),
Member: string(feedItem),
}).Err(); err != nil {
log.Printf("WARN: redis projection update failed for post %s: %v", p.ID, err)
// Do NOT return error — the write succeeded; Redis is eventually fixable.
}
return nil
}
func (s *PostService) GetFeed(authorID string) ([]map[string]any, error) {
raw, err := s.rdb.ZRevRange(ctx, fmt.Sprintf("feed:%s", authorID), 0, 19).Result()
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(raw))
for _, r := range raw {
var item map[string]any
if json.Unmarshal([]byte(r), &item) == nil {
out = append(out, item)
}
}
return out, nil
}
func main() {
fmt.Println("Synchronous CQRS: PostgreSQL writes, Redis reads.")
fmt.Println("Step 1: INSERT into posts table (PostgreSQL)")
fmt.Println("Step 2: ZADD into feed:{authorId} (Redis)")
fmt.Println("Step 3: GET /feed reads from Redis only — zero SQL joins.")
}go run main.go