Reputation: 2876
I have an IoC question that for the moment is abstract. I have not yet chosen an IoC framework for started coding. I am still mentally planning the methods I am going to use for an imminent project.
My coding style generally follows this pattern:
My problem is this. I don't like that my AccessLayer in turn needs to pull the connection string from the config file, for instance. Maybe I want my users to be able to specify a config file or a Db Table for settings. Having the access layer check the config file to see if it should use the config file is circular and silly. And the Access Layer cannot likewise call a Persistence object to pull the settings, or query the Application Framework to see if it is a web app with a Web.Config or a desktop app with DbSettings.
So I was thinking that the best thing for me to do is to use an IoC container of some kind. I could then inject whatever settings I needed. This could also allow me to mock objects for testing, which is another difficult (but not impossible) task with my current method. So from my reading, my vague Processor implementation would look like this:
public class VagueProcessor{
public VagueProcessor(IValidator validator,
IPersistence persistence,
IAccessLayer accessLayer,
ISettings settings) { ... }
}
Here is my snag. In the application I am planning, the Business Object have a variety of implementations each with their own configurable rules. Say one BO is for the state of CA and another for the state of NY, and both states have their own special rules to be validated by their governing bodies. So the validator could be a CAValidator or a NYValidator just depending on the state of the Business Object.
Ok, so my question after all that preamble and backstory is this: in this scenario, would I pass a ValidatorFactory to the Processor and the Factory would instantiate the appropriate type of Validator based on the state of the Business Object? And if so, would I register each type with the IoC container, or just the Factory?
Thanks for your thoughts on this matter!!
Upvotes: 1
Views: 901
Reputation: 56869
Validation is a crosscutting concern, so typically the validation service doesn't know about the details of the object it is validating. It only knows about its boolean valid state and how to get validation errors that are typically displayed on the UI.
As a crosscutting concern, the validation rules are abstracted from the services that read them. This is usually done via an interface and/or .NET attributes.
public class ValidateMe : IValidatableObject
{
[Required]
public bool Enable { get; set; }
[Range(1, 5)]
public int Prop1 { get; set; }
[Range(1, 5)]
public int Prop2 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!this.Enable)
{
/* Return valid result here.
* I don't care if Prop1 and Prop2 are out of range
* if the whole object is not "enabled"
*/
}
else
{
/* Check if Prop1 and Prop2 meet their range requirements here
* and return accordingly.
*/
}
}
}
The validation service then only needs to have a mechanism to process the rules (returning a true/false for each rule) in order to ensure all of them are valid, and a way to retrieve the errors for display.
The validation service can do all of this by simply passing the model (the runtime state) to the service.
if (validationService.IsValid(model));
{
// persist
}
This can also be done using a proxy pattern to ensure that it always happens if the interface and/or attributes are available to process.
NOTE: The term Business Object implies that you want to build some sort of Smart Object Framework using objects that know how to save and retrieve their own state (internally implementing CRUD). This sort of design doesn't lend itself to DI very well. That isn't to say you can't use DI and a Smart Object design at the same time, it is just more difficult to build, more difficult to test, and then more difficult to maintain.
A design that uses models to abstract the runtime state of the application away from the services that use the models makes for an easier path. A design that I have found works pretty well for some applications is Command Query Segregation, which turns every update or request for data into its own object. It works well with a proxy or a decorator pattern to implement crosscutting concerns. It sounds strange if you are used to working with smart objects, but a loosely coupled design like this is simpler to test which makes it just as reliable, and since query and command classes are used like
var productDetails = this.queryProcessor.Execute(new GetProductDetailsQuery
{
ProductId = id
});
Or
// This command executes a long and complicated workflow,
// but this is all that is done inside of the action method
var command = new AddToCartCommand
{
ProductId = model.Id,
Quantity = model.Qty,
Selections = model.Selections,
ShoppingCartId = this.anonymousIdAccessor.AnonymousID
};
this.addToCartHandler.Handle(command);
it is almost as easy to use. You can even easily break out different steps of a complicated workflow into their own commands so it can be tested and verified at each step of the way, which is something that is difficult to do on a smart object design.
Upvotes: 1
Reputation: 4958
That's a vague question as you don't have a problem yet, only the idea.
From what I understand from your question, I'd say:
The IOC solves the problem of creating the new object, not exactly deciding which object to create. In most IOC containers you can at some level choose the implementation you're asking, but in your case the logic looks very application centric, and no IOC container will help you deciding which one to use. In that case, you should indeed have a factory passed to your processor where you can ask something like factory.CreateValidatorFrom(myBusinessObject)
.
Internally, that factory can still use DI to instantiate each component. If you use .NET Core DI for example, you can pass a IServiceProvider
to the factory, and call inside the factory serviceProvider.GetService<CAValidator>()
. All DI providers will have an object like that.
So, in a sense, the factory and the DI can co-exist and each of them solve part of the problem. If you're using DI, you shouldn't ever have to instantiate the actual class. That will make it easier for each validator to have their own dependencies and you don't have to care how to get them.
And yes, in that case you'd register each validator in the DI, and also the factory. In cases like this, you can easily loop through all of them through reflection and register them dynamically by name or interface, if that is bothering you.
And in the end, if you're using .NET Core, I strongly suggest you to simply use the built-in DI. It's simple and good enough for most cases.
Upvotes: 2