To better understand Soya, we distinguish between service deployment and service execution. First, we describe how a service implementation is turned into an executable instance and exposed to the network. Then, we illustrate what happens inside Soya when other services interact with a deployed service and how the Soya runtime enforces a service's SSDL contract.
In Soya a service implementation typically consists of code representing the core application logic, metadata attributes describing the service's SSDL contract, and configuration files specifying service endpoints, security settings, and so on. A developer can deploy a service implementation by opening a SoyaServiceHost instance, which is a custom host implementation of WCF's ServiceHostBase. The following two lines show how a service (in this case MyService) is deployed:
host = new SoyaServiceHost(typeof(MyService)); host.Open();
Opening a SoyaServiceHost triggers the three following major activities, which are also graphically illustrated further below:
Building the internal model constitutes most of the work the SoyaServiceHost performs after the Open() method is called. The internal model can be seen as an intermediary language between the service implementation and the SSDL contract description. It is used by the runtime as a blueprint for creating new stateful protocol instances and also to generate and expose SSDL metadata.
The SoyaServiceHost builds the internal model by reflecting over the service classes and applying configuration settings. It first of all identifies the SSDL protocol framework that has been used to model the service's messaging behavior and then uses protocol specific classes to process the class and attribute metadata. This includes inferring XML schemas, interaction protocols, and message descriptions and adding them to the model. Next, it processes the application's configuration files and adds further artifacts, such as service endpoints or custom behaviors, to the internal model. Finally, both the WCF and the Soya runtimes are created and configured. This includes injecting a message inspector into the WCF runtime that will later be used to intercept inbound and outbound messages. The message inspector, shown in the figure below, bridges the two runtimes by passing intercepted messages from WCF to the Soya runtime. This terminates the service deployment and enables other services to start interacting with the deployed service through the specified endpoints.
When an incoming message is received from the network, it is first of all pushed through WCF's channel stack. The channel stack consists of different elements that deserialize, decode, and decrypt the incoming bits into an untyped Message instance. Immediately after the message exits the channel stack, it is intercepted by a custom MessageInspector that has been injected into the WCF runtime at service deployment time. This class connects the WCF and the Soya runtimes by passing all messages from the former to the latter for further processing.
Once a message is passed to the Soya runtime, the runtime firstly uses an XsdValidator to validate the structure of the message's elements. It compares the header and body elements with the SSDL contract that is represented by the service's internal model. If the message validation fails, the message is rejected. Otherwise, the message is further processed by an IProtocolValidator. This validator checks if the incoming message is valid in terms of the messaging behavior defined in the service's contract (i.e. the protocol definition).
As opposed to the XsdValidator, which is stateless, the IProtocolValidator needs to maintain state between interactions, as validation is based on the state of a conversation in which the interacting services are at a given point in time. Internally, this validation is performed with a state machine. It is built from the protocol definition and the incoming and outgoing messages represent the state transitions. If the message causes the state machine to transit to an invalid state, the message is rejected. Otherwise, it is returned to the WCF runtime, where the untyped Message instance is mapped into a user-defined message instance. Finally, this user-defined message instance is dispatched to a local method of the service implementation. The above figure illustrates this mechanism, and the same process (in reverse) applies to outgoing messages.