Claude-skill-registry kratos-new-service
Creates a new go-kratos microservice skeleton with clean architecture (server/service/biz/data), minimal Wire setup, HTTP + gRPC metrics, and complete project structure. Use when scaffolding new services in the brizy-go-services monorepo.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/kratos-new-service" ~/.claude/skills/majiayu000-claude-skill-registry-kratos-new-service && rm -rf "$T"
skills/data/kratos-new-service/SKILL.md<quick_start> When invoked, this skill will:
- Ask for the service name interactively
- Validate the name (lowercase, alphanumeric, hyphens only)
- Create complete service skeleton in
services/{service-name}/ - Generate Wire dependency injection code
- Create initial configs/config.yaml
The service will be ready to build but will need proto definitions added separately. </quick_start>
<workflow> ## Step 1: Get and Validate Service NameAsk the user for the service name:
What is the name of the new service? Requirements: - Lowercase letters, numbers, and hyphens only - Must be a valid Go module name - Will be used for directory name, module path, and service identification Example: auth, user-management, notification-service
Validate the input:
- Only lowercase letters (a-z), numbers (0-9), and hyphens (-) allowed
- No consecutive hyphens
- Cannot start or end with a hyphen
- Length: 3-50 characters
If invalid, explain the issue and ask again.
Step 2: Check for Conflicts
Verify that
services/{service-name}/ does not already exist:
ls services/{service-name} 2>/dev/null
If it exists, inform the user and stop. Do not overwrite existing services.
Step 3: Create Directory Structure
Create the complete directory structure:
mkdir -p services/{service-name}/cmd/{service-name} mkdir -p services/{service-name}/internal/biz mkdir -p services/{service-name}/internal/data/model mkdir -p services/{service-name}/internal/data/repo mkdir -p services/{service-name}/internal/service mkdir -p services/{service-name}/internal/server mkdir -p services/{service-name}/internal/conf mkdir -p services/{service-name}/configs mkdir -p services/{service-name}/bin
Step 4: Create Configuration Files
4.1 Create internal/conf/conf.proto
syntax = "proto3"; package conf; option go_package = "services/{service-name}/internal/conf;conf"; import "google/protobuf/duration.proto"; message Bootstrap { Server server = 1; Data data = 2; Metrics metrics = 3; } message Server { message HTTP { string network = 1; string addr = 2; google.protobuf.Duration timeout = 3; } message GRPC { string network = 1; string addr = 2; google.protobuf.Duration timeout = 3; } HTTP http = 1; GRPC grpc = 2; } message Data { message Database { string driver = 1; string source = 2; } Database database = 1; } message Metrics { bool enabled = 1; string service_name = 2; string path = 3; bool include_runtime = 4; }
4.2 Create configs/config.yaml
server: http: addr: 0.0.0.0:8000 timeout: 5s grpc: addr: 0.0.0.0:9000 timeout: 5s data: database: driver: postgres source: postgres://user:password@localhost:5432/{service-name}?sslmode=disable metrics: enabled: true path: /metrics include_runtime: true
Replace
{service-name} with the actual service name in the YAML.
Step 5: Create Data Layer Files
5.1 Create internal/data/data.go
package data import ( "github.com/go-kratos/kratos/v2/log" "github.com/google/wire" "services/{service-name}/internal/conf" ) // ProviderSet is data providers. var ProviderSet = wire.NewSet( NewData, ) // Data encapsulates all data access dependencies. type Data struct { log *log.Helper // db *gorm.DB - uncomment when adding GORM } // NewData creates a new Data instance. func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) { logHelper := log.NewHelper(log.With(logger, "module", "data")) d := &Data{ log: logHelper, } cleanup := func() { logHelper.Info("closing data resources") } return d, cleanup, nil }
Replace
{service-name} with the actual service name.
Step 6: Create Business Logic Layer Files
6.1 Create internal/biz/interfaces.go
package biz // Repository interfaces will be defined here // Example: // type ExampleRepo interface { // Save(ctx context.Context, example *Example) error // FindByID(ctx context.Context, id int64) (*Example, error) // }
6.2 Create internal/biz/biz.go
package biz import ( "github.com/go-kratos/kratos/v2/log" "github.com/google/wire" ) // ProviderSet is business logic providers. var ProviderSet = wire.NewSet()
Step 7: Create Service Layer Files
7.1 Create internal/service/service.go
package service import ( "github.com/google/wire" "services/{service-name}/internal/biz" ) // ProviderSet is service providers. var ProviderSet = wire.NewSet( NewService, ) // Service encapsulates business logic dependencies. type Service struct { log *log.Helper } // NewService creates a new Service instance. func NewService(logger log.Logger) *Service { return &Service{ log: log.NewHelper(log.With(logger, "module", "service")), } }
Replace
{service-name} with the actual service name.
Step 8: Create Server Layer Files
8.1 Create internal/server/http.go
package server import ( "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware/logging" "github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/transport/http" "platform/metrics" "services/{service-name}/internal/conf" "services/{service-name}/internal/service" ) // NewHTTPServer creates a new HTTP server. func NewHTTPServer( c *conf.Server, logger log.Logger, svc *service.Service, metricsRegistry *metrics.Registry, ) *http.Server { var opts = []http.ServerOption{ http.Middleware( recovery.Recovery(), logging.Server(logger), metrics.HTTPMiddleware(metricsRegistry), ), } if c.Http.Network != "" { opts = append(opts, http.Network(c.Http.Network)) } if c.Http.Addr != "" { opts = append(opts, http.Address(c.Http.Addr)) } if c.Http.Timeout != nil { opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration())) } srv := http.NewServer(opts...) // Register service handlers here // Example: // v1.RegisterExampleServiceHTTPServer(srv, svc) return srv }
Replace
{service-name} with the actual service name.
8.2 Create internal/server/grpc.go
package server import ( "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware/logging" "github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/transport/grpc" "platform/metrics" "services/{service-name}/internal/conf" "services/{service-name}/internal/service" ) // NewGRPCServer creates a new gRPC server. func NewGRPCServer( c *conf.Server, logger log.Logger, svc *service.Service, metricsRegistry *metrics.Registry, ) *grpc.Server { var opts = []grpc.ServerOption{ grpc.Middleware( recovery.Recovery(), logging.Server(logger), metrics.GRPCMiddleware(metricsRegistry), ), } if c.Grpc.Network != "" { opts = append(opts, grpc.Network(c.Grpc.Network)) } if c.Grpc.Addr != "" { opts = append(opts, grpc.Address(c.Grpc.Addr)) } if c.Grpc.Timeout != nil { opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration())) } srv := grpc.NewServer(opts...) // Register service handlers here // Example: // v1.RegisterExampleServiceServer(srv, svc) return srv }
Replace
{service-name} with the actual service name.
8.3 Create internal/server/server.go
package server import ( "github.com/google/wire" ) // ProviderSet is server providers. var ProviderSet = wire.NewSet( NewHTTPServer, NewGRPCServer, NewMetricsRegistry, )
8.4 Create internal/server/metrics.go
package server import ( "platform/metrics" "services/{service-name}/internal/conf" ) // NewMetricsRegistry creates a new metrics registry. func NewMetricsRegistry(c *conf.Metrics) *metrics.Registry { if !c.Enabled { return nil } return metrics.NewRegistry(c.ServiceName, c.IncludeRuntime) }
Replace
{service-name} with the actual service name.
Step 9: Create Command Layer Files
9.1 Create cmd/{service-name}/main.go
package main import ( "flag" "os" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/config/file" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport/http" "services/{service-name}/internal/conf" ) // go build -ldflags "-X main.Version=x.y.z" var ( // Name is the name of the compiled software. Name string = "symbol-service" // Version is the version of the compiled software. Version string = "1.0" // configFile is the config flag. configFile string id, _ = os.Hostname() ) func init() { flag.StringVar(&configFile, "conf", "configs/config.yaml", "config path, eg: --conf config.yaml") } var buildInfo = build.NewBuildInfo(Name, Version) func main() { flag.Parse() logger := log.With(log.NewStdLogger(os.Stdout), "ts", log.DefaultTimestamp, "caller", log.DefaultCaller, "service.name", "{service-name}", ) c := config.New( config.WithSource( file.NewSource(flagconf), ), ) defer c.Close() if err := c.Load(); err != nil { panic(err) } var bc conf.Bootstrap if err := c.Scan(&bc); err != nil { panic(err) } app, cleanup, err := wireApp(buildInfo, bc.Server, bc.Data, bc.Metrics, logger) if err != nil { panic(err) } defer cleanup() // Start and wait for stop signal if err := app.Run(); err != nil { panic(err) } } func newApp(logger log.Logger, hs *http.Server, gs *grpc.Server) *kratos.App { return kratos.New( kratos.ID(id), kratos.Name(Name), kratos.Version(Version), kratos.Metadata(map[string]string{}), kratos.Logger(logger), kratos.Server( gs, hs, ), ) }
Replace
{service-name} with the actual service name.
9.2 Create cmd/{service-name}/wire.go
//go:build wireinject // +build wireinject package main import ( platform_build_info "platform/build" platform_logger "platform/logger" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/log" "github.com/google/wire" "services/{service-name}/internal/biz" "services/{service-name}/internal/conf" "services/{service-name}/internal/data" "services/{service-name}/internal/server" "services/{service-name}/internal/service" ) // wireApp builds the application with dependency injection. func wireApp(*platform_build_info.ServiceBuildInfo, *conf.Data, *conf.Metrics, log.Logger) (*kratos.App, func(), error) { panic(wire.Build( platform_logger.ProviderSet, server.ProviderSet, service.ProviderSet, biz.ProviderSet, data.ProviderSet, newApp, )) }
Replace
{service-name} with the actual service name.
Step 10: Create Build Configuration Files
10.1 Create go.mod
module services/{service-name} go 1.25.0 require ( github.com/envoyproxy/protoc-gen-validate v1.3.0 github.com/go-kratos/kratos/contrib/middleware/validate/v2 v2.0.0-20251217105121-fb8e43efb207 github.com/go-kratos/kratos/v2 v2.9.2 github.com/go-playground/validator/v10 v10.30.1 github.com/google/wire v0.7.0 github.com/gorilla/handlers v1.5.2 github.com/stretchr/testify v1.11.1 go.uber.org/automaxprocs v1.6.0 google.golang.org/protobuf v1.36.11 gorm.io/driver/mysql v1.6.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 )
Replace
{service-name} with the actual service name.
10.2 Create Makefile
GOHOSTOS:=$(shell go env GOHOSTOS) GOPATH:=$(shell go env GOPATH) VERSION=$(shell git describe --tags --always) ifeq ($(GOHOSTOS), windows) #the `find.exe` is different from `find` in bash/shell. #to see https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/find. #changed to use git-bash.exe to run find cli or other cli friendly, caused of every developer has a Git. #Git_Bash= $(subst cmd\,bin\bash.exe,$(dir $(shell where git))) Git_Bash=$(subst \,/, $(subst cmd\,bin\bash.exe,$(dir $(shell where git)))) INTERNAL_PROTO_FILES=$(shell $(Git_Bash) -c "find internal -name *.proto") API_PROTO_FILES=$(shell $(Git_Bash) -c "find api -name *.proto") else INTERNAL_PROTO_FILES=$(shell find internal -name *.proto) API_PROTO_FILES=$(shell find api -name *.proto) endif .PHONY: init config api build generate all test coverage help APP_NAME := symbols BIN_DIR := bin # init env init: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest go install github.com/go-kratos/kratos/cmd/kratos/v2@latest go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest go install github.com/google/wire/cmd/wire@latest go install github.com/envoyproxy/protoc-gen-validate@latest # generate internal proto config: buf dep update buf lint buf format -w buf generate # build build: service worker service: generate go build -o $(BIN_DIR)/$(APP_NAME) ./cmd/$(APP_NAME) # generate wire files generate: wire ./cmd/symbols go mod tidy # generate all all: make config; make generate; # run unit tests test: go test -v -race -coverprofile=coverage.out ./internal/... # generate coverage report coverage: go test -v -coverprofile=coverage.out ./... go tool cover -html=coverage.out -o coverage.html @echo "Coverage report generated: coverage.html" # show help help: @echo '' @echo 'Usage:' @echo ' make [target]' @echo '' @echo 'Targets:' @awk '/^[a-zA-Z\-\_0-9]+:/ { \ helpMessage = match(lastLine, /^# (.*)/); \ if (helpMessage) { \ helpCommand = substr($$1, 0, index($$1, ":")); \ helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \ } \ } \ { lastLine = $$0 }' $(MAKEFILE_LIST) .DEFAULT_GOAL := help
Replace
{service-name} with the actual service name.
10.3 Create internal/conf/buf.yaml
version: v2 modules: - path: internal/conf/ deps: - buf.build/envoyproxy/protoc-gen-validate lint: use: - STANDARD breaking: use: - FILE
10.4 Create internal/conf/buf.gen.yaml
version: v2 managed: enabled: true plugins: # Generate Go protobuf code for service configuration schema # Used to define strongly-typed config loaded from YAML at runtime - remote: buf.build/protocolbuffers/go out: internal/conf/gen opt: paths=source_relative # Generate validation code from protoc-gen-validate annotations - local: protoc-gen-validate out: internal/conf/gen opt: paths=source_relative,lang=go inputs: - directory: internal/conf
Replace
{service-name} with the actual service name.
10.5 Create .gitignore
# Binaries bin/ # Wire generated wire_gen.go # Test coverage coverage.out coverage.html # IDE .idea/ .vscode/ *.swp *.swo *~ # OS .DS_Store Thumbs.db
10.6 Create README.md
# {Service Name} {Brief description of what this service does} ## Architecture This service follows clean architecture with the following layers: - **cmd/** - Application entry point and dependency injection (Wire) - **internal/server/** - HTTP and gRPC server setup - **internal/service/** - Service handlers (implements proto service interfaces) - **internal/biz/** - Business logic and use cases - **internal/data/** - Data access layer (repositories, database) - **internal/conf/** - Configuration protobuf definitions ## Development ### Prerequisites ```bash make init
Generate Wire Code
make generate
Build
make build
Run
./bin/{service-name} -conf configs/config.yaml
Test
make test
Coverage
make coverage
Configuration
Configuration is defined in
internal/conf/conf.proto and loaded from configs/config.yaml.
Server
- HTTP server on port 8000
- gRPC server on port 9000
Metrics
Prometheus metrics exposed at
/metrics:
- HTTP request metrics
- gRPC request metrics
- Go runtime metrics (if enabled)
API
Proto definitions should be added to
api/service/{service-name}/v1/ in the monorepo root.
After defining protos:
- Run
from monorepo rootmake contracts-generate - Import generated code in service handlers
- Register handlers in
andinternal/server/http.gointernal/server/grpc.go
Adding Features
Add a new use case
- Define repository interface in
internal/biz/interfaces.go - Create use case in
internal/biz/my_usecase.go - Add to
biz.ProviderSet - Implement repository in
internal/data/repo/my_repo.go - Add to
data.ProviderSet - Inject into service layer
Add database
- Uncomment GORM in
internal/data/data.go - Add models in
internal/data/model/ - Add repositories in
internal/data/repo/ - Update config to include database connection
Replace `{Service Name}` and `{service-name}` with the actual service name. ## Step 11: Generate Wire Code Navigate to the cmd directory and generate Wire code: ```bash cd services/{service-name}/cmd/{service-name} GOWORK=off go generate
This will create
wire_gen.go with the dependency injection graph.
Step 12: Verify Build
Build the service to verify everything is wired correctly:
cd services/{service-name} make build
If there are any import or compilation errors, fix them before proceeding.
Step 13: Summary
Inform the user of what was created:
</workflow>✓ Created new service: {service-name} ✓ Location: services/{service-name}/ ✓ Structure: Clean architecture with server/service/biz/data layers ✓ Config: Complete config with Server, Data, Metrics ✓ Wire: Dependency injection configured and generated ✓ Metrics: HTTP and gRPC metrics enabled ✓ Build: Makefile with all standard targets Next steps: 1. Define proto API in api/service/{service-name}/v1/ 2. Run 'make contracts-generate' from monorepo root 3. Implement service handlers in internal/service/ 4. Add business logic in internal/biz/ 5. Implement repositories in internal/data/ The service is ready to build and run, but needs proto definitions to be useful.
<success_criteria> Service skeleton is complete when:
- Service name validated (lowercase, alphanumeric, hyphens only)
- No conflicts with existing services
- Complete directory structure created
- All layer files created with correct imports
- Configuration files in place (conf.proto, config.yaml)
- Wire setup complete (wire.go and wire_gen.go generated)
- Metrics registry configured in server layer
- HTTP and gRPC servers have metrics middleware
- Makefile has all standard targets
- Service builds successfully with
make build - All placeholder
replaced with actual service name{service-name} - README.md provides clear next steps
- No event handlers, publishers, or subscribers included
- No proto definitions created (user adds separately) </success_criteria>