AutoFac IoC, DDD и зависимости между репозиториями

#domain-driven-design #repository #inversion-of-control #autofac

#проектирование, управляемое доменом #репозиторий #инверсия контроля #autofac

Вопрос:

У меня есть два типа POCO, A и B. У меня есть репозиторий для каждого из них, Rep <A > и Rep <B >, оба из которых реализуют IRep <A > и IRep <B > обслуживается контейнером IoC (в данном случае AutoFac).

Существует несколько видов репозиториев — загрузка по требованию из базы данных (или чего-либо еще), отложенная загрузка коллекций в память, кэшированные результаты веб-службы и т.д. Вызывающие пользователи не могут заметить разницу. Как Rep <A >, так и Rep <B > являются коллекциями в памяти, поскольку A и B не сильно меняются и живут долгое время.

Одним из свойств B является A. Что я делаю сейчас, так это то, что когда у B запрашивается его A, B получает IRep <A > для поиска своего A и возвращает его. Это происходит каждый раз — каждый запрос для B’s A включает IRep <A >.Find(). Преимущество в том, что B никогда не привязываются к A, и каждый запрос учитывает независимо от состояния Rep, в котором он находится. Недостатком является большой отток IoC / IRep <A .

Я подумываю использовать здесь шаблон Lazy <T >, чтобы a B запрашивал IRep <A > один раз и сохранял то, что он получил. Но что произойдет, если A будет удален из его репозитория?

Я ищу чистый способ для представителя <A > уведомлять всех, кто заинтересован, когда это изменилось. В моем примере может быть удален определенный B A, поэтому я хотел бы, чтобы Rep <A > вызывал событие, когда что-то удаляется или добавляется и т.д. Представитель <B > может подписаться на это событие, чтобы очистить любые B, которые ссылаются на A, которые теперь исчезли, и т.д. Как это подключить?

В идеале ничего не меняется при создании экземпляра Rep <A >. Он не должен иметь представления о том, кто слушает, и A могут манипулировать весь день, даже не вызывая представителя.

Но когда появляется Rep <B >, ему нужен способ подписаться на событие Rep <A >. Возможно, представителя <A > еще нет в живых, но, несомненно, он появится, как только B попросят указать его A, так что, кажется, можно запустить представителя <A >.

По сути, при создании экземпляра Rep <B > требуется, чтобы он регистрировался в Rep <A > для уведомления о событии. Я не хочу загрязнять интерфейс IRep <T >, потому что это не должно иметь значения ни для кого за пределами уровня репозитория. И другим типам репозиториев, возможно, вообще не придется беспокоиться об этом.

Имеет ли это какой-либо смысл?

Ответ №1:

Что, если вы заставили Rep<A> возвращать «наблюдаемый» объект, который может вычисляться до A, а также имеет событие подписки, которое возникает, когда что-то в этом A изменяется? Просто мысль. Таким образом, вам не нужно проверять обработчики, чтобы убедиться, что их A изменилось; если событие, которое они прослушивают, запущено, это касается их экземпляра, а не любого другого.

Вы могли бы закодировать это следующим образом:

 public class Observable<T>:IDisposable
{
   private T instance;
   public T Instance
   {
      get{return instance;}
      set{
         instance = value;
         var handlers = ReferenceChanged;
         if(handlers != null) handlers(this, instance);
   }

   public static implicit operator T(Observable<T> obs)
   {
      return obs.Instance;
    }

   //DO NOT attach anonymous delegates or lambdas to this event, or you'll cause a leak
   public event EventHandler<T> ReferenceChanged;

   public void Dispose()
   {
      var handlers = ReferenceChanged;
      if(handlers != null) handlers(this, null);
      foreach(var handler in handlers) ReferenceChanged -= handler;
   }
}

public class Rep<T>
{
   private Dictionary<T, Observable<T>> observableDictionary = new Dictionary<T, Observable<T>>();

   ...
   public Observable<T> GetObservableFactory(Predicate<T> criteria)
   {
      //criteria should test only uniquely-identifying information
      if(observableDictionary.Keys.Any(criteria))
         return observableDictionary[observableDictionary.Keys.First(criteria)];
      else
      {
         //TODO: get object from source according to criteria and set to variable queryResult
         var observable = new Observable<T>{Instance = queryResult};
         observableDictionary.Add(queryResult, observable);
         return observable;
      }
   }
}

...

var observableA = myRepA.GetObservable(myCriteria);
observableA.ReferenceChanged  = DoSomethingWhenReferenceChanges;
  

Теперь потребляющий код будет уведомлен, если внутренняя ссылка изменена или observable удален (что также удаляет внутреннюю ссылку). Чтобы observable также уведомлял потребляющий код, если изменяются дочерние ссылки As, A само по себе должно быть observable, вызывая событие, обрабатываемое Observable<T> , которое «всплывает» либо через ReferenceChanged, либо через более конкретный обработчик, такой как InstanceDataChanged , (или как вы хотите это назвать).

Комментарии:

1. О, мне это нравится! Я должен провести некоторые эксперименты, но это отличный способ взглянуть на это. Спасибо, Кит!