Agent skill
golang-enterprise-patterns
Enterprise-level Go architecture patterns including clean architecture, hexagonal architecture, DDD, and production-ready application structure.
Install this agent skill to your Project
npx add-skill https://github.com/aiskillstore/marketplace/tree/main/skills/89jobrien/golang-enterprise-patterns
SKILL.md
Golang Enterprise Patterns
This skill provides guidance on enterprise-level Go application architecture, design patterns, and production-ready code organization.
When to Use This Skill
- When designing new Go applications with complex business logic
- When implementing clean architecture or hexagonal architecture
- When applying Domain-Driven Design (DDD) principles
- When organizing large Go codebases
- When establishing patterns for team consistency
Clean Architecture
Layer Structure
/cmd
/api - HTTP/gRPC entry points
/worker - Background job runners
/internal
/domain - Business entities and interfaces
/application - Use cases and application services
/infrastructure
/persistence - Database implementations
/messaging - Queue implementations
/http - HTTP client implementations
/interfaces
/api - HTTP handlers
/grpc - gRPC handlers
/pkg - Shared libraries (public)
Dependency Rule
Dependencies flow inward only:
Interfaces → Application → Domain
↓ ↓
Infrastructure (implements domain interfaces)
Domain Layer
// domain/user.go
package domain
import "time"
type UserID string
type User struct {
ID UserID
Email string
Name string
CreatedAt time.Time
}
// UserRepository defines the contract for user persistence
type UserRepository interface {
FindByID(ctx context.Context, id UserID) (*User, error)
FindByEmail(ctx context.Context, email string) (*User, error)
Save(ctx context.Context, user *User) error
Delete(ctx context.Context, id UserID) error
}
// UserService defines domain business logic
type UserService interface {
Register(ctx context.Context, email, name string) (*User, error)
Authenticate(ctx context.Context, email, password string) (*User, error)
}
Application Layer
// application/user_service.go
package application
type UserServiceImpl struct {
repo domain.UserRepository
hasher PasswordHasher
logger Logger
}
func NewUserService(repo domain.UserRepository, hasher PasswordHasher, logger Logger) *UserServiceImpl {
return &UserServiceImpl{repo: repo, hasher: hasher, logger: logger}
}
func (s *UserServiceImpl) Register(ctx context.Context, email, name string) (*domain.User, error) {
// Check if user exists
existing, err := s.repo.FindByEmail(ctx, email)
if err != nil && !errors.Is(err, domain.ErrNotFound) {
return nil, fmt.Errorf("checking existing user: %w", err)
}
if existing != nil {
return nil, domain.ErrUserAlreadyExists
}
user := &domain.User{
ID: domain.UserID(uuid.New().String()),
Email: email,
Name: name,
CreatedAt: time.Now(),
}
if err := s.repo.Save(ctx, user); err != nil {
return nil, fmt.Errorf("saving user: %w", err)
}
return user, nil
}
Hexagonal Architecture (Ports & Adapters)
Port Definitions
// ports/primary.go - Driving ports (input)
package ports
type UserAPI interface {
CreateUser(ctx context.Context, req CreateUserRequest) (*UserResponse, error)
GetUser(ctx context.Context, id string) (*UserResponse, error)
}
// ports/secondary.go - Driven ports (output)
type UserStorage interface {
Save(ctx context.Context, user *domain.User) error
FindByID(ctx context.Context, id string) (*domain.User, error)
}
type NotificationSender interface {
SendWelcomeEmail(ctx context.Context, user *domain.User) error
}
Adapter Implementations
// adapters/postgres/user_repository.go
package postgres
type UserRepository struct {
db *sql.DB
}
func (r *UserRepository) Save(ctx context.Context, user *domain.User) error {
query := `INSERT INTO users (id, email, name, created_at) VALUES ($1, $2, $3, $4)`
_, err := r.db.ExecContext(ctx, query, user.ID, user.Email, user.Name, user.CreatedAt)
return err
}
Domain-Driven Design (DDD)
Aggregate Roots
// domain/order/aggregate.go
package order
type Order struct {
id OrderID
customerID CustomerID
items []OrderItem
status OrderStatus
events []DomainEvent
}
func NewOrder(customerID CustomerID) *Order {
o := &Order{
id: OrderID(uuid.New().String()),
customerID: customerID,
status: StatusPending,
}
o.recordEvent(OrderCreated{OrderID: o.id, CustomerID: customerID})
return o
}
func (o *Order) AddItem(productID ProductID, quantity int, price Money) error {
if o.status != StatusPending {
return ErrOrderNotModifiable
}
o.items = append(o.items, OrderItem{
ProductID: productID,
Quantity: quantity,
Price: price,
})
return nil
}
func (o *Order) Submit() error {
if len(o.items) == 0 {
return ErrEmptyOrder
}
o.status = StatusSubmitted
o.recordEvent(OrderSubmitted{OrderID: o.id})
return nil
}
Value Objects
// domain/money.go
type Money struct {
amount int64 // cents
currency string
}
func NewMoney(amount int64, currency string) (Money, error) {
if amount < 0 {
return Money{}, ErrNegativeAmount
}
return Money{amount: amount, currency: currency}, nil
}
func (m Money) Add(other Money) (Money, error) {
if m.currency != other.currency {
return Money{}, ErrCurrencyMismatch
}
return Money{amount: m.amount + other.amount, currency: m.currency}, nil
}
Domain Events
// domain/events.go
type DomainEvent interface {
EventName() string
OccurredAt() time.Time
}
type OrderCreated struct {
OrderID OrderID
CustomerID CustomerID
occurredAt time.Time
}
func (e OrderCreated) EventName() string { return "order.created" }
func (e OrderCreated) OccurredAt() time.Time { return e.occurredAt }
Dependency Injection
Wire-Style DI
// wire.go
//+build wireinject
func InitializeApp(cfg *config.Config) (*App, error) {
wire.Build(
NewDatabase,
NewUserRepository,
NewUserService,
NewHTTPServer,
NewApp,
)
return nil, nil
}
Manual DI (Preferred for Simplicity)
// main.go
func main() {
cfg := config.Load()
db := database.Connect(cfg.DatabaseURL)
userRepo := postgres.NewUserRepository(db)
orderRepo := postgres.NewOrderRepository(db)
userService := application.NewUserService(userRepo)
orderService := application.NewOrderService(orderRepo, userRepo)
handler := api.NewHandler(userService, orderService)
server := http.NewServer(cfg.Port, handler)
server.Run()
}
Error Handling Patterns
Custom Error Types
// domain/errors.go
type Error struct {
Code string
Message string
Err error
}
func (e *Error) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}
func (e *Error) Unwrap() error { return e.Err }
var (
ErrNotFound = &Error{Code: "NOT_FOUND", Message: "resource not found"}
ErrUserAlreadyExists = &Error{Code: "USER_EXISTS", Message: "user already exists"}
ErrInvalidInput = &Error{Code: "INVALID_INPUT", Message: "invalid input"}
)
Configuration Management
// config/config.go
type Config struct {
Server ServerConfig
Database DatabaseConfig
Redis RedisConfig
}
func Load() (*Config, error) {
cfg := &Config{}
cfg.Server.Port = getEnvInt("PORT", 8080)
cfg.Server.ReadTimeout = getEnvDuration("READ_TIMEOUT", 30*time.Second)
cfg.Database.URL = mustGetEnv("DATABASE_URL")
cfg.Database.MaxConns = getEnvInt("DB_MAX_CONNS", 25)
return cfg, nil
}
Best Practices
- Keep domain pure - No framework dependencies in domain layer
- Interface segregation - Small, focused interfaces
- Dependency inversion - Depend on abstractions, not concretions
- Explicit dependencies - Pass dependencies via constructor
- Fail fast - Validate at boundaries, trust internal code
- Make illegal states unrepresentable - Use types to enforce invariants
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
perigon-backend
Perigon ASP.NET Core + EF Core + Aspire conventions
perigon-agent
Pointers for Copilot/agents to apply Perigon conventions
perigon-angular
Angular 21+ standalone/Material/signal conventions for Perigon WebApp
fastapi-mastery
Comprehensive FastAPI development skill covering REST API creation, routing, request/response handling, validation, authentication, database integration, middleware, and deployment. Use when working with FastAPI projects, building APIs, implementing CRUD operations, setting up authentication/authorization, integrating databases (SQL/NoSQL), adding middleware, handling WebSockets, or deploying FastAPI applications. Triggered by requests involving .py files with FastAPI code, API endpoint creation, Pydantic models, or FastAPI-specific features.
context7-efficient
Token-efficient library documentation fetcher using Context7 MCP with 86.8% token savings through intelligent shell pipeline filtering. Fetches code examples, API references, and best practices for JavaScript, Python, Go, Rust, and other libraries. Use when users ask about library documentation, need code examples, want API usage patterns, are learning a new framework, need syntax reference, or troubleshooting with library-specific information. Triggers include questions like "Show me React hooks", "How do I use Prisma", "What's the Next.js routing syntax", or any request for library/framework documentation.
browser-use
Browser automation using Playwright MCP. Navigate websites, fill forms, click elements, take screenshots, and extract data. Use when tasks require web browsing, form submission, web scraping, UI testing, or any browser interaction.
Didn't find tool you were looking for?