go-service
npx skills add https://github.com/cristiano-pacheco/ai-rules --skill go-service
Agent 安装分布
Skill 文档
Go Service
Generate service files for GO modular architecture conventions.
Three-File Pattern
Every service requires up to three files:
- DTO structs (if needed):
internal/modules/<module>/dto/<service_name>_dto.go - Port interface:
internal/modules/<module>/ports/<service_name>_service.go - Service implementation:
internal/modules/<module>/service/<service_name>_service.go
DTO File Layout Order
- Input/output structs
Port File Layout Order
- Interface definition (
XxxServiceâ no suffix)
Service File Layout Order
- Implementation struct (
XxxService) - Compile-time interface assertion
- Constructor (
NewXxxService) - Methods
DTO Structure
Location: internal/modules/<module>/dto/<service_name>_dto.go
package dto
type DoSomethingInput struct {
Field string
}
Port Interface Structure
Location: internal/modules/<module>/ports/<service_name>_service.go
package ports
import (
"context"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/dto"
)
type DoSomethingService interface {
Execute(ctx context.Context, input dto.DoSomethingInput) error
}
Service Implementation Structure
Location: internal/modules/<module>/service/<service_name>_service.go
package service
import (
"context"
"github.com/cristiano-pacheco/bricks/pkg/logger"
"github.com/cristiano-pacheco/bricks/pkg/otel/trace"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/dto"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/ports"
)
type DoSomethingService struct {
logger logger.Logger
// other dependencies
}
var _ ports.DoSomethingService = (*DoSomethingService)(nil)
func NewDoSomethingService(
logger logger.Logger,
) *DoSomethingService {
return &DoSomethingService{
logger: logger,
}
}
func (s *DoSomethingService) Execute(ctx context.Context, input dto.DoSomethingInput) error {
ctx, span := trace.Span(ctx, "DoSomethingService.Execute")
defer span.End()
// Business logic here
// if err != nil {
// s.logger.Error("DoSomethingService.Execute failed", logger.Error(err))
// return err
// }
return nil
}
Service Variants
Single-action service (Execute pattern)
Use Execute method with a dedicated input struct when the service does one thing.
DTO (dto/send_email_confirmation_dto.go):
type SendEmailConfirmationInput struct {
UserModel model.UserModel
ConfirmationTokenHash []byte
}
Port (ports/send_email_confirmation_service.go):
type SendEmailConfirmationService interface {
Execute(ctx context.Context, input dto.SendEmailConfirmationInput) error
}
Multi-method service (named methods)
Use descriptive method names when the service groups related operations.
Port (ports/hash_service.go):
type HashService interface {
GenerateFromPassword(password []byte) ([]byte, error)
CompareHashAndPassword(hashedPassword, password []byte) error
GenerateRandomBytes() ([]byte, error)
}
Stateless service (no dependencies)
Omit logger and config when the service is a pure utility with no I/O.
type HashService struct{}
func NewHashService() *HashService {
return &HashService{}
}
Tracing
Services performing I/O MUST use trace.Span. Pure utilities (hashing, template compilation) skip tracing.
ctx, span := trace.Span(ctx, "ServiceName.MethodName")
defer span.End()
Span name format: "StructName.MethodName"
Naming
- Port interface:
XxxService(inportspackage, no suffix) - Implementation struct:
XxxService(inservicepackage, same name â disambiguated by package) - Constructor:
NewXxxService, returns a pointer of the struct implementation
Fx Wiring
Add to internal/modules/<module>/fx.go:
fx.Provide(
fx.Annotate(
service.NewXxxService,
fx.As(new(ports.XxxService)),
),
),
Dependencies
Services depend on interfaces only. Common dependencies:
logger.Loggerâ structured logging- Other
ports.XxxServiceinterfaces â compose services ports.XxxRepositoryâ data accessports.XxxCacheâ caching layer
Error Logging Rule
- Always use the Bricks logger package:
github.com/cristiano-pacheco/bricks/pkg/logger - Every time a service method returns an error, log it immediately before returning
- Preferred pattern:
if err != nil {
s.logger.Error("ServiceName.MethodName failed", logger.Error(err))
return err
}
Critical Rules
- No standalone functions: When a file contains a struct with methods, do not add standalone functions. Use private methods on the struct instead.
- Three files: DTOs in
dto/, port interface inports/, implementation inservice/ - Interface in ports: Interface lives in
ports/<name>_service.go - DTOs in dto: Input/output structs live in
dto/<name>_dto.go - Interface assertion: Add
var _ ports.XxxService = (*XxxService)(nil)below the struct - Constructor: MUST return pointer
*XxxService - Tracing: Every I/O method MUST use
trace.Spanwithdefer span.End() - Context: Methods performing I/O accept
context.Contextas first parameter - No comments on implementations: Do not add redundant comments above methods in the implementations
- Add detailed comment on interfaces: Provide comprehensive comments on the port interfaces to describe their purpose and usage
- Dependencies: Always depend on port interfaces, never concrete implementations
- Error logging: Every returned error must be logged first using Bricks logger (
s.logger.Error(..., logger.Error(err)))
Workflow
- Create DTO file in
dto/<name>_dto.go(if input/output structs are needed) - Create port interface in
ports/<name>_service.go - Create service implementation in
service/<name>_service.go - Add Fx wiring to module’s
fx.go - Run
make lintto verify - Run
make nilawayfor static analysis