Dependecy injection on Factories

Dependecy injection on Factories

Old forum URL: forums.lhotka.net/forums/t/9654.aspx


Calin posted on Tuesday, October 19, 2010

Hi,

I was wondering if there is a way I can do dependency injection on factories.

The way I think this will work is the same as in MVC, there is a way to have your own dependency injection framework create your controllers.

So where do I start if I want to have my DI framework create the factories ?

Thanks,

RockfordLhotka replied on Tuesday, October 19, 2010

Exactly what do you want to do? Where are you trying to add abstraction?

When you say "factories", there are at least two types of factory in CSLA.

There are the static factory methods used to create objects from the interface control layer (like from an MVC controller). And there are the factory objects that run server-side if you use the ObjectFactory attribute with the data portal.

The server-side factory objects are created by a factory loader, which you can replace. In that case you assume responsibility for translating the factory name from the ObjectFactory attribute into an instance of a factory object. How you do that is up to you, and you could certainly use an IoC container there if you'd like.

The client-side factory methods are static by convention, not by necessity. The various CSLA UI helpers (CslaDataSource, ViewModelBase, CslaDataProvider, etc) are all designed to work against static factory methods, so if you don't use static factory methods you can't use those UI helpers. But if you are using ASP.NET MVC that doesn't matter a lot, since there is no UI helper like that for MVC (no need for it).

BUT, there's no requirement that the client-side factory methods actually be static. You can create separate factory objects that contain instance methods that call the data portal in the same way. For example:

public class CustomerFactory
{
  public CustomerEdit GetCustomer(int id)
  {
    return DataPortal.Fetch<CustomerEdit>(id);
  }
}

This is the same code you'd write in a static factory, but now it is in an instance method of a factory object. So now you can use DI to create this factory, so you can call the GetCustomer method.

Of course the next question is whether this gained you anything? In general the answer is no. The data portal is already a multi-layer series of indirections for creating or retrieving business objects. It is virtually impossible to properly create a CSLA business object without using the data portal. So creating an alternative factory that doesn't use the data portal is somewhat problematic.

Then again, for ASP.NET MVC maybe it isn't so terribly bad. Because you could have a whole set of mock business objects that don't really emulate the CSLA objects, and you could have a factory that creates them. That's true.

The thing is, really emulating a CSLA object means implementing the myriad interfaces implemented by BusinessBase (for example). And since most UI code does interact with IDataErrorInfo, INotifyPropertyChanged, ITrackStatus, ISuppressRuleChecking and so forth, your mock object would need to implement all those interfaces too.

In short, you can absolutely use IoC/DI along with separate factory objects to eliminate data portal calls. Just be warned that creating the mock business objects will almost certainly be extremely non-trivial.

Calin replied on Tuesday, October 19, 2010

Hi Rocky,

Thank you very much for your answer. 

The server-side factory objects are created by a factory loader, which you can replace. In that case you assume responsibility for translating the factory name from the ObjectFactory attribute into an instance of a factory object. How you do that is up to you, and you could certainly use an IoC container there if you'd like

The above is what I am really trying to achieve. So my factory will can look something like this:

public class MyFacyFactory : ObjectFactory

{

public MyFacyFactory(ISomeRepository repository, IUser loggedUser)

{

 _repository = repository;

_loggedUser = loggedUser

}

}

 

ISomeRepository and IUser will be injected by Ninject let's say. This will make my live a lot easier when testing the factories and will keep factories more DRY. Now if you could only detail a little about the FactoryLoader and how/where I replace it.

 

Thank you,

RockfordLhotka replied on Tuesday, October 19, 2010

I think the factory loader is discussed in Chapter 18 of Expert 2008 Business Objects. And I know we have unit tests that test the factory loader concept. It isn't something I do daily (usually 0 or 1 time per project) so I can't provide code off the top of my head.

Look at Csla.Server.IObjectFactoryLoader (that's what you implement) and the CslaObjectFactoryLoader config value is what you set to the full type name in your web.config file. CSLA will load that type and use it instead of the default factory loader.

One thing - in terms of current user, remember that the data portal (or Windows) typically impersonates the client user, and Csla.ApplicationContext.User contains that user information. Business objects always use Csla.ApplicationContext.User to apply authz rules, so that value is important. Any testing environment that interacts with business objects must make sure that User property is set.

 

Calin replied on Tuesday, October 19, 2010

Hi,

 

Thanks for the tip, I will take a look at that chapter. I will also have to figure out how to bootstrap the dataaccess assembly, I will need to register the classes in the DI container somehow.

 

Have a nice day,

ajj3085 replied on Tuesday, October 19, 2010

RockfordLhotka
Of course the next question is whether this gained you anything? In general the answer is no. The data portal is already a multi-layer series of indirections for creating or retrieving business objects. It is virtually impossible to properly create a CSLA business object without using the data portal. So creating an alternative factory that doesn't use the data portal is somewhat problematic.

Then again, for ASP.NET MVC maybe it isn't so terribly bad. Because you could have a whole set of mock business objects that don't really emulate the CSLA objects, and you could have a factory that creates them. That's true.

The thing is, really emulating a CSLA object means implementing the myriad interfaces implemented by BusinessBase (for example). And since most UI code does interact with IDataErrorInfo, INotifyPropertyChanged, ITrackStatus, ISuppressRuleChecking and so forth, your mock object would need to implement all those interfaces too.

So this kind of confuses me a bit, since I thought the major benefit to things like MVC and MVVM is you can unit test much of your UI code.  At my current employer, we're using Asp.Net MVC but not using Csla (although I'm trying to get there), and we do have unit tests that interact with mock BOs.  We use Moq and verify that Save or Gets are called as appropriate, or that methods on the BOs are called. 

The other alternative (if we had Csla in the mix) would require us to spin up a mock repository for the actual BOs to use in place of the real database, but it seems that the unit tests would be losing some focus as it would feel  more like an integration test than a unit test. 

What are your thoughts around this?

RockfordLhotka replied on Wednesday, October 20, 2010

My question is this: what are you testing?

If you are using CSLA, the odds of having a controller method that does anything remotely interesting is very small. Or to put it another way, if you have a controller method that does anything interesting, you are probably violating the architecture by putting business logic into the presentation layer.

This isn't to say you shouldn't test the controller methods, it is to say that those tests had better be testing trivial code.

The most complex controller method tends to be the Edit() postback method. That typically has 4 lines of code, using the CslaModelBinder to populate the model from the postback data, then the Csla.Web.Mvc.Controller base class to save the object. So even this "most complex" controller method doesn't actually do anything interesting.

Again, not to say you shouldn't test it - just that the test is mind-numbingly trivial and virtually incidental.

However, to actually invoke this method, you'd need a mock business object that implements the interfaces required by CslaModelBinder and the base class helper methods - which means you'll need to mock the metastate management and broken rules features of CSLA.

And you can surely do that. But my question is whether it is worth it to go to such effort to test a controller method that is so trivial?

In fact, if the model binder doesn't really bind to a real model, and the save helper doesn't really save a real model, then the controller method doesn't actually do anything anymore - well, almost.

I suppose you can determine 2 things: if your mock refuses to save did the Edit method generate the right view (this is the hard scenario, because I assume you'd want to establish that it echoed all the right broken rules into the view through the MVC validation mechanisms), or if the mock allowed the save did the Edit method generate the right view.

The same type of issue exists with a viewmodel. Most viewmodels are mind-numbingly trivial, because all the interesting work is in ViewModelBase, which isn't your code (so you shouldn't be testing it). Yet it is worth testing viewmodel methods, primarily to determine that success/failure causes the viewmodel to navigate to the right place as a result.

But that's about all you can test, because that's all a controller/viewmodel should be doing.

ajj3085 replied on Thursday, October 21, 2010

Hmm, maybe I'm being colored too much by the aritcheture we have at work, or by my web forms application, but there are things like "if x, y and z is true, then send user to this page, otherwise if just z then this page."  Or "make sure method X is called on the BO."  Things that do seem to come up (and come back after being fixed once), especially if one BO managing some kind of workflow.

It's not every exciting, but these things do come up and sometimes the flow from one view to another can be somewhat complex.  I wouldn't want to check that the controller saw broken rule 1 2 and 3, just that it actually  did ask for the broken rules list. or that it did (or didn't) try to do the save.

RockfordLhotka replied on Thursday, October 21, 2010

Fair enough, though that is the kind of logic I'd expect - pure navigation stuff, not really business logic in any meaningful sense.

The question I'm trying to pose is this: just how much time/money are you willing to pay to get a "pure" unit test for that logic? At what point does the cost become so high that you decide it might be OK to create an "impure" test that relies on the actual model, just loaded with known mock data? Because that's radically cheaper than trying to mock enough of BusinessBase to actually get a working test.

This isn't unique to CSLA by the way. Try unit testing a xaml control. To do this, you either need to actually host it in a real visual container (at which point you are doing integration testing) or you need to mock a non-trivial subset of WPF and/or Silverlight. So the same question applies - just how much time/money are we willing to commit to get a pure unit test? For my part, I'm not willing to replicate huge chunks of three XAML runtimes to test my control, so I compromise.

Testing is not an end or goal in and of itself. Just like reuse, it is terribly easy to forget the real goal and focus overmuch on the intermediate tools.

Reuse and testing are a means to an end - and the end is software that is cheaper to build and maintain over its lifetime, while meeting the domain requirements.

In other words, it always, always, always comes down to a financial business decision - cost vs benefit.

Some people might value testing to such a high degree that they choose what I would consider to be inferior overall architectures. While the end result might be highly tested, it might involve a lot more code and complexity - so is that a net win? Perhaps not.

In the end it is a balance. Prioritize code based on what really needs to be tested and test the hell out of that, with less rigorous testing for areas that are less risky. Evaluate whether the cost of writing and maintaining the test can ever be offset by the increased quality.

Yes, I know, I'm a heretic - or would be if I subscribed to this particular religion. Fortunately I don't, so I guess I'm just an infidel :)

Calin replied on Friday, October 22, 2010

Hi,

Thanks for your advice, I've ended up with this implementation that suits all my needs (for now)

    public class ObjectFactoryLoader : IObjectFactoryLoader

    {

        private static IKernel _kernel;

 

        public static IKernel Kernel

        {

            get

            {

                if (_kernel == null)

                {

                    _kernel = new StandardKernel();

 

                    // scan repositories

                    _kernel.Scan(x =>

                                     {

                                         x.FromAssemblyContaining<IDeviceRepository>();

                                         x.Where(type => type.Name.EndsWith("Repository"));

                                         x.BindWithDefaultConventions();

                                         x.InSingletonScope();

                                     });

 

                    // scan factories

                    _kernel.Scan(x =>

                                     {

                                         x.FromAssemblyContaining<IDeviceFactory>();

                                         x.Where(type => type.Name.EndsWith("Factory"));

                                         x.BindWithDefaultConventions();

                                     });

                }

 

                return _kernel;

            }

        }

 

        public Type GetFactoryType(string factoryName)

        {

            return Type.GetType(factoryName);

        }

 

        public object GetFactory(string factoryName)

        {

            var factoryType = Type.GetType(factoryName);

            if (factoryType == null)

            {

                throw new InvalidOperationException(string.Format("Factory type or assembly could not be loaded ({0})", factoryName));

            }

 

            object factory = Kernel.Get(factoryType);

            return factory;

        }

    }

So I can have factories as beautiful as this one:
    public class DeviceListFactory : ObjectFactory, IDeviceListFactory
    {
        [Inject]
        public IDeviceRepository Repository { get; set; }
        [Inject]
        public IDeviceFactory DeviceFactory { get; set; }
        public DeviceList Fetch()
        {
            using (UnitOfWork.Start())
            {
                DeviceList result = (DeviceList)Activator.CreateInstance(typeof(DeviceList), null);
                IList<DeviceEntity> deviceEntities = Repository.Find();
                foreach (var deviceEntity in deviceEntities)
                {
                    Device device = DeviceFactory.CreateLight(deviceEntity);
                    MarkAsChild(device);
                    result.Add(device);
                }
                return result;
            }
        }
    };
I welcome everybody's feedback.
Regards,

RockfordLhotka replied on Friday, October 22, 2010

The type name you provide to the ObjectFactory attribute is the interface type?

This seems very nice, and is exactly the kind of thing we had in mind when designing the factory loader concept. Thank you for sharing your implementation.

ajj3085 replied on Sunday, October 24, 2010

Thanks Rocky, that does make sense.  Hopefully if someone complains about not being as testable as before, I'll be able to eloquently repeat what you said. :-)

Copyright (c) Marimer LLC