The last two chapters focused on the SCA programming model; in this chapter, we take a closer look at how applications are assembled using composites. Here we introduce another key concept, composition. In short, composition is the capability to build larger components and services from a series of smaller ones. The power of composition is that it provides a mechanism for more easily maintaining and evolving applications over time by making them more modular. With composition, sets of services encompassing various functional areas of an application can be more easily reused, upgraded, replaced, and changed. After working through this chapter, you will have a solid foundation in how composites are used to achieve a modular application design.
Most modern programming languages and models support some form of encapsulation. That is, they have constructs for breaking down parts and isolating them from one another. Modern programming languages and models also have mechanisms for reuse. Object-oriented languages are often designed around interfaces and classes, which serve both functions. SCA has services and components.
Up to this point, we have discussed how services and components provide reuse and encapsulation in several ways. First, services provide a way for multiple clients to address and invoke a unit of code contained in a component. Services also provide encapsulation as they hide implementation details from clients. Component implementations may be reused multiple times, potentially with different property values and wiring.
For many applications, this level of reuse and encapsulation is sufficient. However, as an application becomes more complex and the number of components grows, the need may arise to encapsulate sets of components that expose a few services. A credit appraisal process may be composed of multiple components but needs to expose only one service to its clients. Here, the fact that the credit appraisal process is handled by multiple components is an implementation detail; in the future, these “internal” components and their wiring may change.
In addition, as system complexity grows, the need may arise to reuse not just single components, but sets of components. Perhaps a group of components together perform an operation such as validating and persisting an employee record to a database. It would be beneficial to reuse this set of components as a single unit across a number of disparate applications, hiding the details of the components from clients. It would also be useful if there were facilities for making slight configuration changes to the components as a whole, rather than modifying the individual components.
To handle these cases—encapsulation and reuse of multiple, related components—SCA supports composition, or the capability to assemble larger components from smaller ones. SCA does this in a very simple but powerful way. Consider the visual representation of encapsulation shown in Figure 5.1.
The composite contains four components, three of which interact to provide a service to the fourth. A component that performs credit scoring may use data validation and auditing components. These details should be hidden from clients using the credit-scoring service. A solution to this problem would be to allow components to be composed from other components like building blocks. We can modify the previous diagram to include this “composite” component, as illustrated in Figure 5.2.
The composite component encapsulates the three components and their wires by exposing a single service to clients. SCA takes this a step further and makes the composite itself a type of component. In other words, composites are a component implementation type just like Java, BPEL, or C++. Our earlier diagram can now be represented as a series of nested components (see Figure 5.3).
Because composites are just a particular type of component, they can also be reused like other components. As we explain in the next sections, composites can have services, references, and properties that are configured in their parent composite. We now look at how to use a composite as a component implementation.
Suppose the previous credit-scoring function performed by the loan application involved a multistep process consisting of data validation, score calculation, and producing an audit record for legal compliance. This may best be architected using four components: one that functions as a central coordinator (the CreditComponent
) and delegates to the other services; one that performs data validation (the ValidationComponent
); one that serves as a scoring engine (the ScoringComponent
); and one that writes audit messages to a log (the AuditingComponent
). CreditServiceComposite
, shown in Listing 5.1, assembles these four components.
We could have chosen to include these four components in the original LoanApplication
composite. However, as the application grows, that strategy will likely result in a brittle and difficult-to-maintain system. In the future, all or part of the credit-scoring components may need to be changed. In addition, configuring all components in one composite is likely to result in a very unstructured application that is difficult to decipher (not to mention developers stepping on one another as they modify parts of the single composite).
Instead, good design suggests that we encapsulate the credit-scoring function in a composite, which is then used as a component by the LoanApplication
composite. Figure 5.4 depicts this visually.
The SCDL in Listing 5.2 configures LoanComponent
and CreditComponent
.
The key part of the preceding SCDL is the use of the <implementation.composite>
element. This instructs the SCA runtime to use a composite as the component implementation, just as if we had specified <implementation.java>
or <implementation.bpel>
. However, instead of referencing the class name, we refer to the fully qualified name of the composite using the “name” attribute. The fully qualified name, or QName, consists of the target namespace and name of the composite. This is equivalent to specifying the package and class name for Java implementation types.
The next step in encapsulating the credit-scoring process is to expose the CreditService
from the CreditComposite
. Exposing—or as SCA terms it, promoting—a service in a composite serves two purposes. It allows other references from outside the composite to be wired to it. It also provides a mechanism for the service to be configured by the composite using it as an implementation. We discuss each of these in turn.
Services are promoted using the <service>
element in the composite, as demonstrated in the SCDL fragment in Listing 5.3.
The <service>
element configures a composite service by setting its name and identifying a service to promote via the promote
attribute. In the example, the CreditService
provided by the CreditComponent
is promoted. Because the CreditComponent
implements only one service, we could have omitted explicitly identifying the CreditService
and written promote="CreditComponent"
.
Wiring to the CreditService
provided by the composite is done like wiring to any other service (see Figure 5.5).
In SCDL, wiring is done as shown in Listing 5.4.
The wire defined in the previous SCDL connects the creditService
reference of LoanComponent
just like any other component, thereby hiding internal implementation details.
Composite services have all the characteristics of a component service, including the capability to be bound to a particular remote communications transport. In Chapter 2, we described how to expose the LoanService
as a web service via the SCA web services binding (see Listing 5.5).
Binding the LoanService
using <binding.ws>
instructs the runtime to make the service available as a web service endpoint for external clients.
If BigBank also wanted to expose the CreditService
as a web service endpoint, the corresponding SCDL for the composite component would look the same (see Listing 5.6).
Recalling that the CreditComposite
promoted the CreditService
from the CreditComponent
, look at Listing 5.7.
The SCDL in Listing 5.8 instructs the SCA runtime to bind the CreditService
provided by the CreditComponent
as a web services endpoint.
SCA also allows bindings to be specified on a promoted service inside a composite. Instead of specifying <binding.ws>
on the composite component, the prior example could be recast as that shown in Listing 5.8.
The component that uses the composite could be recast, as shown in Listing 5.9.
The SCDL in Listing 5.8 also instructs the SCA runtime to make the CreditService
available as a web service endpoint. However, it is subtly different than the previous example. Specifying the binding on a composite service will apply to all uses of the composite. In contrast, specifying the binding in the component configuration will only apply to the specific component. For example, if the CreditComposite
were reused several times, multiple CreditService
endpoints would be activated.
Components implemented by composites can also have references that are wired to services. Similar to a composite service, a composite reference is created by promoting the reference of a contained component. The earlier version of the credit score component contained an auditing component. It is likely that this auditing capability will be needed by other components and is therefore a good candidate to be refactored into a generalized service used by the various loan application composites. Refactoring the auditing component can be done by moving it to the parent LoanApplication
composite, as shown in Figure 5.8.
Figure 5.8 also demonstrates how reference promotion is used to wire from a component contained in the credit score component to the auditing service. In SCDL, reference promotion is done using the <reference>
element (see Listing 5.10).
The <reference>
entry creates a composite reference that promotes the auditService
reference on the CreditComponent
. When the CreditComposite
is used as a component implementation, this reference must be wired to a service as done in the SCDL in Listing 5.11.
In a slightly more complex scenario, a promoted reference may be wired to a promoted service. This is shown in Figure 5.9, which changes the AuditComponent
to be implemented by a composite containing two components.
Take the audit composite SCDL shown in Listing 5.12.
The wiring to the audit service would remain the same as in Listing 5.11. The only difference would be the substitution of <implementation.composite>
for <implementation.java>
(see Listing 5.13).
In Chapter 1, “Introducing SCA,” we discussed how references can be bound to a particular communication protocol. This is typically done when a component requires a service that is external to the SCA domain. (Perhaps it is a web service not written using SCA.) For example, a component may be dependent on an external web service that provides interest rates, as shown in Listing 5.14.
The <binding.ws>
element configures the reference to connect to the web service endpoint located at http://www.bigbank.com/rateService. Invocations from the component will be dispatched via web services to the endpoint by the SCA runtime.
Because composite references are like any other component reference, they may also be bound. And, as with composite services, the binding may be configured either in the composite SCDL or as part of the composite component configuration. We look at both examples in turn.
Listing 5.15 demonstrates binding the rateService
reference using promotion—it is almost identical to the previous example.
Alternatively, if we wanted to configure the binding as part of the composite component configuration, we would first remove the binding from the promoted the reference, as shown in Listing 5.16.
Then we’d configure the composite component reference, as shown in Listing 5.17.
It is important to bear in mind that binding a promoted reference (as represented by Listing 5.15) and binding the reference as part of the composite component configuration (Listing 5.16 and Listing 5.17) are not the same. The SCDL in Listing 5.15 will bind the reference for every use of the composite as a component implementation. In contrast, the SCDL in Listing 5.16 leaves the binding open. In other words, the binding may be changed each time the composite is used as a component.
Given that composite components may have services and references, it should come as no surprise that they may also have properties. The composite in Listing 5.18 declares two integer type properties.
The example provides default values of 200
and 700
, respectively.
The properties can be set when the composite is used as a component implementation, as shown in Listing 5.19.
The preceding example sets the min
and max
properties to 100
and 800
, respectively, overriding the default values.
Note that in Listing 5.18, we declared the property type to be intege
r as defined by XML Schema (the use of the “xs” prefix). SCA defines the range of valid property types to include those defined by XML Schema—for example, string
, integer
—and user-defined complex types (more on that later).
Composite properties are optional by default—that is, they do not need to be configured when the composite is used as a component implementation. If a property is not configured and a default value is provided, the property will be set to that value. If the CreditComponent
in Listing 5.19 had not specified min
and max
property values, the SCA runtime would have substituted the default values of 200
and 700
.
A property configuration can be made mandatory by setting the mustSupply
attribute on the property declaration in the composite to true
(by default, it is set to false
). This is shown in Listing 5.20.
When @mustSupply
is set to true
, a property must be configured when the composite is used as a component implementation.
Composite properties would not be very useful unless they could be accessed by contained components. The SCDL in Listing 5.21 demonstrates how to do this.
The @source
attribute of the <property>
element instructs the SCA runtime to set the value of the property to the given composite property value. The source attribute value is an XPath expression. We discuss XPath in more detail later in the chapter, but if you are not familiar with it, XPath is essentially a technology for addressing parts of an XML document. Because SCDL is XML, SCA uses XPath to refer to XML values. In the example, the $
character is an XPath operator that instructs the SCA runtime to select the min
and max
properties.
Why did the SCA authors choose XPath as the expression language for referencing composite properties as opposed to something simpler, such as just referring to the property name? As we show in the next sidebar, basic XPath is relatively easy to write. Also, there are times when only part of a composite property or subelement needs to be selected, such as when the property is a complex type containing several data parts. (We cover complex types in a later section.) XPath is a widely accepted standard for doing this, and inventing a technology would likely lead to more complexity as people would need to master a new approach.
Fortunately, most applications are likely to make much more use of simple property types, such as string and integer, than complex ones. In the cases where simple types are used, the only XPath you need to remember is the $
character preceding the composite property name being referenced.
Property values may contain multiple values, such as a collection of strings or integers. Multivalued properties are declared by setting the @many
attribute to true
(the default is false
), as shown in Listing 5.22.
The property declaration in Listing 5.22 provides multiple default values. When the composite is used as a component, values can be set for property by creating multiple <property>
entries (see Listing 5.23).
At runtime, any component that accesses the validStates
composite property will be given a collection containing the strings CA
and MA
. If no value were set for the property, the default values as defined in Listing 5.23 would be provided: namely CA
, MA
, and NY
.
Sometimes configuration consists of information that is best represented using a data structure as opposed to a simple type. Consider the case where the BigBank loan application contains a number of components that validate data at various stages in the approval process. The credit data validator ensures that all required information is present and in the correct format. The validation rules are name-value pairs, where the name corresponds to a data element name and the value is a regular expression defining the formatting rules for the field.
Instead of hardcoding the formatting rules in the component implementation, BigBank has decided to use a composite property. BigBank could have used a database to store this configuration but opted not to do so for two reasons. First, validation information is static and does not change. Requiring an additional database table will make the application more difficult to configure than is necessary. Second, more practically, storing the configuration information in a database table would require changes to the corporate database, which is often subject to a lengthy review processes.
Listing 5.24 is the property declaration for the validation rules using a complex type, validationRules
.
As shown in this example, where the @type
attribute is set to bb:validationRules
, complex property types are defined in SCA using XML Schema.
XML Schema can be somewhat complex and verbose, but it is the most widely accepted way to specify the set of rules to which an XML document must conform (also known as a “schema language”). Other alternative schema languages exist, such as DTDs, RELAX NG, and Schematron, but SCA chose XML Schema largely due to its ubiquity and existing software support. The XML Schema for validationRules
is listed in Listing 5.25.
When the loan application is deployed, the XML Schema file would typically be packaged as part of the contribution containing the CreditComposite
.
The SCDL in Listing 5.26 sets the validationRules
property on the CreditComponent
.
This approach of including the value of a complex property within a composite file is awkward, because the application developer is likely going to want to manage the contents of this configuration as part of a separate file. In this example, it would be most natural to have a validationRules.xml
configuration file. That way, the configuration rules can be modified without modifying the composite that uses it.
It is possible to specify the value of any property by referring to the contents of a separate file. The file is specified using a relative URI—in this case, relative to the location of the composite file. So, if the validationRules.xml
file is kept in the same directory as the composite, the new composite would look like Listing 5.27.
The validationRules.xml
document contents can then be a little simpler, because it can use the bigbank.com namespace as the default namespace (see Listing 5.28). It also is easier for tools to validate it against its schema.
Having walked through the configuration of complex property types, is the complexity and time required to define an XML Schema worth it? Most applications can likely make do with simple types. However, considering that an XML Schema only needs to be written once, the benefits it affords (most notably validation of property values) are well worth it.
Previously, we mentioned that it is possible for a component property to reference part of a composite property via an XPath expression. Suppose a component needs access to only one validation rule, such as the correct format for Social Security numbers. This can be referenced using the “source” attribute with the XPath shown in Listing 5.29.
The XPath expression, $validationRules//rule[@name='ssn']
, instructs the SCA runtime to select the rule whose name is ssn
(remember that the actual rule values are set when the composite is used as a component; refer to Listing 5.25). As you can see, even nontrivial XPath expressions are easy to read. At the same time, they are a powerful tool, as evidenced by our example of selecting a specific validation rule.
In some situations, it is necessary to override a service, reference, or property configuration at a higher level of composition. In this section, we look at several of the more common override scenarios.
One of the most common cases where a service or reference configuration needs to be overridden involves changing a binding. Overriding a binding is done by promoting the service or reference. Suppose the RateComposite
bound the rateService
using the web services binding in the following manner:
Now suppose that after the rate composite has been packaged, installed, and activated in a domain, BigBank offers a new loan type to small businesses, which requires a different rating service from its consumer division. Fortunately, the rate composite can be reused by having the rateService
reference use a different service. To do this, the reference binding can be overridden when the composite is used as a component implementation. The SCDL in Listing 5.30 shows how to do this.
In the preceding SCDL, reconfiguring the binding in the component definition overrides the binding information in the composite implementation. In addition to overriding binding settings, reference and service bindings can be changed entirely at outer levels of a composition. When overriding the rate service binding, JMS could have been substituted for web services. The basic rule for bindings is that the outer level always replaces inner levels of composition. (For other service and reference configuration, such as policies, the rules are different—we deal with these in later chapters.)
Sometimes it is necessary to “unbind” a reference. Again, take the example we used previously where the reference to the rate service was bound to a web service endpoint:
Suppose the new rate service for commercial loans was not a web service but rather provided by another SCA component deployed in the domain. The reference binding needs to be overridden and the reference wired to the target service instead.
In order to accomplish this, SCA defines a binding called the “SCA binding,” or binding.sca. This binding can be used to override a binding set in a composite and instruct the SCA runtime to create a wire to a target service. Listing 5.31 details how the SCA binding is used.
The configuration overrides the web services binding set in the composite SCDL and replaces it with a wire to a target service provided by another component in the domain.
In SCA, properties are not, strictly speaking, overridden. Rather, a combination of using the default value and the @source
attribute on the <property>
element can be used to expose a property for configuration higher up in the composition hierarchy. Taking the earlier min
and max
properties:
To make the min
and max
properties optionally configurable when the loan application composite is used, declare the RateComponent
properties to be set by two properties in the LoanApplication
composite using the @source
attribute (see Listing 5.32).
The SCDL in Listing 5.32 effectively allows the default property values defined in the CreditServiceComposite
to be overridden outside the loan application composite.
Often, different developers may be responsible for component implementations that reside within a composite. This can create difficulties, as multiple people need to modify a composite file during development. One solution to this problem is to use composition to break the composite into multiple composites, which are then contained in a common parent composite.
Using composition in this manner can be somewhat tedious, particularly if components in different composites need to be wired together. Because composites provide encapsulation, components in different composites cannot be wired directly. Instead, the source reference and target service would each need to be promoted in their respective composites. Then, the promoted reference would need to be wired to the promoted service in the parent composite. With multiple wires, this can result in a lot of extra configuration that will be difficult to maintain.
In cases where encapsulation is not required but it is helpful to separate a composite into multiple files, SCA supports the ability to include a composite in another. Specifically, inclusion inlines a composite in another. For those familiar with C, inclusion is similar to #include
: including a composite within another merges its contents. Inclusion is done using the <include>
element and setting the @name
attribute to the qualified name of the included composite. Unlike C’s #include
, composite inclusion is not a textual include, because XML concepts like namespace prefix declarations don’t apply across an include (see Listing 5.33).
In the preceding, two composites are inlined into the ParentComposite
. When inlining a composite, its contained components become children of the parent composite. This means that encapsulation is not enforced between two composites included in a common parent. Consequently, it is possible to wire between them without requiring promotion. For example, given CompositeB
:
It is possible to wire to ComponentB
directly from a component contained in CompositeA
:
It is important to note that because inclusion inlines components directly in the parent, the same rules and conventions apply as if the components were all defined in a single composite. In particular, care should be taken to avoid component name clashes.
This chapter illustrated how composition is used to achieve application modularity in SCA. Composition allows services to be built from composites that contain more fine-grained components. Composition fosters modularity in two ways. First, it provides encapsulation by allowing composites to be treated as single components that provide services to clients. Additionally, composition fosters reuse by allowing composites to be configured as component implementations multiple times, potentially using different property values or wiring. With composition in hand, you will be able to create applications that are easier to manage, maintain, and evolve.