go-create-repository

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Create Repository

Go 仓库代码生成

Generate repository port interfaces and implementations for Pingo GO modular architechture conventions.
按照Pingo Go模块化架构规范生成仓库的端口接口与实现。

Two-File Pattern

双文件模式

Every repository requires two files:
  1. Port interface:
    internal/modules/<module>/ports/<entity>_repository.go
  2. Repository implementation:
    internal/modules/<module>/repository/<entity>_repository.go
每个仓库需要两个文件:
  1. 端口接口
    internal/modules/<module>/ports/<entity>_repository.go
  2. 仓库实现
    internal/modules/<module>/repository/<entity>_repository.go

Port Interface Structure

端口接口结构

Location:
internal/modules/<module>/ports/<entity>_repository.go
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:
go
FindAll(ctx context.Context, page, pageSize int) ([]model.EntityModel, int64, error)
Custom methods: Add domain-specific queries as needed (e.g.,
FindByName
,
AssignContacts
).
位置
internal/modules/<module>/ports/<entity>_repository.go
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
}
分页变体:
go
FindAll(ctx context.Context, page, pageSize int) ([]model.EntityModel, int64, error)
自定义方法:根据需要添加领域特定的查询(如
FindByName
AssignContacts
)。

Repository Implementation Structure

仓库实现结构

Location:
internal/modules/<module>/repository/<entity>_repository.go
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}
}
位置
internal/modules/<module>/repository/<entity>_repository.go
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)

FindAll(简单版)

go
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
}
go
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)

FindAll(分页版)

go
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
}
go
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

FindByID

go
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
}
go
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

Create

go
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
}
go
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

Update

go
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
}
go
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

Delete

go
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
}
go
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)

自定义查询(按字段)

go
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
}
go
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)

事务(关联操作)

go
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
}
go
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

Fx 依赖注入配置

Add to
internal/modules/<module>/fx.go
:
go
fx.Provide(
	fx.Annotate(
		repository.NewEntityRepository,
		fx.As(new(ports.EntityRepository)),
	),
),
添加到
internal/modules/<module>/fx.go
go
fx.Provide(
	fx.Annotate(
		repository.NewEntityRepository,
		fx.As(new(ports.EntityRepository)),
	),
),

Critical Rules

关键规则

  1. Struct: Embed
    *database.PingoDB
    only
  2. Constructor: MUST return pointer
    *EntityRepository
  3. Interface assertion: Add
    var _ ports.EntityRepository = (*EntityRepository)(nil)
    below struct
  4. Tracing: Every method MUST start with
    ctx, otelSpan := trace.Span(ctx, "Repo.Method")
    and
    defer otelSpan.End()
  5. Queries: Use
    gorm.G[Model](r.DB)
    pattern for all queries
  6. First queries: Add
    .Limit(1)
    before
    .First(ctx)
  7. Not found: Return
    errs.ErrRecordNotFound
    when
    errors.Is(err, gorm.ErrRecordNotFound)
  8. Delete: Check
    rowsAffected == 0
    and return
    errs.ErrRecordNotFound
  9. Transactions: Use
    tx := r.DB.Begin()
    , rollback on error, commit at end
  10. No comments: Do not add redundant comments above methods
  11. Validation: Run
    make lint
    and
    make nilaway
    after generation
  1. 结构体:仅嵌入
    *database.PingoDB
  2. 构造函数:必须返回指针类型
    *EntityRepository
  3. 接口断言:在结构体下方添加
    var _ ports.EntityRepository = (*EntityRepository)(nil)
  4. 链路追踪:每个方法必须以
    ctx, otelSpan := trace.Span(ctx, "Repo.Method")
    开头,并以
    defer otelSpan.End()
    结尾
  5. 查询语句:所有查询均使用
    gorm.G[Model](r.DB)
    模式
  6. First查询:在
    .First(ctx)
    前添加
    .Limit(1)
  7. 未找到记录:当
    errors.Is(err, gorm.ErrRecordNotFound)
    时,返回
    errs.ErrRecordNotFound
  8. 删除操作:检查
    rowsAffected == 0
    并返回
    errs.ErrRecordNotFound
  9. 事务处理:使用
    tx := r.DB.Begin()
    开启事务,出错时回滚,最后提交
  10. 注释规范:不要在方法上方添加冗余注释
  11. 验证检查:生成代码后运行
    make lint
    make nilaway

Workflow

  1. Create port interface in
    ports/<entity>_repository.go
  2. Create repository implementation in
    repository/<entity>_repository.go
  3. Add Fx wiring to module's
    fx.go
  4. Run
    make lint
    to verify
  5. Run
    make nilaway
    for static analysis