Skip to content
kratos-repo logo

Kratos Repository Implementation Skill

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.

SKILL.md

Full skill instructions

Kratos Repository Implementation Skill

Purpose

Generate repository implementations that handle data persistence using GORM while adhering to Clean Architecture principles. Repositories implement interfaces defined in the business layer (`biz`).

Essential Patterns

1. Repository Structure

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
}

2. CRUD Operations Pattern

Create Operation
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
}
Update Operation
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)
}
FindByID Operation
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
}
List with Pagination and Filters
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
}
Delete Operation
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
}

3. Error Mapping Pattern

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 found
  • domain.ErrDataDuplicateEntry - Unique constraint violation
  • domain.ErrDataTransactionFailed - Transaction operation failed
  • domain.ErrDataDatabase - Generic database error

NOTE: These are data layer errors. The business layer (use case) will map these to domain-specific errors (e.g., domain.ErrSymbolNotFound, domain.ErrDuplicateSymbol)

4. Mapper Functions

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
}

Critical Rules

Context Propagation

ALWAYS use `WithContext(ctx)` for all database operations:

r.db.WithContext(ctx).Find(&entities)  // ✅ Correct
r.db.Find(&entities)                    // ❌ Wrong - no context

FullSaveAssociations

Use for Create/​Update operations with nested relationships:

Session(&gorm.Session{FullSaveAssociations: true})

Filter Application

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

Error Handling

  1. Log all errors with context
  2. Map GORM errors to business errors
  3. Check RowsAffected for Update/​Delete operations

Logging Pattern

r.log.WithContext(ctx).Errorf("Failed to {operation}: %v", err)

File Structure

services/​{service}/​internal/​data/​repo/
├── {entity}.go              # Repository implementation
├── {entity}_test.go         # Repository tests
└── mapper.go or helpers     # Optional separate mapper file

Validation Checklist

  • Constructor function returns interface type (`domain.{Entity}Repo`)
  • All DB operations use `WithContext(ctx)`
  • Create/​Update use `FullSaveAssociations` if nested data exists
  • Update checks `RowsAffected == 0` for not found
  • Delete checks `RowsAffected == 0` for not found
  • All errors are logged with context
  • GORM errors are mapped to data layer errors (`domain.ErrData*`)
  • FindByID preloads related entities
  • List applies filters before count and find
  • List returns pagination metadata
  • Mappers handle nil inputs safely
  • Mappers transform nested relationships

Anti-Patterns

❌ DON'T:

  • Return GORM errors directly (must map to `domain.ErrData*`)
  • Forget context propagation (`WithContext`)
  • Apply filters only to find, not count
  • Ignore `RowsAffected` in Update/​Delete
  • Use value receivers (use pointer receivers)
  • Forget to preload relationships in FindByID
  • Return business errors from repo (return data layer errors instead)

✅ DO:

  • Implement interface defined in `biz/​domain/​interfaces.go`
  • Always map GORM errors to data layer errors (`domain.ErrData*`)
  • Use context for all database operations
  • Apply filters to both count and find queries
  • Check `RowsAffected` for Update/​Delete
  • Use pointer receivers for struct methods
  • Preload relationships when needed
  • Let business layer map data errors to domain errors

Success Criteria

Repository MUST:

  1. Implement all methods from business layer interface
  2. Pass all unit tests with proper error handling
  3. Support soft deletes (via BaseModel)
  4. Handle pagination correctly with accurate metadata
  5. Transform all data between entity and domain models
  6. Log errors appropriately with context
  7. Map all GORM errors to business errors

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

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...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

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...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

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.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View