Component References

  • by Anastas Fonotov

Introduction

Developing systems out of loosely-coupled components significantly reduces complexity, improves testing, and increases developer productivity. The Pip.Services Toolkit offers a flexible and simple set of primitives for referencing components that is symmetrically implemented in all of the supported programming languages.

The Locator Pattern

Developing loosely-coupled components has recently become very popular. There exist great implementations of the Inversion of Control pattern, which allows components to be linked to one another. In Java, the Spring framework is often used, and in .NET - unity. However, all of these implementations are tailored to the language they were initially made for. Furthermore, they are usually based upon Dependency Injection, which relies on runtime reflection. Since the Pip.Services Toolkit strives to provide symmetrical code across all of the languages it supports, its Inversion of Control mechanism was designed with a few distinctive features in mind.

The main distinction is that our implementation of Inversion of Control uses the Locator Pattern. The main concept behind this pattern is that, instead of injecting dependencies through constructors and attributes, the component receives a special object that is responsible for “finding” all of the necessary services. This approach provides developers with more flexibility and control, doesn’t require reflection, and can be implemented in any programming language.

The IReferences Interface

The IReferences interface can be used to pass a so-called References object to a component. This References object can be used by the component to retrieve any and all required dependencies. IReferences is defined as follows:

interface IReferences {
	put(locator: any, component: any);
	remove(locator: any): any;
	removeAll(locator: any): any[];	
	getAllLocators(): any[];	
	getAll(): any[];	
	getOptional<T>(locator: any): T[];
	getRequired<T>(locator: any): T[];
	getOneOptional<T>(locator: any): T;
	getOneRequired<T>(locator: any): T;
	find<T>(locator: any, required: boolean): T[];
}

public interface IReferences
{
    void Put(object locator, object component);

    object Remove(object locator);

    List<object> RemoveAll(object locator);

    List<object> GetAllLocators();

    List<object> GetAll();

    List<object> GetOptional(object locator);

    List<T> GetOptional<T>(object locator);

    List<object> GetRequired(object locator);

    List<T> GetRequired<T>(object locator);

    object GetOneOptional(object locator);

    T GetOneOptional<T>(object locator);

    object GetOneRequired(object locator);

    T GetOneRequired<T>(object locator);

    List<object> Find(object locator, bool required);

    List<T> Find<T>(object locator, bool required);

}


type IReferences interface {

	Put(locator interface{}, component interface{})

	Remove(locator interface{}) interface{}

	RemoveAll(locator interface{}) []interface{}

	GetAllLocators() []interface{}

	GetAll() []interface{}

	GetOptional(locator interface{}) []interface{}

	GetRequired(locator interface{}) ([]interface{}, error)

	GetOneOptional(locator interface{}) interface{}

	GetOneRequired(locator interface{}) (interface{}, error)

	Find(locator interface{}, required bool) ([]interface{}, error)
}

abstract class IReferences {

  void put(locator, component);

  dynamic remove(locator);

  List removeAll(locator);

  List getAllLocators();

  List getAll();

  List<T> getOptional<T>(locator);

  List<T> getRequired<T>(locator);

  T? getOneOptional<T>(locator);

  T getOneRequired<T>(locator);

  List<T> find<T>(locator, bool required);
}

class IReferences(ABC):

    def put(self, locator: Any = None, reference: Any = None):
        raise NotImplementedError('Method from interface definition')

    def remove(self, locator: Any) -> Any:
        raise NotImplementedError('Method from interface definition')

    def remove_all(self, locator: Any) -> List[Any]:
        raise NotImplementedError('Method from interface definition')

    def get_all_locators(self) -> List[Any]:
        raise NotImplementedError('Method from interface definition')

    def get_all(self) -> List[Any]:
        raise NotImplementedError('Method from interface definition')

    def get_optional(self, locator: Any) -> List[Any]:
        raise NotImplementedError('Method from interface definition')

    def get_required(self, locator: Any) -> List[Any]:
        raise NotImplementedError('Method from interface definition')

    def get_one_optional(self, locator: Any) -> Any:
        raise NotImplementedError('Method from interface definition')

    def get_one_required(self, locator: Any) -> Any:
        raise NotImplementedError('Method from interface definition')

    def get_one_before(self, reference, locator) -> Any:
        raise NotImplementedError('Method from interface definition')

    def find(self, locator: Any, required: bool) -> List[Any]:
        raise NotImplementedError('Method from interface definition')


Not available

The “locator” parameters are special keys that are used to search for necessary dependencies. Technically, any primitive value can be a locator - a number, string, or even a complex object (as long as it supports the “equals” operation). The put method is used to add a component and its locator/key to the list of dependencies. The rest of the methods are used for extracting dependencies. For example, the get_required method can be used for extracting essential dependencies, as an exception will be raised if no matches are found. The get_one_optional method, on the other hand, can be used for optional dependencies - it will simply return None if no matching dependencies are found.

The IReferenceable & IUnreferenceable Interfaces

A component must implement the IReferenceable interface to be able to receive dependencies. Dependencies are set in the component’s setReferences method, which is called with a link to a References object (described in the previous section).

interface IUnreferenceable {
 	unsetReferences(): void;
}

public interface IUnreferenceable
{
    void UnsetReferences();
}


type IUnreferenceable interface {
	UnsetReferences()
}

abstract class IUnreferenceable {

  void unsetReferences();
}

class IUnreferenceable(ABC):

    def unset_references(self):
        raise NotImplementedError('Method from interface definition')

Not available

Dependencies can be set and removed either manually, or automatically by the component container. The setting of dependencies should be performed right after component creation and configuration, and their deletion - after stopping all active processes and right before destroying the component. For more information, see the Component Lifecycle Recipe.

Example of Dependency Setting

Let’s take a look at a simple example of setting dependencies between components using the Pip.Services Toolkit’s References pattern. Suppose we have 2 services, Worker1 and Worker2, which are defined as follows:

class Worker1 {
    private _defaultName: string;

    constructor(name) {
        this._defaultName = name || "Default name1";
    }

    public do(level, message) {
        console.log('Write to ${this._defaultName}.${level} message: ${message}');
    }
}

class Worker2 {
    private _defaultName: string;
    
    constructor(name) {
        this._defaultName = name || "Default name2";
    }

    public do(level, message) {
        console.log('Write to ${this._defaultName}.${level} message: ${message}');
    }
}
class Worker1
{
    protected string defaultName;

    public Worker1(string name = null)
    {
        this.defaultName = name ?? "Default name1";
    }

    public void Do(LogLevel level, string message)
    {
        Console.WriteLine($"Write to {defaultName}.{level} message: {message}");
    }
}

class Worker2
{
    protected string defaultName;

    public Worker2(string name = null)
    {
        this.defaultName = name ?? "Default name2";
    }

    public void Do(LogLevel level, string message)
    {
        Console.WriteLine($"Write to {defaultName}.{level} message: {message}");
    }
}


import "fmt"

type Worker interface {
	Do(level int, message string)
}

type Worker1 struct {
	_defaultName string
}

func NewWorker1(name string) *Worker1 {
	if name != "" {
		return &Worker1{_defaultName: "default name1"}
	}
	return &Worker1{_defaultName: "default name1"}

}

func (c *Worker1) Do(level string, message string) {
	fmt.Printf("Write to %v.%v message: %v", c._defaultName, level, message)
}

type Worker2 struct {
	_defaultName string
}

func NewWorker2(name string) *Worker2 {
	if name != "" {
		return &Worker2{_defaultName: "default name2"}
	}
	return &Worker2{_defaultName: "default name2"}

}

func (c *Worker2) Do(level string, message string) {
	fmt.Printf("Write to %v.%v message: %v", c._defaultName, level, message)
}
class Worker1 {
  String _defaultName;

  Worker1([String? name]) : _defaultName = name ?? 'Default name1';

  void do_(level, message) {
    print('Write to $_defaultName.$level message: $message');
  }
}

class Worker2 {
  String _defaultName;

  Worker2([String? name]) : _defaultName = name ?? 'Default name2';

  void do_(level, message) {
    print('Write to $_defaultName.$level message: $message');
  }
}


class Worker1:
  def __init(self, name):
		self._default_name = name or "Default name1"
  
	def do(self, level, message):
    	print(f'Write to {self._default_name}.${level} message: ${message}')
  

class Worker2: 
	def __init(self, name):
		self._default_name = name or "Default name2"
  
	def do(self, level, message):
		print(f'Write to {self._default_name}.${level} message: ${message}')

Not available

Now let’s add a SimpleController component with a greeting() method. This method will perform just one simple operation - output a message that was generated by one of the Worker services. This component will be implementing the IReferenceable interface, and its set_references method will receive and set a reference to the Worker service via the 111 locator. We can also implement IUnreferenceable and use the unset_references method to delete the previously set reference.

class SimpleController implements IReferenceable, IUnreferenceable {
    private _worker: any;
    
    constructor() {}

    public setReferences(references) {
        this._worker = references.getOneRequired(111)
    }
    public unsetReferences() {
        this._worker = null;
    }
    public greeting(name) {
        this._worker.do(LogLevel.Debug,  "Hello, " + (name) + "!");
    }
}
  

interface Worker
{
    public void Do(LogLevel level, string message);
}

class Worker1: Worker
{

    protected string defaultName;

    public Worker1(string name = null)
    {
        this.defaultName = name ?? "Default name1";
    }

    public void Do(LogLevel level, string message)
    {
        Console.WriteLine($"Write to {defaultName}.{level} message: {message}");
    }
}

class Worker2: Worker
{
    protected string defaultName;

    public Worker2(string name = null)
    {
        this.defaultName = name ?? "Default name2";
    }

    public void Do(LogLevel level, string message)
    {
        Console.WriteLine($"Write to {defaultName}.{level} message: {message}");
    }
}



class SimpleController : IReferenceable, IUnreferenceable
{
    protected Worker worker;

    public void SetReferences(IReferences references)
    {
        worker = references.GetOneRequired<Worker>(111);
    }

    public void UnsetReferences()
    {
        worker = null;
    }

    public void Greeting(string name)
    {
        worker.Do(LogLevel.Info, "Hello, " + (name) + "!");
    }
}


import (
	"fmt"

	crefer "github.com/pip-services3-go/pip-services3-commons-go/refer"
	clog "github.com/pip-services3-go/pip-services3-components-go/log"
)

type Worker interface {
	Do(level int, message string)
}

type Worker1 struct {
	_defaultName string
}

func NewWorker1(name string) *Worker1 {
	if name != "" {
		return &Worker1{_defaultName: "default name1"}
	}
	return &Worker1{_defaultName: "default name1"}

}

func (c *Worker1) Do(level int, message string) {
	fmt.Printf("Write to %v.%v message: %v", c._defaultName, level, message)
}

type Worker2 struct {
	_defaultName string
}

func NewWorker2(name string) *Worker2 {
	if name != "" {
		return &Worker2{_defaultName: "default name2"}
	}
	return &Worker2{_defaultName: "default name2"}

}

func (c *Worker2) Do(level int, message string) {
	fmt.Printf("Write to %v.%v message: %v", c._defaultName, level, message)
}

type SimpleController struct {
	_worker interface{}
}

func (c *SimpleController) SetReferences(references crefer.IReferences) {
	c._worker, _ = references.GetOneRequired(111)
}

func (c *SimpleController) UnsetReferences() {
	c._worker = nil
}

func (c *SimpleController) Greeting(name string) {

	c._worker.(Worker).Do(clog.Debug, "Hello, "+name+"!")
}
class SimpleController implements IReferenceable, IUnreferenceable {
  dynamic _worker;

  SimpleController();

  @override
  void setReferences(references) {
    _worker = references.getOneRequired(111);
  }

  @override
  void unsetReferences() {
    _worker = null;
  }

  void greeting(name) {
    _worker.do_('level', 'Hello, ' + (name) + '!');
  }
}


class SimpleController(IReferenceable, IUnreferenceable):
  
  def set_references(self, references):
    self._worker = self._references.get_one_required(111)
  
  def unset_references(self):
    self._worker = None
  
  def greeting(self, name):
    self._worker.do(LogLevel.Info,  "Hello, " + (name) + "!")
  

Not available

We will be using the References class to pass dependencies into our components. This class is defined in the Commons module and implements the IReferenceable interface. We can use the References.from_tuples method to populate our list of dependencies using locator-reference pairs.


let references = References.fromTuples(
    111, new Worker1(null),
    222, new Worker2(null)
);

let controller = new SimpleController();
controller.setReferences(references);
controller.greeting("world");
controller.unsetReferences();
controller = null;



var references = References.FromTuples(
    111, new Worker1("worker1"),
    222, new Worker2("worker2")
);

var controller = new SimpleController();
controller.SetReferences(references);
controller.Greeting("world");
controller.UnsetReferences();
controller = null;

references := crefer.NewReferencesFromTuples(
	111, mymodule.NewWorker1("worker1"),
	222, mymodule.NewWorker2("worker2"),
)

controller := mymodule.NewSimpleController()
controller.SetReferences(references)
controller.Greeting("world")
controller.UnsetReferences()
controller = nil
var references = References.fromTuples(
    [111, Worker1(), 222, Worker2()]
);

SimpleController? controller = SimpleController();
controller.setReferences(references);
controller.greeting('world');
controller.unsetReferences();
controller = null;



references = References.from_tuples(
	111, Worker1('worker1'),
	222, Worker2('worker2')
)

controller = SimpleController()
controller.set_references(references)
controller.greeting("world")
controller.unset_references()
controller = None


Not available

Component Descriptors

Using simple values as locators (keys) can be sufficient for dependency extraction in certain simple cases. However, when working with complex systems, you may come across a case where there are a multitude of dependencies, and you need to create complex configurations in accordance with various criteria.
For such complex cases, the Pip.Services Toolkit includes a special locator that is called a Descriptor. Descriptor locators allow dependencies to be found using complete or partial Descriptor matches. A Descriptor consists of the following 5 fields:

  1. Group - logical group of objects. Usually, this is the name of the library or microservice. E.g. “pip-services”.
  2. Type - logical type or object interface that presumes some general functionality. E.g. “logger”.
  3. Kind - specific implementation of the logical type. E.g. (for logger components) “null”, “console”, “elasticsearch”, “fluentd”, etc.
  4. Name - unique object name. E.g. “logger1” or “logger2”.
  5. Version - object’s interface version. This is mainly used for finding compatible dependencies. E.g. “1.0”.

The Descriptor class’s definition is as follows:

class Descriptor {
	private _group: string;
	private _type: string;
	private _kind: string;
	private _name: string;
	private _version: string;
	public getGroup(): string;
	public getType(): string;
	public getKind(): string;
	public getName(): string;
	public getVersion(): string;
	private matchField(field1: string, field2: string): boolean;
	public match(descriptor: Descriptor): boolean;
	private exactMatchField(field1: string, field2: string): boolean;
	public exactMatch(descriptor: Descriptor): boolean;
	public isComplete(): boolean;

public equals(value: any): boolean;
	public toString(): string;
	public static fromString(value: String): Descriptor;
}


public class Descriptor
{
    public Descriptor(string group, string type, string kind, string name, string version);

    public string Group { get; private set; }
    public string Type { get; private set; }
    public string Kind { get; private set; }
    public string Name { get; private set; }
    public string Version { get; private set; }

    public bool Match(Descriptor descriptor);
    public bool ExactMatch(Descriptor descriptor);
    public bool IsComplete();
    public override bool Equals(object obj);

    public override int GetHashCode();
    public override string ToString();
    public static Descriptor FromString(string value);
}

type Descriptor struct {
	group   string
	typ     string
	kind    string
	name    string
	version string
}

func NewDescriptor(group string, typ string, kind string, name string, version string) *Descriptor {
	if "*" == group {
		group = ""
	}
	if "*" == typ {
		typ = ""
	}
	if "*" == kind {
		kind = ""
	}
	if "*" == name {
		name = ""
	}
	if "*" == version {
		version = ""
	}

	return &Descriptor{group: group, typ: typ, kind: kind, name: name, version: version}
}

func (c *Descriptor) Group() string {
	return c.group
}

func (c *Descriptor) Type() string {
	return c.typ
}

func (c *Descriptor) Kind() string {
	return c.kind
}

func (c *Descriptor) Name() string {
	return c.name
}

func (c *Descriptor) Version() string {
	return c.version
}

func matchField(field1 string, field2 string) bool {
	return field1 == "" || field2 == "" || field1 == field2
}

func (c *Descriptor) Match(descriptor *Descriptor) bool {
	return matchField(c.group, descriptor.Group()) &&
		matchField(c.typ, descriptor.Type()) &&
		matchField(c.kind, descriptor.Kind()) &&
		matchField(c.name, descriptor.Name()) &&
		matchField(c.version, descriptor.Version())
}

func exactMatchField(field1 string, field2 string) bool {
	if field1 == "" && field2 == "" {
		return true
	}
	if field1 == "" || field2 == "" {
		return false
	}
	return field1 == field2
}

func (c *Descriptor) ExactMatch(descriptor *Descriptor) bool {
	return exactMatchField(c.group, descriptor.Group()) &&
		exactMatchField(c.typ, descriptor.Type()) &&
		exactMatchField(c.kind, descriptor.Kind()) &&
		exactMatchField(c.name, descriptor.Name()) &&
		exactMatchField(c.version, descriptor.Version())
}

func (c *Descriptor) IsComplete() bool {
	return c.group != "" && c.typ != "" && c.kind != "" &&
		c.name != "" && c.version != ""
}

func (c *Descriptor) Equals(value interface{}) bool {
	descriptor, ok := value.(*Descriptor)
	if ok {
		return c.Match(descriptor)
	}
	return false
}

func (c *Descriptor) String() string {
	result := ""
	if c.group == "" {
		result += "*"
	} else {
		result += c.group
	}
	if c.typ == "" {
		result += ":*"
	} else {
		result += ":" + c.typ
	}
	if c.kind == "" {
		result += ":*"
	} else {
		result += ":" + c.kind
	}
	if c.name == "" {
		result += ":*"
	} else {
		result += ":" + c.name
	}
	if c.version == "" {
		result += ":*"
	} else {
		result += ":" + c.version
	}
	return result
}

func ParseDescriptorFromString(value string) (*Descriptor, error) {
	if value == "" {
		return nil, nil
	}

	tokens := strings.Split(value, ":")
	if len(tokens) != 5 {
		return nil, errors.NewConfigError("", "BAD_DESCRIPTOR", "Descriptor "+value+" is in wrong format")
	}

	return NewDescriptor(strings.TrimSpace(tokens[0]), strings.TrimSpace(tokens[1]),
		strings.TrimSpace(tokens[2]), strings.TrimSpace(tokens[3]), strings.TrimSpace(tokens[4])), nil
}
class Descriptor {
  String? _group;
  String? _type;
  String? _kind;
  String? _name;
  String? _version;

  Descriptor(String? group, String? type, String? kind, String? name, String? version)

  String? getGroup()

  String? getType()

  String? getKind()

  String? getName()

  String? getVersion()

  bool match(Descriptor descriptor)

  bool _exactMatchField(String? field1, String? field2)

  bool exactMatch(Descriptor descriptor)

  bool isComplete()

  bool equals(value)

  @override
  String toString()

  static Descriptor? fromString(String? value)
}

class Descriptor:

    def __init__(self, group: Optional[str], type: Optional[str], kind: Optional[str], name: Optional[str],
        ...

    def get_group(self) -> str:
        ...

    def get_type(self) -> str:
       ...

    def get_kind(self) -> str:
        ...

    def get_name(self) -> str:
        ...

    def get_version(self) -> str:
        ...

    def __match_field(self, field1: str, field2: str) -> bool:
        ...

    def match(self, descriptor: 'Descriptor') -> bool:
        ...

    def __exact_match_field(self, field1: str, field2: str) -> bool:
        ...

    def exact_match(self, descriptor: 'Descriptor') -> bool:
        ...

    def is_complete(self) -> bool:
        ...

    def equals(self, value: Any) -> bool:
        ...

    def to_string(self) -> str:
        ...

    def __eq__(self, other):
        ...

    def __ne__(self, other):
        ...

    def __str__(self):
        ...

    @staticmethod
    def from_string(value: str) -> Optional['Descriptor']:
		...


Not available

This way, we can define more than one logger in the system

pip-services:logger:console:logger1:1.0
pip-services:logger:elasticsearch:logger2:1.0
my-library:logger:mylog:logger3:1.0

The use of Descriptors also adds flexibility to the dependency searching process. Instead of having to find a complete match, we can indicate which Descriptor fields we want matched, and which can be skipped during comparison. If we want a field to be skipped during comparison, we simply set its value to ‘*’. For example, we can retrieve all of the logger currently registered in the system:

*:logger:*:*:1.0

Or just loggers that output to the console:

*:logger:console:*:1.0

Likewise, just the logger named “logger2”:

*:logger:*:logger2:1.0

And even all dependencies from a library called “my_library”:

my_library:*:*:*:*

Returning to our “worker” example, we could use Descriptors in the following manner:

class SimpleController implements IReferenceable, IUnreferenceable {
...
    public setReferences(references) {
        this._worker = this._references.getOneRequired(
            new Descriptor("*", "worker", "worker1", "*", "1.0")
        );
    }
...
}

let references = References.fromTuples(
    new Descriptor("sample", "worker", "worker1", "111", "1.0"), new Worker1(),
    new Descriptor("sample", "worker", "worker2", "222", "1.0"), new Worker2()
);

let controller = new SimpleController();
controller.setReferences(references);
console.log(controller.greeting("world"));
controller.unsetReferences();
controller = null;


class SimpleController : IReferenceable, IUnreferenceable
{
    protected Worker worker;

    public void SetReferences(IReferences references)
    {
        worker = references.GetOneRequired<Worker>(new Descriptor("*", "worker", "worker1", "*", "1.0"));
    }

    ...
}


var references = References.FromTuples(
    new Descriptor("sample", "worker", "worker1", "111", "1.0"), new Worker1(),
    new Descriptor("sample", "worker", "worker2", "222", "1.0"), new Worker2()

);

var controller = new SimpleController();
controller.SetReferences(references);
controller.Greeting("world");
controller.UnsetReferences();
controller = null;


...

func (c *SimpleController) SetReferences(references crefer.IReferences) {
	c._worker, _ = references.GetOneRequired(crefer.NewDescriptor("*", "worker", "worker1", "*", "1.0"))
}

...

references := crefer.NewReferencesFromTuples(
	crefer.NewDescriptor("sample", "worker", "worker1", "111", "1.0"), mymodule.NewWorker1(""),
	crefer.NewDescriptor("sample", "worker", "worker2", "222", "1.0"), mymodule.NewWorker2(""),
)

controller := mymodule.NewSimpleController()
controller.SetReferences(references)
controller.Greeting("world")
controller.UnsetReferences()
controller = nil
class SimpleController implements IReferenceable, IUnreferenceable {
...
  @override
  void setReferences(references) {
    _worker = references
        .getOneRequired(Descriptor('*', 'worker', 'worker1', '*', '1.0'));
  }
...
}

  var references = References.fromTuples([
    Descriptor('sample', 'worker', 'worker1', '111', '1.0'),
    Worker1(),
    Descriptor('sample', 'worker', 'worker2', '222', '1.0'),
    Worker2()
  ]);

  SimpleController? controller = SimpleController();
  controller.setReferences(references);
  controller.greeting('world');
  controller.unsetReferences();
  controller = null;


class SimpleController(IReferenceable, IUnreferenceable):
	...
  	def set_references(self, references):
        self._worker = self._references.get_one_required(
    	    Descriptor("*", "worker", "worker1", "*", "1.0")
        )
  
	...

references = References.from_tuples(
	Descriptor("sample", "worker", "worker1", "111", "1.0"), Worker1(),
	Descriptor("sample", "worker", "worker2", "222", "1.0"), Worker2()
)
controller = SimpleController()
controller.set_references(references)
controller.greeting("world")
controller.unset_references();
controller = None

Not available

The Dependency Resolver

In complex systems, which often contain a number of components of the same type, it can be impossible to select a dependency from the list using just a set of predefined rules (descriptors). That’s where the DependencyResolver helper class steps in. This class allows for dependency extraction using flexible configurations. When a DependencyResolver is created, a set of dependency names and corresponding default descriptors are defined inside it. Afterwards, these descriptors can be changed in the configuration’s “dependencies” section (see the Component Configuration Recipe for more info on the specifics of component configuration). The DependencyResolver class:

class DependencyResolver implements IReferenceable, IReconfigurable {
	private _dependencies: any = {};
	private _references: IReferences;
public configure(config: ConfigParams): void;
	public setReferences(references: IReferences): void;
	public put(name: string, locator: any): void;
	private locate(name: string): any;
	public getOptional<T>(name: string): T[];
	public getRequired<T>(name: string): T[];
	public getOneOptional<T>(name: string): T;
	public getOneRequired<T>(name: string): T;
	public find<T>(name: string, required: boolean): T[];
	public static fromTuples(...tuples: any[]): DependencyResolver;
}


public class DependencyResolver : IReferenceable, IReconfigurable
{
    public DependencyResolver(ConfigParams config);
    public void Configure(ConfigParams config);
    public void SetReferences(IReferences references);
    public void Put(string name, object locator);
    public List<object> GetOptional(string name);
    public List<T> GetOptional<T>(string name);
    public List<object> GetRequired(string name);
    public List<T> GetRequired<T>(string name);
    public object GetOneOptional(string name);
    public T GetOneOptional<T>(string name);
    public object GetOneRequired(string name);
    public T GetOneRequired<T>(string name);
    public List<object> Find(string name, bool required);
    public List<T> Find<T>(string name, bool required);
    public static DependencyResolver FromTuples(params object[] tuples);
}


type DependencyResolver struct {
	dependencies map[string]interface{}
	references   IReferences
}

func NewDependencyResolver() *DependencyResolver {
	return &DependencyResolver{
		dependencies: map[string]interface{}{},
		references:   nil,
	}
}

func NewDependencyResolverWithParams(config *conf.ConfigParams, references IReferences) *DependencyResolver {
	c := NewDependencyResolver()

	if config != nil {
		c.Configure(config)
	}

	if references != nil {
		c.SetReferences(references)
	}

	return c
}

func (c *DependencyResolver) Configure(config *conf.ConfigParams) {
	dependencies := config.GetSection("dependencies")
	names := dependencies.Keys()
	for _, name := range names {
		locator := dependencies.Get(name)
		if locator == "" {
			continue
		}

		descriptor, err := ParseDescriptorFromString(locator)
		if err == nil {
			c.dependencies[name] = descriptor
		} else {
			c.dependencies[name] = locator
		}
	}
}

func (c *DependencyResolver) SetReferences(references IReferences) {
	c.references = references
}

func (c *DependencyResolver) Put(name string, locator interface{}) {
	c.dependencies[name] = locator
}

func (c *DependencyResolver) Locate(name string) interface{} {
	if name == "" {
		panic("Dependency name cannot be empty")
	}

	if c.references == nil {
		panic("References shall be set")
	}

	return c.dependencies[name]
}

func (c *DependencyResolver) GetOptional(name string) []interface{} {
	locator := c.Locate(name)
	if locator == nil {
		return []interface{}{}
	}
	return c.references.GetOptional(locator)
}

func (c *DependencyResolver) GetRequired(name string) ([]interface{}, error) {
	locator := c.Locate(name)
	if locator == nil {
		err := NewReferenceError("", name)
		return []interface{}{}, err
	}

	return c.references.GetRequired(locator)
}

func (c *DependencyResolver) GetOneOptional(name string) interface{} {
	locator := c.Locate(name)
	if locator == nil {
		return nil
	}
	return c.references.GetOneOptional(locator)
}

func (c *DependencyResolver) GetOneRequired(name string) (interface{}, error) {
	locator := c.Locate(name)
	if locator == nil {
		err := NewReferenceError("", name)
		return nil, err
	}
	return c.references.GetOneRequired(locator)
}

func (c *DependencyResolver) Find(name string, required bool) ([]interface{}, error) {
	if name == "" {
		panic("Name cannot be empty")
	}

	locator := c.Locate(name)
	if locator == nil {
		if required {
			err := NewReferenceError("", name)
			return []interface{}{}, err
		}
		return []interface{}{}, nil
	}

	return c.references.Find(locator, required)
}

func NewDependencyResolverFromTuples(tuples ...interface{}) *DependencyResolver {
	result := NewDependencyResolver()
	if len(tuples) == 0 {
		return result
	}

	for index := 0; index < len(tuples); index += 2 {
		if index+1 >= len(tuples) {
			break
		}

		name := convert.StringConverter.ToString(tuples[index])
		locator := tuples[index+1]

		result.Put(name, locator)
	}

	return result
}
class DependencyResolver implements IReferenceable, IReconfigurable {

  DependencyResolver([ConfigParams? config, IReferences? references]) 

  @override
  void configure(ConfigParams config)

  @override
  void setReferences(IReferences references)

  void put(String name, locator)

  dynamic locate(String? name)

  List<T> getOptional<T>(String name)

  List<T> getRequired<T>(String name)

  T? getOneOptional<T>(String name)

  T getOneRequired<T>(String name)

  List<T> find<T>(String name, bool required)

  static DependencyResolver fromTuples(List tuples)
}


class DependencyResolver(IReconfigurable, IReferenceable):

    def __init__(self, config: ConfigParams = None, references: IReferences = None):
        ...

    def configure(self, config: ConfigParams):
        ...

    def set_references(self, references: IReferences):
        ...

    def put(self, name: str, locator: Any):
        ...

    def __locate(self, name: str) -> Any:
        ...

    def get_optional(self, name: str) -> List[Any]:
        ...

    def get_required(self, name: str) -> List[Any]:
        ...

    def get_one_optional(self, name: str) -> Any:
        ...

    def get_one_required(self, name: str) -> Any:
        ...

    def find(self, name: str, required: bool) -> Optional[List[Any]]:
        ...

    @staticmethod
    def from_tuples(*tuples: Any) -> 'DependencyResolver':
        ...



Not available

Below is the final version of our “worker” example, which now utilizes the DependencyResolver. By default, the SimpleController is capable of working with either of the worker services. However, once we configure SimpleController and, in turn, the DependencyResolver - the component is re-configured to work with just Worker1.

class SimpleController implements IConfigurable, IReferenceable, IUnreferenceable {
    private _worker: any;
    
    private _depedencyResolver = DependencyResolver.fromTuples(
        "worker", new Descriptor("*","worker","*","*","1.0")
    );

    public configure(config) {
      this._dependencyResolver.configure(config);
    }

    public setReferences(references) {
      this._dependencyResolver.setReferences(references);
      this._worker = this._dependencyResolver.getOneRequired("worker");
    }

    public unsetReferences() {
      this._dependencyResolver = new DependencyResolver();
    }
...
}

let references = References.fromTuples(
    new Descriptor("sample", "worker", "worker1", "111", "1.0"), new Worker1(),
    new Descriptor("sample", "worker", "worker2", "222", "1.0"), new Worker2()
);

let config = ConfigParams.fromTuples(
    "dependencies.worker", "*:worker:worker1:111:1.0"
);

let controller = new SimpleController();
controller.configure(config);
controller.setReferences(references);
controller.greeting("world");
controller.unsetReferences();
controller = null;


class SimpleController : IReferenceable, IUnreferenceable, IConfigurable
{
    protected Worker worker;
    protected DependencyResolver depedencyResolver = DependencyResolver.FromTuples(
        "worker", new Descriptor("*", "worker", "*", "*", "1.0")
    );

    public void Configure(ConfigParams config)
    {
        depedencyResolver.Configure(config);
    }

    public void SetReferences(IReferences references)
    {
        depedencyResolver.SetReferences(references);
        worker = references.GetOneRequired<Worker>(new Descriptor("*", "worker", "worker1", "*", "1.0"));
    }

    public void UnsetReferences()
    {
        depedencyResolver = null;
    }

    ...
}


var references = References.FromTuples(
    new Descriptor("sample", "worker", "worker1", "111", "1.0"), new Worker1(),
    new Descriptor("sample", "worker", "worker2", "222", "1.0"), new Worker2()
);

var config = ConfigParams.FromTuples(
    "dependencies.worker", "*:worker:worker1:111:1.0"
);

var controller = new SimpleController();
controller.SetReferences(references);
controller.Greetenig("world");
controller.UnsetReferences();
controller = null;


type SimpleController struct {
	_worker            interface{}
	_depedencyResolver crefer.DependencyResolver
}

func NewSimpleController() *SimpleController {
	return &SimpleController{_depedencyResolver: *crefer.NewDependencyResolverFromTuples(
		"worker", crefer.NewDescriptor("*", "worker", "*", "*", "1.0"),
	)}
}

func (c *SimpleController) Configure(config *cconfig.ConfigParams) {
	c._depedencyResolver.Configure(config)
}

func (c *SimpleController) SetReferences(references crefer.IReferences) {
	c._depedencyResolver.SetReferences(references)
	c._worker, _ = c._depedencyResolver.GetOneRequired("worker")
}

func (c *SimpleController) UnsetReferences() {
	c._depedencyResolver = *crefer.NewDependencyResolver()
}

...

references := crefer.NewReferencesFromTuples(
	crefer.NewDescriptor("sample", "worker", "worker1", "111", "1.0"), mymodule.NewWorker1(""),
	crefer.NewDescriptor("sample", "worker", "worker2", "222", "1.0"), mymodule.NewWorker2(""),
)

config := cconfig.NewConfigParamsFromTuples(
	"dependencies.worker", "*:worker:worker1:111:1.0",
)

controller := mymodule.NewSimpleController()
controller.Configure(config)
controller.SetReferences(references)
controller.Greeting("world")
controller.UnsetReferences()
controller = nil

class SimpleController
    implements IConfigurable, IReferenceable, IUnreferenceable {
  dynamic _worker;

  var _dependencyResolver = DependencyResolver.fromTuples(
      ['worker', Descriptor('*', 'worker', '*', '*', '1.0')]);

  @override
  void setReferences(references) {
    _dependencyResolver.setReferences(references);
    _worker = _dependencyResolver.getOneRequired('worker');
  }

  @override
  void configure(ConfigParams config) {
    _dependencyResolver.configure(config);
  }

  @override
  void unsetReferences() {
    _dependencyResolver = DependencyResolver();
  }
...
}

var references = References.fromTuples([
    Descriptor('sample', 'worker', 'worker1', '111', '1.0'),
    Worker1(),
    Descriptor('sample', 'worker', 'worker2', '222', '1.0'),
    Worker2()
  ]);

var config = ConfigParams.fromTuples(
    ['dependencies.worker', '*:worker:worker1:111:1.0']);

SimpleController? controller = SimpleController();
controller.configure(config);
controller.setReferences(references);
controller.greeting('world');
controller.unsetReferences();
controller = null;

class SimpleController(IConfigurable, IReferenceable, IUnreferenceable):
	_depedency_resolver = DependencyResolver.from_tuples(
    	"worker", Descriptor("*", "worker", "*", "*", "1.0")
  	)

  	def configure(self, config):
    	self._depedency_resolver.configure(config);
  
  	def set_references(self, references):
    	self._depedency_resolver.set_references(references)
    	self._worker = self._depedency_resolver.get_one_required("worker")
  
  	def unset_references():
    	self._dependency_resolver = DependencyResolver()
  
	...

references = References.from_tuples(
	Descriptor("sample", "worker", "worker1", "111", "1.0"), Worker1(),
	Descriptor("sample", "worker", "worker2", "222", "1.0"), Worker2()
)
config = ConfigParams.from_tuples(
	"dependencies.worker", "*:worker:worker1:111:1.0"
)
controller = SimpleController()
controller.configure(config)
controller.setReferences(references)
controller.greeting("world")
controller.unset_references()
controller = None


Not available

When creating such a configuration for a container, the configuration file might look something like the example below (see the Container Configuration Recipe for more details):

- descriptor: "sample-references:worker:worker1:*:1.0"
  default_name: "Worker1"

- descriptor: "sample-references:worker:worker2:*:1.0"
  default_name: "Worker2"

- descriptor: "sample-references:controller:default:default:1.0"
  default_name: "Sample"
  dependencies:
    workers: "sample-references:worker:worker2:*:1.0"

The Referencer

The Referencer helper class can be used as well for setting and removing dependencies:

class Referencer {
	public static setReferencesForOne(references: IReferences, component: any): void;
	public static setReferences(references: IReferences, components: any[]): void;
	public static unsetReferencesForOne(component: any): void;
	public static unsetReferences(components: any[]): void;
}

class SimpleController : IReferenceable, IUnreferenceable, IConfigurable
{
    protected Worker worker;
    protected DependencyResolver depedencyResolver = DependencyResolver.FromTuples(
        "worker", new Descriptor("*", "worker", "*", "*", "1.0")
    );

    public void Configure(ConfigParams config)
    {
        depedencyResolver.Configure(config);
    }

    public void SetReferences(IReferences references)
    {
        depedencyResolver.SetReferences(references);
        worker = references.GetOneRequired<Worker>(new Descriptor("*", "worker", "worker1", "*", "1.0"));
    }

    public void UnsetReferences()
    {
        depedencyResolver = null;
    }

    ...
}


var references = References.FromTuples(
    new Descriptor("sample", "worker", "worker1", "111", "1.0"), new Worker1(),
    new Descriptor("sample", "worker", "worker2", "222", "1.0"), new Worker2()
);

var config = ConfigParams.FromTuples(
    "dependencies.worker", "*:worker:worker1:111:1.0"
);

var controller = new SimpleController();
controller.SetReferences(references);
controller.Greetenig("world");
controller.UnsetReferences();
controller = null;


type Reference struct {
	locator   interface{}
	component interface{}
}

func NewReference(locator interface{}, component interface{}) *Reference {
	...
}

func (c *Reference) Component() interface{} {
	...
}

func (c *Reference) Locator() interface{} {
	...
}

func (c *Reference) Match(locator interface{}) bool {
	...
}

class Referencer {

  static void setReferencesForOne(IReferences references, component)

  static void setReferences(IReferences references, List components)

  static void unsetReferencesForOne(component)

  static void unsetReferences(List components)
}

class Reference(object):

    def __init__(self, locator: Any, component: Any):
        ...

    def match(self, locator: Any) -> bool:
        ...

    def get_component(self) -> Any:
        ...

    def get_locator(self) -> Any:
        ...

Not available

See Also