Overview
Application configuration must provide the following information:
- Services provided by the application
- Services referenced (i.e. consumed) by the application
- The components used to implement the services provided by the application
- The bindings used to interact with services outside the application
- Component-specific configuration required to implement or bind to a service
Application configuration appears in a few forms:
- The native form of the configuration. What is meant here is that the user does not have to represent the entire configuration for the application in a single configuration file. For example, a service implemented by the bean component will only have annotations.
- An API representation of the configuration. There is a core API for details that apply to all applications. There are also extension points to represent configuration elements specific to a given component (e.g. SOAP binding configuration).
- A serialized form of the configuration. This is included with a deployed application and used to configure runtime components. It can also be stored along with other application artifacts in a service repository to support service governance.
Example
Let's build up an example configuration one step at a time.
Step 1 - Bean component provides a service
Create a service interface and implementation class.
package org.example; interface MyService { void doSomethingCool(); } @Service(MyService.class) public class MyServiceBean implements MyService { ... }
Step 2 - Bean component consumes a service
MyServiceBean consumes another service as part of it's implementation, so add a reference to the bean class.
package org.example; @Service(MyService.class) public class MyServiceBean implements MyService { @Reference("{urn:services:example}AnotherService") private YourService anotherService; ... }
Step 3 - Bind the Bean service to SOAP using a SOAP Gateway
There are a number of ways that this can be done, but for now we'll focus on the most direct which is (a) providing a WSDL that will be used as the contract for the WS, and (b) connecting the WSDL port to the service name in the Bean component.
service.wsdl
<definitions targetNamespace="http://example.org/services"> <service name="MyPublicService"> ... <portType> ... <binding> ... <message> ... </definitions>
switchyard.xml
<switchyard xmlns="urn:switchyard-config:switchyard:1.0"> <composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912"> <service name="MyPublicService"> <binding.soap xmlns="urn:switchyard-component-soap:config:1.0"> <serverPort>18001</serverPort> <wsdl>service.wsdl</wsdl> </binding.soap> </service> </composite> </switchyard>
Deploying this configuration would result in the SOAP gateway exposing a web service with the contract and endpoint details specified in "service.wsdl". SOAP messages received at this SOAP endpoint would be sent to the internal "MyService" service provided by the Bean component. Note that the definition of the service (by the bean component) and the binding details are completely separate.
API
This a high-level overview of the configuration model based on the requirements listed above.
Note: Please refer to SwitchYard Configuration and Model APIs for more foundational information on models.
package org.switchyard.config.model.switchyard; public interface SwitchYardModel extends NamedModel { CompositeModel getComposite(); } package org.switchyard.config.model.composite; public interface CompositeModel extends NamedModel { List<CompositeServiceModel> getServices(); List<ComponentModel> getComponents(); } package org.switchyard.config.model.composite; public interface CompositeServiceModel extends NamedModel { // 0:* bindings for the service List<BindingModel> getBindings(); } package org.switchyard.config.model.composite; public interface BindingModel extends TypedModel {} // example of a Model extension package org.switchyard.component.soap.model; public class SOAPBindingModel implements BindingModel { public int getServerPort() {...} public String getWsdl() {...} } // Note that this is a service component and not a SwitchYard component. package org.switchyard.config.model.composite; public interface ComponentModel extends NamedModel() { // Implementation details for the component public ComponentImplementationModel getImplementation(); public List<ComponentServiceModel> getServices(); // Any services that the implementation depends on public List<ComponentReferenceModel> getReferences(); } package org.switchyard.config.model.composite; public interface ComponentImplementationModel extends TypedModel {} package org.switchyard.config.model.composite; public interface ComponentServiceModel extends NamedModel { public ComponentServiceInterfaceModel getInterface(); } package org.switchyard.config.model.composite; public interface ComponentServiceInterfaceModel extends InterfaceModel {} package org.switchyard.config.model.composite; public interface ComponentReferenceModel extends NamedModel() { public ComponentReferenceInterfaceModel getInterface(); } package org.switchyard.config.model.composite; public interface ComponentReferenceInterfaceModel extends InterfaceModel {}
Serialization Format
During the build and packaging process, the various native configuration definitions along with any configuration the user has provided directly in the switchyard configuration file are gathered up to produce the complete configuration model for the application. This model is then serialized and included in the deployable switchyard archive. While leveraging the SCA Assembly model, we keep in mind that this does not mean that we have to build a complete SCA runtime or anything like that; it is just a way to represent our configuration. The primary advantage with this approach is the fact that SCA is a widely known standard in the SOA space, so users don't have to learn something completely different and specific to SwitchYard. As a bonus, this may help us integrate with SCA compatible runtimes (e.g. Tuscany) in a cleaner, easier way.
Based on the example above, the following SCDL representation would be produced:
<switchyard xmlns="urn:switchyard-config:switchyard:1.0"> <composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912"> <service name="MyPublicService"> <binding.soap xmlns="urn:switchyard-component-soap:config:1.0"> <serverPort>18001</serverPort> <wsdl>service.wsdl</wsdl> </binding.soap> </service> <component name="MyService"> <implementation.bean xmlns="urn:switchyard-component-bean:config:1.0" class="org.example.MyServiceBean"/> <service name="MyService"> <interface.java xmlns="urn:switchyard-component-bean:config:1.0" interface="org.example.MyService"/> </service> <reference name="anotherService"> <interface.java xmlns="urn:switchyard-component-bean:config:1.0" interface="org.example.YourService"/> </reference> </component> </composite> </switchyard>
A few notes:
- The structure of binding.soap is defined by a schema provided by the SOAP gateway.
- The structure of implementation.bean is defined by a schema provided by the Bean component.
- We could use binding.ws and implementation.java, which are defined in SCA standards, if we feel that we can meet the implementation requirements of those specs. However for now, we just went with a switchyard-specific configuration.
The SCA representation matches our requirements pretty closely. Having an SCA expression of the configuration will make things like service governance more straightforward, since we can represent service configuration using a standard. This doesn't mean we need to create a full-blown SCA runtime. We should look at integrating with Tuscany if we need other SCA features such as SDO, integration with non-Java environments, etc.
Comments