A brainteaser for you!
I am developing a modular system, in such a way that module A could need module B and module B could also need module A. But if module B is disabled, it will simply not execute that code and do nothing / return null.
A little bit more into perspective:
Let's say InvoiceBusinessLogic
is within module "Core". We also have a "Ecommerce" module which has a OrderBusinessLogic
. The InvoiceBusinessLogic
could then look like this:
public class InvoiceBusinessLogic : IInvoiceBusinessLogic
{
private readonly IOrderBusinessLogic _orderBusinessLogic;
public InvoiceBusinessLogic(IOrderBusinessLogic orderBusinessLogic)
{
_orderBusinessLogic = orderBusinessLogic;
}
public void UpdateInvoicePaymentStatus(InvoiceModel invoice)
{
_orderBusinessLogic.UpdateOrderStatus(invoice.OrderId);
}
}
So what I want is: When the module "Ecommerce" is enabled, it would actually do something at the OrderBusinessLogic
. When not, it would simply not do anything. In this example it returns nothing so it can simply do nothing, in other examples where something would be returned, it would return null.
Notes:
- As you can probably tell, I am using Dependency Injection, it is a ASP.NET Core application so the
IServiceCollection
takes care of defining the implementations. - Simply not defining the implementation for
IOrderBusinessLogic
will cause a runtime issue, logically. - From a lot of research done, I do not want to make calls to the container within my domain / logic of the app. Don't call the DI Container, it'll call you
- These kind of interactions between modules are kept to a minimum, preferably done within the controller, but sometimes you cannot get around it (and also in the controller I would then need a way to inject them and use them or not).
So there are 3 options that I figured out so far:
- I never make calls from module "Core" to module "Ecommerce", in theory this sounds the best way, but in practice it's more complicated for advanced scenarios. Not an option
- I could create a lot of fake implementations, depending on the configuration decide on which one to implement. But that would of course result in double code and I would constantly have to update the fake class when a new method is introduced. So not perfectly.
- I can build up a fake implementation by using reflection and
ExpandoObject
, and just do nothing or return null when the particular method is called.
And the last option is what I am now after:
private static void SetupEcommerceLogic(IServiceCollection services, bool enabled)
{
if (enabled)
{
services.AddTransient<IOrderBusinessLogic, OrderBusinessLogic>();
return;
}
dynamic expendo = new ExpandoObject();
IOrderBusinessLogic fakeBusinessLogic = Impromptu.ActLike(expendo);
services.AddTransient<IOrderBusinessLogic>(x => fakeBusinessLogic);
}
By using Impromptu Interface, I am able to successfully create a fake implementation. But what I now need to solve is that the dynamic object also contains all the methods (mostly properties not needed), but those ones are easy to add. So currently I am able to run the code and get up until the point it will call the OrderBusinessLogic
, then it will, logically, throw an exception that the method does not exist.
By using reflection, I can iterate over all the methods within the interface, but how do I add them to the dynamic object?
dynamic expendo = new ExpandoObject();
var dictionary = (IDictionary<string, object>)expendo;
var methods = typeof(IOrderBusinessLogic).GetMethods(BindingFlags.Public);
foreach (MethodInfo method in methods)
{
var parameters = method.GetParameters();
//insert magic here
}
Note: For now directly calling typeof(IOrderBusinessLogic)
, but later I would iterate over all the interfaces within a certain assembly.
Impromptu has an example as follows:
expando.Meth1 = Return<bool>.Arguments<int>(it => it > 5);
But of course I want this to be dynamic so how do I dynamically insert the return type and the parameters.
I do understand that a interface acts like a contract, and that contract should be followed, I also understand that this is an anti-pattern, but extensive research and negotiations have been done prior to reaching this point, for the result system we want, we think this is the best option, just a little missing piece :).
- I have looked at this question, I am not really planning on leaving .dll's out, because most likely I would not be able to have any form of
IOrderBusinessLogic
usable withinInvoiceBusinessLogic
. - I have looked at this question, but I did not really understand how TypeBuilder could be used in my scenario
- I have also looked into Mocking the interfaces, but mostly you would then need to define the 'mocking implementation' for each method that you want to change, correct me if I am wrong.
For as long as there is no other answer for the solution I am looking for, I came up with the following extension:
And then use it like:
And actually it has the advantage that it is clear (in the code) that the return type can be null or the method won't be called when the module is disabled. The only thing that should be documented carefully, or in another way enforced, that is has to be clear which classes do not belong to the current module. The only thing I could think of right now is by not including the
using
automatically, but use the full namespace or add summaries to the included_orderBusinessLogic
, so when someone is using it, it is clear this belongs to another module, and a null check should be performed.For those that are interested, here is the code to correctly add all fake implementations: