APAM/APAM Details/APAM Dependecy Management/APAM Dependecy Resolution

From Wiki Adele Team
Jump to: navigation, search
This APAM Wiki is no longer maintained. Please switch to the following github Wiki for newest APAM information:

The traditional resource management strategy is to first gather all the resources needed by an application before starting it. Unfortunately, in our context, between time t0 at which a service s is started and time t1 at which it needs a service provider P, many things may occur. P may be non-existing at t0, but created before t1; P may be unavailable or used at t0 but released before t1; a provider of P (say p1) may be available at t0 but at t1 it is another provider (say p2) that is available. Therefore, each service (and applications) should get the resources it needs only when they are really needed. Conversely, resources must be released as soon as possible because they may be needed by other services. It is the lazy strategy. Therefore Apam is fully lazy by default. However, an eager strategy can be imposed by the composite (see XXX).

We call resolution the process by which a client finds the service provider (an instance) it requires.

In Apam, a dependency is defined towards a component (specification, implementation or instance) or a resource (an interface or a message) defined by their name, constraints and preferences (see the metamodel above). If the dependency is defined toward a component, the resolution consists first in finding that component and then to select one of its member satisfying the constraints and preferences, and recursively until to find the instance(s). If the dependency is defined toward a resource, the resolution consists in finding a component providing that resource and satisfying the constraints and preferences, and recursively until to find the instance(s). If no instance satisfies the constraint but an implementation is available, an instance is created; otherwise the resolution fails. The components are found either in the platform (the currently running services), or in a repository, local or distant (OBR, Maven, …). Since the component description is the same in all repositories, including the platform, the same constraints and preferences apply indifferently in all repositories. The available repositories are per composite, (see “Execution and OBR repositories” above). If found in a repository, the selected component is transparently deployed and instantiated; therefore, for the client developer, it makes no difference if the component is found in the machine or in any repository. Conceptually, all the components are in the machine (like between the virtual memories and the physical memory).

Nevertheless it is always possible for a resolution to fail i.e. no convenient implementation or instance can be found, in that case, by default, null is returned to the client i.e. the client code must check its variable before any use, which is relevant only if the dependency is optional. On all the other cases, the client would like to assume that its variable is always conveniently initialized. The strategy in this case is controlled by the “fail” property associated with dependencies. For example:

<dependency specification=”S2” field=”s2” id=”fastS2” fail= “wait” | “exception” exception=”fr.imag. ….failedException” />

Fail= “wait” means that if the resolution fails, the client current thread is halted. When a convenient provider appears, the client thread is resumed with its dependency resolved against that provider. Therefore, the client code can always rely on a satisfactory resolution, but may have to wait. Fail =”exception” mean that, if the dependency fails, an exception is thrown, as defined in the exception tag. If no user exception is defined the Apam default ”ResolutionException” is thrown. The source code is supposed to catch that exception. Exception=”Exception class” mean that, if the dependency fails, the associated exception is thrown. The Exception class must be exported in order for Apam to see the class (using the Admin), and to throw the exception.

If, for any reason (failure, disconnection, …) the instance used by a dependency disappears, Apam simple removed the wire, and a new resolution of that dependency will be intended at the next use of the associated variable. It means that dynamic substitution is the default behavior.

Dependency cardinality A “simple” dependency is associated with a simple variable in the Java code. At any point in time, the variable points to zero or one provider. A multiple dependency is associated with a variable that is a collection i.e. an “array”, a “Set”, a “Vector” or a “List”. Such a dependency therefore leads to a set of service providers. When the dependency is resolved for the first Apam, the dependency is associated with all the instances implementing the required resources, available at the time of resolution. If none are available, one is instantiated if possible, the resolution fails otherwise.

<dependency specification="S3Compile" id="S3Id" multiple=”true”>

<interface field="fieldS3" multiple=”true”/> <!— multiple is useless -->

The multiple attribute is very useful only for specification dependencies, since there is no other way, at that level, to know. For implementations, the field type (Collection or not) indicates if the dependency is multiple or not. If the field is a collection, the attribute multiple can be missing, it is assumed to be true, it can be set to true, but is cannot be false.

Once the dependency resolved, any new instance (of the right type) appearing in the system is automatically added to the set initially computed; similarly, each time an instance disappears, it is removed from the set of instances. This even can be captured in the program, if callbacks are indicated:

<dependency field="fieldT" added="newT" removed="removedT" />

In this example, if fieldT is a set of type T, the Java program must contain a method newT and removedT (names are fully arbitrary) :

Set<T> fieldT ;

public void newT (T t) { } or public void newT (Instance inst) { } public void removedT () {} or public void removedT (Instance inst) {}

The method newT must have as parameter either an object of type T, or an object of type Instance (fr.imag.apam.Instance). This method is called each time an object (of type T) is added in the set of references, this object is the parameter. Similarly, the method removedT is called each time an object is removed from the set; it may have the Apam instance object as parameter (warning: it is an isolated object without a real instance inst.getServiceObject()==null) About messages, the newM1 method is called each time a new provider is added in the set of the M1 message providers, and removedM1 is called when an M1 provider is removed.

Complex dependencies A complex dependency is such that different fields and messages are associated with the same provider instance. The provider must implement a specification, and the different fields must reference the different resources defined by that specification.

<dependency specification="S3Compile" id="S3Id"> <interface field="fieldS3" /> <message method="mes1" /> <interface field="field2S3" /> </dependency>

In the example, the dependency S3Id is a dependency toward one instance of the specification S3Compile. That instance is the target of fields fieldS3 and field2S3, and the provider of message mes1. For dependencies with cardinality multiple, all variables are bound to the same set of service providers (internally, it is the same array of providers). It means that that dependency is resolved once (when the first field is accessed), and if it changes, it changes simultaneously for all fields.

Messages Following our metamodel, a component provides resources (interfaces or messages) and dependency can be defined against interfaces or messages. Therefore a component can be a message provider, or a message requester.

A message provider must indicate in its declaration header, as for interfaces, the type of the provided messages, and for implementations, the associated fields (see example above). <specification name="S2" interfaces="apam.test.S2" messages="apam.test.M1, apam.Test.M2" > …..

<implementation name="S2Impl" specification=”S2” message-methods="producerM1, producerM2" interfaces=" apam.test.AC" ……

The S2Impl implementation should contain the methods producerM1 and producerM2:

public M1 producerM1 (M1 m1) { return m1; }

Each time the producer calls the produceM1 method, Apam considers that a new M1 message is produced. There is no constraint on the method producerM1 parameters, but it must return an M1 object. A dependency can be defined against messages in a similar way as interfaces, but methods instead of fields must be indicated, as in the following examples.

<dependency method="getM1" /> <dependency field="fieldS2" />

<dependency specification="S2Compile" > <interface field="anotherS2" /> <message method="getAlsoM1" /> </dependency>

<dependency method="gotM2" />

The first line is a simple declaration of a message dependency; analyzing the source code it is found that getM1 is a method that returns a message of type M1 and therefore is associated with the message M1 dependency. The associated Java program should contain:

Set<S2> fieldS2 ; S2 anotherS2 ;

public M1 getM1 () { return null;} public M1 getAlsoM1 () { return null;}

public void gotM2 (M2 m2) { …… }

The method getM1 and getAlsoM1 are very special method: calling them does not execute their body, but calls Apam to get the next message of type M1. If there is no new M1 value available, the client is waiting for a new value to be available. At the first call to these methods, the corresponding M1producers are resolved and connected to the method. If the dependency is multiple, all the valid M1 producer will be associated to the method, otherwise a single producer is connected. In this case, as for usual dependencies, it is the client that has the initiative to get a new value. We call it the “pull” mode.

In the pull mode, we can also declare methods that return a set of message: public Set<M1> getM1 () { return null;} public Set<M1> getAlsoM1 () { return null;}

When these methods are called, ApAM will consider that all the returned objects are provided messages.

If the declared method is void, with a message type as parameter (M2 here), this method will be called by Apam each time a message of type M2 is available. In this case it is the message provider that has the initiative to call its client(s). The connection between client and provider is established at the first call by the provider to its produceM2 method. In the example, the method gotM2 will be call each time an M2 message is produced by one of the valid M2 producers.

In the previous examples, the raw data of type M1 and M2 is received by the clients. If more context is required, the injected methods can declare Message<M1> instead of M1; Message being a generic type defined in Apam that contains an M1 values and information about the message: producer id, time stamp, and so on.

For multiple message dependencies, as for interfaces, it is possible to be aware of the “arrival” and “departure” of a message provider:

<dependency method="getM1" added="newM1" removed="removedM1" />

With the associated methods, as shown above for interfaces.

Constraints and preferences

<dependency specification="S3Compile" id="S3Id"> <interface field="fieldS3" /> <constraints> <implementation filter="(apam-composite=true)" /> <instance filter="(&(testEnum*>v1,v2,v3)(x=6))" /> <instance filter="(&(A2=8)(MyBool=false))" /> </constraints> <preferences> <implementation filter="(x=10)" /> <instance filter="(MyBool=false)" /> </preferences> </dependency>

<definition name="testEnum" type="v1, v2, v3, v4, v5" value="v3" />

In the general case, many provider implementations and even more provider instances can be the target of a dependency; however it is likely that not all these providers fit the client requirements. Therefore, clients can set filters expressing their requirements on the dependency target to select. Two classes of filters are defined: constraints and preferences. Filters can be defined on implementations or instances. Constraints on implementation are a set of LDAP expression that the selected implementations MUST ALL satisfy. An arbitrary number of implementation constraints can be defined; they are ANDed. Similarly, constraints on instance are a set of LDAP expression that the selected instances MUST ALL satisfy. An arbitrary number of instance constraints can be defined; they are ANDed.

Despite the constraints, the resolution process can return more than one implementation, and more than one instance. If the dependency is multiple, all these instances are solutions. However, for a simple dependency, only one instance must be selected: which one ? The preference clause gives a number of hints to find the “best” implementation and instance to select. The algorithm used for interpreting the preference clauses is as follows: Suppose that the preference has n clauses, and the set of candidates contains m candidates. Suppose that the first preference selects m’ candidates (among the m). If m’ = 1, it is the selected candidate; if m’=0 the preference is ignored, otherwise repeat with the following preference and the m’ candidates. At the end, if more than one candidate remains, one of them is selected arbitrarily.