xiaohongshu
xiaohongshu
kratos-repo
Implements go-kratos data layer repositories following Clean Architecture patterns with GORM, transactions, pagination, and error handling. Use when adding new data access layers to kratos microservices that need database persistence.
Full skill instructions
Generate repository implementations that handle data persistence using GORM while adhering to Clean Architecture principles. Repositories implement interfaces defined in the business layer (`biz`).
package repo
import (
"context"
"platform/pagination"
"{service}/internal/biz/domain"
"{service}/internal/data/model"
"github.com/go-kratos/kratos/v2/log"
"gorm.io/gorm"
)
// Constructor function
func New{Entity}Repo(db *gorm.DB, tx common.Transaction, logger log.Logger) domain.{Entity}Repo {
return &{entity}Repo{
db: db,
tx: tx,
log: log.NewHelper(logger),
}
}
// Private struct
type {entity}Repo struct {
db *gorm.DB
tx common.Transaction
log *log.Helper
}
func (r *{entity}Repo) Create(ctx context.Context, s *domain.{Entity}) (*domain.{Entity}, error) {
entity := toEntity{Entity}(s)
// Use FullSaveAssociations for nested relationships
if err := r.db.WithContext(ctx).Session(&gorm.Session{FullSaveAssociations: true}).Create(entity).Error; err != nil {
r.log.WithContext(ctx).Errorf("Failed to save {entity}: %v", err)
return nil, r.mapGormError(err)
}
return toDomain{Entity}(entity), nil
}
func (r *{entity}Repo) Update(ctx context.Context, entity *domain.{Entity}) (*domain.{Entity}, error) {
// Transform to GORM entity
e := toEntity{Entity}(entity)
// Update with FullSaveAssociations
result := r.db.WithContext(ctx).Session(&gorm.Session{FullSaveAssociations: true}).Model(&model.{Entity}{}).Where("id = ?", entity.Id).Updates(e)
if result.Error != nil {
return nil, r.mapGormError(result.Error)
}
if result.RowsAffected == 0 {
return nil, biz.ErrNotFound
}
// Return fresh data
return r.FindByID(ctx, entity.Id)
}
func (r *{entity}Repo) FindByID(ctx context.Context, id uint64) (*domain.{Entity}, error) {
var entity *model.{Entity}
err := r.db.WithContext(ctx).
Preload("{RelationshipField}"). // Preload relationships
Where("id = ?", id).
First(&entity).Error
if err != nil {
r.log.WithContext(ctx).Errorf("Failed to find {entity} by ID %d: %v", id, err)
return nil, r.mapGormError(err)
}
return toDomain{Entity}(entity), nil
}
func (r *{entity}Repo) List{Entities}(ctx context.Context, offset uint64, limit uint32, filter map[string]interface{}) ([]*domain.{Entity}, *pagination.Meta, error) {
var entities []*model.{Entity}
var totalCount int64
// Base query
query := r.db.WithContext(ctx).Model(&model.{Entity}{})
// Apply filters BEFORE count and find
if filter != nil && len(filter) > 0 {
query = query.Where(filter)
}
// Count with filters applied
if err := query.Count(&totalCount).Error; err != nil {
r.log.WithContext(ctx).Errorf("Failed to count: %v", err)
return nil, nil, r.mapGormError(err)
}
// Apply pagination
query = query.Limit(int(limit)).Offset(int(offset))
// Execute query
if err := query.Find(&entities).Error; err != nil {
r.log.WithContext(ctx).Errorf("Failed to list: %v", err)
return nil, nil, r.mapGormError(err)
}
// Transform to domain
results := make([]*domain.{Entity}, 0, len(entities))
for _, e := range entities {
results = append(results, toDomain{Entity}(e))
}
// Build metadata
meta := &pagination.Meta{
TotalCount: uint64(totalCount),
Offset: offset,
Limit: limit,
HasNextPage: offset+uint64(len(entities)) < uint64(totalCount),
HasPreviousPage: offset > 0,
}
return results, meta, nil
}
func (r *{entity}Repo) Delete(ctx context.Context, id uint64) error {
result := r.db.WithContext(ctx).Delete(&model.{Entity}{}, id)
if result.Error != nil {
r.log.WithContext(ctx).Errorf("Failed to delete: %v", result.Error)
return r.mapGormError(result.Error)
}
if result.RowsAffected == 0 {
return domain.ErrDataNotFound
}
return nil
}
CRITICAL: Always map GORM errors to data layer errors (not business errors)
func (r *{entity}Repo) mapGormError(err error) error {
if err == nil {
return nil
}
// Map GORM errors to data layer errors
if errors.Is(err, gorm.ErrRecordNotFound) {
return domain.ErrDataNotFound
}
if r.isDuplicateKeyError(err) {
return domain.ErrDataDuplicateEntry
}
// Wrap other database errors
return domain.ErrDataDatabase
}
// isDuplicateKeyError checks if error is a duplicate key violation
func (r *{entity}Repo) isDuplicateKeyError(err error) bool {
errMsg := err.Error()
return strings.Contains(errMsg, "Error 1062") ||
strings.Contains(errMsg, "Duplicate entry") ||
strings.Contains(errMsg, "UNIQUE constraint failed")
}
Data Layer Error Types:
domain.ErrDataNotFound - Record not founddomain.ErrDataDuplicateEntry - Unique constraint violationdomain.ErrDataTransactionFailed - Transaction operation faileddomain.ErrDataDatabase - Generic database errorNOTE: These are data layer errors. The business layer (use case) will map these to domain-specific errors (e.g., domain.ErrSymbolNotFound, domain.ErrDuplicateSymbol)
Always provide bidirectional mappers between domain and entity:
// Domain to Entity
func toEntity{Entity}(d *domain.{Entity}) *model.{Entity} {
if d == nil {
return nil
}
entity := &model.{Entity}{
ProjectID: d.Project,
Field1: d.Field1,
Field2: d.Field2,
}
// Handle nested relationships
if d.RelatedData != nil {
entity.RelatedData = &model.RelatedData{
Field: d.RelatedData.Field,
Data: d.RelatedData.Data,
}
}
return entity
}
// Entity to Domain
func toDomain{Entity}(e *model.{Entity}) *domain.{Entity} {
if e == nil {
return nil
}
domain := &domain.{Entity}{
Id: e.ID,
Project: e.ProjectID,
Field1: e.Field1,
Field2: e.Field2,
}
// Handle nested relationships
if e.RelatedData != nil {
domain.RelatedData = &domain.RelatedData{
Id: e.RelatedData.ID,
Project: e.RelatedData.ProjectID,
Field: e.RelatedData.Field,
Data: e.RelatedData.Data,
}
}
return domain
}
ALWAYS use `WithContext(ctx)` for all database operations:
r.db.WithContext(ctx).Find(&entities) // ✅ Correct
r.db.Find(&entities) // ❌ Wrong - no context
Use for Create/Update operations with nested relationships:
Session(&gorm.Session{FullSaveAssociations: true})
Apply filters BEFORE both count and find queries:
query := r.db.WithContext(ctx).Model(&model.Entity{})
if filter != nil && len(filter) > 0 {
query = query.Where(filter) // Apply first
}
query.Count(&totalCount) // Count filtered results
query.Limit(...).Find(&entities) // Find filtered results
r.log.WithContext(ctx).Errorf("Failed to {operation}: %v", err)
services/{service}/internal/data/repo/
├── {entity}.go # Repository implementation
├── {entity}_test.go # Repository tests
└── mapper.go or helpers # Optional separate mapper file
❌ DON'T:
✅ DO:
Repository MUST:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience