Loading...
Loading...
Go backend development standards using DDD architecture, ent ORM, fx DI, and kiwi-lib utilities. Covers project structure, domain design, API/application/infrastructure layers, database workflow, and coding requirements.
npx skill4agent add yet-another-ai-project/kiwi-skills kiwi-go-backendapi -> application -> domain <- infrastructure| Layer | Responsibility | Key Rule |
|---|---|---|
| API | HTTP handling, param parsing, response serialization | Thin layer. No business logic. |
| Application | Orchestrates domain services for use cases | Can READ from repo. CANNOT WRITE directly. |
| Domain | Business logic, entities, aggregate roots, VOs | Pure Go. No framework dependencies. |
| Infrastructure | Repository implementations, external clients, DI | Implements domain interfaces. |
*EntityUserEntity*AggregateRootOrderAggregateRootIIOrderRepositoryKeyEntity[]ChildEntityfield.JSONGetAll*()type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending"
OrderStatusCompleted OrderStatus = "completed"
)
func GetAllOrderStatuses() []OrderStatus {
return []OrderStatus{OrderStatusPending, OrderStatusCompleted}
}domain/{domain}/repository/interface.gotype IOrderRepositoryRead interface {
FindByID(ctx context.Context, id uuid.UUID) (*aggregate.OrderAggregateRoot, error)
}
type IOrderRepositoryWrite interface {
Save(ctx context.Context, root *aggregate.OrderAggregateRoot) (*aggregate.OrderAggregateRoot, error)
}
type IOrderRepository interface {
IOrderRepositoryRead
IOrderRepositoryWrite
contract.ITransactionDecorator
}*Entity*AggregateRoot*AggregateRootdomain/common/contract/transaction.gotype ITransactionDecorator interface {
WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
}RepositoryReadRepositoryWritelibfacade.Error// application/service/error/error.go
var (
ErrOrderNotFound = libfacade.NewNotFoundError("order not found")
ErrForbidden = libfacade.NewForbiddenError("access denied")
)api/dto/jsonbinding// Normal endpoint (no auth)
NormalHandler[T any](f func(*gin.Context) (T, *libfacade.Error))
// Auth-required endpoint (extracts userID from middleware)
RequireUserHandler[T any](f func(*gin.Context, string) (T, *libfacade.Error))
// SSE streaming
EventStreamHandler(f func(*gin.Context) *libfacade.Error)
EventStreamRequireUserHandler(f func(*gin.Context, string) *libfacade.Error)libfacade.BaseResponse{Status, Data}transactionDecoratorclientGetterwithDbClientfunc (r *repo) FindByID(ctx context.Context, id uuid.UUID) (*aggregate.Root, error) {
var root *aggregate.Root
err := r.clientGetter.withDbClient(ctx, func(ctx context.Context, dbClient *ent.Client) error {
entItem, err := dbClient.Order.Query().Where(order.ID(id)).Only(ctx)
if err != nil {
if ent.IsNotFound(err) { return OrderNotFoundError }
return xerror.Wrap(err)
}
root = r.entToDomain(entItem)
return nil
})
return root, err
}idfield.UUID("id", uuid.UUID{}).Default(func() uuid.UUID { id, _ := uuid.NewV7(); return id }).Immutable()created_atupdated_atmixin.Time{}Mixin()created_atupdated_atcreated_attime.Now()updated_attime.Now()UpdateDefaultidcreated_atupdated_atfield.Enum("status").Values(utils.EnumToStrings(vo.GetAllOrderStatuses())...)field.JSON("metadata", &vo.OrderMetadata{}).Optional()infrastructure/repository/ent/schema/make generate-repository-codemake generate-migration-repository-dbmake apply-migration-repository-dbgo.uber.org/fxModule// main.go
app := fx.New(
fx.NopLogger,
api.Module,
application.Module,
domain.Module,
infrastructure.Module,
)
app.Run()fx.Annotatefx.Asfx.Annotate(
repository.NewOrderRepository,
fx.As(new(orderrepo.IOrderRepository)),
fx.As(new(orderrepo.IOrderRepositoryRead)),
fx.As(new(orderrepo.IOrderRepositoryWrite)),
),| Layer | Method | Example |
|---|---|---|
| Domain/Infrastructure | | |
| Application | | |
| API | Handler wrappers auto-format | Automatic |
fmt.Errorf()errors.New()import (
libgin "github.com/Yet-Another-AI-Project/kiwi-lib/server/gin"
libfacade "github.com/Yet-Another-AI-Project/kiwi-lib/server/facade"
"github.com/futurxlab/golanggraph/logger" // logger.ILogger
"github.com/futurxlab/golanggraph/xerror" // xerror.Wrap, xerror.New
"entgo.io/ent" // ORM
"go.uber.org/fx" // DI
"github.com/google/uuid" // uuid.NewV7()
"github.com/gookit/config/v2" // Config loading
"github.com/redis/go-redis/v9" // Redis client
)gookit/config/v2config:"field_name"type Config struct {
Server ServerConfig `config:"server"`
Postgresql PostgresqlConfig `config:"postgres"`
Redis RedisConfig `config:"redis"`
Log LogConfig `config:"log"`
}GetAll*()return errxerror.Wrap(err)xerror.New("message")fmt.Errorf()errors.New()utils.EnumToStrings()idmixin.Time{}created_atupdated_atwithDbClientRepositoryReadfx.Annotatefx.Asmake generate-repository-code// utils/enum_converter.go
type EnumType interface{ ~string }
func EnumToStrings[T EnumType](enums []T) []string {
result := make([]string, len(enums))
for i, e := range enums { result[i] = string(e) }
return result
}