Using WCF Services with Axum: A load generation sample
Following my previous post, I’ve decided to start blogging some examples of the use of Axum on distributed programming scenarios. The purpose of these samples is not to detail the specific features of the Axum language but rather to highlight some practical scenarios that can be improved by the combination of Axum and other technologies.
As any other CLR-based language, Axum will require to interoperate seamlessly with other Microsoft technologies in order to gain adoption in the real world. Distributed or Service Oriented systems are some of the areas on which Axum’s capabilities for modeling and executing highly concurrent tasks can drastically improve the way we implement some message exchange scenarios with technologies like WCF.
In order to illustrate some of these concepts, let’s try implementing a classic message load generation scenario on which a client needs to distribute multiple copies of a message to a specific service endpoint. These types of mechanism have been traditionally implemented by frameworks such as SOAPUI or LoadRunner.
For the simplicity of this sample let’s use the service illustrated in the following code.
Our client application will interact with the service using WCF’s channel API (we can consider a similar implementation using a service proxy). Creating WCF artifacts such as messages or channels can introduce potential side effects into Axum’s runtime. In order to mitigate these side effects, we can abstract the access to the message and channel factories using the following isolated class.
1: isolated class ClientContext
2: {
3: [Readable]
4: public static ChannelFactory<IRequestChannel> channelFactory;
5: public static MessageBuffer messageBuffer;
6: }
The highlighted isolated clause instructs Axum that the class has been optimized for concurrent access. Having this component, we can create an Axum agent that sends a single message to the service using an IRequestChannel. The following code illustrates that technique.
1: channel RequestReplyChannel
2: {
3: input bool sendRequest;
4: output bool success;
5: }
6:
7: private agent RequestReplyAgent : channel RequestReplyChannel
8: {
9: public RequestReplyAgent()
10: {
11: var sendRequest= receive(PrimaryChannel::sendRequest);
12: unsafe
13: {
14: var requestMessage= ClientContext.messageBuffer.CreateMessage();
15: var clientChannel= ClientContext.channelFactory.CreateChannel();
16: var responseMessage= clientChannel.Request(requestMessage);
17: clientChannel.Close();
18: PrimaryChannel::success <-- true ;
19:
20: }
21:
22:
23: }
24:
25: }
Notice that the agent exposes two ports that use Boolean values to indicate whether to send a message to the WCF service and whether the response was successful or not. This simple design can enable interesting message exchange scenarios like collecting all the successful responses and all SOAP faults into separate lists that can be communicated back to the caller. Additionally, we should consider the fact that passing WCF message objects as part of Axum ports introduces some challenges given a lot potential side effects.
At this point, we just need to implement the code that activates multiple instances of the previous agent asynchronously. We can accomplish this by sending a Boolean message to the our Axum agent for each WCF message we want to send to our target service. After receiving the message, the agent will retrieve an instance of the WCF message and the channel factory from the isolated context class. At that point, the agent will create a new IRequestChannel instance using the channel factory and it will send the message to the WCF service.
1: public domain Program
2: {
3: private writer agent MainAgent : channel Microsoft.Axum.Application
4: {
5: public MainAgent()
6: {
7: Message baseMessage= null;
8: ChannelFactory<IRequestChannel> factory= null;
9:
10:
11: unsafe
12: {
13: XmlDictionaryReader msgreader = XmlDictionaryReader.CreateDictionaryReader(XmlReader.Create("C:\\Projects\\Tellago\\WCFPerformance\\Services\\SampleServices\\SampleClients\\Messages\\EchoMessage.xml"));
14: baseMessage = Message.CreateMessage(msgreader , Int32.MaxValue, MessageVersion.Soap11);
15: baseMessage.Headers.Action= "Action";
16: factory= new ChannelFactory<IRequestChannel>(new BasicHttpBinding(),
17: new EndpointAddress("http://localhost:9092/SampleServices/Service.svc"));
18: ClientContext.channelFactory= factory;
19: ClientContext.messageBuffer= baseMessage.CreateBufferedCopy(Int32.MaxValue);
20:
21: }
22:
23: for(int index= 0; index<= 10000; index++)
24: {
25: var wcfAgent= RequestReplyAgent.CreateInNewDomain();
26: wcfAgent::sendRequest <-- true;
27: }
28:
29: Console.ReadLine();
30:
31: PrimaryChannel::Done <-- Signal.Value;
32: }
33:
34: }
Notice that the creation of the message buffer and channel factory has been enclosed within an unsafe code block. This technique allow Axum to execute code that introduces potential side effects.
After executing this program, multiple copies of the original message will be sent to our sample service.