This is an introduction to the Configuration and Model APIs of SwitchYard. Future articles we will dive deeper to explore some of the more interesting features, but here you can familiarize yourself with the broader concepts and functionality. It is recommended that you also read SwitchYard Application Configuration so you can see how these APIs are initially being put into practice, for our M1 release.
The Configuration API: org.switchyard.config
We start at the lower-level: the Configuration API. The central interface here, not surprisingly, is Configuration. A Configuration is a heirarchal bucket for configuration data. It has attributes and a value, as well as possibly a parent and children:
Configuration +getAttribute(QName):String +setAttribute(QName,String):Configuration +getValue():String +setValue(String):Configuration +getParent():Configuration +getChildren():List<Configuration> // etc.
The first implementation that SwitchYard provides is DOMConfiguration. In the future, we might provide other implementations, but for M1, there is the one. A DOMConfiguration, as one might assume, manipulates an XML Document Object Model underneath the hood. Application code never directly instantiates a DOMConfiguration. Instead, a ConfigurationResource is used as a factory of sorts, able to "pull" in configuration data from a variety of sources (claspath resource, URL, URI, File, InputStream, Reader, InputSource, Document, etc).
Note: The Resource super-type is used/extended in several places across the Configuration and Model APIs, however that will be saved as a future topic.
One of the more powerful features of the Configuration API is the ability to merge Configurations. Think of it as "overlaying" one heirarchal data structure on top of another. To do this, one uses the Configurations helper class:
Configurations +merge(Configuration,Configuration):Configuration // etc.
Some interesting logic is used to best match and overlay elements from one Configuration onto another, but again, that is a topic for another day.
The Model API: org.switchyard.config.model
Next is the higher-level: the Model API. The central interface here, also not surprisingly, is Model. The most important thing about Models is that a Model "wraps" a Configuration. Classic composition for you OO design fans out there. A Model, like a Configuration, also has the concept of parent and children, but for each parent and each child Model, there is a 1:1 associated Configuration.
Model +getModelConfiguration():Configuration +getModelParent():Model +getModelChildren():List<Model> // etc.
Why bother with Models at all? Why not just let application code use Configurations directly? Truthfully, there is no reason why application code can't just use Configurations. However, it would be much more natural for code to be able to use more descriptive names and attributes for what is being represented. That's where Models come in. For example, a CarModel could have car.getColor() instead of config.getAttribute("color"). It could also have car.getEngine().getCylinderCount() instead of Integer.valueOf(config.getFirstChild("engine").getAttribute("cylinder-count")). Thus, the intent is for developers to be able to implement their own Models, exposing just those configuration details they want, and name them in a more natural way for more fluid access.
Note: There is an abstract BaseModel which contains helpful methods intended for sub-classes to easily manipulate the wrapped Configuration.
Just like Configuration merging via the Configurations utility class, one can also merge Models, using the Models utility class. The logic to merge Models is actually quite straightforward, as it delegates most of the work by merging the Models' underlying Configurations.
Models +merge(Model,Model):Model // etc.
Serialization / Deserialization
Note: This is quite a large topic, and warrants it's own article to explain it fully, but the broader concepts will be introduced here.
The work of Model serialization and deserialization is the responsibility of Marshallers. Here is the Marshaller interface:
Marshaller +getDescriptor():Descriptor +read(Configuration):Model +write(Model,OutputStream):void +write(Model,Writer):void
A Marshaller reads a Configuration (provided by a ConfigurationResource) into a Model, and can also write out a Model to an OutputStream or Writer. The big questions here are most likely, "How does the code know which Marshaller to use for what type of Model?", and "How does a Marshaller know what type of Model to create?" The answer to both of these questions is, "via Descriptors".
A Descriptor contains all the information necessary to map Configurations to Models to Marshallers. How does it keep everything straight? Via namespaces. Using a namespace, core SwitchYard code, as well as user-developed code, describes which Marshallers should be used for which Models, and even which Schemas can be used to validate those models (another future topic). All that is required is for an org/switchyard/config/model/descriptor.properties to be available to the classloader, containing the mappings. For example, this is the content for the SwitchYard Bean Component:
bean1.namespace=urn:switchyard-component-bean:config:1.0 bean1.schema=bean-v1.xsd bean1.location=/org/switchyard/component/bean/config/model/v1/ bean1.marshaller=org.switchyard.component.bean.config.model.v1.V1BeanMarshaller
With this, when the framework comes across this line in a switchyard.xml configuration file, it knows what to do:
<implementation.bean xmlns="urn:switchyard-component-bean:config:1.0" class="org.switchyard.quickstarts.m1app.InventoryServiceBean"/>
Note: Read SwitchYard Application Configuration for more information on switchyard.xml.
Using namespaces gives developers a way to extend and customize the configuration/model structure, as well as a way to allow for configuration versioning (and future-proofing, to some extent).
The SwitchYard Configuration and Model APIs provide a flexible mechanism for applications (and SwitchYard internals) to easily access and externalize configuration data, as well as a natural, fluid way of manipulating that data.
Check back for future articles and examples!