Go Create Service
Generate service files for GO modular architechture 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
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` (in `ports` package, no suffix) - Implementation struct: `XxxService` (in `service` package, same name — disambiguated by package) - Constructor: `NewXxxService`, returns a pointer of the struct implementation ## Fx Wiring Add to `internal/modules/<module>/fx.go`: ```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 access - •
ports.XxxCache— caching layer
Critical Rules
- •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
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
module.go(orfx.go) - •Run
make lintto verify - •Run
make nilawayfor static analysis