DI / IoC, NHibernate и помощь в их совместной работе - PullRequest
5 голосов
/ 23 декабря 2008

Я пытаюсь разобраться с DI / IoC, NHibernate и заставить их хорошо работать вместе для приложения, которое я разрабатываю. Я довольно новичок в NHibernate и DI / IoC, поэтому не совсем уверен, что то, что я делаю, является разумным способом добиться этого. Это сценарий:

Приложение предоставляет пользователям возможность рассчитать конкретное значение (известное как маржа) для конкретной финансовой транзакции. Расчет значения разметки для каждой транзакции выполняется с помощью конкретных реализаций абстрактного класса MarginCalculator, а конкретная используемая реализация зависит от типа продукта для конкретной транзакции (заданной определенным полем объекта продукта). Доступ к конкретному классу калькулятора осуществляется через свойство класса продукта. т.е.

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();
    }
}

public class Product
{
    private string _id;
    private string _productType;
    ... Other fields

    public string Id { get; }
    public string ProductType { get; }
    public MarginCalculator MarginCalculator
    {
        get { return MarginCalculatorAssembler.Instance.CreateMarginCalculatorFor(this.ProductType); }
    }
}

public class MarginCalculatorAssembler
{
    public static readonly MarginCalculatorAssembler Instance = new MarginCalculatorAssembler();

    private MarginCalculatorAssembler ()
    {
    }

    public MarginCalculator CreateMarginCalculatorFor(string productType)
    {
        switch (productType)
        {
            case "A":
                return new ConcreteMarginCalculatorA();
            case "B":
                return new ConcreteMarginCalculatorB();
            default:
                throw new ArgumentException();
        }
    }
}

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

public class ConcreteMarginCalculatorA : MarginCalculator
{
    public override double CalculateMargin
    {
        // Perform actual calculation
    }
}

public class ConcreteMarginCalculatorB : MarginCalculator
{
    public override double CalculateMargin
    {
        // Perform actual calculation
    }
}

Пользователи выбирают определенный клиент и продукт из раскрывающихся списков, а соответствующие clientId и productId передаются в репозитории, которые затем используют NHibernate для заполнения объектов product и client перед их внедрением в объект транзакции. В моей текущей настройке Транзакция получает свои зависимости Product и Client посредством внедрения зависимостей конструктора (пока еще не использовался контейнер IoC), т.е.

public class ProductRepository : IRepository<Product>
{
    public Product GetById(string id)
    {
        using (ISession session = NHibernateHelper.OpenSession())
            return session.Get<Product>(id);
    }
}

/* Similar repository for Clients */

IRepository<Client> clientRepository = new ClientRepository();
IRepository<Product> productRepository = new ProductRepository();
Client c = clientRepository.GetById(clientId);
Product p = productRepository.GetById(productId);

Transaction t = new Transaction(p, c);

Вот то, на что я надеюсь получить идеи:

A. Считается ли нормальным доступ к MarginCalculator (который по сути является службой) через объект домена Product или должен, как предлагается здесь, (/236783/vnedrenie-zavisimostei-s-obektami-nhibernate) код должен быть реструктурирован таким образом, чтобы удалить служебные зависимости из объектов домена и вместо этого создать новый класс TransactionProcessor, который принимает абстрактный MarginCalculator в качестве зависимости (в соответствии с тем, что описано здесь (http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/03/31/ptom-the-dependency-inversion-principle.aspx) т.е.

public class TransactionProcessor
{
    private readonly MarginCalculator _marginCalculator;

    public TransactionProcessor(MarginCalculator marginCalculator)
    {
        _marginCalculator = marginCalculator;
    }

    public double CalculateMargin(Transaction t)
    {
        return _marginCalculator.CalculateMargin(Transaction t);
    }
}

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

B. Можно ли использовать контейнер IoC для получения объекта транзакции с введенной зависимостью заполненных / сгенерированных продуктов и клиентов NHibernate? т.е. ProductId и clientId, оба предоставленные пользователем, могут иметь что-то вроде:

// pseudocode
Transaction t = IoC.Resolve<Transaction>(productId, clientId);

таким образом, что контейнер разрешает зависимости Product и Client объекта Transaction, NHibernate используется для заполнения Product и Client на основе productId и clientId, а затем заполненные Product и Client вводятся в транзакцию?

C. В типичном сценарии DI, если класс A зависит от интерфейса B, тогда может быть сделано следующее:

IInterfaceB b = new ClassB();
A a = new A(b);

interface IInterfaceB
{
}

class B : IInterfaceB
{
}

public class A
{
    private IIntefaceB _b;

    public A(IInterfaceB b)
    {
        _b = b;
    }
}

Тем не менее, это, как практически все примеры DI, предполагается, что разработчик IInterfaceB (в данном случае класса B) известен во время разработки. Есть ли способ использовать DI таким образом, чтобы средство реализации определялось во время выполнения?

Большое спасибо

Мэтью

Ответы [ 6 ]

1 голос
/ 25 декабря 2008

Вот мой второй ответ на ваши вопросы:

A: С точки зрения наилучшей практики, вы можете оставить зависимость службы в объекте домена, если убедитесь, что вы зависите от типа интерфейса. Большинство (если не все) контейнеры могут делать такие инъекции для вас, и довольно просто смоделировать каждую зависимость службы, чтобы вы могли проверить каждое поведение в ваших конкретных классах. Я рекомендую использовать абстрактные классы только в том случае, если вы хотите реорганизовать стандартную реализацию для конкретной реализации интерфейса, например, использовать базовый класс для выполнения вашей общей работы над сохранением CRUD.

B и C:

Приятно осознавать, что такая функциональность доступна. Я полагаю, что более важный вопрос заключается в том, является ли то, что я пытаюсь сделать, на самом деле обычной практикой и считается ли это хорошей практикой. т.е. * +1008 *

  1. Имейте в виду, что контейнер разрешает и вводит зависимости, которые были предварительно заполнены,> используя постоянную среду (например, NHibernate) и
  2. Пусть контейнер внедрит конкретную реализацию абстрактных зависимостей, где конкретная реализация определяется во время выполнения.

Кроме того, в терминологии IoC / DI / NHibernate то, о чем я говорю, имеет конкретное имя? Это, например, одна из функций, перечисленных в этом сравнении или в этом сравнении .net IoC Framework? Я хотел бы прочитать о том, включают ли другие платформы IoC (например, Castle Windsor) эти функции, как у LinFu, но я не знаю, имеет ли то, что я описываю, конкретное имя, поэтому я не знаю, что искать :)

Я полагаю, что вы на самом деле имеете в виду сравнение, опубликованное по по этой ссылке .

1) AFAIK, это стандартная практика для внедрения службы, но тип внедрения, на который вы ссылаетесь, будет труден для некоторых других структур, так как для разрешения этих зависимостей необходимо использовать идентификаторы объектов домена. во время выполнения, и не все контейнеры поддерживают этот тип динамического разрешения (он же «контекстная привязка»). При прочих равных условиях (и при условии, что это может быть сделано с другими контейнерами), единственная «лучшая практика», которая, кажется, применима к DI / IoC, - это то, что вы должны использовать интерфейсы для служебных зависимостей.

То, как эти зависимости должны быть в конечном итоге построены и разрешены, должно полностью зависеть от вас, и в вашем случае, действительно, не имеет значения, заполняете ли вы эти зависимости из среды персистентности, если сам контейнер способен устранить большая часть стандартного кода разрешения для вас.

2) Внедрение конкретного сервиса является стандартным для сред DI / IOC, и большинство из них может разрешать зависимости во время выполнения; однако эти рамки различаются в зависимости от того, как и где можно выполнить эту инъекцию.

К вашему сведению, следует обратить внимание на две функции: Инъекция конструкторов и Инъекция свойств . Основываясь на ваших примерах кода, я бы сказал, что вы будете более склонны использовать инъекцию конструктора, поэтому вы, возможно, захотите следить за тем, как каждая соответствующая среда делает такой тип внедрения для вас. HTH:)

1 голос
/ 23 декабря 2008

A) Если вы собираетесь получить доступ к MarginCalculator через объект домена Product, вы также можете вырезать посредника и позволить контейнеру DI / IOC ввести MarginCalculator для вас. Вы даже можете избавиться от MarginCalculatorAssembler, потому что большинство контейнеров DI / IOC выполняют большую часть стандартного кода построения объекта для вас.

B и C ) Это очень возможно. Фактически, вот как будет выглядеть ваш код, если вы используете 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();
    }
}

Было бы неплохо, если бы вы могли получить DI / IOC для внедрения экземпляров продукта и клиента в конструктор - но прежде чем мы это сделаем, вам нужно зарегистрировать зависимости в контейнере. Вот как вы делаете это с 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
    }
}

Как только у вас есть весь этот код, скомпилированный в одной из ваших сборок, вот все, что вам нужно сделать, чтобы загрузить его в контейнер:

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

... Теперь самое интересное. Чтобы создать объект транзакции с указанным идентификатором продукта и клиента, необходимо выполнить вызов контейнера LinFu.IOC:

int productId = 12345;
int clientId = 54321;
string serviceName = null;

// Not pseudocode :)
var transaction = container.GetService(serviceName, productId, clientId);

Что делает это интересным, так это то, что, несмотря на количество зависимостей, которые вы можете иметь, контейнер IOC LinFu будет обрабатывать 90% стандартного кода для вас, поэтому вам не придется делать все это самостоятельно. Самое приятное то, что все реализации, перечисленные выше, все будут определены / разрешены во время выполнения .

Вы можете практически менять реализации во время работы программы, и вы даже можете заменить реализации, даже не перекомпилировав ваше приложение. Вы можете найти больше информации здесь:

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

HTH:)

0 голосов
/ 25 декабря 2008
0 голосов
/ 23 декабря 2008

Пабло,

Спасибо за ваши комментарии.

Может быть, если я подробнее остановлюсь на одной области, где я намереваюсь использовать DI в рамках проекта (не только, как вы говорите, чтобы узнать о DI, но и потому, что я считаю это необходимым), а затем дополнительные комментарии могут быть сделаны в виде о том, является ли это правильным местом для использования DI.

Как упоминалось в исходном сообщении, приложение будет использовать Сервис MarginCalculator:

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

Примечание: сервис может быть абстрактным классом или интерфейсом.

Конкретные реализации (компоненты в терминологии DI?) Будут выглядеть следующим образом:

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.
    }
}

Разве конкретные Калькуляторы маржи и их конструкция не являются идеальным примером того, где следует использовать внедрение зависимостей и как можно использовать контейнер IoC для обработки внедрения зависимостей?

Я думаю, что то, что я пытаюсь сделать, очень похоже на то, как DI / IoC описаны в таких статьях, как этот и этот .

Наконец, я буду использовать фабричный класс, возможно, с внутренним / дочерним контейнером, для динамического разрешения компонентов / разработчиков (ConcreteMarginCalculatorA, ConcreteMarginCalculatorB и т. Д.) На основе значения параметра. Чтобы достичь этого, я склоняюсь к Autofac (http://code.google.com/p/autofac/), который позволяет выбрать разработчика на основе значения параметра (http://code.google.com/p/autofac/wiki/ComponentCreation - Раздел «Выбор исполнителя на основе Значение параметра "):

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);
}

}

Так что в итоге я могу сделать:

marginCalculatorFactory.CreateCalculator(productType);

в коде клиента и получите полностью разрешенный калькулятор. В свою очередь, калькулятор может быть введен зависимостью в Сервис TransactionProcessor:

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);
    }
}

Я могу ошибаться, поскольку я новичок во всей игре IoC / DI, но мне кажется, что это точно сценарий, для которого используется Di / IoC. Что думают другие?

Спасибо

Мэтью

0 голосов
/ 23 декабря 2008

Согласно «Управляемому доменом дизайну», ваша служба будет «Доменной службой», и остальная часть вашего домена может напрямую вызывать ее или зависеть от нее.

Если вы собираетесь использовать Nhibernate, проверьте Spring.net, очень популярную платформу DI, предоставляющую вам DAOS, в которую уже вставлен сеанс. Это также позволяет вам использовать декларативные транзакции (методы маркировки с атрибутами). Документы проекта очень хорошие.

Последнее, но не менее важное, и не поймите меня неправильно, я думаю, что вы используете технологию только потому, что (я не вижу, что у вас НУЖНО для DI), это здорово, если вы делаете это учить вещи, но в любом другом случае это неправильно.

Привет

0 голосов
/ 23 декабря 2008

Philip

Спасибо за ваш ответ!

B и C :

Приятно осознавать, что такая функциональность доступна. Я полагаю, что более важный вопрос - , является ли то, что я пытаюсь сделать, на самом деле обычной практикой и считается ли это хорошей практикой. , т.е.

  1. Имейте в виду, что контейнер разрешает и вводит зависимости, которые были предварительно заполнены, используя постоянную среду (например, NHibernate) и
  2. Пусть контейнер внедрит конкретную реализацию абстрактных зависимостей, где конкретная реализация определяется во время выполнения.

Также, в терминологии IoC / DI / NHibernate, имеет ли то, о чем я говорю, конкретное имя? Это, например, одна из функций, перечисленных в это сравнение или это сравнение .net IoC-фреймворков? Я хотел бы прочитать о том, включают ли другие платформы IoC (например, Castle Windsor) эти функции, как у LinFu, но я не знаю, имеет ли то, что я описываю, конкретное имя, поэтому я не знаю, что искать :)

A:

С точки зрения наилучшей практики (т. Е. Слабой связи, тестирования и т. Д.), Было бы лучше удалить зависимость службы от объекта домена или оставить ее там?

Спасибо

Мэтью

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...