Внедрение зависимости с несколькими репозиториями

У меня есть служба wcf, а на клиенте есть:

var service = new ServiceReference1.CACSServiceClient()

Фактический сервисный код:

public CACSService() : this(new UserRepository(), new BusinessRepository()) { }

public CACSService(IUserRepository Repository, IBusinessRepository businessRepository)
{
     _IRepository = Repository;
     _IBusinessRepository = businessRepository;
}

Итак, все это работает нормально, но мне не нравится, как я обновляю все репозитории одновременно, потому что клиентский код может не нуждаться в обновлении UserRepository и заинтересован только в обновлении BusinessRepository. Итак, есть ли способ передать что-то в этот код:
var service = new ServiceReference1.CACSServiceClient()
, чтобы сообщить ему, какой репозиторий нужно обновить, на основе кода, вызывающего службу, или любых других советов, которые мне нужно предпринять при разработке репозитории для моей структуры сущностей. Спасибо


person user282807    schedule 27.02.2010    source источник


Ответы (4)


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

(Кроме того, вам следует избавиться от ваших текущих конструкторов Bastard Injection. Выбросьте конструктор без параметров и оставьте тот, который явно объявляет свои зависимости.)

Сохраните свой конструктор в таком виде и при необходимости используйте _IRepository и _IBusinessRepository:

public CACSService(IUserRepository Repository, IBusinessRepository businessRepository) 
{ 
    _IRepository = Repository; 
    _IBusinessRepository = businessRepository; 
} 

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

Предположим, что IUserRepository выглядит так:

public interface IUserRepository
{
    IUser SelectUser(int userId);
}

Теперь вы можете реализовать такую ​​реализацию отложенной загрузки:

public class LazyUserRepository : IUserRepository
{
    private IUserRepository uRep;

    public IUser SelectUser(int userId)
    {
        if (this.uRep == null)
        {
            this.uRep = new UserRepository();
        }
        return this.uRep.SelectUser(userId);
    }
}

Когда вы создаете CACService, вы можете сделать это, внедрив в него LazyUserRepository, что гарантирует, что настоящий UserRepository будет инициализирован только в случае необходимости.

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

Сначала я описал технику ленивых зависимостей здесь и здесь.

person Mark Seemann    schedule 27.02.2010
comment
дать согласие. Не ограничивайте себя ctor по умолчанию, потому что это то, что предлагает WCF. используйте IInstanceProvider для создания экземпляра службы. - person Krzysztof Kozmic; 28.02.2010
comment
Спасибо Марку за ответ. У меня может быть 10 или 20 репозиториев для моих классов домена, мне нужно реализовать ленивую загрузку для каждого? это было бы много кода. - person user282807; 28.02.2010
comment
Что ж, один из моих основных моментов заключается в том, что вам, возможно, вообще не придется этого делать. Тщательно подумайте, может ли это быть преждевременной оптимизацией. В качестве одного примера, если вы используете LINQ to SQL или LINQ to Entities, вы вполне можете в конечном итоге реализовать все свои репозитории на основе одного и того же контекста, и в таком случае накладные расходы на создание нескольких экземпляров репозитория могут быть незначительными. - person Mark Seemann; 28.02.2010
comment
Однако, если вы можете измерить производительность своей службы и убедиться, что накладные расходы на создание каждого репозитория значительны, вам придется заключить каждый в ленивую версию. Однако есть вероятность, что вы можете написать универсальную реализацию многократного использования, так что вам нужно будет написать только одну реализацию, но это зависит от того, насколько отличаются друг от друга каждый из ваших репозиториев. Если вы используете контейнер DI, многие из них предлагают возможности перехвата, чтобы вы могли централизованно определять это поведение для всех репозиториев. - person Mark Seemann; 28.02.2010
comment
да, прямо сейчас у меня есть контекст, созданный в каждом репозитории, поэтому я беспокоился обо всем этом, потому что я не хочу создавать кучу контекстных объектов. Но я просто подумал об идее, которая могла бы упростить задачу. в CACSServise я просто собираюсь создать новый SuperRepository, в репозитории Supper есть только 1 контекст для всех дочерних репозиториев, и это позаботится о том, чтобы контекст был создан во всех репозиториях. Затем добавьте интерфейс с именем ISuperRepository, где я объявляю дочерние репозитории, а в коде службы я могу просто выполнить SuperRepository.UserRepository.GetUser () - person user282807; 28.02.2010
comment
общедоступный IUserRepository UserRepository {получить {IUserRepository _IUserRepository = новый UserRepository (); return _IUserRepository; }} Вот как будет выглядеть пользовательский репозиторий внутри класса SuperRepository. Таким образом, будут обновляться только репозитории, запрошенные вызывающей стороной. - person user282807; 28.02.2010
comment
Лучшая практика для L2S и L2E - иметь единый общий контекст, поэтому лучше внедрить контекст в каждый репозиторий. Это также означает, что вам, скорее всего, не нужно беспокоиться о ленивых зависимостях. - person Mark Seemann; 28.02.2010
comment
Спасибо Марк. Я отправлю код после его завершения и надеюсь, что получу от вас отзывы, если мой код будет выглядеть правильно. - person user282807; 28.02.2010

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

В противном случае пользователь мог бы назначить их по мере необходимости.

Например:

public class CACSService
{
    public CACSService() {}

    public CACSService(IUserRepository Repository, IBusinessRepository businessRepository)
    {
        _IRepository = Repository;
        _IBusinessRepository = businessRepository;
    }

    private IUserRepository _IRepository;
    public IUserRepository Repository
    {
        get {
             if (this._IRepository == null)
                  this._IRepository = new UserRepository();
             return this._IRepository;
        }
    }

   // Add same for IBusinessRepository
}
person Reed Copsey    schedule 27.02.2010

Есть ли у ваших репозиториев состояние объектного уровня? Скорее всего, нет, поэтому создайте их как одиночные и пусть контейнер DI предоставит их CACService.

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

При использовании контейнера внедрения зависимостей Ninject ваша служба CACService может выглядеть следующим образом. Другие контейнеры DI имеют столь же лаконичные механизмы для этого.

public class CACSService
{
    public CACService
    {
        // need to do this since WCF creates us
        KernelContainer.Inject( this );
    }

    [Inject]
    public IUserRepository Repository
    { set; get; }

    [Inject]
    public IBusinessRepository BusinessRepository
    { set; get; }
}

И во время запуска вашего приложения вы должны сообщить Ninject об этих типах.

Bind<IUserRepository>().To<UserRepository>().InSingletonScope();
Bind<IBusinessRepository>().To<BusinessRepository>().InSingletonScope();
person Lachlan Roche    schedule 27.02.2010
comment
Нет необходимости прибегать к синглетонам и конструкторам по умолчанию. WCF прекрасно поддерживает внедрение конструктора. - person Mark Seemann; 28.02.2010
comment
Эта реализация звучит круто, я попробую, а также попробую, что сказал Марк Симан о ленивой загрузке репозитория. - person user282807; 28.02.2010
comment
Друзья не позволяют друзьям писать синглтоны. - person Tom Stickel; 06.12.2011

Предисловие: это общее руководство по инверсии зависимостей. Если вам нужен конструктор по умолчанию для выполнения работы (например, если он создан путем отражения или чего-то еще), тогда будет сложнее сделать это чисто.

Если вы хотите сделать свое приложение настраиваемым, это означает возможность изменять способ построения графа объектов. Проще говоря, если вы хотите изменить реализацию чего-либо (например, иногда вам нужен экземпляр UserRepository, в других случаях вам нужен экземпляр MemoryUserRepository), тогда тип, который использует реализацию (CACService в этом случае) не следует обвинять в обновлении. Каждое использование new связывает вас с определенной реализацией. Миско написал несколько хороших статей о этот момент.

Принцип инверсии зависимостей часто называют «параметризацией сверху», поскольку каждый конкретный тип получает свои (уже созданные) зависимости от вызывающей стороны.

Чтобы применить это на практике, переместите код создания объекта из конструктора без параметров CACService и вместо этого поместите его в фабрику.

Затем вы можете по-разному подключать устройства в зависимости от таких вещей, как:

  • чтение в файле конфигурации
  • передача аргументов фабрике
  • создание фабрики другого типа

Разделение типов на две категории (типы, которые создают вещи, и типы, которые делают вещи) - мощный прием.

Например. вот один относительно простой способ сделать это с использованием интерфейса фабрики - мы просто создаем новую фабрику, которая подходит для наших нужд, и вызываем ее метод Create. Мы используем контейнер внедрения зависимостей (Autofac) для выполнения этих задач на работе, но он может быть излишним для ваших нужд.

public interface ICACServiceFactory
{
    CACService Create();
}

// A factory responsible for creating a 'real' version
public class RemoteCACServiceFactory : ICACServiceFactory
{
    public CACService Create()
    {
         return new CACService(new UserRepository(), new BusinessRepository());
    }        
}

// Returns a service configuration for local runs & unit testing
public class LocalCACServiceFactory : ICACServiceFactory
{
    public CACService Create()
    {
         return new CACService(
               new MemoryUserRepository(), 
               new MemoryBusinessRepository());
    }     
}
person Mark Simpson    schedule 27.02.2010