mips
mips

Reputation: 2177

How to implement this mixed object Lifetime with Simple Injector

I'd like to know how to register the classes and setup a Simple Injector container to instantiate the classes in the following way. ie go from manual DI to having the below Consumer class have the CompositeService injected and the object graph and lifetimes setup as follows:

To bring some context (if it helps) the Consumer class might be a ViewModel from a WPF application which gets instantiated when the View is requested.

public class Consumer
{
   public Consumer()
   {
      var sharedSvc = new SharedService();
      var productSvc = new ProductService(sharedSvc, new MathHelper());
      var compositeSvc = new CompositeService(sharedSvc, productSvc, new MathHelper());
      compositeSvc.Process();
   }
}

where: MyContext should be shared within the scope of the calls. ProductService and CompositeService can be transient or shared within the scope. MathHelper must be transient.

Q: How can the above be achieved with Simple Injector?

OR more specifically: How can I instantiate multiple MathHelpers within the context of the Simple Injector Scope?

I've read up on http://simpleinjector.readthedocs.org/en/latest/lifetimes.html and read and followed the SO answer https://stackoverflow.com/a/29808487/625113 however, it seems either everything can be transient or scoped but not certain specific objects scoped and the rest transient (which seems odd).

Update 1

The following with Simple Injector will achieve the SharedService result, but if I want ProductService and CompositeService to also have a scoped lifetime it wont work:

 cont.RegisterLifetimeScope<SharedService>();
 cont.Register<MathHelper>();
 cont.Register<ProductService>();
 cont.Register<CompositeService>();

 using (cont.BeginLifetimeScope())
 {
    var compositeSvc = cont.GetInstance<CompositeService>();
    compositeSvc.Process();
 }

If I register either or both of the ProductService or CompositeService as RegisterLifetimeScope I get a Lifetime mismatch exception. ie

 cont.RegisterLifetimeScope<SharedService>();
 cont.Register<MathHelper>();
 cont.RegisterLifetimeScope<ProductService>();
 cont.Register<CompositeService>(); // or cont.RegisterLifetimeScope<CompositeService>();

 using (cont.BeginLifetimeScope())
 {
    var compositeSvc = cont.GetInstance<CompositeService>(); // Exception thrown
    compositeSvc.Process();
 }

Throws an exception leading to this page: https://simpleinjector.readthedocs.org/en/latest/LifestyleMismatches.html

I can under that in relation to Singleton should be dependent on Transient and can infer a sort of understanding that the same could be said in this case that Simple Injector is warning that Scoped can't depend on Transient because Transient isn't managed within the scope.

So my question is more specifically how can I instantiate multiple MathHelpers within the context of the Simple Injector Scope?

Update 2 - Further background and example

Brief background - This situation arose as I have a 4 year old, 2-tier, WPF based application currently using Ninject which has the bloated mixed Service architecture that @Steven describes in his blog series (ie the Services have become a mash of mixed, semi-related, command and queries). Most of these services are a good candidate for separating out into ICommandHandler/IQueryHandler architecture...but you can't do things overnight, so first crack was to convert from Ninject to SimpleInjector (yes I know Ninject can do the same thing in regards to this architecture but there are other reasons for moving to SimpleInjector).

As far as "scoping" the dependency resolution, a "scope" (in this application) is considered to be for the life of a form so one DbContext (like the SharedService in the example above) is shared amoungst the services that the form/viewModel require and MOST of the services are per scope with some injected services or helper classes needing to be injected as Transient.

This (to me) is analogous to Mark Seemann's hand-coded example from http://blog.ploeh.dk/2014/06/03/compile-time-lifetime-matching/ where he has a Per Request (singleton-scoped) service which has Transient objects injected into it.

Edit: I had misread Mark Seemann's example and was reading the code as if the BarController were a service. So whilst the BarController object composition is the same the lifetime is not. That said the SomeThreadUnsafeService could just as easily have a new SomeServiceThatMustBeTransient injected into it but, I stand corrected, his example doesn't do this.

Hence I was wanting to know how to do the object composition Mark Seemann does in Simple Injector but outside the context of web reqeusts (my assumption is that Simple Injector's Per web request scoping is in essence a specific type of Lifetime Scoping).

To address @Steve and @Ric .net's comment and answer, I can see that there is the potential to end up with the scenario where 2 different services use another, shared service that uses a transient object (storing state) and the supposedly transient object becomes a Singleton Scoped object in the context of "some" of those services. eg

public class SingletonScopedService1
{
   private readonly TransientX _transientA;

   public SingletonScopedService1(TransientX transientA)
   {
      _transientA = transientA;
   }

   public void PokeTransient()
   {
      _transientA.Poke();
   }
}

public class SingletonScopedService2 
{
   private readonly SingletonScopedService1 _service1;
   private readonly TransientX _transientB;

   public SingletonScopedService2(SingletonScopedService1 service1, TransientX transientB) 
   {
      _service1 = service1;
      _transientB = transientB;
   }

   public void GoFishing()
   {
      _service1.PokeTransient();
      // This TransientX instance isn't affected
      _transientB.Poke();
   }
}

public class SingletonService3 
{
   private readonly SingletonScopedService1 _service1;

   public SingletonService3(SingletonScopedService1 service1)
   { 
      _service1 = service1; 
   }

   public void DoSomething()
   {
      _service1.PokeTransient();
   }
}

If DoSomething() is called on SingletonScopedService3 and GoFishing() is called on SingletonScopedService2 (and assuming TransientX maintains state) then results "may" be unexpected depending on the purpose of TransientX.

So I'm happy to accept this since the application is operating as expected (but also accept that the current composition is fragile).

With that said, can my original composition or Mark Seemann's example be registered with Simple Injector with the required life-times or is it strictly not possible by design and better to manually compose the object graph (or inject a Func as @Ric .net suggests) for the instances where this is required until further refactoring/hardening can be done?

Update 3 - Conclusion

Whilst Ninject allows you to register my original composition like:

     var kernel = new StandardKernel();
     kernel.Bind<SharedService>().ToSelf().InCallScope();
     kernel.Bind<MathHelper>().ToSelf();
     kernel.Bind<ProductService>().ToSelf().InCallScope();
     kernel.Bind<CompositeService>().ToSelf().InCallScope();

Simple Injector by design does not and I believe my second example is an example as to why.

FYI: In my real-world case, for the few instances that the object graph had this, I've manually constructed the graph (just to get switched to Simple Injector) with the intent on refactoring these potential issues out.

Upvotes: 2

Views: 1157

Answers (1)

Ric .Net
Ric .Net

Reputation: 5540

As explained in the linked SO question what the Lifestyle Mismatch exception is basically saying is that you're creating a so called captive dependency when you let a object depend on another object which has a shorter lifestyle.

While it maybe sounds odd to you, a captive dependency is:

  1. A real life problem which, if undetected, would typically lead to very strange bugs which are very hard to debug
  2. A sign, as indicated by Steven, that the service has some kind of state which is never a good sign

If MathHelper has mutable state and therefore needs to be injected in other services as transient, MathHelper has become some sort of 'runtime data'. And injecting runtime data is an anti-pattern.

This blogpost describes in detail what the problem is with runtime data and how to solve this. Solving your problem as described there is the way to go!

Because you did not specify the implementation details of MathHelper, it is hard to give you some advice how you should refactor MathHelper. So the only advice I can give you is, let runtime data or 'state' flow through the system as messages. You can read about message based design here and here.

There are however several other options, which will work but aren't good design IMO. So the advice is not to use these, but to be complete:

Instead of injecting MathHelper as a transient you could inject a MathHelperProvider or even simpler inject a Func<MathHelper> which you could register as singleton:

container.RegisterSingleton<Func<MathHelper>>(() => container.GetInstance<MathHelper>());

Notice that by registering a delegate you will make the container blind. It won't be able to warn you of misconfigurations in this part of the object graph anymore.

The other solutions I had in mind are so ugly in its design, that after writing them, I decided to leave them out of this answer!

If you would add some details about why MathHelper needs to be transient, I could give you some advice where you could make adjustments to make it scoped or even better: singleton.

Upvotes: 3

Related Questions