Creating Component Implementations
Well-designed service-based architectures typically have a limited number of coarse-grained services that coordinate other services to perform specific tasks. The heart of the LoanApplication composite is LoanComponent, which is responsible for receiving loan application data through its LoanService interface and delegating to other services for processing. The implementation is a basic Java class that takes a reference proxy to a CreditService interface as part of its constructor signature. The LoanComponent component uses the service to provide a credit score for the applicant. When reviewing the implementation, take note of the @Reference annotation in the constructor (see Listing 2.4).
Listing 2.4. The LoanComponent Implementation
public class LoanComponent implements LoanService { private CreditService service; public void LoanComponent (@Reference CreditService service){ this.service = service; } public LoanResult apply(LoanRequest request) { // .... } public int checkStatus(String applicationID){ // .... } }
In Listing 2.4, the @Reference annotation instructs the SCA runtime that LoanComponent requires a reference to CreditService. An implementation of CreditService is provided by CreditComponent, shown in Listing 2.5.
Listing 2.5. The CreditComponent Implementation
public class CreditComponent implements CreditService { public int checkCredit(String id){ // .... } }
Although the code has been simplified from what would be typically encountered in a real-world scenario, the implementation—like LoanComponent—is straight Java. Even though both components may be hosted on different machines, the only thing required to facilitate remote communication is the presence of @Remotable on the CreditService interface.
SCA leaves the heavy lifting associated with establishing remote communications to the runtime, as opposed to application code and API calls. As we saw in the introductory chapter, SCA does this through wires. Conceptually, a wire is a connection provided by the runtime to another service. A wire is specified—in this case, the wire between LoanComponent and CreditComponent—in the composite file, which we show in the next section. For now, we will assume a wire has been specified and describe how an SCA runtime goes about connecting LoanComponent to the CreditService interface of CreditComponent.
In Java, the runtime provides a wire by doing one of the following: calling a setter method annotated with @Reference and passing in a reference to the service; setting a field marked with @Reference; or passing a reference to the service as a constructor parameter annotated with @Reference, as in the example given previously in Figure 2.3.
In actuality, when the SCA runtime injects the CreditService, it is likely not a “direct” reference to CreditComponent but instead a generated “proxy” that implements the CreditService interface (see Figure 2.4).
Figure 2.4 Reference proxy injection
The proxy is responsible for taking an invocation and flowing it to the target service, whether it is co-located or hosted in a remote JVM. From the perspective of LoanComponent, however, CreditService behaves as a typical Java reference.
An important characteristic of wires is that their details are hidden from the client implementation. In our example, LoanComponent does not have knowledge of the wire communication protocol or the address of CreditService. This approach will be familiar to Spring developers. SCA is based on Inversion of Control (IoC), also known as dependency injection, popularized by the Spring framework. Instead of requiring a component to find its dependent services through a service locator API and invoke them using transport-specific APIs, the runtime provides service references when an instance is created. In this case, CreditService is injected as a constructor parameter when LoanComponent is instantiated.
There are a number of advantages to IoC. Because the endpoint address of CreditService is not present in application code, it is possible for a system administrator or runtime to make the decision at deployment whether to co-locate the components (possibly for performance reasons) or host them in separate processes. Further, it is possible to “rewire” LoanComponent to another implementation of CreditService without having to change the LoanComponent code itself. And, because the client does not make use of any protocol-specific APIs, the actual selection of a communication protocol can be deferred until deployment or changed at a later time.
Injection Styles
In the current version of LoanComponent, we elected to define the reference to CreditService as a constructor parameter. This is commonly referred to as constructor-based injection. Some developers prefer to inject dependencies through setter methods or directly on fields. The SCA Java programming model accommodates these alternative approaches as well by supporting injecting references on methods and fields. We will take a closer look at each injection style in turn.
Constructor-Based Injection
Constructor-based injection has the advantage of making dependencies explicit at compile time. In our example, LoanComponent cannot be instantiated without CreditService. This is particularly useful for testing, where component implementations are instantiated directly in test cases. Constructor-based injection also enables fields to be marked as final so that they cannot be inadvertently changed later on. When other forms of injection are used, final fields can’t be used. The primary drawback of constructor-based injection is that the constructor parameter list can become unwieldy for components that depend on a number of services.
In some cases, component implementations may have more than one constructor. The SCA Java programming model defines a rule for selecting the appropriate constructor in cases where there is more than one. If one constructor has parameters marked with @Reference or @Property, it will be used. Otherwise, a developer can explicitly mark a constructor with the SCA @Constructor annotation, as shown in Listing 2.6.
Listing 2.6. The @Constructor Annotation
@Constructor public CreditComponent (double min, double max) { // ... }
Setter-Based Injection
SCA supports method-based reference injection as an alternative to constructor-based injection. For example, LoanComponent could have been written as shown in Listing 2.7.
Listing 2.7. Setter-Based Injection
public class LoanComponent{ public LoanComponent () {} @Reference public setCreditService(CreditService creditService) { // ... } }
When LoanComponent is instantiated, the SCA runtime will invoke the setCreditService method, passing a reference proxy to CreditService. An important restriction SCA places on this style of injection is that setter methods must be either public or protected; private setter methods are not allowed because it violates the object-oriented principle of encapsulation. (That is, private methods and fields should not be visible outside a class.)
The main benefit of setter-based injection is that it allows for reinjection of wires dynamically at runtime. We cover wire reinjection in Chapter 7, “Wires.”
There are two major downsides to setter injection. Component dependencies are dispersed across a number of setter methods, making them less obvious and increasing the verbosity of the code because a method needs to be created for every reference. In addition, setter methods make references that potentially should be immutable subject to change, because the fields they are assigned to cannot be declared final.
Field-Based Injection
The final form of injection supported by SCA is field-based. This style enables fields to be directly injected with reference proxies (see Listing 2.8).
Listing 2.8. Field-Based Injection
public class LoanComponent { @Reference protected CreditService CreditService; //.... }
Field-injection follows the basic pattern set by method-injection except that they may be private and public or protected. In the absence of a name attribute declared on @Reference, the field name is used as the name of the reference. Again, the preceding example would be configured using the same composite syntax as the previous examples.
A major advantage of field-based injection is that it is concise. (Methods do not need to be created for each reference.) It also avoids long constructor parameter lists. The main disadvantage of field-based injection is it is difficult to unit test; component classes must either be subclassed to expose reference fields or those fields must be set through Java reflection.
Defining Properties
Consider the case where we want to add the capability to set configuration parameters on the CreditComponent component, such as minimum and maximum scores. SCA supports configuration through component properties, which in Java are specified using the @Property annotation. CreditComponent is modified to take maximum and minimum scores in Listing 2.9.
Listing 2.9. Defining Component Properties
public void CreditComponent implements CreditService { private int min; private int max; public CreditComponent (@Property(name="min") int min, @Property(name="max") int max) { this.min = min; this.max = max; } // .... }
Like a reference, a property name is specified using the “name” attribute, whereas the “required” attribute determines whether a property value must be provided in the composite file (that is, when it is set to true) or it is optional (that is, it is set to false, the default). In addition, properties follow the same injection guidelines as references: constructor-, method-, and field-based injection are supported.
Given that most IoC frameworks do not distinguish between properties and references, why does SCA? The short answer is they are different. References provide access to services, whereas properties provide configuration data. Differentiating properties and references makes it clear to someone configuring a component whether a property value needs to be supplied or a reference wired to a service. Further, as we will see in later chapters, references may have various qualities of service applied to them, such as reliability, transactions, and security. The benefits of distinguishing properties and references also extends to tooling: Knowing if a particular value is a property or reference makes for better validation and visual feedback, such as displaying specific icons in graphical tooling.