DI/IoC, NHibernate and help in getting them to work together

后端 未结 6 1721
再見小時候
再見小時候 2020-12-30 16:20

I\'m trying to get my head around DI/IoC, NHibernate and getting them to work nicely together for an application that i\'m developing. I\'m quite new to both NHibernate and

相关标签:
6条回答
  • 2020-12-30 16:52

    Take a look to this post http://fabiomaulo.blogspot.com/2008/11/entities-behavior-injection.html

    0 讨论(0)
  • 2020-12-30 16:59

    Pablo,

    Thanks for you comments.

    Maybe if i elaborate a bit more on one area where i intend to use DI within the project (not only, as you say, to learn about DI but also because i think it's necessary) and then further comments can be made as to whether it's the correct place to be using DI.

    As mentioned in the original post the application will make use of a MarginCalculator Service:

    public abstract class MarginCalculator
    {
        public abstract double CalculateMargin();
    }
    

    Note: the service might be an abstract class or an Interface.

    Concrete implementations (components in DI terminology?) will be as follows:

    public class ConcreteMarginCalculatorA : MarginCalculator
    {
        private IDependencyService1 _dependencyService1;
        private IDependencyService2 _dependencyService2;
    
        // Constructor dependency injection
        public ConcreteMarginCalculatorA(
            IDependencyService1 dependencyService1,
            IDependencyService2 dependencyService2)
        {
            this._dependencyService1 = dependencyService1;
            this._dependencyService2 = dependencyService2;
        }
    
        public override double CalculateMargin
        {
            // _dependencyService1 and _dependencyService2 
            // required here to perform calcuation.
        }
    }
    
    public class ConcreteMarginCalculatorB : MarginCalculator
    {
        private IDependencyService3 _dependencyService3;
        private IDependencyService4 _dependencyService4;
    
        // Constructor dependency injection
        public ConcreteMarginCalculatorB(
            IDependencyService3 dependencyService3,
            IDependencyService4 dependencyService4)
        {
            this._dependencyService3 = dependencyService3;
            this._dependencyService4 = dependencyService4;
        }
    
        public override double CalculateMargin
        {
            // _dependencyService3 and _dependencyService4 
            // required here to perform calcuation.
        }
    }
    

    Aren't the concrete Margin Calculators and their construction a perfect example of where dependency injection should be used and how an IoC container can be used to handle the dependency injection?

    I think what i'm trying to do is very similar to how DI/IoC are described in articles such as this one and this one.

    Finally, i will then use a factory class, possibly with an inner/child container, in order to dynamically resolve components/implementors (ConcreteMarginCalculatorA, ConcreteMarginCalculatorB etc...) based on a parameter value. To achieve this i'm leaning toward Autofac (http://code.google.com/p/autofac/) which allows for selecting an implementor based on a parameter value (http://code.google.com/p/autofac/wiki/ComponentCreation - Section "Selection of an Implementer based on a Parameter Value"):

    public class MarginCalculatorFactory
    {
        private readonly IContainer _factoryLevelContainer;
    
        public MarginCalculatorFactory(IContainer mainContainer)
        {
            _factoryLevelContainer = mainContainer.CreateChildContainer()
            _factoryLevelContainer.RegisterType<MarginCalculator, ConcreteMarginCalculatorA>("ConcMC1");
            _factoryLevelContainer.RegisterType<MarginCalculator, ConcreteMarginCalculatorB>("ConcMC2");
    }
    
    public MarginCalculator CreateCalculator(string productType)
    {
        return _factoryLevelContainer.Resolve<MarginCalculator>(productType);
    }
    

    }

    So that in the end i can do:

    marginCalculatorFactory.CreateCalculator(productType);
    

    in the client code and get a fully resolved calculator. The calculator could then in turn be dependency injected into the TransactionProcessor Service:

    public class TransactionProcessor
    {
        private readonly MarginCalculator _marginCalculator;
        private readonly Transaction _transaction;
    
        public TransactionProcessor(MarginCalculator marginCalculator
            ,Transaction transaction)
        {
                _marginCalculator = marginCalculator;
                _transaction = transaction
        }
    
        public double CalculateMargin(Transaction t)
        {
                return _marginCalculator.CalculateMargin(transaction);
        }
    }
    

    I might be wrong as i'm new to the whole IoC/DI game but it seems to me that this is precisely the kind of scenario that Di/IoC is used for. What do others think?

    Thanks

    Matthew

    0 讨论(0)
  • 2020-12-30 17:01

    Here's my second take on your questions:

    A: In terms of best practice, you can leave the service dependency into the domain object as long as you make sure that you're depending on an interface type. Most (if not all) containers can do that type of injection for you, and it's pretty trivial to mock out each service dependency so you can test every behavior in your concrete classes. I only recommend using abstract classes if you want to refactor out the boilerplate implementation for a particular interface implementation, such as using a base class to do your generic CRUD persistence work.

    B and C:

    It's good to know that this kind of functionality is available. I suppose a more important question is whether what i'm trying to do is in fact common practice and whether it's considered good practice. i.e.

    1. Have a container resolve and inject dependencies that have been pre-populated >using a persistence framework (e.g. NHibernate) and
    2. Have the container inject concrete implementation of abstract dependencies where the concrete implementation are determined at runtime.

    Also, in IoC/DI/NHibernate terminology, does what i'm talking about, have a particular name? Is it, for example, one of the features listed in this comparison or this comparison of .net IoC frameworks? I'd like to read about whether other IoC frameworks (like Castle Windsor) include these functionalities like LinFu does but i don't know whether what i'm describing has a particular name so i don't know what to search for :)

    I believe you're actually referring to the comparison posted at this link.

    1) AFAIK, it's standard practice to do service injection, but the type of injection that you're referring to would be difficult to do for some of the other frameworks since you have to use domain object IDs to resolve these dependencies at run time, and not all containers support that type of dynamic resolution (aka 'contextual binding'). All things being equal (and assuming that this can be done with the other containers), the only 'best practice' that seems to apply with DI/IoC is that you must use interfaces for your service dependencies.

    How these dependencies should be ultimately constructed and resolved should be completely up to you, and in your case, it really doesn't matter if you get these dependencies populated from a persistence framework as long as the container itself is able to eliminate most of the boilerplate resolution code for you.

    2) Concrete service injection is standard among DI/IOC frameworks, and most of them can resolve dependencies at runtime; however, these frameworks differ on how and where that injection can be done.

    FYI, the two features that you should pay attention to are Constructor Injection and Property Injection. Based on your code examples, I'd say that you'd be more inclined to use constructor injection, so you might want to keep an eye out for how each respective framework does that type of injection for you. HTH :)

    0 讨论(0)
  • 2020-12-30 17:10

    A) If you're going to access the MarginCalculator through the Product domain object, you might as well cut out the middle man and let the DI/IOC container inject the MarginCalculator for you. You can even get rid of the MarginCalculatorAssembler because most DI/IOC containers do most of the boilerplate code of object construction for you.

    B and C) It's very possible. In fact, here's how your code would look like if you used LinFu:

    
    // No need to change the Transaction class
    public class Transaction
    {
        private double _margin;
        private Product _product;
        private Client _client;
    
        public double Margin { get; }
        public Product Product { get; }
        public Client Client { get; }
    
        public Transaction(Product p, Client c)
        {
            _product = p;
            _client = c;
        }
    
        public void CalculateMargin()
        {
            _margin = _product.MarginCalculator.CalculateMargin();
        }
    }
    

    It would be nice if you could get a DI/IOC to inject the product and client instances into the constructor--but before we do that, you need to register the dependencies with the container. Here's how you do it with LinFu.IOC:

    // Next, you'd have to tell LinFu to automatically register your product class:
    [Factory(typeof(Product))]
    public class ProductFactory : IFactory
    {
         object CreateInstance(IServiceRequest request)
         {
              // Grab a copy of the IRepository from the container
              var repository = container.GetService>();
    
              // Get the id (this assumes that your id is an Int32)
              var id = (int)request.Arguments[0];
    
              // Return the product itself
              return repository.GetById(id);
         }
    }
    
    // Do the same thing with the Client class
    // (Note: I did a simple cut and paste to keep things simple--please forgive the duplication)
    [Factory(typeof(Client))]
    public class ClientFactory : IFactory
    {
         object CreateInstance(IServiceRequest request)
         {
              // Grab a copy of the IRepository from the container
              var repository = container.GetService>();
    
              // Get the id (this assumes that your id is an Int32)
              var id = (int)request.Arguments[0];
    
              // Return the client itself
              return repository.GetById(id);
         }
    }
    
    [Factory(typeof(Transaction))]
    public class TransactionFactory : IFactory
    {
         object CreateInstance(IServiceRequest request)
         {
            // Note: Argument checking has been removed for brevity
            var container = request.Container;
            var arguments = request.Arguments;
            var productId = (int)arguments[0];
            var clientId = (int)arguments[1];
    
            // Get the product and the client
            var product = container.GetService(productId);
            var client = container.GetService(clientId);
    
            // Create the transaction itself
            return new Transaction(product, client);
         }
    }
    
    // Make this implementation a singleton
    [Implements(typeof(MarginCalculator), LifecycleType.Singleton)]
    public class ConcreteMarginCalculatorA : MarginCalculator
    {
        public override double CalculateMargin()
        {
            // Perform actual calculation
        }
    }
    

    Once you have all that code compiled in one of your assemblies, here's all you need to do to load it into the container:

    var container = new ServiceContainer();
    container.LoadFrom(AppDomain.CurrentDomain.BaseDIrectory, "YourAssembly.dll");
    

    ...Now for the fun part. In order to create your transaction object with the given product and client ID, here's the call you need to make to LinFu.IOC's container:

    int productId = 12345;
    int clientId = 54321;
    string serviceName = null;
    
    // Not pseudocode :)
    var transaction = container.GetService(serviceName, productId, clientId);
    

    What makes this interesting is that despite the number of dependencies you might have, LinFu's IOC container will handle 90% of the boilerplate code for you so you don't have to do all this stuff on your own. The best part is that all the implementations above will all be determined/resolved at runtime.

    You can practically swap implementations while the program is running, and you can even replace implementations without even recompiling your application. You can find more info here:

    http://www.codeproject.com/KB/cs/LinFu_IOC.aspx

    HTH :)

    0 讨论(0)
  • 2020-12-30 17:11

    Philip,

    Thanks for your answer!

    B and C:

    It's good to know that this kind of functionality is available. I suppose a more important question is whether what i'm trying to do is in fact common practice and whether it's considered good practice. i.e.

    1. Have a container resolve and inject dependencies that have been pre-populated using a persistence framework (e.g. NHibernate) and
    2. Have the container inject concrete implementation of abstract dependencies where the concrete implementation are determined at runtime.

    Also, in IoC/DI/NHibernate terminology, does what i'm talking about, have a particular name? Is it, for example, one of the features listed in this comparison or this comparison of .net IoC frameworks? I'd like to read about whether other IoC frameworks (like Castle Windsor) include these functionalities like LinFu does but i don't know whether what i'm describing has a particular name so i don't know what to search for :)

    A:

    In terms of best practice (i.e. loose coupling, testing etc...), would it be better to remove the service dependency from the domain object or leave it there?

    Thanks

    Matthew

    0 讨论(0)
  • 2020-12-30 17:12

    According to 'Domain Driven Design', your service would be a 'Domain Service', and it's ok for the rest of your domain to call it directly or depend on it.

    If you're going to use Nhibernate, check Spring.net, a very popular DI framework that provides you with DAOS, that already have a session injected on them. It also allows you to use declarative transactions (marking methods with attributes). The docs of the project are very very good.

    Last but not least, and don't get me wrong, I think you are using the technology just because (I don't see that you have the NEED for DI), this is cool if you're doing it to learn stuff, but wrong in every other case.

    Regards

    0 讨论(0)
提交回复
热议问题