Use when adding a cross-cutting concern (OTEL tracing, logging, metrics, caching, auth) to a business domain, creating files under business/domain/*/extensions/*, or adding the extension seam (ExtBusiness/Extension) to a *bus package.
74
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
The business layer layers cross-cutting concerns onto core domain logic with a
decorator pattern: each extension wraps an ExtBusiness and delegates to it.
Wrap, never modify the core Business. An extension that adds tracing to one
method must still implement every other method as a plain pass-through.
It comes in three coordinated pieces: the seam on the bus (Piece 1), the extension package itself (Piece 2), and the wiring that registers it (Piece 3).
<x>bus.go has ExtBusiness, Extension,
and the reverse-apply loop in NewBusiness. If not, add them (piece 1).extensions/<x><concern>/<x><concern>.go (piece 2).ExtBusiness method — wrap the relevant ones, pass the rest
straight through to ext.bus. Missing one fails to satisfy the interface and
won't compile.api/services/*/main.go in the right order (piece 3).<x>bus/<x>bus.go)// ExtBusiness lists every public method an extension can wrap.
type ExtBusiness interface {
Create(ctx context.Context, input CreateInput) (Model, error)
// ... every other public business method
}
// Extension wraps a new layer of business logic around the existing logic.
type Extension func(ExtBusiness) ExtBusiness
func NewBusiness(log *logger.Logger, delegate *delegate.Delegate, storer Storer, extensions ...Extension) ExtBusiness {
b := ExtBusiness(&Business{ /* ... */ })
for i := len(extensions) - 1; i >= 0; i-- { // reverse: first-listed = outermost
if ext := extensions[i]; ext != nil {
b = ext(b)
}
}
return b
}extensions/<x><concern>/<x><concern>.go)Package name is <x><concern> (e.g. widgetotel, widgetlog). Struct holds the
wrapped bus plus any deps; NewExtension returns the closure.
// Package widgetotel provides an extension for WidgetBus to add OTEL related
// code on top of it without changing the existing logic.
package widgetotel
type Extension struct {
bus widgetbus.ExtBusiness
}
func NewExtension() widgetbus.Extension {
return func(bus widgetbus.ExtBusiness) widgetbus.ExtBusiness {
return &Extension{bus: bus}
}
}
// Wrapped method — adds the concern, then delegates.
func (ext *Extension) Create(ctx context.Context, input widgetbus.CreateWidgetInput) (widgetbus.Widget, error) {
ctx, span := otel.AddSpan(ctx, "business.widgetbus.create")
defer span.End()
w, err := ext.bus.Create(ctx, input)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return w, err
}
span.SetStatus(codes.Ok, "")
return w, nil
}
// Pass-through method — NOT relevant to this concern, but REQUIRED for the interface.
func (ext *Extension) Delete(ctx context.Context, w widgetbus.Widget) error {
return ext.bus.Delete(ctx, w)
}For a Prometheus metrics extension, follow the repo house style: a package-level
singleton built in init() with promauto, _total counter naming, and a status
label. Record after delegating, then return the original result.
var metrics = struct {
created *prometheus.CounterVec
}{
created: promauto.NewCounterVec(prometheus.CounterOpts{
Name: "widgetbus_created_total",
Help: "The total number of widget create operations.",
}, []string{"status"}),
}
func status(err error) string {
if err != nil {
return "error"
}
return "success"
}
func (ext *Extension) Create(ctx context.Context, input widgetbus.CreateWidgetInput) (widgetbus.Widget, error) {
w, err := ext.bus.Create(ctx, input)
metrics.created.WithLabelValues(status(err)).Inc()
return w, err
}api/services/*/main.go)Construct each extension, then pass them as the trailing variadic args to NewBusiness
in outermost-first order:
widgetOtelExt := widgetotel.NewExtension() // first = outermost wrapper
widgetLogExt := widgetlog.NewExtension(log)
widgetBus := widgetbus.NewBusiness(log, delegate, widgetStorage, widgetOtelExt, widgetLogExt)Order matters. NewBusiness applies extensions in reverse, so the first one passed is
the outermost layer (runs first on the way in, last on the way out). The repo convention
is OTEL first (outermost span), then logging/metrics inside it.
ExtBusiness method first, then fill them in.Business to add the concern instead of wrapping it. The whole
point is to leave core logic untouched.layered-architecture-types.Use use-modern-go for syntax.
7c54ee3
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.