LightInject
LightInject copied to clipboard
Discussion: Design Considerations
Hi again. :)
Something that has been on my mind after using LightInject. Everything is super nice, with of course as I previously the mentioned the Decorate method being tops and exactly what I was looking for. The one hitch that I have noticed is the interface design. Currently it is designed as fluent, but with its calls as currently designed, it also can be seen as implying immutability. This, of course, is not the case which is why I wanted to point this out and have a discussion around it.
So consider a simple Register call:
IServiceRegistry Register(Type serviceType, Type implementingType);
This would seem to imply that a new registry would be returned after the call is complete. When in fact it is the same registry. It might make more sense to do the following:
public interface IServiceRegistry
{
void Register(Type serviceType, Type implementingType);
}
public static class ServiceRegistryExtensions
{
public static IServiceRegistry Registering(this IServiceRegistry @this, Type serviceType, Type implementingType)
{
@this.Register(serviceType, implementingType);
return @this;
}
}
There are two considerations here:
- Implied mutability. As mentioned earlier, by returning an instance type, you are (or can be) implying an immutable result. Here, the return type is
voidso that implies that a behavior or action is being applied to the current object state (mutable and/or side effects). - Easier to extend. An extending consumer will have an easier time implementing your
IServiceRegistryinterface above, as there should be much less operations on it to implement. For instance, the genericRegisterinterface is really calling the non-generic interface method above. An extending consumer shouldn't need to worry about implementing this, but currently has to do so. If this was moved to an extension method, it would be less required surface area to implement in case a consumer wishes to (for instance) decorateIServiceRespositoryfor their own solutions.
Now this certainly is all open to discussion and I would be very interested to hear your thoughts around this. From my perspective, I have been reading the likes of the following articles so they comprise the basis of my understanding:
http://blog.ploeh.dk/2016/05/06/cqs-and-server-generated-entity-ids/ http://blog.ploeh.dk/2014/08/11/cqs-versus-server-generated-ids/
There is also the whole idea of immutability/mutability which is something that functional programming seems to hold as a prized tenet, and something that I have been trying to incorporate in my own code as well. But, I can certainly concede that maybe it's the new shiny hammer that I am trying to hit every nail with. :)
Some good, recent articles here that I have read and compelled me to start this thread to share: http://sidburn.github.io/blog/2017/02/27/mutability-vs-immutability-validation http://sidburn.github.io/blog/2016/03/14/immutability-and-pure-functions
Hope you don't find this intrusive! FWIW, LightInject has been incorporated as the internal container solution for the extension model over over at the v2 efforts at ExtendedXmlSerializer. I ended up wrapping/decorating the IServiceRepository which ended up in some work as described above, so I wanted to share here and get your feedback as such.
We of course will give you credit in our readme once we complete v2 (hopefully in the next few weeks). I know @wojtpl2 was excited to see that you offer the super convenient ability to pull source directly into projects. 😄
Please let me know if there is anything else you would like to see and we will certainly accommodate. 👍
First of all, thanks a lot for valuable input. I'm just gonna quickly comment on the things I already have an answer for here :) Busy day at work :)
Register extension methods
As you may have noticed we did exactly this for the IServiceFactory interface. The IServiceFactory interface only contains the "core" GetInstance methods and the rest of the methods are implemented as extension methods. In fact we needed to do this when we started allowing services to be resolved directly from a given scope.
I just recently had to implement IServiceRegistry myself in an application at work and I clearly see that we could benefit from taking the same approach there. It would also provide a more consistent design all over. Then we would only need to implement just a few methods and the rest would be taken care of using extension methods. That is probably something that will be prioritized rather quickly.
Now, on the question on whether a fluent API always implies immutability, I am not so sure. I do however see your point as many fluent API's does exactly that.
The reason we made the API "fluent" was to remove clutter from the composition root and we are probably not going to revert this or make this somehow immutable, but again I need to think this through :)
Cool. :) Yeah, it's just some thoughts I had, and admittedly I think I am coming at this from a F#/functional-ish point of view. To be clear, I am saying that interface methods imply immutability. If I saw a void interface method and a static extension method return the type of the extending instance, I would not think this, but would rather think fluent, as suggested.
But again that is just me and I have had my nose is functionalish concepts for some time now.
So cool, seems like you have already put some thought into this and have experience already doing so. That was really all my intention with this issue. You can feel free to close it if you wish or to keep it open if you intend it for a placeholder for the prioritization you mention. Thanks again for your time and consideration!