Chapter 3. SCA composite applications

This chapter covers

  • Running a composite application in a single process
  • Understanding the SCA domain
  • Running a distributed composite application
  • Using SCA composites as application building blocks

In this chapter we’ll look at how to use SCA and Tuscany to create and run composite applications. As the name suggests, a composite application is an application composed from other things. These other things are the basic SCA artifacts that we looked at in the previous chapter: components, implementations, services, interfaces, references, wires, properties, and bindings. To combine these into a composite application, you’ll need to understand the additional concepts of contributions, composites, domains, and execution nodes, and we’ll take a detailed look at all of these in this chapter using the travel-booking application as an example.

The code samples in this chapter are based on the simplified travel-booking application that we introduced in chapter 1. In some cases the chapter 1 code is used unchanged, and in other cases it’s extended to illustrate specific points. The code in this chapter isn’t used directly in the complete travel application. You can find the sample code for this chapter in the travel sample’s directories contributions/ introducing-tours, contributions/introducing-trips, contributions/introducing-client, and contributions/buildingblocks.

Composite applications are important because any realistic SCA application would contain more than a single component offering a single service. Also, the only thing that you can run in an SCA runtime, such as Tuscany, is an SCA composite. You can run a single component in Tuscany but only by creating and running a composite application that contains a single component. This might sound like an unnecessary complication, but it makes more sense when you consider that SCA is designed to represent applications with more than one component and could be used for applications with as many as hundreds of components. The power of SCA is that the composite application provides a precise and concise mechanism for describing and deploying the assembly of components.

We’ll start by looking at how you can use Tuscany to create and run a composite application within a single process. We’ll go on to explore the SCA domain and show how you can use it to create applications that can be distributed across multiple processes or a network of computers. Finally, we’ll look at how you can make your composite applications easier to change and extend by using SCA composites to organize your components and services into modular assemblies that are easy to compose and recompose when business needs change.

3.1. Running a composite application in a single process

In the previous chapter you saw how to create composites containing your component definitions. The next step is to fire up the application and see how it runs! In this section we’ll show you how to run a composite application within a single process and JVM (known as local execution in SCA terminology). Later in this chapter you’ll see how you can use the SCA domain to run the same application in a distributed execution environment using multiple processes or a network of computers.

There are many ways to run a composite application with Tuscany. For example, the application can be run as a standalone Java main program or within a hosting environment such as a web application server. Chapter 11 gives details of all the possibilities. In this chapter we’ll keep things simple by showing how to run a standalone Java application with Tuscany in local and distributed modes.

To run a local composite application in Tuscany, we’ll need some contributions and some launcher code. We’ll use the contributions and launcher code from the travel-booking application that we used in chapter 1. Figure 3.1 shows the structure of this sample application.

Figure 3.1. The sample composite application consists of three composites and four components, with wires connecting the component references to services in other components.

You can see that the composite application consists of three composites: client. composite, tours.composite, and trips.composite. Each of these composites contains one or more components, and there are wires connecting the component references to services in other components.

3.1.1. Preparing the contributions

The first step in running a composite application is to create contributions containing the code that you want to run. A contribution is an SCA packaging mechanism that can contain executable code, composites, and any other artifacts used by the application such as schema files or WSDL files. In Tuscany, a contribution can be a JAR or zip archive file, a directory structure, or a web application archive (WAR file). Details of how to use WAR files as contributions can be found in chapter 11.

To run the introductory travel-booking application that we’ll use to illustrate this section, you need to install the Tuscany binary distribution and the Tuscany travel sample on your computer, and you’ll need to build the travel sample. You can find full instructions for doing this in appendix A. After you’ve built the travel sample, you can find the code we’re using for this section as the contents of the following directories relative to the top-level travelsample directory:

  • A contribution in the directory contributions/introducing-tours/target/classes containing tours.composite and its implementation code
  • A contribution in the directory contributions/introducing-trips/target/classes containing trips.composite and its implementation code
  • A contribution in the directory contributions/introducing-client/target/ classes containing test client code for components in tours.composite and trips.composite

Let’s look inside the first of these contributions. It contains the following files:

  • A number of .class files for Java implementation class and interface code in directories corresponding to their Java packages. For example, the .class file for the interface com.tuscanyscatours.Bookings is in the com/tuscanyscatours directory.
  • An SCA composite definition in the tours.composite file in the root directory. Composite definitions need to have a .composite file extension; they can be anywhere in the contribution, and they can have any filename preceding the .composite extension.
  • A META-INF directory containing a file named sca-contribution.xml. This file consists of a <contribution> element with child elements declaring the contribution’s exports, imports, and deployable composites.

This contribution doesn’t contain any other files. If other files (such as WSDL or XSD files) are needed, they can be placed anywhere in the contribution’s directory structure.

Contributions can export and import XML namespaces and Java packages. This allows files to be shared between contributions and avoids the need to duplicate the same files in different contributions. Exports and imports are declared in the scacontribution.xml file. The following example shows the sca-contribution.xml file from the introducing-tours contribution.

Listing 3.1. The sca-contribution.xml file from the introducing-tours contribution
<contribution xmlns="http://www.osoa.org/xmlns/sca/1.0"
xmlns:tst="http://tuscanyscatours.com/">
<export.java package="com.tuscanyscatours" />
<deployable composite="tst:Tours" />
</contribution>

The <export.java> element exports the Java package com.tuscanyscatours from this contribution. It’s also possible to export an XML namespace using the <export> element.

To import an exported Java package into another contribution, the importing contribution’s sca-contribution.xml file needs to include an <import.java> element. You can see an example of this in the sca-contribution.xml file from the introducing-client contribution shown here.

Listing 3.2. The sca-contribution.xml file from the introducing-client contribution
<contribution xmlns="http://www.osoa.org/xmlns/sca/1.0"
xmlns:client="http://client.scatours/">
<import.java package="com.tuscanyscatours" />
<deployable composite="client:Client" />
</contribution>

The <import.java> element declares that the com.tuscanyscatours package is imported into this contribution. To import an XML namespace, the <import> element would be used.

We’ve imported the com.tuscanyscatours package so that reference declarations in the introducing-client contribution can use the com.tuscanyscatours.Bookings and com.tuscanyscatours.Checkout interfaces from the introducing-tours contribution. Importing these interfaces means that we don’t need to package separate copies in the introducing-client contribution.

As well as declaring exports and imports, the sca-contribution.xml file lists the deployable composites within the contribution. Each deployable composite is declared using a <deployable> element with a composite attribute specifying the XML qualified name of the composite. You can see in listings 3.1 and 3.2 that the introducing-tours and introducing–client contributions each contain a single deployable composite. You’ll see later in this section how deployable composites are used.

We’ve shown how to prepare contributions for the introductory travel-booking application code. Next we’ll look at how to write a launcher for the application.

3.1.2. Writing the launcher

A launcher is a small piece of code that uses Tuscany APIs to load contributions and run them using a Tuscany execution node. The launcher might also invoke services in the contributions it has loaded. The launcher code is customized for the contributions it loads and the services it invokes. Our launcher needs to load the following contributions:

  • contributions/introducing-tours/target/classes
  • contributions/introducing-trips/target/classes
  • contributions/introducing-client/target/classes

Listing 3.3 shows the launcher code that we’ll use to load these contributions and call a method run() of a service in the test client contribution. You first saw this code in chapter 1, and we’ll describe what it does in more detail here. The code is in the travel sample directory launchers/introducing.

Listing 3.3. Launcher code to load an application’s contributions and call a test method

In listing 3.3 the createSCANode() call creates a local (in-process) execution node for the three contributions passed as arguments, which are returned by the three locate() calls . The first argument for the createSCANode() call is the name of the composite to be deployed. It’s null in this case, which means the node’s execution environment will be constructed by combining all the deployable composites in the contributions. The execution node is returned as an SCANode object, and the launcher starts this node by calling its start() method. Starting the node creates executable runtime components from the component definitions in the deployable composites. The services in these components are now available for invocation, but there’s no code running yet. To run some code, the launcher uses the SCAClient.getService() call to create a proxy for the Runnable service of the TestClient component and calls the proxy’s run() method , which in turn calls the run() method in the TestClient component. When this method returns, the launcher stops the node by calling its stop() method.

 

Warning

If you pass multiple contributions to the createSCANode() method, you need to make sure that any contribution with imports is placed later in the sequence of contributions than the contribution that provides the corresponding exports. This is because of a limitation in the processing of imports and exports in the Tuscany 1.x runtime. In this example the introducing-client contribution imports a Java package from the introducing-tours contribution, so introducing-tours needs to appear before introducing-client.

 

You’ve seen what a launcher does and how to write it. In some simple cases it’s possible to load contributions and create a node without writing any launcher code. See chapter 11 for details of how this works.

3.1.3. Running the launcher

The launcher is just a main() method of a Java class, so it can be run directly from the command line in the usual way. To run the launcher for the sample application, open a command prompt and navigate to the launchers/introducing directory. This directory contains the launcher code from listing 3.3 together with a suitable build.xml file. To run this launcher, enter the command

ant run

This uses the build.xml file in the launchers/introducing directory to run the launcher.

You’ve seen how to use Tuscany to run an application with three contributions in a single process. In the next section you’ll see how the SCA domain supports distributed execution across multiple processes or a network of computers.

3.2. Understanding the SCA domain

The SCA domain contains all the executable code and other related application artifacts used by composite applications. It also includes the execution environment for SCA components and services. In this section we’ll explore how the domain works, and you’ll see how Tuscany makes the domain configuration available to a single execution node or a distributed network of execution nodes. The SCA domain is used for a number of purposes:

  • It acts as a repository for the contributions used by the SCA runtime.
  • It’s the naming and visibility boundary for a distributed SCA runtime.
  • It includes a local or distributed execution environment for SCA components.

In this section we’ll explore these different facets of the domain. We’ll start by looking at how SCA uses contributions to install application code and other related artifacts into the domain. Then we’ll look at how SCA resolves references to named artifacts within the context of the domain. We’ll show how composites are deployed into the domain to make their contents available for execution, and we’ll look at different options for how to use one or more execution nodes within a domain. Finally, we’ll show how Tuscany makes the domain configuration available to execution nodes.

3.2.1. The domain as a contribution repository

The executable code, composite definitions, and any other artifacts needed by a composite application are packaged into one or more SCA contributions. Before these contributions can be used by the SCA runtime, they need to be installed into the SCA domain. The mechanism used for installing contributions depends on the SCA runtime; the domain could be implemented as a repository, a registry, a filesystem, or in any other way. Tuscany installs a contribution into the domain by registering the contribution’s URL and doesn’t do any physical copying.

Installing a contribution into the domain code repository doesn’t automatically make its contents available to the SCA runtime for execution. This requires the further step of deploying composites contained in the installed contributions, as described in section 3.2.3.

Contributions can share selected parts of their contents with other contributions by using import and export declarations for XML namespaces, Java packages, or other artifact types. Figure 3.2 illustrates how artifacts can be packaged in contributions and shared between contributions.

Figure 3.2. Exporting an XML namespace from the payment-bpel-process contribution and importing the same namespace into the payment-bpel contribution

Here the payment-bpel contribution contains a composite payment.composite and some WSDL files that define types used by this composite. The payment-bpel-process contribution contains the file payment.bpel, which defines a BPEL process named Payment in the XML namespace http://tuscanyscatours.com/Payment and also contains some WSDL files that define types used by the BPEL process.

The XML namespace http://tuscanyscatours.com/Payment is exported by the payment-bpel-process contribution and imported by the payment-bpel contribution. This allows the payment-bpel contribution to access XML definitions in this namespace that are packaged in the payment-bpel-process contribution. In this case the BPEL process Payment that’s defined in payment.bpel is referenced by the <implementation.bpel> element in payment.composite. There aren’t any exports or imports for the XML namespaces used by the WSDL files, so the definitions in these files are visible only within their respective contributions.

It’s possible to structure contributions and their exports and imports so that interface definitions used by a service are exported from the service’s contribution and imported into other contributions that contain references wired to the service. This might seem attractive at first, but it’s likely to cause problems because of the tight coupling it creates between the reference and the service. For example, a service with a Java interface might be changed to a different implementation with a compatible service interface in a different Java package or even in a different interface definition language. SCA’s flexible interface matching is designed to ensure the reference still works in these cases, but this would fail if the reference interface were imported from the service’s contribution.

Another option is to package shared interfaces in a separate contribution and import them into all the contributions that need them. This is more flexible than packaging shared interfaces with services, but it can create complex dependency relationships between contributions. Packaging duplicate copies of shared interfaces in all the contributions that use them is the simplest and most flexible approach, but it has the disadvantage of requiring updates to these interfaces to be coordinated across contributions. You’ll need to consider these trade-offs when you choose a packaging approach for your applications.

SCA doesn’t specify how contributions are installed into the domain. In Tuscany there are two mechanisms for doing this, depending on whether the domain is local to a single execution node or distributed across execution nodes. For a local execution node created by calling createSCANode(), as IntroducingLauncher does, the node contains its own domain, and contributions are installed into this domain by passing them as arguments to createSCANode(). For a distributed domain, contributions are installed using the domain manager, as you’ll see in section 3.3.2.

You’ve seen how the domain provides a repository for contributions that include executable code and other artifacts. Now we’ll look at how the domain handles naming and visibility for the things it contains.

3.2.2. The domain as a naming and visibility boundary

The SCA mechanisms for resolving names and accessing artifacts operate only within the scope of a single SCA domain. For example, contribution exports and imports can be resolved only within the same domain. This means that if a contribution named ShoppingCart exports the Java package com.tuscanyscatours.shoppingcart, the classes in this package within the ShoppingCart contribution can’t be imported by an SCA contribution installed into some other domain, because exports and imports don’t work across domain boundaries.

In the same way, SCA components can only be wired within a domain. If a component named TripBooking is deployed into an SCA domain and a reference in this component specifies a target service name of TripProvider/Trips, the reference and service can be wired only if the TripProvider component is deployed into the same SCA domain. If the TripProvider component is deployed into some other SCA domain, TripBooking can’t be wired to TripProvider.

The naming and visibility boundary provided by the SCA domain doesn’t prevent composite applications from using resources that aren’t part of the domain. For example, an SCA component implementation can load Java classes using non-SCA mechanisms such as the Java class path, Java class loaders, or OSGi bundles. Classes loaded in these ways don’t need to be packaged in an SCA contribution or be present in the domain. Also, if an SCA component needs to invoke a service that isn’t in the domain, this isn’t a problem; either the component can use a suitable SCA binding to invoke the service through an SCA reference, or the component’s implementation code can use non-SCA APIs such as Web Services, JMS, or CORBA client APIs to make the invocation. This flexibility makes it easy for new code written using SCA to integrate with existing code that uses other approaches for locating resources and invoking services.

The domain boundary for SCA name resolution helps prevent accidental naming conflicts. For example, you might choose the name TripProvider for a component, and someone else might choose the same name for a different component. SCA component names don’t use XML namespaces, so what’s to stop these two component names from colliding with each other? The answer is the domain boundary. As long as the composites containing the duplicate component names are never deployed into the same domain, there’s no chance of a name collision or of using the wrong component by mistake.

We’ve shown how naming and visibility work in the SCA domain. Next we’ll look at how the SCA domain provides an execution environment (either local or distributed) for running components in the domain either with a single execution node or using a network of communicating execution nodes.

3.2.3. The domain as an execution environment

The execution context provided by the domain is represented as a special composite known as the domain composite. Unlike other composites that are defined statically in .composite files, the contents of the domain composite are maintained by the domain and are updated dynamically as a result of deployment actions.

When a composite is deployed to the domain, all of its contents become part of the domain composite, just as if the deployed composite had been included in the domain composite using an <include> element. Composite inclusion is described later in this chapter in section 3.4.3. Deployment makes the components in the deployed composite available for execution by the SCA runtime and also makes the services and references of these components available for wiring to other references and services in the domain.

SCA doesn’t define how composites are deployed into the domain. In a local Tuscany execution node, the createSCANode() call deploys one or more composites, as discussed in section 2.1.2. For a distributed domain, composites are deployed using the domain manager, as we’ll describe in section 3.3.3.

As an example, suppose that the TripBooking and TripProvider components are defined as follows:

<component name="TripProvider">
<implementation.java
class="com.goodvaluetrips.impl.TripProviderImpl" />
<service name="Trips" />
</component>

<component name="TripBooking">
<implementation.java
class="com.tuscanyscatours.impl.TripBookingImpl" />
<service name="Bookings" />
<reference name="mytrips" target="TripProvider/Trips" />
</component>

When the TripProvider component is deployed to the domain, the Trips service of TripProvider becomes available for wiring. If the TripBooking component is then deployed to the domain, the Tuscany runtime wires the mytrips reference of TripBooking to the Trips service of TripProvider, and the Bookings service of TripBooking is available for wiring.

 

How big is a domain?

An SCA domain can be as big or as small as you want it to be. The domain’s execution context can be shared by multiple processes on a computer or by multiple computers across a network. At the other end of the spectrum, Tuscany supports running one or more domains entirely within a single process and JVM. This flexibility in the scope of a domain enables SCA and Tuscany to meet a variety of application deployment needs ranging from small embedded systems all the way up to distributed enterprise environments.

 

Composites deployed into the domain are executed by the Tuscany runtime in one of the following ways:

  • Local domain The entire contents of the domain composite are used exclusively by a single execution node, with the node and domain running together locally within a single process. Other execution nodes belonging to different domains can also be running in the same process and JVM. You saw how to run Tuscany as a local domain in section 3.1.
  • Distributed domain Each composite deployed into the domain is assigned to its own execution node. This means that the components and services in the domain are distributed across multiple execution nodes. Communication between the execution nodes is coordinated by the Tuscany domain manager. The execution nodes and domain manager can run in different processes on a single computer, or they can run on different computers across a network. The nodes use the domain manager at startup time to obtain network addressing information for services they provide and services they use. You’ll see how to use the domain manager and how to run a Tuscany distributed domain in section 3.3.

Figure 3.3 illustrates these two kinds of domain configuration and shows how the same domain contents can be executed in either a local or distributed manner. In this figure the local domain contains a single execution node that runs the entire contents of the domain, and the components communicate using direct calls within a single JVM. The distributed domain uses the domain manager to allocate the components in the domain to three execution nodes, with remote network invocations between these nodes. The component implementations and component definitions are the same in both cases.

Figure 3.3. A local domain with a single execution node and a distributed domain with a domain manager and multiple execution nodes. The contents of the domain are the same in both cases.

In the rest of this section we’ll look at both of these approaches in more detail and see how they affect the ways that services are invoked and configured.

3.2.4. Using a single execution node with a local domain

A common case is for the entire SCA domain to run within a single execution node as a local domain. We showed how to do this in section 3.1. Because all the components in the domain are running within a single process and JVM, the Tuscany runtime automatically uses optimized direct calls for references and services that are wired using binding.sca. For other bindings that communicate using network endpoints (such as binding.ws), the binding elements in the deployed composite need to specify the necessary network endpoint information. The Tuscany domain manager isn’t used in this scenario.

As an example, let’s look at three components: TripBooking, ShoppingCart, and TripProvider. We’ll also be referring to these components later when we talk about distributed domains. The deployed composites containing these component definitions are shown here.

Listing 3.4. Deployed composites containing component definitions
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0"
targetNamespace="http://tuscanyscatours.com/"
name="Tours">
<component name="TripBooking">
<implementation.java
class="com.tuscanyscatours.impl.TripBookingImpl" />
<service name="Bookings" />
<reference name="mytrips" target="TripProvider/Trips" />
<reference name="cart" target="ShoppingCart/Updates" />
</component>

<component name="ShoppingCart">
<implementation.java
class="com.tuscanyscatours.impl.ShoppingCartImpl" />
<service name="Checkout" />
<service name="Updates" />
</component>
</composite>

<composite xmlns="http://www.osoa.org/xmlns/sca/1.0"
targetNamespace="http://goodvaluetrips.com/"
name="Trips">
<component name="TripProvider">
<implementation.java
class="com.goodvaluetrips.impl.TripProviderImpl" />
<service name="Trips" />
</component>
</composite>

The references in TripBooking are wired to services in TripProvider and ShoppingCart using binding.sca. If all these components are running in the same execution node and TripBooking calls TripProvider using the mytrips reference, the Tuscany runtime will invoke the TripProvider code by a direct call within the same JVM. The same thing will happen when TripBooking calls ShoppingCart using the cart reference.

You’ve seen how running a local domain allows Tuscany to make direct calls for service invocations across wires that use binding.sca. Next you’ll see how the same service invocations are handled when using a distributed domain with the Tuscany domain manager.

3.2.5. Distributed execution within a domain

SCA is designed to support distributed execution across multiple processes or computers within the same SCA domain. In Tuscany, this setup is represented by a number of execution nodes and a separate domain manager. The network endpoints of all the execution nodes are registered with the domain manager, and the nodes use the domain manager to obtain network endpoint information for their services and references. The details of how to register network endpoint information with the domain manager are provided later in this chapter in section 3.3.4.

It’s important to point out that the Tuscany domain manager isn’t a distributed runtime for executing SCA components. These components are executed by the nodes to which they have been assigned, and the nodes execute without any interaction with the domain after their initial setup. Also, the domain manager doesn’t make decisions on which components should be executed by which nodes. The domain manager acts as a registry that holds information about execution node assignments and endpoint configurations and provides this information to execution nodes when requested.

As an example, we’ll take the same three components—TripBooking, ShoppingCart, and TripProvider—that we used in listing 3.4. When running these components in a distributed configuration using the domain manager, the same deployed composites and component definitions are used. These component definitions don’t say anything about the execution-time locations of TripBooking, ShoppingCart, and TripProvider.

In this example let’s suppose that we want to run the deployed composite Tours containing the TripBooking and ShoppingCart components on a computer with host-name tuscanyscatours.com and port number 8081, and run the deployed composite Trips containing the TripProvider component on another computer with host-name goodvaluetrips.com and port number 8082. Figure 3.4 illustrates this configuration.

Figure 3.4. Running the deployed composites Tours and Trips on two computers with hostnames tuscanyscatours.com and goodvaluetrips.com

With the distributed configuration shown in figure 3.4, the Tuscany runtime can’t use optimized direct calls when TripBooking invokes TripProvider, because these components are running on different computers across a network. Instead, the runtime for TripBooking needs to make these calls by sending network requests to a remote endpoint that the runtime for TripProvider has exposed.

Tuscany handles this by having each execution node contact the domain manager to get a modified version of the node’s deployed composite with resolved network endpoint information for the component services and references. The endpoint information for each node needs to have been registered previously with the domain manager, and we’ll show how to do this in section 3.3.4. When the node’s start() method is called, it contacts the domain manager to get the modified deployed composite for the node. For the execution node for tuscanyscatours.com, the component definitions in this modified composite are as follows:

<component name="TripBooking">
<implementation.java
class="com.tuscanyscatours.impl.TripBookingImpl" />
<service name="Bookings">
<binding.sca uri="http://tuscanyscatours.com:8081/TripBooking" />
</service>
<reference name="mytrips">
<binding.sca uri="http://goodvaluetrips.com:8082/TripProvider" />
</reference>
<reference name="cart">
<binding.sca uri="/ShoppingCart/Updates" />
</reference>
</component>

<component name="ShoppingCart">
<implementation.java
class="com.tuscanyscatours.impl.ShoppingCartImpl" />
<service name="Checkout">
<binding.sca uri=
"http://tuscanyscatours.com:8081/ShoppingCart/Checkout" />
</service>
<service name="Updates">
<binding.sca uri="/ShoppingCart/Updates" />
</service>
</component>

These modified component definitions are the same as the component definitions in listing 3.4, with the addition of binding URIs for all the services and references. These URIs are either network endpoint URIs or local URIs, depending on whether remote or local invocations are required. In this example the Bookings service, the Checkout service, and the mytrips reference have network endpoint URIs because they need to use remote invocation, and the Updates service and the cart reference have local URIs because they’re running in the same process and can use the optimized invocation path for binding.sca.

The other execution node running on goodvaluetrips.com gets the following modified component definition from the domain manager:

<component name="TripProvider">
<implementation.java
class="com.goodvaluetrips.impl.TripProviderImpl" />
<service name="Trips">
<binding.sca uri="http://goodvaluetrips.com:8082/TripProvider" />
</service>
</component>

The Trips service in this component has a network endpoint URI so that it can receive remote invocations from the TripBooking component.

The domain manager can also hold network endpoint configuration for binding types other than binding.sca, such as binding.ws. To use the domain manager with binding.ws, we could change the component definitions in the deployed composites by adding empty <binding.ws/> elements on the Bookings service, the mytrips reference, and the Trips service. We’d also need to add endpoint configuration for binding.ws to the domain manager registry. With these changes, the domain manager will add the correct network endpoint information to the <binding.ws/> elements in the modified component definitions that it sends to the execution nodes.

It’s sometimes necessary to change the network endpoint of an execution node after it’s been started and is running services. After the necessary changes have been made to the domain manager configuration, the affected node needs to be restarted so that it reloads its network endpoint information from the domain manager. It’s also necessary to stop and restart all other nodes that use services running in the restarted node, so that the endpoint information in these other nodes for references to the updated node can be refreshed.

You’ve seen what the SCA domain provides and the different ways it can be used when running composite applications. A single execution node can act as an SCA domain, or the Tuscany domain manager can be used to control a domain involving many execution nodes. Where more than one execution node is running in the SCA domain, these execution nodes can be running in the same JVM, in different JVMs on the same computer, or on different computers. Regardless of the physical configuration of execution nodes, the SCA domain is doing the same job of providing a repository of contributions, defining the visibility boundary for contributed artifacts and SCA component names, and providing fully configured composite definitions to execution nodes.

In section 3.1 we showed how you can run a composite application with three contributions using a single process. In the next section we’ll show how you can run the same three contributions in a distributed execution environment using the Tuscany domain manager.

3.3. Running a distributed composite application

To run a distributed composite application in Tuscany, a number of steps are needed. In this section we’ll go through these steps in the order in which you need to do them, using the same sample application that we used in section 3.1.

Figure 3.5 shows this application running in a distributed configuration. This figure shows the same composites and components that you saw in figure 3.1. In the distributed configuration these composites and components have been allocated to the execution nodes ClientNode, ToursNode, and TripsNode. These execution nodes are running as separate processes, and they’re coordinated by the Tuscany domain manager.

Figure 3.5. The sample application running in a distributed configuration, with three execution nodes coordinated by a separate domain manager

To run this distributed application, we’ll begin by creating an empty SCA domain using the Tuscany domain manager. Next we’ll install the contributions for our application into the domain. Then we’ll need to deploy the composites containing the executable components for the application. After this, we can define the location and binding information for the execution nodes that will run the deployed composites. The final step is to run the distributed application by creating execution nodes that load and run the deployed composites.

3.3.1. Creating an SCA domain

In Tuscany the state of the SCA domain is maintained by the domain manager, which usually runs as a separate process. This process can be started using the following command line:

java –cp <tuscany-dir>/lib/tuscany-sca-manifest.jar org.apache.tuscany.sca.
node.launcher.DomainManagerLauncher

You must enter this command as a single line, replacing <tuscany-dir> with the installation directory for the Tuscany binary distribution. This is quite a long command to type, so the travel sample includes Ant scripts to do this where needed. It’s also possible to use Tuscany APIs to run the domain manager within an existing process.

When the domain manager starts up, it looks to see if the current working directory contains a saved domain configuration. The domain configuration files are described in chapter 11. If there’s a saved configuration, the domain manager uses this configuration for the initial state of the domain. If no configuration files are found, the domain manager creates an empty domain. In this example we’re showing the complete process of building a domain from scratch, so you should start the domain manager from a directory that doesn’t contain a domain configuration. In the travel sample code tree there’s a directory called testdomain, which is provided for this purpose, and in the rest of this section we’ll assume you’ve started the domain manager from this directory. You can do this by opening a new command prompt, navigating to the testdomain directory, and typing ant run to start the domain manager.

If anything goes wrong while you’re going through this example, you might want to delete your domain configuration and make a clean start. To do this, you can stop the domain manager by typing q, and then run either ant clean or mvn clean from the testdomain directory.

When the domain manager has loaded the startup configuration, it starts an HTTP listener on port 9990. This port is used to provide a browser-based user interface (the Domain Manager GUI), which you can access at the URL http://<domainhost>:9990/ui/workspace where <domainhost> is the hostname of the computer running the domain manager.

 

Warning

The Domain Manager GUI has compatibility issues with some browsers. It works in Firefox, Safari, and Chrome but has problems when running in Internet Explorer and Opera.

 

The domain manager also supports programmatic access to domain resources using HTTP GET and POST requests. If you want to use this advanced capability, you can find some information in the article “Deploy an SCA application using the Tuscany domain manager” listed on the Books and Articles page on the Tuscany website, and you can refer to the Tuscany source code for full details of how to use this protocol. All interactions with the domain manager (either from a browser or using the API) are by HTTP requests to its listener port.

3.3.2. Installing contributions into the domain

You can use the Domain Manager GUI to install new contributions into the domain and to manage installed contributions. To do this, navigate to the Contributions page by browsing to http://<domainhost>:9990/ui/workspace, where <domainhost> is the hostname of the computer running the domain manager, or click the Contributions link from any page in the Domain Manager GUI.

The Contributions page displays a list of the contributions already installed in the domain. If the domain manager is started from a directory without a saved domain configuration, this list will be empty. To install a new contribution into the domain, click the Add link of the Contributions page. This brings up an Add Contribution section with entry fields to enter the contribution URI and the contribution location.

To run the travel-booking application you need to install three contributions. Let’s start with the introducing-tours contribution. The contribution URI is an identifier of your choice that will uniquely identify the installed contribution within the domain. To keep life simple, let’s use introducing-tours as the URI.

For the contribution location you can use a URL (for example, an http: URL), or you can use a file path if the domain manager and the execution nodes are running as different processes on the same computer. The file path can be absolute, or it can be a relative path from the directory that you used to launch the Domain Manager GUI. The contribution URL or file path can point to a JAR or zip file or to a directory.

If you started the domain manager from the testdomain directory, you can use the location file path ../contributions/introducing-tours/target/scatours-contribution-introducing-tours.jar. This relative file path works because you’re going to run the execution nodes as different processes on the same computer. Figure 3.6 shows the result of entering this URI and location in the Add Contribution section.

Figure 3.6. Adding a contribution to the domain using the Contributions page of the Domain Manager GUI

After entering the URI and location, click the Add button to install the contribution into the domain. For the second contribution, you can use the URI introducing-trips and path ../contributions/introducing-trips/target/scatours-contribution-introducing-trips.jar. The final contribution is the test client, with the URI introducing-client and the path ../contributions/introducing-client/target/scatours-contribution-introducing-client.jar. When you’ve finished adding contributions, you can hide the Add Contribution section of the page by clicking the Add link (not the Add button).

Figure 3.7 shows how the Contributions page will look after installing all three contributions for the introductory travel-booking sample application. The Contribution column lists the URIs of all the installed contributions. The Deployable Composites column lists the XML qualified names of the deployable composites in each contribution, with the namespace and local name separated by a semicolon. The Dependencies column shows which contributions depend on other contributions, based on their imports and exports. In this example, the introducing-client contribution imports the Java package com.tuscanyscatours, which is exported by the introducing-tours contribution.

Figure 3.7. The Contributions page of the Domain Manager GUI after all the contributions for the introductory travel-booking application have been installed in the domain

The domain manager checks the contribution dependency relationships and reports any errors found by displaying error messages in the Dependencies column. For example, a contribution dependency might not be found in the domain. This could be caused by an incorrect import or export declaration or because the contribution containing the export hasn’t yet been installed in the domain.

3.3.3. Deploying composites for execution

Before any components can be executed in the domain, the composites containing the components need to be deployed. Deploying a composite adds it to the domain’s distributed execution environment and makes it available to run on an execution node. To deploy a composite using the Domain Manager GUI, you’ll need to display the Composites page of the Domain Manager GUI by clicking the Composites link from any other page or by browsing directly to http://<domainhost>:9990/ui/composite, where <domainhost> is the hostname of the computer running the domain manager.

The Composites page displays a list of the composites that have been deployed for execution. Installing a contribution doesn’t automatically deploy any composites within the contribution, so this list will be empty at first. To deploy a composite, click the Add link on the Composites page to reveal an Add Composite section containing entry fields for the composite namespace, the composite name, and the URI of the contribution containing the composite. There’s no need to type anything into these fields, because you can click inside each field to see a selection prompt with all the possible choices for the deployable composites that are installed. Figure 3.8 shows the values you need to enter to deploy the Tours composite.

Figure 3.8. The Composites page of the Domain Manager GUI showing the contents of the entry fields for deploying a composite to the domain

Note that composites are identified by their XML names (namespace and local part), not by their filenames. After clicking the Add button to deploy the Tours composite, you can repeat the same procedure to deploy the Trips and Client composites. After clicking the Add link to remove the Add Composite section, the Composites page should look like figure 3.9, with the qualified XML name of each deployed composite, the URI of the contribution containing the composite, and the names of the components in the composite.

Figure 3.9. The Composites page of the Domain Manager GUI after all the composites in the application have been deployed

Now we’re ready to assign the deployed composites to execution nodes that will run the executable code for the component implementations in the composites.

3.3.4. Assigning composites to execution nodes

The final step before running our distributed application is to tell the domain manager about the execution nodes that will run the deployed composites. To do this, you need to bring up the Cloud page of the Domain Manager GUI by clicking the Cloud link at the top of any other page or entering the URL http://<domainhost>:9990/ui/cloud, where <domainhost> is the hostname of the computer running the domain manager.

To add an execution node to the domain configuration, click the Add link on the Cloud page. This displays an Add a Node section with fields for the node information and the composite that will run on the node, as shown in figure 3.10.

Figure 3.10. The Cloud page of the Domain Manager GUI showing an example of what would be entered to add an execution node for a deployed composite

The node name can be any name that you choose to identify this execution node. The node URI contains the protocol, hostname, and port number that the node’s runtime will use to listen for service requests. In this example we’ll use http: as the protocol, localhost as the hostname (the nodes will run as separate processes on the same computer), and 8081, 8082, and 8083 as the port numbers for the Tours, Trips, and Client composites respectively. The Composite namespace, Composite name, and Contribution URI fields identify the deployed composite that will be run by this node. Instead of typing into these fields, you can click inside them to get selection prompts for all deployed composites.

 

Node with multiple protocols or listener ports

The Add a Node section of the Cloud page assumes that the node uses the same protocol, hostname, and port number for all the services offered by the deployed composite. For composites with a number of different types of service bindings, this limitation may be a problem. In such cases you can edit the domain manager configuration files directly to specify different listener protocols or ports for different service bindings, as described in chapter 11.

 

Clicking the Add button adds the ToursNode execution node to the domain configuration. In the same way you can define the TripsNode and ClientNode execution nodes for the Trips and Client composites. After you click the Add link to remove the Add a Node section, the Cloud page should look like figure 3.11.

Figure 3.11. The Cloud page of the Domain Manager GUI after defining all the execution nodes

This figure shows the name of each node, the name of the deployed composite that will run on the node, and the URI of the contribution containing the composite. All the nodes are shown as stopped in the Status column because the domain manager hasn’t been told to start them. The Node Config column contains symbols representing Atom feeds that are used by each node’s runtime to get its startup configuration. If you click this symbol and then click the first entry in the feed, you’ll see the domain manager’s version of the deployed composite (with fully resolved absolute binding URIs) that will be sent to the execution node.

The Cloud page has a Start link that can be used to start execution nodes within the domain manager process. Don’t try this at home! It was added to the domain manager user interface as a testing convenience; however, execution nodes are best run in some other process so that any failure doesn’t bring down the domain manager or compromise its integrity. If you don’t use the Start link, the Status column will always show the nodes as stopped, even if they’ve been started in other processes. We’re ready to do this now.

3.3.5. Creating and starting execution nodes

The components within an SCA domain run on one or more execution nodes. The SCA specifications leave it up to implementations to define APIs for creating execution nodes and giving them components to run. In Tuscany these capabilities are provided by the SCANodeFactory class. Tuscany also provides a standalone node launcher that can be run from the command line. You can run the node launcher by entering the following command:

java –cp <tuscany-dir>/lib/tuscany-sca-manifest.jar org.apache.tuscany.sca.
node.launcher.NodeLauncher http://<domainhost>:9990/node-config/<node>

You must enter this command as a single line, replacing <tuscany-dir> with your installation directory for the Tuscany binary distribution. You also need to replace <domainhost> with the hostname of the computer running the domain manager and replace <node> with the node name you used when creating the node in the Domain Manager GUI (for example, ToursNode). To avoid the need to type all this, the travel sample includes Ant scripts for running this command where it’s needed (that is, in the launchers/introducing-tours and launchers/introducing-trips directories).

The http: URI argument passed to the NodeLauncher class is the URI of an Atom feed that the node launcher uses to obtain the node’s configuration from the domain manager. For example, if the domain manager is running in a different process on the same computer as the node, the URI of the Atom feed for the ToursNode configuration would be

http://localhost:9990/node-config/ToursNode

You can open this as a URL in your browser if you’re interested in what information is sent to the node by the domain manager. This can be useful to debug problems if the node doesn’t start correctly for some reason.

The Tuscany node launcher reads the information from the Atom feed and uses this to configure and start the node, and then waits at a command prompt. The services running on the node are now available and ready to handle method invocations.

In this example, we have two nodes running services for our application: ToursNode and TripsNode. For convenience, the sample code has launcher directories for these nodes with Ant scripts to start the nodes. To run the launcher for ToursNode, you can open a new command prompt, navigate to the launchers/introducing-tours directory, and type ant run to run the launcher command. The node startup should complete successfully and wait for input from the command line. The next step is to open another new command prompt and type ant run in the launchers/introducing-trips directory.

The application’s services are now ready for action! To run them we’ll need some client code that will invoke these services. It’s good practice to use separate contributions for business service code and test client code, so we’ve created another contribution named introducing-client containing our test client code. To run this client code, we’ll need to write a small launcher program, which uses the SCANodeFactory API to create a test client node and then invokes a service method on this node. The test node runs in the same process as the launcher code. The following listing shows this launcher program.

Listing 3.5. A simple launcher that creates a test client node and runs some test code

The code in listing 3.5 is similar to the launcher code that you saw in listing 3.3. Here the launcher code creates a client node using the createSCANodeFromURL() method of the SCANodeFactory class. The SCANode object returned by this call represents an execution node that will run in the same process as the launcher code. The launcher calls the node object’s start() method to start the node. Next the launcher code uses the SCAClient.getService() method to get a proxy for the Runnable service of the TestClient component and calls the proxy to run the test code. When the test code returns, the launcher calls the node’s stop() method to stop the node’s services.

To run this test client launcher, open a new command prompt, navigate to the launcher/introducing-client directory, and type ant run. The launcher will run the test client code on the ClientNode node in one process. This code will call services in the Tours composite running on the ToursNode node in another process, which will in turn call services in the Trips composite running on the TripsNode node in a third process.

In this example we’ve run three execution nodes and the domain manager in different processes on a single computer, as shown in figure 3.5. To run these on four different computers in a network, you’d only need to change the node URI settings in the domain manager’s Cloud configuration and the configuration URIs passed to the nodes at startup. You wouldn’t need to make any changes to the application contributions or to any other domain configuration settings. This ability to quickly and easily change the deployment configuration in a distributed runtime is a key feature of Tuscany and SCA and is made possible by SCA’s clean separation between business service configuration and runtime deployment configuration.

3.3.6. Running the domain manager from a saved configuration

Congratulations! You’ve created an SCA domain from scratch using the Tuscany Domain Manager GUI, and you’ve run a distributed composite application within your domain. Now it’s time to take a well-earned break from Tuscany and do something completely different for a while before coming back to continue your Tuscany domain adventures. When you fire up the domain manager again, you probably won’t want to repeat all the same steps to set up your domain configuration from scratch using the Domain Manager GUI. The good news is that there’s no need to repeat anything you did previously because the domain manager automatically saves the current domain configuration to disk whenever you do anything in the Domain Manager GUI, and it restores this saved configuration the next time it’s started.

The domain configuration is saved as a number of XML files. By default, these files are stored in the working directory from which the domain manager was started. The next time you start the domain manager from the same directory, it will automatically find the previously saved configuration and use it to restore the domain to its previous state.

If you’re configuring the domain using the Domain Manager GUI or if you’re restoring a previously saved domain configuration, there’s no need for you to understand the contents of the files that the domain manager uses to save the domain configuration. However, you might want to modify an existing domain configuration by directly editing these configuration files, or you might want to create a predefined domain configuration for the domain manager to use when it’s started. To do these things you’ll need to understand what the domain manager configuration files contain, and you’ll find a full description of this in chapter 11.

We’ve looked at how you can run a composite application using either a local configuration or a distributed configuration. In both of these cases, our example application has been made up of components with self-contained implementations and SCA wiring to connect them together. For some applications, this simple, flat structure with custom-built components is fine; in other cases, it’s worth doing a bit of extra work to design parts of the application as reusable building blocks that can also be used in other applications. In the next section we’ll show you how to create these reusable building blocks using SCA composites.

3.4. Using SCA composites as application building blocks

You’ve seen how SCA composites are used to contain component definitions for deployment into the SCA domain. SCA composites have another important use as building blocks in the internal structure of a composite application, as you’ll see in this section.

Conventional applications have a hierarchical structure with a top-level layer of code that’s built specifically for the application and isn’t designed to be reused. Lower layers often use reusable libraries. In a composite application this hierarchical approach is replaced by a network of service connections between components. New components are created by combining existing components in different ways, and new services are created by wrapping and extending existing services. The resulting application is a loose federation of software components and services, organized to perform a particular task. These components and services are expected to be reused in ways that their original authors didn’t anticipate, so they’re designed to be as flexible and configurable as possible, and they make no assumptions about running in a specific environment.

An example of a composite application is the travel-booking application that we use in this book. Some components and services such as those that handle payments or manage the contents of a shopping cart aren’t specific to travel booking and have been designed to avoid making travel-specific assumptions that would prevent reusing them for other purposes. Other services such as hotel reservation can either be consumed directly by a customer or used to compose other services such as booking a packaged trip with flights, hotel, rental car, and other activities. The result isn’t a single specialized application in the traditional sense but is more like a collection of many different applications that can be used for a variety of purposes and extended further to meet additional needs.

To implement this kind of composite application we’ll need some reusable building blocks that can be composed and recomposed in many different ways. In SCA, these building blocks are implemented as composites. We’ve already shown how SCA composites are used to define components that will be deployed into the domain. In this section we’ll complete the picture by showing how to use composites to create building blocks for composite applications. We’ll begin by looking at using composites as component implementations, and then we’ll show how a composite can include other composites. By providing these different ways to use composites as building blocks within your applications, SCA gives you the flexibility to choose the best approach for each application.

3.4.1. Different ways of using SCA composites

SCA composites can be used as component implementations. When used in this way, a composite contains an assembly of components, which together form a higher-level component that acts as a black box for what’s inside. For example, a computer storage drive can be manufactured from rotating platters (a hard disk drive) or from solid-state memory (a solid-state disk) together with other components such as a read/write cache and firmware. The drive is itself a component that can be used in higher-level assemblies such as a personal computer or network-attached storage. When plugging this drive into a computer, the other components in the system don’t need to know whether the internal technology of the drive is hard disk or solid state, because this doesn’t affect how other system components use the drive.

SCA composites can also be used by inclusion within other composites. When used in this way, a composite is a white-box container for the things it contains. For example, a self-assembly electronics kit for building an amplifier might contain transistors, resistors, capacitors, controls, and a display. Some kits are designed to provide choices between different design alternatives that require the kit components to be connected in different ways. The person assembling the kit needs to choose one of the alternatives and connect the components in the right way to produce the desired result.

Both of these approaches have their advantages. The black-box approach is more disciplined because the composite needs to be designed with a formal contract for how it can be used as a component implementation. The white-box approach is more flexible because there are no limitations on what the composite can contain, and the composite’s internal structure is fully exposed.

When you create a composite, the composite definition doesn’t say how the composite will be used. If the composite is designed with a formal contract, it can be used either as a component implementation or by inclusion in another composite. If the composite doesn’t have a formal contract, it can be used only by inclusion in another composite. In the rest of this section we’ll show you how to do both of these.

SCA composites are also used as containers for deploying components and wires. This is a special case of reuse by inclusion, where the deployed composites are included in the domain composite provided by the SCA runtime, as described in section 3.2.3.

3.4.2. Using composites as component implementations

You can use a composite as the implementation of a component. To do this, you need to define the services, references, and properties of the composite, which together make up the component type of the composite implementation. Figure 3.12 shows an example of this in diagrammatic form.

Figure 3.12. The Tours composite can be used as a component implementation, with composite services BookTrip and Checkout, a composite reference trips, and a composite property currency.

In this figure, some services, references, and properties are attached to the edges of the lighter outer box representing the Tours composite. These are known as composite services, composite references, and composite properties to distinguish them from the component services, component references, and component properties that are attached to the darker boxes representing the TripBooking and ShoppingCart components. Composite services, references, and properties are visible outside the composite when the composite is used as a component implementation.

The composite services and references in figure 3.12 are shown connected to component services and references using dashed lines. These component services and references will be exposed outside the composite when the composite is used as a component implementation. Exposing a component service or reference in this way is described as promoting it.

An example of a promoted service is the Bookings service of the TripBooking component, which is promoted by the BookTrip composite service. This means that invoking the BookTrip service from outside the composite will actually invoke the Bookings service and execute code in the com.tuscanyscatours.impl.TripBookingImpl implementation class. Similarly, the trips composite reference promotes the mytrips component reference, which means that wiring a service to trips will have the effect of wiring it to mytrips. Any component services and references that aren’t promoted aren’t exposed by the composite implementation.

Composite services and references can have different names from their promoted component services and references, or they can have the same names. For example, the composite service for the promoted service Checkout is also called Checkout. There’s no ambiguity because these names are used in different contexts.

Composite properties don’t use promotion. Instead, component properties can be configured to get their values from composite properties. This relationship is shown in figure 3.12 by the dashed line connecting the currency composite property to the currency component property.

A composite used as a component implementation needs to have a complete external contract. For example, it can’t contain a component with a reference of multiplicity 1..1 or 1..n that isn’t either wired within the composite or promoted by the composite. An unpromoted reference isn’t visible outside the composite, so it wouldn’t be possible to satisfy the reference’s multiplicity requirement by wiring it to a service.

The next listing shows how to write an XML definition for the Tours composite pictured in figure 3.12. You can find this composite in the travel sample’s contributions/ buildingblocks directory as the file tours.composite.

Listing 3.6. Creating a composite for use as a component implementation

In listing 3.6 the <composite> element contains <service>, <reference>, and <property> elements directly within the composite and outside the component definitions that are also part of the composite. These elements define the composite services, composite references, and composite properties that are exposed by the Tours composite.

Each composite <service> and <reference> element has a promote attribute that identifies a component service or reference that will be exposed outside the composite when the composite is used as a component implementation. For example, the <service> element for BookTrip has a promote attribute with a value of "TripBooking/Bookings", which means that the Bookings component service is visible externally as the BookTrip composite service. The Updates component service isn’t promoted, which means it’s visible only within the Tours composite.

There’s no promote attribute for composite properties. Instead, the component property gets its value from a composite property by naming a composite property in the source attribute of the <property> element using an XPath expression. For example, the currency property of the ShoppingCart component has a source attribute of $currency, which means it gets its value from the currency composite property. This is different from service and reference promotion because the relationship between the component property and the composite property is defined within the component and not at the composite level. The composite property definition specifies a default value of "USD", which means that the component property will have this value unless it’s overridden in the definition of the higher-level component that uses the composite as its implementation.

Next we’ll look at how to create a component that uses the Tours composite as its component implementation. In figure 3.13 the MyTours component has the Tours composite as its implementation. The component services, references, and properties of the MyTours component are the same as the composite services, references, and properties of the Tours composite that you saw in figure 3.12. The ToursImpl composite also contains another component TripProvider, and the trips reference of the MyTours component is wired to the Trips service of the TripProvider component.

Figure 3.13. The Tours composite is used as the implementation of the MyTours component in the ToursImpl composite.

The next listing shows the XML definition for the ToursImpl composite pictured in figure 3.13. You can find this composite in the travel sample’s contributions/building-blocks directory as the file tours-impl.composite.

Listing 3.7. Using a composite as a component implementation
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0"
targetNamespace="http://tuscanyscatours.com/"
xmlns:bb="http://bb.tuscanyscatours.com/"
name="ToursImpl">

<component name="MyTours">
<implementation.composite name="bb:Tours" />
<reference name="trips" target="TripProvider/Trips" />
<property name="currency">GBP</property>
</component>

<component name="TripProvider">
<implementation.java
class="scatours.impl.TripProviderImpl" />
</component>
</composite>

The implementation type of the MyTours component is implementation.composite with the XML qualified name of the Tours composite from listing 3.6. This means that the composite services, references, and properties of the Tours composite are used as the component services, references, and properties of the MyTours component.

As with other implementation types, the component definition can customize the services, references, and properties of its implementation. In this example the MyTours component has its trips reference wired to the Trips service of the TripProvider component. The currency property of the MyTours component is set to the value "GBP", which overrides the default value of "USD" defined in the Tours composite. The TripProvider component used here is a scaffolded test version, which doesn’t contain the production implementation of the Trips service.

You’ve seen what you can do with the black-box style of reuse, where composites are used as component implementations. Next we’ll look at the white-box approach of including composites in other composites.

3.4.3. Including composites in other composites

Another way to reuse composites is by including them in other composites. This is equivalent to the include facilities in many programming languages, which bring in the contents of a separate file and treat the contents as if they had been placed inline.

We’ll use the previous example to illustrate the value of composite inclusion. When we’ve finished testing the MyTours component, we’d like to connect it to the production version of the TripProvider component. This component is part of an existing composite named Trips in the trips.composite file, which you first saw in listing 1.3. We could copy the production TripProvider component definition from the Trips composite into the ToursImpl composite, but there’s a better way to handle this. By using composite inclusion, we can reuse the existing Tours composite directly, as shown in the following ToursImplInclude composite. You can find this composite in the travel sample’s contributions/buildingblocks directory as the file tours-implinclude.composite.

Listing 3.8. Including a composite in another composite

In listing 3.8 the ToursImplInclude composite contains an <include> element for the Trips composite. This <include> element is replaced by the entire contents of the Trips composite, except for its outer <composite> element, as shown in the following listing.

Listing 3.9. Result of including a composite

Listing 3.9 shows the result of the <include> directive in listing 3.8. The component definition for TripProvider from the Trips composite has been added to the ToursImplInclude composite in place of the <include> directive.

When using composite inclusion, the included composite doesn’t need to have a complete external contract. For example, a component service defined in one included composite can be used as the target of a component reference defined in another included composite. It’s also possible to include a composite that contains only wires, where the sources and targets of these wires are defined in other included composites.

3.4.4. Composite reuse in action

Composite implementations can be reused in any number of components, just like other implementation types such as implementation.java. For example, we can create components WSTours and JMSTours that expose the TripBook and Checkout services using Web Services and JMS bindings, as shown in the following listing. You can find this composite in the travel sample’s contributions/buildingblocks directory as the file tours-appl.composite.

Listing 3.10. Reusing a composite implementation in different components

There are a few interesting things going on in listing 3.10. The WSTours and JMSTours components both use the Tours composite from the tours.composite file as their implementations , with different customizations of the composite’s exposed services and references. WSTours uses binding.ws to expose the services as web service endpoints , and JMSTours uses binding.jms to expose them as JMS endpoints . There’s also a difference in how the components wire the trips reference. In WSTours this reference is wired to the Trips service of the TripProvider component (implemented by the GoodValueTrips service provider and reused here by inclusion). In JMSTours the same reference is wired to a different service, Tours , in the TourProvider component from the BudgetTours service provider. The most interesting thing of all is that we’ve built this composite application by writing a single XML file without any new or changed implementation code.

 

Life, the universe, and composite applications

Now that we’ve shown how you can build a fully-fledged composite application using composites as building blocks, let’s look at the main features that make it a composite application and not just an application.

First, the application is modular and componentized. The benefits of this are well known, so we don’t need to dwell on them here. The modular parts (building blocks) of the ToursAppl application are the ToursAppl composite, the Tours composite, and the Java implementation classes TripBookingImpl, ShoppingCartImpl, TripProviderImpl, and TourProviderImpl. Each of these provides a contract through its services, references, and properties, and its internal implementation can be changed without affecting its external contract.

Second, the application uses different levels of composition. At the top level, the ToursAppl composite is composed from the Tours composite and the TripProviderImpl and TourProviderImpl implementations. At the next level, the Tours composite is composed from the TripBookingImpl and ShoppingCartImpl implementations. This ability to use one level of composition to build the next-higher level and continue doing this at multiple levels is called recursive composition, and it’s a very important feature of SCA.

Third, each level of composition can include customization of the level below it. Without this, building a higher-level composition would often require us to make small changes to the building blocks that we’re using. We’ve all seen the problems caused by this cut-and-paste style of reuse! SCA’s ability to make customizations at the point of reuse means that existing building blocks can be used directly without needing to tweak them.

 

In this example you’re starting to see the power and flexibility of using SCA to build composite applications. Using a few small XML files, we’ve taken some basic building blocks (Java implementation classes) and combined them in a particular way to make an application. By editing these XML files, we can update this application without needing to change any implementation code. Our application is modular and componentized, so we can change one part of it without affecting other parts.

In this section we’ve shown how composites can be reused within composite applications through inclusion in other composites or by using a composite to provide the implementation of a component. The inclusion approach allows for white-box reuse, where the internals of the composite being included are exposed and expected to be understood. The use of the composite as a component implementation supports a black-box approach, where the contents of the composite are hidden and not expected to be understood.

3.5. Summary

If you feel like a traveler who’s just done Europe in a week, we’re not surprised! We’ve covered a lot of ground in these first three chapters. In chapters 1 and 2 we introduced Tuscany and looked at the basic concepts of SCA, explaining and illustrating how you can use components, implementations, services, interfaces, references, wires, properties, and bindings to create a composite application. In this chapter, we completed the picture by looking at the detailed steps of packaging and running a composite application using Tuscany, and we showed how composite applications can be constructed from reusable building blocks.

The most important lesson to remember from this chapter is that to run an application in SCA and Tuscany, you need to describe your application’s components and services as a composite application using a composite file. We also showed you how these composite files and related artifacts such as Java classes, XML documents, XSD files, and WSDL files are packaged into SCA contributions. Contributions are installed into the SCA domain, which is configured either as a single process or using multiple processes with the Tuscany domain manager. Composites can be deployed into the SCA domain, used as component implementations, or included in other composites.

Now it’s time for a change of pace as we explore what Tuscany has to offer in greater depth and take a closer look at some of the important parts of Tuscany. We’ll start by examining the different interaction patterns you can use in your services. Enjoy!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.240.230