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:
See: IReferences, References
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[];
}
See: IReferences, References
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);
}
See: IReferences, References
type IReferences interface {
Put(ctx context.Context, locator any, component any)
Remove(ctx context.Context, locator any) any
RemoveAll(ctx context.Context, locator any) []any
GetAllLocators() []any
GetAll() []any
GetOptional(locator any) []any
GetRequired(locator any) ([]any, error)
GetOneOptional(locator any) any
GetOneRequired(locator any) (any, error)
Find(locator any, required bool) ([]any, error)
}
See: IReferences, References
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);
}
See: IReferences, References
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')
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).
See: IReferenceable
interface IUnreferenceable {
unsetReferences(): void;
}
See: IReferenceable
public interface IUnreferenceable
{
void UnsetReferences();
}
See: IReferenceable
type IUnreferenceable interface {
UnsetReferences(ctx context.Context)
}
See: IReferenceable
abstract class IUnreferenceable {
void unsetReferences();
}
See: IReferenceable
class IUnreferenceable(ABC):
def unset_references(self):
raise NotImplementedError('Method from interface definition')
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.
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}')
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.
See: IUnreferenceable
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) + "!");
}
}
See: IUnreferenceable
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) + "!");
}
}
See: IUnreferenceable
import (
"fmt"
crefer "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
clog "github.com/pip-services3-gox/pip-services3-components-gox/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(ctx context.Context, references crefer.IReferences) {
c._worker, _ = references.GetOneRequired(111)
}
func (c *SimpleController) UnsetReferences(ctx context.Context) {
c._worker = nil
}
func (c *SimpleController) Greeting(ctx context.Context, name string) {
c._worker.(Worker).Do(clog.Debug, "Hello, "+name+"!")
}
See: IUnreferenceable
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) + '!');
}
}
See: IUnreferenceable
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) + "!")
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(context.Background(),
111, mymodule.NewWorker1("worker1"),
222, mymodule.NewWorker2("worker2"),
)
controller := mymodule.NewSimpleController()
controller.SetReferences(context.Background(), references)
controller.Greeting(context.Background(), "world")
controller.UnsetReferences(context.Background())
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
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:
- Group - logical group of objects. Usually, this is the name of the library or microservice. E.g. “pip-services”.
- Type - logical type or object interface that presumes some general functionality. E.g. “logger”.
- Kind - specific implementation of the logical type. E.g. (for logger components) “null”, “console”, “elasticsearch”, “fluentd”, etc.
- Name - unique object name. E.g. “logger1” or “logger2”.
- 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:
See: Descriptor
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;
}
See: 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);
}
See: Descriptor
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
}
See: Descriptor
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)
}
See: Descriptor
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']:
...
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(ctx context.Background(), references crefer.IReferences) {
c._worker, _ = references.GetOneRequired(crefer.NewDescriptor("*", "worker", "worker1", "*", "1.0"))
}
...
references := crefer.NewReferencesFromTuples(context.Background(),
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(context.Background(), references)
controller.Greeting(context.Background(), "world")
controller.UnsetReferences(context.Background())
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
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 for more info on the specifics of component configuration). The DependencyResolver class:
See: DependencyResolver
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;
}
See: 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);
}
See: DependencyResolver
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(context.Background(), config)
}
if references != nil {
c.SetReferences(context.Background(), references)
}
return c
}
func (c *DependencyResolver) Configure(ctx context.Context, 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(ctx context.Context, references IReferences) {
c.references = references
}
func (c *DependencyResolver) Put(ctx context.Context, 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
}
See: DependencyResolver
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)
}
See: DependencyResolver
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':
...
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(ctx context.Context, config *cconfig.ConfigParams) {
c._depedencyResolver.Configure(ctx, config)
}
func (c *SimpleController) SetReferences(ctx, references crefer.IReferences) {
c._depedencyResolver.SetReferences(ctx, references)
c._worker, _ = c._depedencyResolver.GetOneRequired("worker")
}
func (c *SimpleController) UnsetReferences(ctx) {
c._depedencyResolver = *crefer.NewDependencyResolver()
}
...
references := crefer.NewReferencesFromTuples(context.Background(),
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(context.Background(),config)
controller.SetReferences(context.Background(),references)
controller.Greeting(context.Background(),"world")
controller.UnsetReferences(context.Background())
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
When creating such a configuration for a container, the configuration file might look something like the example below (see the Component Configuration 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:
See: Referencer
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;
}
See: Referencer
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;
See: Referencer
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 {
...
}
See: Referencer
class Referencer {
static void setReferencesForOne(IReferences references, component)
static void setReferences(IReferences references, List components)
static void unsetReferencesForOne(component)
static void unsetReferences(List components)
}
See: Referencer
class Reference(object):
def __init__(self, locator: Any, component: Any):
#...
def match(self, locator: Any) -> bool:
E...
def get_component(self) -> Any:
#...
def get_locator(self) -> Any:
#...
See Also
- Service Locator Pattern
- wiki Service Locator Pattern
- Using a Service Locator
- Component Configuration
- Component Lifecycle
- Component Creation