• Nenhum resultado encontrado

Part III: Robusta

8.3 Component analysis

8.3.1 Decoupling implementations

Section 6.1 detailed the requirements for decoupling implementations. Namely, decoupling implementations requires identifying the Service Contract, starting from the service interface, and packaging classes into modules that can then evolve at runtime independently.

Figure 66: Summary of the 5 module approach for decoupling implementations.

Figure 66 graphically summarizes the approach to decoupling implementations by using a 5 module approach, in which the Service Contract and Contract Extensions are packaged into modules that are independent of the component implementation modules. In this way the implementation modules can be removed at runtime independently.

At design time, developers can be assisted thanks to the identification of the Service Contract. Once the service interface has been defined, we can calculate the transitive closure of classes that are referenced from the service interface, and propose packaging them into a Service Contract module or group of modules. Furthermore, a search for any classes that implement interfaces or inherit classes from the Service Contract is performed in order to place such classes into the Extension modules. All implementation classes can be left in their respective implementation modules. Tight cyclic dependencies between contract and implementation classes indicate that they are not easily separable and that the service should probably be redesigned.

Finally, a verification process using the same analysis is possible to ensure that an existing packaging solution is indeed properly decoupled.

8.3.2 Decoupling instances

Service interfaces that use Managed objects must be specially handled in order to ensure the Managed objects are released when required. There are various calculations that can assist developers in ensuring that component instances are properly decoupled.

 Components that receive a managed object should implement the callback method for notifications to release the Managed object.

 References to Managed objects should not escape the component unless they do so

8.3 Component analysis

 Managed objects are probably better off being stored inside the implementation classs fields, and should avoid being copied throughout the component less a reference be incorrectly withheld. Developers can be warned of reference copies when compiling.

Finally, we have proposed the use of Free and Managed objects when defining a service interface. The difference in choosing one or the other resides in if the object can be used for as long as desired or if the object has an inherent retention policy that requires it being treated differently.

We have alluded to the fact that Managed objects should be special objects, carefully and selectively used when necessary. However, from a practical point of view, when a service interface is initially identified, and before we know if the parameters and return values are either Free or Managed, it is unclear if they should be considered either Free or Managed by default. Indeed, if we were to err on the side of safety, any non-characterized object should be a Managed object, forcing developers to verify and indicate Free objects one by one. This would undoubtedly lead to lots of false coupling detections, where coupling is found although it does not exist. Doing the opposite, considering all objects to be Free unless indicated to be Managed could potentially lead to undetected coupling, causing memory leaks or unexpected behavior. Either solution requires configurability. In section 8.5 (Defensive programming techniques) we go over some best practices when implementing services. Such practices can also be detected and would assist in identifying Free objects.

8.3.3 Dependency resilience

We have described component and dependency resilience in section 7.2. Resilience can be internally handled by the component (i.e., application specific isolation barrier) or externally managed by the framework. The former is easy to verify at design-time by checking that service invocations are wrapped in a Try-Catch-Finally clause that catches the ServiceUnavailableException and recovers. The latter is more difficult to detect automatically.

In essence, the resilience of a dependency depends on how corruptible the service invocations are, should the service fail at runtime (e.g., a volatile component becomes unavailable).

Because a service invocation passes parameters from one component to the other, we are specifically wondering how corruptible these parameters are. In the case of application specific isolation barriers, we expect the component to verify the parameters and recover or decide to fail.

However, in the case of external recovery mechanisms, it is more difficult to know if the parameters are still valid and if we can re-invoke the service once we find another valid provider.

By default, we consider the parameters to be invalid unless the developer tells us otherwise, meaning that by default components are not resilient. However, there are two calculations that can assist in automatically finding out if the invocation is safe.

The first solution is to verify that the receiving component does not modify or further propagate the parameters. If the component never changes the parameters then there is no reason for them to become invalid. If they are propagated, the same calculation can be performed on the next component to see if they are modified or propagated. The main problem with this calculation is that it can only be done once we know the architecture, which is not intrinsic to the component itself. It is a natural candidate for dynamic instrumentation with the objective of minimizing the impact of unexpected dynamism by improving corruption targeting.

The second solution is to analyze the corruptibility of the parameters themselves. Immutable or incorruptible parameters are naturally safe. These properties are further explored in section 8.5.

If all parameters are incorruptible then we are sure the external recovery mechanism can be used.

The third solution is to see if the parameters are defensively copied, that is, that the invoking component makes a copy of the parameters before invoking the service (although, if this is done then the required step to implementing an application-specific recovery mechanism is slim), or the receiving component makes a copy before modifying the parameters.

These properties can be resumed as:

 @Unchanged: the component does not change the parameters.

 @Immutable: the parameters cannot be changed and are inherently safe.

 @DefensiveCopy: the receiving component makes a copy and does not change the originals (implies @Unchanged). Defensive copies can be shallow (only the initial object is duplicated), deep (the entire object graph is copied) or lazy (an initial shallow copy where a deep copy is performed on a write operation).

8.3.4 Propagation analysis

We have presented an initial case of coupling propagation by means of passing Managed objects in section 6.2.7 (Coupling propagation: passing Managed objects), and we have shown how a group of components can become unknowingly coupled. The same basic principles of propagation apply to passing other objects, such as passing objects defined by hidden classes (i.e., classes not properly decoupled from implementations and put into the Extended Service Contract) and of passing parameters that we wish to verify have not been corrupted.

To assist developers, we can determine at design-time if a component actually receives, retains, uses or propagates an object that causes coupling. Indeed, components that are not programmed to be aware of dynamism for example they do not implement notification callback methods might in fact not actually retain the coupled objects.

If a component is potentially coupled because it interacts with a service that uses a Managed object or has a hidden coupled class, we can verify if the component retains the object or not. More specifically, we are interested in the following properties:

 @Stored: the component saves the reference in its internal state (e.g., its fields). This indicates the component is effectively coupled.

 @Transient: the component only uses the object reference for the duration of a method but does not retain the object. This indicates that the component is not coupled.

 @Propagated: the component leaks the reference by passing it to other components.

This means that it contaminates others. In the case of Managed objects and hidden classes, this means that other components are unknowingly coupled.