This project is read-only.

Code to be executed at start up - with result needed by others

Jun 13, 2013 at 10:34 PM
Edited Jun 13, 2013 at 10:53 PM
I have the following scenario:
  • A central database with a job queue
  • Multiple job processors (technical detail: each job processor is a process on its own machine). A job processor is self registering and can be in one of the following states:
    • Offline
    • Available
    • Busy
  • A central processor registry
  • A central job distributor
When a new job gets into the system, the job distributor assigns it to a job processor that is available.
To do that, it looks into the processor registry and picks one.

The context of this discussion is the implementation of the job processor. How the job distributor performs its work in detail or how it is implemented is irrelevant.

Now, for all that to work, the job processor needs to register itself at start-up. The result of that registration is a JobProcessorContext that would contain the ID of this job processor and is required by the parts of the job processor that query the job queue for jobs assigned to this concrete processor (we don't care for jobs assigned to other processors).

An example of a consumer of JobProcessorContext would be the implementation of IQueryHandler<FindAssignedJobsQuery, Job[]>.

My naive attempt to do all this looked like so:

container.RegisterType<IProcessorRegisterer, ProcessorRegisterer>();
container.RegisterSingle(() => container.GetInstance<IProcessorRegisterer>().Register());

IProcessorRegisterer.Register would return the JobProcessorContext instance that is the result of the registration.

While this approach actually works when running the application, it has two big problems:
  1. The registration is not explicitly performed at start-up. It is performed upon requesting JobProcessorContext, which just happens to be at start-up because the root of the object graph takes it as an indirect dependency.
  2. It makes unit testing the composition root impossible. The call to container.Verify would try to resolve JobProcessorContext, thus triggering the registration. That in turn tries to access the database which is not available in my unit tests for good reasons.
I am currently unsure on how I would solve those problems. Any ideas?
Jun 14, 2013 at 8:48 AM
You should always try to separate the composition and verification of the DI configuration from application startup and running the application. (Simple Injector even tries to force this up to you, by disallowing making new registrations after the first service is resolved from the container).

To solve this you will either have delay the resolving of the JobProcessorContext (by using a Lazy<T> for instance) or prevent from registering it at all. Take a look at this solution for instance:
public WindowsServiceJobProcessorContext 
    : IJobProcessorContext
{
        // Must be set after the Job Processor is registered
    public static JobProcessorContext Context;

    public int JobProcessorId 
    { 
        get { return this.Context.JobProcessorId; }
    }
}

// DI Configuration
container.RegisterType<IProcessorRegisterer, ProcessorRegisterer>();
container.RegisterSingle<IJobProcessorContext,
    WindowsServiceJobProcessorContext>();
    
container.Verify();

// Start application
var registerer = container.GetInstance<IProcessorRegisterer>()
var context = registerer.Register();
WindowsServiceJobProcessorContext.Context = context;
Another option would do the registration of the Job Processorr before making the DI configuration:
void Main()
{
    // Start application
    var registerer = new ProcessorRegisterer();
    var context = processorRegisterer.Register();
    
        // Configure the container: pass the context
    var container = Bootstrap(registerer, context);
    
    // Optional
    container.Verify();
    
    container.GetInstance<IJobProcessor>().Run();
}

public static Container Bootstrap(
    IProcessorRegisterer processorRegisterer,
    JobProcessorContext processorContext)
{
    var container = new Container();

    container.RegisterSingle<IProcessorRegisterer>(processorRegisterer);
    container.RegisterSingle<JobProcessorContext>(processorContext);
    
    return container;
}

[TestMethod]
public void Bootstrap_VerifyContainer_Succeeds()
{
    // Arrange
    IProcessorRegisterer processorRegisterer = new FakeProcessorRegisterer();
    JobProcessorContext processorContext = new JobProcessorContext();

    var container = CompositionRoot.Bootstrap(registerer, context);
    
    // Act
    container.Verify();
}
Jun 14, 2013 at 9:20 AM
Edited Jun 14, 2013 at 9:22 AM
Every single time you answer me, I have the need to thank you for your thorough and well explained posts. So yet again: Thanks! :-)

I have to admit, both options you present leave me with a feeling that they aren't perfect.

Option 1:
This option has the problem of temporal coupling. You can't use WindowsServiceJobProcessorContext without first setting the static field and so this class would need to contain checks that make sure that the field is actually set.
I am thinking of providing a decorator for this sole purpose. It would contain the static field and it even could take a dependency on IProcessorRegisterer and register the processor of the field is null.
The WindowsServiceJobProcessorContext in turn could be changed back to a simple immutable DTO.
YAGNI, however, could speak against this approach: I know that the registration is to be performed at startup and that the static field will be set. I can even write some tests to prove it... So all the work that the decorater performs might not actually be needed...
What do you think?

Option 2:
For me, this is not an actual option, because it would mean that all the dependencies - think database access, registration rules, ... - of ProcessorRegisterer would need to be wired up by hand, too.
While this could be solved by two containers - one for the dependencies of ProcessorRegisterer and one for the application itself - I don't think this is a good approach.
Jun 14, 2013 at 10:07 AM
I'm not sure I fully understand how you want to implement option 1 with a decorator, so I can't really comment on that.

Okay, here's a third option:
void Main()
{
    // Start application
    // Configure the container: pass the context
    var container = Bootstrap();

    // Don't verify. Verification is done in a unit test.

    container.GetInstance<IJobProcessor>().Run();
}

public static Container Bootstrap()
{
    var container = new Container();

    container.RegisterType<IProcessorRegisterer, ProcessorRegisterer>();

    container.RegisterSingle<JobProcessorContext>(() =>
        container.GetInstance<IProcessorRegisterer>().Register());
    
    return container;
}

[TestMethod]
public void Bootstrap_VerifyContainer_Succeeds()
{
    // Arrange
    var container = CompositionRoot.Bootstrap();
    
    container.Options.AllowOverridingRegistrations = true;

    // Override this registration, because it registers the processor.
    container.RegisterSingle<JobProcessorContext>(
        new JobProcessorContext());    
    
    // Act
    container.Verify();
}
I know you're not fond of overriding registrations, but I believe it to be a valid option for cases like these. You'll need to remember however, that the real registration is untested. In your case that is not a problem, because it is tested at application startup when you call container.GetInstance<IJobProcessor>().
Jun 14, 2013 at 10:33 AM
Edited Jun 14, 2013 at 10:36 AM
I just wanted to show you an implementation of Option 1 with a decorator, but while actually implementing it I noticed that it really makes no sense at all. :-)

Just for reference, it would look like so:

// Useless code - don't use
public class WindowsServiceJobProcessorContext 
    : IJobProcessorContext
{
    private readonly int _jobProcessorId;

    public WindowsServiceJobProcessorContext(int jobProcessorId)
    {
        _jobProcessorId = jobProcessorId;
    }

    public int JobProcessorId 
    { 
        get { return _jobProcessorId; }
    }
}

public class StaticJobProcessorContext : IJobProcessorContext
{
    public StaticJobProcessorContext(Func<int, IJobProcessorContext> factory)
    {
        _factory = factory;
    }

    public static JobProcessorContext Context;
    
    public int JobProcessorId 
    { 
        get
        {
            if(_decorated == null)
                CreateDecorated();
            return _decorated.JobProcessorId;
        }
    }
    
    private void CreateDecorated()
    {
        if(Context == null)
            throw new TemporalCouplingViolatedException("...");
        _decorated = _factory(Context.JobProcessorId);
    }
}

container.Register<IJobProcessorContext, WindowsServiceJobProcessorContext>();
container.RegisterDecorator(typeof(IJobProcessorContext), typeof(StaticJobProcessorContext));

It introduces heaps of new code without any benefit. This teaches us, that another layer of indirection not always is a good idea :-)

About Option 3: I think I can use this for my other problem, but for the current problem, I actually like Option 1 better. Because of the explicit registration of the processor (registerer.Register) it becomes very clear that the registration happens at start up. Putting that code into its own Method Object even allows me to unit test two things:
  1. The registration happens
  2. The result of the registration is stored in the static field
I like!

The only thing that is untested is whether or not the Method Object is actually used and invoked. I could however create an IStartUpHandler interface that the Method Object implements and unit test that this interface is resolved by the start-up routine and that it's Handle method is called. With this mechanism I could even add more start-up handlers that perform different actions.
This teaches us, that another layer of indirection might not always be a good idea, but most of the time, it is. ;-)

What do you think?
Jun 14, 2013 at 11:24 AM
I think that there is not a problem in software we can't solve with an extra layer of abstraction, except of course the problem of too many layers of abstraction :-)
Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:37 PM