DI/IoC, NHibernate и помощь в их совместной работе
Я пытаюсь разобраться с 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);
Вот то, на что я надеюсь получить идеи:
О. Считается ли нормальным доступ к MarginCalculator (который по сути является службой) через объект домена Product или должен, как предлагается здесь, ( http://stackru.com/questions/340461/dependency-injection-with-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 ответов
Вот мой второй ответ на ваши вопросы:
A: С точки зрения наилучшей практики, вы можете оставить служебную зависимость в доменном объекте, если вы уверены, что вы зависите от типа интерфейса. Большинство (если не все) контейнеры могут делать такие инъекции для вас, и довольно просто смоделировать каждую зависимость службы, чтобы вы могли проверить каждое поведение в ваших конкретных классах. Я рекомендую использовать абстрактные классы только в том случае, если вы хотите реорганизовать стандартную реализацию для конкретной реализации интерфейса, например, использовать базовый класс для выполнения общей работы над сохранением CRUD.
B и C:
Приятно знать, что такая функциональность доступна. Я полагаю, что более важный вопрос заключается в том, является ли то, что я пытаюсь сделать, на самом деле обычной практикой и считается ли это хорошей практикой. т.е.
- Контейнер разрешает и внедряет зависимости, которые были предварительно заполнены> с использованием инфраструктуры постоянства (например, NHibernate) и
- Пусть контейнер внедрит конкретную реализацию абстрактных зависимостей, где конкретная реализация определяется во время выполнения.
Кроме того, в терминологии IoC/DI/NHibernate то, о чем я говорю, имеет конкретное имя? Это, например, одна из функций, перечисленных в этом сравнении или в сравнении платформ.net IoC? Я хотел бы прочитать о том, включают ли другие платформы IoC (например, Castle Windsor) эти функции, как у LinFu, но я не знаю, имеет ли то, что я описываю, конкретное имя, поэтому я не знаю, что искать:)
Я полагаю, что вы на самом деле ссылаетесь на сравнение, размещенное по этой ссылке.
1) AFAIK, это стандартная практика для внедрения сервисов, но тип внедрения, на который вы ссылаетесь, будет труден для некоторых других платформ, так как вы должны использовать доменные идентификаторы объектов для разрешения этих зависимостей во время выполнения, и не все контейнеры поддерживают такой тип динамического разрешения (так называемое "контекстное связывание"). При прочих равных условиях (и при условии, что это можно сделать с другими контейнерами), единственная "лучшая практика", которая, кажется, применима к DI/IoC, - это то, что вы должны использовать интерфейсы для своих служебных зависимостей.
То, как эти зависимости должны быть в конечном счете построены и разрешены, должно полностью зависеть от вас, и в вашем случае действительно не имеет значения, заполняете ли вы эти зависимости из среды персистентности, если сам контейнер способен устранить большую часть стандартный код разрешения для вас.
2) Внедрение конкретного сервиса является стандартным для платформ DI/IOC, и большинство из них может разрешать зависимости во время выполнения; тем не менее, эти рамки различаются в отношении того, как и где эта инъекция может быть сделана.
К вашему сведению, две функции, на которые вы должны обратить внимание - это инъекция конструктора и инъекция свойства. Основываясь на ваших примерах кода, я бы сказал, что вы будете более склонны использовать инъекцию конструктора, поэтому вы, возможно, захотите следить за тем, как каждая соответствующая среда делает такой тип внедрения для вас. HTH:)
A) Если вы собираетесь получить доступ к MarginCalculator через объект домена Product, вы также можете отключить посредника и позволить контейнеру DI/IOC ввести MarginCalculator для вас. Вы даже можете избавиться от MarginCalculatorAssembler, потому что большинство контейнеров DI/IOC выполняют большую часть стандартного кода построения объекта для вас.
Б и В) Это очень возможно. Фактически, вот как будет выглядеть ваш код, если вы используете LinFu:
// Нет необходимости менять класс транзакции сделка публичного класса { приватный двойной _margin; частный продукт _продукт; частный клиент _client; public double Margin {get; } public Product Product {получить; } публичный клиент клиент {получить; } публичная сделка (продукт p, клиент c) { _product = p; _client = c; } public void CalculateMargin () { _margin = _product.MarginCalculator.CalculateMargin (); } }
Было бы хорошо, если бы вы могли получить DI/IOC для внедрения экземпляров продукта и клиента в конструктор - но прежде чем мы это сделаем, вам нужно зарегистрировать зависимости с контейнером. Вот как вы делаете это с LinFu.IOC:
// Затем вы должны указать LinFu автоматически зарегистрировать класс вашего продукта: [Factory(typeof(Product))] открытый класс ProductFactory: IFactory { object CreateInstance(запрос IServiceRequest) { // Получить копию IRepository из контейнер var repository = container.GetService>(); // Получить идентификатор (предполагается, что ваш идентификатор - Int32) var id = (int)request.Arguments[0]; // Вернуть сам продукт return repository.GetById(id); } } // Сделайте то же самое с классом Client // (Примечание: я сделал простую вырезку и вставку, чтобы все было просто - пожалуйста, простите за дублирование) [Factory(typeof(Client))] открытый класс ClientFactory: IFactory { object CreateInstance(запрос IServiceRequest) { // Получение копии IRepository из контейнера var repository = container.GetService>(); // Получить идентификатор (предполагается, что ваш идентификатор - Int32) var id = (int)request.Arguments[0]; // Вернуть самого клиента return repository.GetById(id); } } [Factory(typeof(Transaction))] открытый класс TransactionFactory: IFactory { object CreateInstance(запрос IServiceRequest) { // Примечание: проверка аргументов удалена для краткости var container = request.Container; var arguments = request.Arguments; var productId = (int)arguments[0]; var clientId = (int)arguments[1]; // Получить продукт и клиента var product = container.GetService(productId); var client = container.GetService(clientId); // Создаем саму транзакцию return new Transaction(product, client); } } // Сделать эту реализацию синглтоном [Implements(typeof(MarginCalculator), LifecycleType.Singleton)] открытый класс ConcreteMarginCalculatorA: MarginCalculator { public override double CalculateMargin() { // Выполнить фактический расчет}}
Как только вы соберете весь этот код в одну из ваших сборок, вот все, что вам нужно сделать, чтобы загрузить его в контейнер:
var container = new ServiceContainer (); container.LoadFrom (AppDomain.CurrentDomain.BaseDIrectory, "YourAssembly.dll");
... Теперь самое интересное. Чтобы создать объект транзакции с указанным идентификатором продукта и клиента, необходимо выполнить вызов контейнера LinFu.IOC:
int productId = 12345; int clientId = 54321; строка serviceName = null; // Не псевдокод:) var транзакция = container.GetService(serviceName, productId, clientId);
Что делает это интересным, так это то, что, несмотря на количество зависимостей, которые у вас могут быть, контейнер IOC LinFu будет обрабатывать 90% стандартного кода для вас, поэтому вам не придется делать все это самостоятельно. Самое приятное то, что все вышеописанные реализации будут определены / разрешены во время выполнения.
Вы можете практически поменять местами реализации во время работы программы, и вы даже можете заменить реализации, даже не перекомпилировав свое приложение. Вы можете найти больше информации здесь:
http://www.codeproject.com/KB/cs/LinFu_IOC.aspx
HTH:)
Согласно "Управляемому доменом дизайну", ваша служба будет "Доменной службой", и остальная часть вашего домена может вызывать ее напрямую или зависеть от нее.
Если вы собираетесь использовать Nhibernate, проверьте Spring.net, очень популярную платформу DI, которая предоставляет вам DAOS, в которую уже вставлен сеанс. Это также позволяет вам использовать декларативные транзакции (методы маркировки с атрибутами). Документы проекта очень, очень хорошие.
Последнее, но не менее важное, и не поймите меня неправильно, я думаю, что вы используете технологию только потому, что (я не вижу, что у вас НУЖНО для DI), это здорово, если вы делаете это, чтобы учиться чему-то, но неправильно в любом другом случае.
С уважением
Филипп,
Спасибо за Ваш ответ!
B и C:
Приятно знать, что такая функциональность доступна. Я полагаю, что более важный вопрос заключается в том, является ли то, что я пытаюсь сделать, на самом деле обычной практикой и считается ли это хорошей практикой. т.е.
- Контейнер разрешает и внедряет зависимости, которые были предварительно заполнены с использованием постоянной структуры (например, NHibernate) и
- Пусть контейнер внедрит конкретную реализацию абстрактных зависимостей, где конкретная реализация определяется во время выполнения.
Кроме того, в терминологии IoC/DI/NHibernate то, о чем я говорю, имеет конкретное имя? Это, например, одна из функций, перечисленных в этом сравнении или в сравнении платформ.net IoC? Я хотел бы прочитать о том, включают ли другие платформы IoC (например, Castle Windsor) эти функции, как у LinFu, но я не знаю, имеет ли то, что я описываю, конкретное имя, поэтому я не знаю, что искать:)
A:
С точки зрения наилучшей практики (т. Е. Слабой связи, тестирования и т. Д.), Было бы лучше удалить зависимость службы от объекта домена или оставить ее там?
Спасибо
Мэтью
Пабло,
Спасибо за ваши комментарии.
Возможно, если я подробнее остановлюсь на одной области, где я намереваюсь использовать 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. Что думают другие?
Спасибо
Мэтью
Взгляните на этот пост http://fabiomaulo.blogspot.com/2008/11/entities-behavior-injection.html