Bootstrapping the Execution Module

Before messages can be send there needs to be at least one configuration call. This call sets the dependency injection (DI) container to be used. The library does not depend directly on a specific container. Instead it defines an IBuilder interface as abstraction. The implementation of this interface is responsible to construct the requested types including possible auto wiring. The library ships with separate assemblies implementing a specific builder using either Autofac or StructureMap

Selecting a Dependency Injection Container
To use Autofac add a reference the OnyxOx.AsyncExecutionLib.AutofacBuilder.dll assembly. This assembly contains an extension method UseAutofac which can be used as shown below.
public void StartExecutionLib(Autofac.IContainer container)
{
  Module.Configure()
          .UseAutofac(container)
          .Build();
}
There is a full example in the source code showing how to do this.

When using StructureMap the initialization of the library can be done similary. Below the UseStructureMap() method is used, which is an extension method not available as long as OnyxOx.AsyncExecutionLib.StructureMapBuilder is not referenced. This example will use the StructureMap default container.
public void StartExecutionLib()
{
  Module.Configure()
          .UseStructureMap()
          .Build();
}

If you have a custom implementation of IBuilder using another DI container configuring the library would look like this:
public void StartExecutionLib(IBuilder customBuilder)
{
  Module.Configure()
          .UseBuilder(customBuilder)
          .Build();
}

Defining Message Handler Ordering
This configuration step is optional. The library allows for multiple message handlers of the same message. If nothing is specified, handlers are executed in any order. However sometimes it may be desirable to execute certain handlers before others.
For example consider the case where you have a LogMessageHandler and a separate ValidationMessageHandler. Both handle messages of type IMessage which means they are called for every message. LogMessageHandler performs some logging while the other executes some custom validation logic. If you want to both handlers before any specific handler is called use the FirstExecute<T> configuration method.
public void StartExecutionLib()
{
  Module.Configure()
          .UseStructureMap()
          .FirstExecute<LogMessageHandler>(x => x.Then<ValidationMessageHandler>())
          .Build();
}
If necessary you can chain any number of additional Then<T> calls inside the delegate parameter.

Execution Pipes
This configuration step is optional. All incoming messages are first wrapped into a custom IJob instance. This interface exposes an Execute(void) method. The execution pipe inside the module is responsible to call this method on any desired thread. By default the library creates an instance of the SingleThreadPipe class, which performs all jobs on a single separate worker thread.
I've found this to be a good trade off. This keeps the UI responsive even for longer operations. Likewise, you don't have to worry about thread synchronization, as long you are careful and perform operations on your business model only if originating from message handler calls. Once there are multiple workers, executing handlers tend to get messy. You have to think about thread synchronization, race conditions and so forth. This makes development a lot more difficult.

The number of worker thread can be changed with the UseMultipleWorkerThreads() extension method. This will create as many workers as specified in the method parameter. The following snippet shows a configuration with 5 worker threads.
public void StartExecutionLib()
{
  Module.Configure()
          .UseStructureMap()
          .UseMultipleWorkerThreads(5)
          .Build();
}

If you want to implement your own execution pipe you are free do so. This makes it possible to define a special worker thread handling only high priority messages, while all others are handled on a default thread. To configure your own execution pipe call the UseExecutionPipe<T> method.
public void StartExecutionLib()
{
  Module.Configure()
          .UseStructureMap()
          .UseExecutionPipe<MyCustomExecutionPipe>()
          .Build();
}

Scanning for Message Handlers and Modules
This configuration step is optional. By default, the library is scanning all assemblies in the current working directory for classes implementing IMessageHandler and IMessageModule.

It is possible to instruct the library to scan only specific assemblies. The list of assemblies is configured in the action passed to the ScanSpecificAssemblies method. The next example sets the library to scan only in the assembly containing the current execution code.
public void StartExecutionLib()
{
  Module.Configure()
          .UseStructureMap()
          .ScanSpecificAssemblies(x => x.Add(Assembly.GetExecutingAssembly()))
          .Build();
}

If you want a different scanning behavior you have to provide your own implemenation of IAssemblyScanner. This interface exposes two methods, each returning an enumerable of types. Configuring the library to use another scanner is done with the UseScanner<T> method.
public void StartExecutionLib()
{
  Module.Configure()
          .UseStructureMap()
          .UseScanner<MyCustomScanner>()
          .Build();
}

Last edited Jul 15, 2012 at 11:28 AM by domo42, version 7

Comments

No comments yet.