Cache & Base de données (Server)
1. Stratégie de cache Redis
Section titled “1. Stratégie de cache Redis”Redis est strictement un cache de lecture. Toute erreur Redis est observée et, selon l’opération, dégradée en cache miss ; elle ne doit pas rendre PostgreSQL incohérent. Les écritures PostgreSQL sont la source de vérité et invalident les clés après succès.
Convention et TTL
Section titled “Convention et TTL”| Donnée | Clé | TTL indicatif |
|---|---|---|
| Application | berth:application:{uuid} |
60 s |
| Liste des applications | berth:applications:list |
15 s |
| Agent | berth:agent:{uuid} |
30 s |
| Session/révocation JWT | berth:auth:revoked:{jti} |
durée restante du JWT |
Le préfixe peut inclure un identifiant d’installation si plusieurs services partagent Redis. Les listes sont invalidées avec les entités qu’elles contiennent.
Implémentation complète du cache application
Section titled “Implémentation complète du cache application”package application
import ( "context" "encoding/json" "fmt" "time"
"github.com/redis/go-redis/v9" "github.com/google/uuid" "github.com/your-org/berth-api/internal/domain")
type Cache interface { GetApplication(context.Context, uuid.UUID) (*domain.Application, error) SetApplication(context.Context, *domain.Application) error InvalidateApplication(context.Context, uuid.UUID) error}
type RedisCache struct { client *redis.Client; ttl time.Duration }func NewRedisCache(client *redis.Client, ttl time.Duration) *RedisCache { return &RedisCache{client: client, ttl: ttl} }func applicationKey(id uuid.UUID) string { return fmt.Sprintf("berth:application:%s", id) }
func (c *RedisCache) GetApplication(ctx context.Context, id uuid.UUID) (*domain.Application, error) { raw, err := c.client.Get(ctx, applicationKey(id)).Bytes() if err != nil { return nil, err } var app domain.Application if err := json.Unmarshal(raw, &app); err != nil { return nil, err } return &app, nil}2. Couches détaillées
Section titled “2. Couches détaillées”Domaine : structs GORM et hooks
Section titled “Domaine : structs GORM et hooks”package domain
import ( "time" "github.com/google/uuid" "gorm.io/gorm")
type Application struct { ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` Name string `gorm:"type:varchar(150);not null;index" json:"name"` AgentID uuid.UUID `gorm:"type:uuid;not null;index" json:"agent_id"` ContainerID string `gorm:"type:varchar(255);not null" json:"container_id"` Status string `gorm:"type:varchar(32);not null;default:'unknown';index" json:"status"` CreatedAt time.Time `gorm:"not null;autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"not null;autoUpdateTime" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`}
func (a *Application) BeforeCreate(_ *gorm.DB) error { if a.ID == uuid.Nil { a.ID = uuid.New() } return nil}Repository GORM idiomatique
Section titled “Repository GORM idiomatique”package application
import ( "context" "errors" "github.com/google/uuid" "gorm.io/gorm" "github.com/your-org/berth-api/internal/domain" "github.com/your-org/berth-api/internal/pkg/apperror")
type GORMRepository struct { db *gorm.DB }func NewGORMRepository(db *gorm.DB) *GORMRepository { return &GORMRepository{db: db} }
func (r *GORMRepository) FindByID(ctx context.Context, id uuid.UUID) (*domain.Application, error) { var app domain.Application if err := r.db.WithContext(ctx).First(&app, "id = ?", id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, apperror.ErrNotFound } return nil, err } return &app, nil}