Skip to content

Pattern CQRS & Couches (Server)

Angular 22 ──HTTP/WS/SSE──> Gin handler ──> Command/Query handler
│ │
Command ─────┘ └──── Query
│ │
GORM write Redis GET ──miss──> GORM read
│ │ │
invalidate cache <───────────────┴──── Redis SET ◄──┘
gRPC agent / événement temps réel
internal/modules/application/repository.go
package application
import (
"context"
"github.com/google/uuid"
"github.com/your-org/berth-api/internal/domain"
)
type Repository interface {
FindByID(ctx context.Context, id uuid.UUID) (*domain.Application, error)
FindAll(ctx context.Context) ([]domain.Application, error)
Create(ctx context.Context, app *domain.Application) error
Save(ctx context.Context, app *domain.Application) error
Delete(ctx context.Context, id uuid.UUID) error
}
type AgentGateway interface {
RestartContainer(ctx context.Context, agentID uuid.UUID, containerID string) error
}

Command handler complet : redémarrer une application

Section titled “Command handler complet : redémarrer une application”
internal/modules/application/command/restart.go
package command
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/your-org/berth-api/internal/modules/application"
)
type RestartApplication struct {
ApplicationID uuid.UUID
}
type RestartApplicationHandler struct {
repo application.Repository
agent application.AgentGateway
cache application.Cache
}
func NewRestartApplicationHandler(repo application.Repository, agent application.AgentGateway, cache application.Cache) *RestartApplicationHandler {
return &RestartApplicationHandler{repo: repo, agent: agent, cache: cache}
}
func (h *RestartApplicationHandler) Handle(ctx context.Context, cmd RestartApplication) error {
app, err := h.repo.FindByID(ctx, cmd.ApplicationID)
if err != nil {
return err
}
if app.AgentID == uuid.Nil || app.ContainerID == "" {
return fmt.Errorf("application %s: agent ou conteneur manquant", app.ID)
}
if err := h.agent.RestartContainer(ctx, app.AgentID, app.ContainerID); err != nil {
return fmt.Errorf("restart container: %w", err)
}
app.Status = "restarting"
if err := h.repo.Save(ctx, app); err != nil {
return err
}
return h.cache.InvalidateApplication(ctx, app.ID)
}

Query handler complet : obtenir une application

Section titled “Query handler complet : obtenir une application”
internal/modules/application/query/get.go
package query
import (
"context"
"github.com/google/uuid"
"github.com/your-org/berth-api/internal/domain"
"github.com/your-org/berth-api/internal/modules/application"
)
type GetApplicationHandler struct {
repo application.Repository
cache application.Cache
}
func NewGetApplicationHandler(repo application.Repository, cache application.Cache) *GetApplicationHandler {
return &GetApplicationHandler{repo: repo, cache: cache}
}
func (h *GetApplicationHandler) Handle(ctx context.Context, id uuid.UUID) (*domain.Application, error) {
if cached, err := h.cache.GetApplication(ctx, id); err == nil && cached != nil {
return cached, nil
}
app, err := h.repo.FindByID(ctx, id)
if err != nil {
return nil, err
}
_ = h.cache.SetApplication(ctx, app)
return app, nil
}
internal/modules/application/handler.go
package application
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/your-org/berth-api/internal/modules/application/command"
"github.com/your-org/berth-api/internal/modules/application/query"
"github.com/your-org/berth-api/internal/pkg/response"
)
type Handler struct {
restart *command.RestartApplicationHandler
get *query.GetApplicationHandler
}
func (h *Handler) Get(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil { response.Error(c, http.StatusBadRequest, "invalid_id", "Identifiant invalide"); return }
app, err := h.get.Handle(c.Request.Context(), id)
if err != nil { response.FromError(c, err); return }
response.JSON(c, http.StatusOK, app)
}
func (h *Handler) Restart(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil { response.Error(c, http.StatusBadRequest, "invalid_id", "Identifiant invalide"); return }
if err := h.restart.Handle(c.Request.Context(), command.RestartApplication{ApplicationID: id}); err != nil {
response.FromError(c, err); return
}
response.JSON(c, http.StatusAccepted, gin.H{"status": "restarting", "id": id})
}