After providing some background on what nodes and networks are and how they work, I’ll get to how they are configured. Warning: This post is pretty dense in terms of content ;-)
Each FABRIQ network is defined in a single configuration document with the root element “fabriq”.
<fabriq configuration="twoSimpleNodes" version="2.15" xmlns="urn:fabriq-europe-microsoft-com:2004-06:fabriq-configuration">
Each configuration document must have a unique name (at least unique amongst the configurations you want to use) and must have a version number. The version number has the notation major.minor and must be incremented whenever you want to submit an updated configuration to a FABRIQ engine. Changed configurations with the same version number are rejected by the engine.
The first (optional) element within a configuration document is the import element. Import is a very powerful facility to create and reuse libraries of handler-types, pipeline-types, and node-type definitions (that I’ll explain further down in this document).
<fabriq … >
Imported definitions become a part of the importing configuration document (the imported document’s configuration name is not a namespace mechanism) and therefore names of pipeline-types, handler-types and node-types should be unique across all configuration files. All names of such items are freeform text and it may make sense to choose a “urn:xxx” naming scheme to name for them (I don’t do that in my simple examples here)
Following the import clause, might be a sequence of handlerType and pipelineType definitions that are already explained here.
The imported and locally declared pipelineTypes can then be applied to nodeTypes.
<nodeType name="FirstNodeType" xmlns:mymsg="urn:myMessages">
A node-type defines a (mostly) deployment independent one-way service. A nodeType defines actions, their input messages and the pipelines that process input messages. The example above shows an exemplary nodeType in most of its glory. Each nodeType must have a unique name.
The nodeType may declare a policy (1) on the nodeType-level that is applied to all messages that flow into any node based on this nodeType. In the example above, the policy defines two assertions; the first mandates a maximum message age and the second mandates the presence of a certain set of message headers (both are WS-SecurityPolicy elements). Policies can also be declared on the action-level as per the configuration schema, but we’re currently not processing such action-level policies.
The nodeType may declare any number of local types via embedded XML schemas (2). These types referenced by the input message declaration (5).
Each nodeType declares a collection (3) of actions. An action (4) is a named activity that has a locally unique name. Each action has a match attribute that is matched against the <wsa:Action> header (those not too familiar with WS-Addressing should think of SOAPAction) of an inbound message to determine whether the message is applicable to the respective action. If the attribute value is “*”, the action applies to any inbound message and the <wsa:Action> value is essentially ignored.
An action may declare a set of accepted input messages (5). If this declaration is missing, any input message is considered valid. If the declaration is present, any message that is declared within the input element is considered valid. In version 1.0, we do not provide a validation handler to enforce this rule, but it’s on the to-do list. The primary reason for having this element is not validation, though. Its presence allows generating WSDL for each node by ways of XSLT transformation quite easily.
Any action must declare a pipeline that defines the processing steps that shall be performed by the action.
A network defines an application consisting of nodes. Each node is based on a nodeType and adds the deployment dependent aspects and the handling of output to the nodeType’s base-definitions.
A node may be assigned to any number of hosts or to “any” host by specifying the wildcard “*”. It may declare for itself a set of ports (the runtime chooses a default endpoint using ES transport if the declaration is absent) at which is can be reached. Each port is declared as an endpoint-reference. If the address specified in the endpoint reference has a hostname of “any”, the address is automatically mapped to a physical address based on the node’s hostname list whenever required. In “SecondNode” in the above example, the runtime will map msmq://any/fabriqport to one of msmq://machine5/fabriqport, msmq://machine6/fabriqport, or msmq://machine7/fabriqport. When the runtime wants to route to the node MyApp/SecondNode, it will pick one of these physical targets at random and send the message there (and therefore balance across the hosting machines).
The output section of the node declaration defines routes. A route can be selected based on a combination of the “@To” or the “@Action” property (analogous to the respective WS-Addressing headers) contained in the message(s) that come(s) out of the pipeline after processing. If you omit the “to” and/or “action” attribute, the route will apply to any destination and/or any action. If multiple routes apply, a copy of the message will be sent along each of those routes.
Each route has a set of destinations. A destination contains a prioritized endpoint reference or the reserved replyTo element. The runtime will load balance across destinations with the same priority and if sending fails on a destination with high priority (1 is highest, 9 is lowest, 0 is disabled), the engine will fall back to a lower priority destination as a backup route. The replyTo destination causes the runtime to pick up the wsa:ReplyTo header contained in the input message and send to the endpoint indicated by that header. The wsa:ReplyTo header that is sent into a FABRIQ network is always propagated throughout the network (even if the message is split) and this element therefore enables the external client to dynamically supply a destination where the network drops off the final message(s) after processing is complete.