#
Advanced Dependency Injection with generated Proxies
The Parsley CLI introduces a powerful feature for advanced dependency injection through its generate proxy
command. This command, combined with //go:generate
annotations, automatically generates proxy services and interfaces for your service contracts.
Use the following command to install the parsley-cli
utility:
go install github.com/matzefriedrich/parsley/cmd/parsley-cli
#
Example
Consider a Greeter
interface and its implementation. By adding the //go:generate parsley-cli generate proxy
annotation in your code, you enable the Parsley CLI to generate a corresponding proxy class. This proxy wraps the original service and allows for method interception, enabling additional behavior to be injected around service calls.
package internal
import "fmt"
//go:generate parsley-cli generate proxy
type Greeter interface {
SayHello(name string, polite bool)
}
type greeter struct {
}
func (g *greeter) SayHello(name string, polite bool) {
if polite {
fmt.Printf("Good day, %s!\n", name)
} else {
fmt.Printf("Hello %s\n", name)
}
}
func NewGreeter() Greeter {
return &greeter{}
}
The code generator creates a greeter.proxy.g.go
that contains a type that also implements the Greeter
interface and thus can be used as drop-in replacements for the actual Greeter
service. The proxy type supports intercepting method calls, allowing custom logic to be added before or after a method is invoked.
// Code generated by parsley-cli; DO NOT EDIT.
//
// This file was automatically generated and any changes to it will be overwritten.
// To extend or modify the behavior of this code, implement the MethodInterceptor interface and provide your custom logic there.
package internal
import (
"github.com/matzefriedrich/parsley/pkg/features"
)
// GreeterProxyImpl A generated proxy service type for Greeter objects.
type GreeterProxyImpl struct {
features.ProxyBase
target Greeter
}
// GreeterProxy An interface type for Greeter objects. Parsley needs this to distinguish the proxy from the actual implementation.
type GreeterProxy interface {
Greeter
}
// NewGreeterProxyImpl Creates a new GreeterProxy object. Register this constructor method with the registry.
func NewGreeterProxyImpl(target Greeter, interceptors []features.MethodInterceptor) GreeterProxy {
return &GreeterProxyImpl{
ProxyBase: features.NewProxyBase(target, interceptors),
target: target,
}
}
func (p *GreeterProxyImpl) SayHello(name string, polite bool) {
const methodName = "SayHello"
parameters := map[string]interface{}{
"name": name,
"polite": polite,
}
callContext := features.NewMethodCallContext(methodName, parameters)
p.InvokeEnterMethodInterceptors(callContext)
defer func() {
p.InvokeExitMethodInterceptors(callContext)
}()
p.target.SayHello(name, polite)
}
var _ Greeter = &GreeterProxyImpl{}
In this example, the GreeterProxy
wraps the Greeter
service, and any registered MethodInterceptor
services can act upon method invocations, adding custom behavior such as logging, validation, or modification of method parameters.
The boilerplate code to register the generated proxies and a custom MethodInterceptor
for logging purposes looks as follows:
package main
import (
"context"
"github.com/matzefriedrich/parsley-docs/examples/advanced/internal"
"github.com/matzefriedrich/parsley/pkg/features"
"github.com/matzefriedrich/parsley/pkg/registration"
"github.com/matzefriedrich/parsley/pkg/resolving"
"github.com/matzefriedrich/parsley/pkg/types"
"log"
)
func main() {
registry := registration.NewServiceRegistry()
registry.Register(internal.NewGreeter, types.LifetimeTransient)
registry.Register(internal.NewGreeterProxyImpl, types.LifetimeTransient)
features.RegisterList[features.MethodInterceptor](registry)
registry.Register(newLoggingInterceptor, types.LifetimeSingleton)
resolver := resolving.NewResolver(registry)
ctx := resolving.NewScopedContext(context.Background())
proxy, _ := resolving.ResolveRequiredService[internal.GreeterProxy](resolver, ctx)
proxy.SayHello("John", true)
}
type loggingInterceptor struct {
features.InterceptorBase
}
func (l loggingInterceptor) Enter(_ any, methodName string, parameters []features.ParameterInfo) {
log.Default().Printf("Enter: %s, Parameters: %v\n", methodName, parameters)
}
func (l loggingInterceptor) Exit(_ any, methodName string, returnValues []features.ReturnValueInfo) {
log.Default().Printf("Exit: %s\n", methodName)
}
func (l loggingInterceptor) OnError(_ any, methodName string, err error) {
log.Default().Printf("OnError: %s, MethodName: %s, Error: %v", methodName, methodName, err)
}
var _ features.MethodInterceptor = &loggingInterceptor{}
func newLoggingInterceptor() features.MethodInterceptor {
return &loggingInterceptor{
InterceptorBase: features.NewInterceptorBase("logging", 0),
}
}
#
Benefits and use cases
Separation of Concerns: Proxies can separate core business logic from cross-cutting concerns like logging or security.
Dynamic Interception: Proxies allow dynamic interception of method calls, making adding or modifying behavior easier without altering the service's core logic.
Extensibility: This feature provides a flexible mechanism to extend the functionality of services, making it ideal for scenarios where services require dynamic behavior adjustments. This advanced feature of Parsley CLI simplifies complex dependency injection scenarios, providing developers with robust tools to manage and extend service behavior effortlessly.