Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Simon says...

Szymon Pobiega o architekturze i inżynierii oprogramowania
Oswajając NServiceBus: ObjectBuilder
Bardziej uważni czytelnicy pamiętają, że od jakiegoś czasu jestem mocno zainteresowany tematem komunikacji asynchronicznej. Zupełnie ostatnio dosyć intensywnie zająłem się biblioteką NServiceBus. Swoją poprzednią notką zainteresowałem kolegę z pracy, który postanowił także wykorzystać NServiceBus (do scenariusza Publish/Subscribe). Kolega skazany jest na wykorzystanie kontenera Unity (w którego to sam go wkopałem, mea culpa).

Na szczęscię NServiceBus ma warstwę abstrakcji ponad infrastrukturą DI. Na nieszczęście, abstrakcja ta nie ma implementacji na Unity. Cóż, jeśli nie ma, to trzeba napisać.

Z początku sprawa wydawała się banalna. Interfejs IObjectBuilder wygląda następująco:

    1 public interface IBuilder

    2     {       

    3         object Build(Type typeToBuild);

    4         T Build<T>();

    5         IEnumerable<T> BuildAll<T>()

    6         IEnumerable<object> BuildAll(Type typeToBuild);

    7         void BuildAndDispatch(Type typeToBuild, Action<object> action);

    8     }


Schody zaczęły się, kiedy natrafiliśmy na interfejs IConfigureComponents, który wygląda bardzo niewinnie:

    1 public interface IConfigureComponents

    2     {    

    3         IComponentConfig ConfigureComponent(Type concreteComponent, ComponentCallModelEnum callModel);

    4         T ConfigureComponent<T>(ComponentCallModelEnum callModel);

    5     }


jednak w środku kryje straszną pułapkę. Służy on bowiem do konfigurowania komponentów kontenera z kodu. Ta część funkcjonalności jest realizowana przez każdy kontener inaczej. Wyodrębnienie wspólnej wersji wydawało się niemożliwe. Wiem to dobrze, bo podczas realizacji projektu MetaKontenera zrezygnowaliśmy z Piotrkiem z obsługi konfigurowania komponentów. Tym razem problem powrócił do mnie z innej strony. I tym razem nie było wyjścia.

Pogrzebałem nieco w źródłach Unity i znalazłem rozszerzenie InjectedMembers, które pozwala na konfigurowanie kontenera podczas rejestracji. Hurra! Już myślałem, że wszystko OK, kiedy okazało się, że to dopiero wierzchołek góry lodowej.

Kolejny problem, który wypłynął na powierzchnię, to obsługa autowiring, czyli automatycznego rozwiązywania zależności. Udi, tworzoąc komponenty NServiceBus, założył, że infrastruktura DI wstrzykuje zależności do wszystkich publicznych property, i to opcjonalnie. Niestety autorzy Unity poczynili dokładnie odwrotne założenia: zależności są wstrzykiwane tylko do oznaczonych (atrybutem dependency) własności, i to jeśli już, to obowiązkowo.

Pozostało zaimplementować autowiring w stylu Spring/Castle dla Unity. Znalazłem taki oto oficjalny guidance, który zdał się na nic, bo nie działa, jeśli opcjonalne zależności nie są ValueType. I to w sposób bardzo perfidny nie działa: rzuca wyjątkiem ExecutionEngineException i kończy działanie CLR. Myślimy więc dalej. W ten sposób wykombinowałem, że tego try...catch zamiast w momencie rozwiązywania zależności dać w momencie wybierania property, które są zależnościami. W ten sposób, jeśli kontener nie jest w stanie spełnić zależności, nie zostanie ona potraktowana (w sensie Unity) jako zależność i wszystko będzie działać. Oto fragment klasy FullAutowirePropertySelectorPolicy pokazujący (pierwszy snippet) część metody wyboru property do wstrzykiwania oraz (drugi snippet) metodę sprawdzającą, czy możliwe jest spełnienie zależności:

    1 foreach (PropertyInfo prop in t.GetProperties(BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance))

    2          {

    3             if (prop.GetIndexParameters().Length == 0 &&              

    4                prop.CanWrite && !ShoudBeIgnored(prop) &&

    5                !propertyNames.Contains(prop.Name) &&

    6                !prop.PropertyType.IsValueType &&

    7                CanBeResolved(prop))                                     

    8             {

    9                yield return CreateSelectedProperty(context, prop);              

   10             }

   11          } 


    1 private bool CanBeResolved(PropertyInfo info)

    2       {

    3          try

    4          {

    5             _container.Resolve(info.PropertyType);

    6             return true;

    7          }

    8          catch (ResolutionFailedException)

    9          {

   10             return false;

   11          }

   12       }


Odpalam i... wyjątek. Znów. Tym razem (po kolejnej długiej sesji z debugerem) okazało się, że InjectedMembers działa w ten specyficzny sposób, że w momencie, gdy konfigurujemy za jego pomocą jakiś kontener, to przestaje działać domyślna polityka zapewniająca mój autowiring (linijka numer 9 jest odpowiedzialna za taki stan rzeczy).

    1 protected virtual SpecifiedPropertiesSelectorPolicy GetSelectorPolicy(IPolicyList policies, Type typeToInject, string name)

    2         {

    3             NamedTypeBuildKey key = new NamedTypeBuildKey(typeToInject, name);

    4             IPropertySelectorPolicy selector =

    5                 policies.GetNoDefault<IPropertySelectorPolicy>(key, false);

    6             if (selector == null || !(selector is SpecifiedPropertiesSelectorPolicy))

    7             {

    8                 selector = new SpecifiedPropertiesSelectorPolicy();

    9                 policies.Set<IPropertySelectorPolicy>(selector, key);

   10             }

   11             return (SpecifiedPropertiesSelectorPolicy)selector;

   12         }


Znowu główkowanie. Tym razem rozwiązaniem okazała się własna klasa AutowireEnabledInjectionProperty, która jest świadoma istniania polityki autowiringu i korzysta z niej w celu obsługi konfigurowanych własności. Poprawiona kasa różni się tylko metodą GetSelectorPolicy. Niestety, ponieważ wspomniana metoda w oryginalnej wersji Unity była private static, musiałem skopiować cały kod klasy...

    1 protected virtual SpecifiedPropertiesSelectorPolicy GetSelectorPolicy(IPolicyList policies, Type typeToInject, string name)

    2       {

    3          NamedTypeBuildKey key = new NamedTypeBuildKey(typeToInject, name);

    4          IPropertySelectorPolicy selector =

    5              policies.GetNoDefault<IPropertySelectorPolicy>(key, false);

    6 

    7          if (selector == null)

    8          {

    9             FullAutowirePropertySelectorPolicy defaultSelector =

   10                policies.Get<IPropertySelectorPolicy>(key, false) as FullAutowirePropertySelectorPolicy;

   11             if (defaultSelector != null)

   12             {

   13                selector = (FullAutowirePropertySelectorPolicy)((ICloneable)defaultSelector).Clone();

   14                policies.Set(selector, key);

   15             }

   16             else

   17             {

   18                throw new InvalidOperationException();

   19             }

   20          }

   21          return ((FullAutowirePropertySelectorPolicy)selector).SpecifiedPropertiesPolicy;

   22       }


Obiekt SpecifiedPropertiesSelectorPolicy jest teraz elementem FullAutowirePropertySelectorPolicy i bierze udział w selekcji właściwości do wstrzykiwania. Odpalam. Znów wyjątek...

Tym razem debugowanie zajęło mi wyjątkowo długo. Okazało się, że problem tkwił w użyciu przeze mnie klasy FixedTypeDependencyResolver. Nie wiem co mnie podkusiło. Generalnie wydawało się, że wszystko jest ok. Prawie.

    1 public object Resolve(IBuilderContext context)

    2         {

    3             Guard.ArgumentNotNull(context, "context");

    4             IBuilderContext recursiveContext = context.CloneForNewBuild(typeToBuild, null);

    5             return recursiveContext.Strategies.ExecuteBuildUp(recursiveContext);

    6         }


Bład znajduje się w linii 4. Jako nowy klucz do budowania brany jest nie NamedTypeBuildKey (jak to zwykle w Unity), ale po prostu instancja typu. Prosta zamiana na:

    1 public object Resolve(IBuilderContext context)

    2       {

    3          Guard.ArgumentNotNull(context, "context");

    4          IBuilderContext recursiveContext = context.CloneForNewBuild(new NamedTypeBuildKey(_typeToBuild), null);

    5          return recursiveContext.Strategies.ExecuteBuildUp(recursiveContext);

    6       }


rozwiązała problem ostatecznie. Uruchomiłem i tym razem zadziałało. Można świętować sukces: NServiceBus nad Unity działa:)

PS. Jutro umieszcze całość kodu gdzieś w sieci, więc każdy będzie mógł sobie ściagnąć i spróbować.

Opublikowane 8 czerwca 2009 21:08 przez simon

Komentarze:

# Simon says... : Oswajając NServiceBus: ObjectBuilder @ 9 czerwca 2009 06:26

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

dotnetomaniak.pl

Komentarze anonimowe wyłączone