中文文档 | English
A Go microservice scaffold based on Domain-Driven Design (DDD), built with CloudWeGo framework, supporting both gRPC and HTTP protocols.
make init# Generate Protocol Buffer code
make api
# Generate dependency injection code
make gen# Development mode
make run
# Or run directly
go run ./cmdServices will start on:
- HTTP: 8080 (Hertz)
- RPC: 9090 (Kitex)
# HTTP request
curl http://localhost:8080/hello/1
# gRPC request (using grpcurl)
grpcurl -plaintext localhost:9090 api.hello.Hello/SayHelloThis scaffold follows Domain-Driven Design (DDD) with clean layered architecture:
internal/
├── boundedcontexts/ # Bounded contexts (business domains)
│ └── hello/ # Hello domain
│ ├── domain/ # Domain layer: business rules
│ │ ├── entities/ # Entities: business objects
│ │ └── repositories/ # Repository interfaces: data access contracts
│ ├── application/ # Application layer: business workflows
│ │ └── handlers/ # Handlers: coordinate business logic
│ └── infrastructure/ # Infrastructure layer: technical implementations
│ └── repositories/ # Repository implementations: database operations
├── di/ # Dependency injection
│ └── providers/ # Wire providers
├── server/ # Server configuration
└── service/ # Service registration (delegation layer)
- Bounded Context: Each business domain is independently organized
- Domain Entity: Core objects containing business rules and validation logic
- Repository Pattern: Abstract interface for data access, domain layer doesn't depend on implementations
- Handler: Shared business logic for both HTTP and gRPC
- Dependency Injection (DI): Using Google Wire for automatic dependency management
Example: Adding user registration feature
// internal/boundedcontexts/hello/domain/entities/user.go
type User struct {
ID string
Email string
Nickname string
}
func NewUser(email, nickname string) (*User, error) {
// Business validation logic
if !isValidEmail(email) {
return nil, errors.New("invalid email")
}
return &User{
ID: uuid.New().String(),
Email: email,
Nickname: nickname,
}, nil
}// internal/boundedcontexts/hello/domain/repositories/user_repository.go
type UserRepository interface {
Save(ctx context.Context, user *entities.User) error
FindByEmail(ctx context.Context, email string) (*entities.User, error)
}// internal/boundedcontexts/hello/infrastructure/repositories/user_repository.go
type UserRepository struct {
db *gorm.DB
}
func (r *UserRepository) Save(ctx context.Context, user *entities.User) error {
return r.db.WithContext(ctx).Create(user).Error
}// internal/boundedcontexts/hello/application/handlers/user_handler.go
type UserHandler struct {
repo repositories.UserRepository
}
func (h *UserHandler) Register(ctx context.Context, req *RegisterRequest) (*RegisterResponse, error) {
// Check if user already exists
existing, _ := h.repo.FindByEmail(ctx, req.Email)
if existing != nil {
return nil, errors.New("user already exists")
}
// Create new user
user, err := entities.NewUser(req.Email, req.Nickname)
if err != nil {
return nil, err
}
// Save to database
if err := h.repo.Save(ctx, user); err != nil {
return nil, err
}
return &RegisterResponse{UserID: user.ID}, nil
}// internal/di/providers/hello/hello_provider.go
var HelloProviderSet = wire.NewSet(
NewUserRepository,
handlers.NewUserHandler,
)Add corresponding service methods in the service layer, simply delegate to handlers.
When creating a new business domain:
internal/boundedcontexts/
├── hello/ # Existing domain
└── order/ # New order domain
├── domain/
│ ├── entities/
│ └── repositories/
├── application/
│ └── handlers/
└── infrastructure/
└── repositories/Each bounded context is independently maintained and integrated via dependency injection.
- Framework: CloudWeGo (Kitex + Hertz)
- Dependency Injection: Google Wire
- Database: GORM
- Configuration: Viper
- Validation: go-playground/validator
make init # Initialize project
make api # Generate Protocol Buffer code
make gen # Generate Wire dependency injection code
make run # Run service
make test # Run tests
make lint # Run linter
make clean # Clean generated filescmd/: Application entry point, Wire DI configurationapi/: Protocol Buffer definitions and generated codeinternal/: Internal code (not exposed)boundedcontexts/: DDD bounded contextsdi/: Dependency injection providersserver/: Server startup and configurationservice/: Service registration (delegates to handlers)config/: Configuration managementmiddleware/: Middleware
configs/: Configuration filesscripts/: Utility scripts
- Separation of Concerns: Business logic separated from technical implementation
- Dependency Inversion: Domain layer doesn't depend on infrastructure layer
- Single Responsibility: Each component has one responsibility
- Protocol Agnostic: Business logic supports both HTTP and gRPC
This scaffold provides basic architecture. Developers can add as needed:
- Cache Layer: Redis cache implementation
- Message Queue: Kafka/RabbitMQ integration
- Service Discovery: Consul/Etcd integration
- Distributed Tracing: OpenTelemetry integration
- Circuit Breaker: Hystrix pattern implementation
- API Gateway: Kong/Traefik integration
Q: Why does the service layer only delegate? A: The service layer handles service registration and protocol conversion. Business logic is unified in handlers for HTTP and gRPC sharing.
Q: How to add database migrations?
A: Add migration files in the internal/boundedcontexts/xxx/infrastructure/migrations/ directory.
Q: How to handle cross-domain communication? A: Communicate through application layer service interfaces, avoid direct dependencies between domains.
MIT