# 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.