Go Create Repository
Generate repository port interfaces and implementations for Pingo GO modular architechture conventions.
Two-File Pattern
Every repository requires two files:
-
Port interface: internal/modules/<module>/ports/<entity>_repository.go
-
Repository implementation: internal/modules/<module>/repository/<entity>_repository.go
Port Interface Structure
Location: internal/modules/<module>/ports/<entity>_repository.go
package ports
import ( "context" "github.com/cristiano-pacheco/pingo/internal/modules/<module>/model" )
type EntityRepository interface { FindAll(ctx context.Context) ([]model.EntityModel, error) FindByID(ctx context.Context, id uint64) (model.EntityModel, error) Create(ctx context.Context, entity model.EntityModel) (model.EntityModel, error) Update(ctx context.Context, entity model.EntityModel) (model.EntityModel, error) Delete(ctx context.Context, id uint64) error }
Pagination variant:
FindAll(ctx context.Context, page, pageSize int) ([]model.EntityModel, int64, error)
Custom methods: Add domain-specific queries as needed (e.g., FindByName , AssignContacts ).
Repository Implementation Structure
Location: internal/modules/<module>/repository/<entity>_repository.go
package repository
import ( "context" "errors"
"github.com/cristiano-pacheco/bricks/pkg/errs"
"github.com/cristiano-pacheco/bricks/pkg/otel/trace"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/model"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/ports"
"github.com/cristiano-pacheco/pingo/internal/shared/database"
"gorm.io/gorm"
)
type EntityRepository struct { *database.PingoDB }
var _ ports.EntityRepository = (*EntityRepository)(nil)
func NewEntityRepository(db *database.PingoDB) *EntityRepository { return &EntityRepository{db} }
Method Implementations
FindAll (Simple)
func (r *EntityRepository) FindAll(ctx context.Context) ([]model.EntityModel, error) { ctx, otelSpan := trace.Span(ctx, "EntityRepository.FindAll") defer otelSpan.End()
entities, err := gorm.G[model.EntityModel](r.DB).Find(ctx)
if err != nil {
return nil, err
}
return entities, nil
}
FindAll (Paginated)
func (r *EntityRepository) FindAll(ctx context.Context, page, pageSize int) ([]model.EntityModel, int64, error) { ctx, otelSpan := trace.Span(ctx, "EntityRepository.FindAll") defer otelSpan.End()
offset := (page - 1) * pageSize
var total int64
if err := r.DB.Model(&model.EntityModel{}).Count(&total).Error; err != nil {
return nil, 0, err
}
entities, err := gorm.G[model.EntityModel](r.DB).
Limit(pageSize).
Offset(offset).
Find(ctx)
if err != nil {
return nil, 0, err
}
return entities, total, nil
}
FindByID
func (r *EntityRepository) FindByID(ctx context.Context, id uint64) (model.EntityModel, error) { ctx, otelSpan := trace.Span(ctx, "EntityRepository.FindByID") defer otelSpan.End()
entity, err := gorm.G[model.EntityModel](r.DB).
Where("id = ?", id).
Limit(1).
First(ctx)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return model.EntityModel{}, errs.ErrRecordNotFound
}
return model.EntityModel{}, err
}
return entity, nil
}
Create
func (r *EntityRepository) Create(ctx context.Context, entity model.EntityModel) (model.EntityModel, error) { ctx, otelSpan := trace.Span(ctx, "EntityRepository.Create") defer otelSpan.End()
err := gorm.G[model.EntityModel](r.DB).Create(ctx, &entity)
return entity, err
}
Update
func (r *EntityRepository) Update(ctx context.Context, entity model.EntityModel) (model.EntityModel, error) { ctx, otelSpan := trace.Span(ctx, "EntityRepository.Update") defer otelSpan.End()
_, err := gorm.G[model.EntityModel](r.DB).Updates(ctx, entity)
if err != nil {
return model.EntityModel{}, err
}
return entity, nil
}
Delete
func (r *EntityRepository) Delete(ctx context.Context, id uint64) error { ctx, otelSpan := trace.Span(ctx, "EntityRepository.Delete") defer otelSpan.End()
rowsAffected, err := gorm.G[model.EntityModel](r.DB).
Where("id = ?", id).
Delete(ctx)
if err != nil {
return err
}
if rowsAffected == 0 {
return errs.ErrRecordNotFound
}
return nil
}
Custom Query (by field)
func (r *EntityRepository) FindByName(ctx context.Context, name string) (model.EntityModel, error) { ctx, otelSpan := trace.Span(ctx, "EntityRepository.FindByName") defer otelSpan.End()
entity, err := gorm.G[model.EntityModel](r.DB).
Where("name = ?", name).
Limit(1).
First(ctx)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return model.EntityModel{}, errs.ErrRecordNotFound
}
return model.EntityModel{}, err
}
return entity, nil
}
Transaction (relationship operations)
func (r *EntityRepository) AssignRelated(ctx context.Context, entityID uint64, relatedIDs []uint64) error { ctx, otelSpan := trace.Span(ctx, "EntityRepository.AssignRelated") defer otelSpan.End()
tx := r.DB.Begin()
_, err := gorm.G[model.EntityRelationModel](tx).
Where("entity_id = ?", entityID).
Delete(ctx)
if err != nil {
tx.Rollback()
return err
}
var relations []model.EntityRelationModel
for _, relatedID := range relatedIDs {
relations = append(relations, model.EntityRelationModel{
EntityID: entityID,
RelatedID: relatedID,
})
}
err = gorm.G[model.EntityRelationModel](tx).CreateInBatches(ctx, &relations, len(relations))
if err != nil {
tx.Rollback()
return err
}
if commitErr := tx.Commit().Error; commitErr != nil {
return commitErr
}
return nil
}
Fx Wiring
Add to internal/modules/<module>/fx.go :
fx.Provide( fx.Annotate( repository.NewEntityRepository, fx.As(new(ports.EntityRepository)), ), ),
Critical Rules
-
Struct: Embed *database.PingoDB only
-
Constructor: MUST return pointer *EntityRepository
-
Interface assertion: Add var _ ports.EntityRepository = (*EntityRepository)(nil) below struct
-
Tracing: Every method MUST start with ctx, otelSpan := trace.Span(ctx, "Repo.Method") and defer otelSpan.End()
-
Queries: Use gorm.GModel pattern for all queries
-
First queries: Add .Limit(1) before .First(ctx)
-
Not found: Return errs.ErrRecordNotFound when errors.Is(err, gorm.ErrRecordNotFound)
-
Delete: Check rowsAffected == 0 and return errs.ErrRecordNotFound
-
Transactions: Use tx := r.DB.Begin() , rollback on error, commit at end
-
No comments: Do not add redundant comments above methods
-
Validation: Run make lint and make nilaway after generation
-
Add detailed comment on interfaces: Provide comprehensive comments on the port interfaces to describe their purpose and usage
Workflow
-
Create port interface in ports/<entity>_repository.go
-
Create repository implementation in repository/<entity>_repository.go
-
Add Fx wiring to module's fx.go
-
Run make lint to verify
-
Run make nilaway for static analysis