go-composer-gen is a tool for your custom generator that allows you to compose multiple small, focused operation
structs into a single, cohesive service implementation. It automatically handles dependency injection and interface
generation, allowing you to build complex services from decoupled units of logic.
go-composer-gen can be used as a standalone CLI tool or integrated into your own custom generator as a library. This
is a component of my code generation toolbox.
When running as a standalone generator, go-composer-gen requires the pkl binary to be available on your PATH. It is
used at runtime to evaluate .pkl configuration files. You can install it via:
# macOS
brew install pklFor other platforms, see pkl-lang installation docs.
First let's set up a golang module
module github.com/gen/project
go 1.24give you have source code with these structs
// file: source.go
package main
import "context"
type Repository interface{}
type Service interface{}
type Mailer interface{}
type createUserOp struct {
repo Repository
}
func (op *createUserOp) execute(ctx context.Context) error {
return nil
}
type updateUserOp struct {
mailer Mailer
service Service
}
func (op *updateUserOp) execute(ctx context.Context, id int) error {
return nil
}
type deleteUserOp struct {
repository Repository
mailer Mailer
}
func (op *deleteUserOp) execute(ctx context.Context, id string) error {
return nil
}using the configuration
// file: composer.pkl
amends "package://nhatp.com/go/composer-gen/pkl@0.3.0#/Config.pkl"
packages {
["github.com/gen/project"] {
interface_name = "Service"
receivers {
new { struct_name = "createUserOp" exported_as = "CreateUser" }
new { struct_name = "updateUserOp" exported_as = "UpdateUser" }
new { struct_name = "deleteUserOp" exported_as = "DeleteUser" }
}
}
}they will be composed to a single generated code
// golden-file: gen_composer.go
// Code generated by go-composer-gen - dev. DO NOT EDIT.
package main
import "context"
type Service interface {
CreateUser(ctx context.Context) error
UpdateUser(ctx context.Context, id int) error
DeleteUser(ctx context.Context, id string) error
}
type serviceImpl struct {
createUserOp *createUserOp
updateUserOp *updateUserOp
deleteUserOp *deleteUserOp
}
func (s *serviceImpl) CreateUser(ctx context.Context) error {
return s.createUserOp.execute(ctx)
}
func (s *serviceImpl) UpdateUser(ctx context.Context, id int) error {
return s.updateUserOp.execute(ctx, id)
}
func (s *serviceImpl) DeleteUser(ctx context.Context, id string) error {
return s.deleteUserOp.execute(ctx, id)
}
type serviceDeps struct {
repository Repository
mailer Mailer
service Service
}
func newService(deps *serviceDeps) Service {
impl := &serviceImpl{
createUserOp: &createUserOp{repo: deps.repository},
deleteUserOp: &deleteUserOp{
mailer: deps.mailer,
repository: deps.repository,
},
updateUserOp: &updateUserOp{
mailer: deps.mailer,
service: deps.service,
},
}
return impl
}
var _ Service = (*serviceImpl)(nil)Example code
package yourgenerator
import (
"fmt"
"nhatp.com/go/composer-gen"
)
func UseAsLibrary(sourceDir string) {
// fileManager instance can be shared between commands in the toolbox
fileManager := composergen.NewFileManager(sourceDir, composergen.WithBinaryName("your-generator"))
var configs []composergen.Config // build your configs here ...
generator := composergen.New(fileManager)
pkgs, err := composergen.LoadPackages(sourceDir)
if err != nil {
panic(err)
}
for _, pkg := range pkgs {
if err := generator.Generate(pkg, configs); err != nil {
panic(err)
}
}
fmt.Println(fileManager.Files())
}Explore the features directory to see how go-composer-gen handles various real-world scenarios:
PRs are welcome! See the CONTRIBUTING. Distributed under the Apache License 2.0.
If you like the project, feel free to buy me a coffee. Thank you!