sobota, 19 listopada 2011

Tworzenie aplikacji modułowej w Prism

Aplikacja modułowa to taka aplikacja która dzieli funkcjonalność na moduły. Każda funkcjonalność tworzy jeden moduł. Moduły następnie łączone są w jedną całość, tworząc pełnowartościową aplikację. Aplikacja tworzona w trybie modułowym jest łatwiejsza w tworzeniu, prosta w testowaniu a także w bardzo łatwy sposób można dodawać nowe funkcjonalności. 

Prism opiera swoje założenia właśnie na budowaniu aplikacji wielo-modułowych. W tym celu mamy wiele mechanizmów z których możemy skorzystać:
  1. Katalog modułów:
    •  możliwość zdefiniowania z jakich modułów składa się aplikacja w kodzie lub XAMLu
    • dla aplikacji WPF: możliwość skorzystania z mechanizmu wyszukiwania modułów w katalogu, przez co nie muszą być jawnie określone w kodzie
    • także w WPF poprzez określenie modułów do załadowania w pliku konfiguracyjny
  2. Użycie atrybutów w celu wskazania zależności pomiędzy modułami
  3. Integrację z kontenerami DI
  4. Ładowanie modułów
    • zapewnienie odpowiedniej kolejności ładowania modułów, oraz załadowania tylko jednej instancji każdego z nich
    • ładowanie selektywne w celu zapewnienia szybszego startu - ładowane są tylko moduły aktualnie wykorzystywane, reszta doładowywana w zależności od potrzeb
Budowanie modułu.
Każdy moduł posiada główną klasę, która jest odpowiedzialna za inicjalizację modułu i integrację jego funkcjonalności w aplikacji. Klasa ta musi implementować interfejs IModule - jest to warunek wystarczający, aby daną bibliotekę identyfikować jako moduł. Interfejs ten posiada jedną metodę Initialize w której implementujemy logikę odpowiedzialną za integrację funkcjonalności zawartych w module z aplikacją. W zależności od potrzeb może rejestrować widoki, eksponować serwisy dodające nowe funkcjonalności do aplikacji.

Cykl życia modułu.
Proces życia modułu w Prism przedstawia się następująco:
  1. Rejestracja/wykrycie modułów - załadowanie modułów z określonych lokalizacji
  2. Ładowanie modułów - załadowanie modułów do pamięci
  3. Inicjalizacja modułów - utworzenie klas modułów i wywołanie metody Initialize
Katalog modułów.
Przechowuje informacje nt. modułów które można załadować do aplikacji. Każdy moduł opisany jest za pomocą klasy ModuleInfo tak więc katalog jest kolekcją tych modułów. Jak wcześniej wspominałem moduły można zdefiniować w różny sposób:
  • w kodzie
  • XAML
  • plik konfiguracyjny (WPF)
  • odnalezienie modułów w katalogu roboczym (WPF)
W zależności od potrzeb moduły mogą być ładowane w następujący sposób:
  • te które są wymagane do uruchomienia aplikacji - w momencie jej startu
  • moduły często wykorzystywane - ładowane w tle i dostępne po ich załadowaniu
  • moduły rzadko wykorzystywane - w tle i inicjowane na żądanie
Komunikacja pomiędzy modułami:
  • Luźno powiązane zdarzenia - moduły mogą emitować zdarzenia a subskrybujące klasy odbierać je. Rozwiązanie sprawdza się w przypadku małej ilości zdarzeń emitowanych, w przypadku ich dużej liczby warto skorzystać z współdzielonych serwisów
  • Współdzielone serwisy - są to serwisy do których można mieć dostęp poprzez wspólny interfejs.
  • Współdzielone zasoby - jeżeli nie chcemy aby moduły wiedziały o sobie nawzajem, można rozwiązać wzajemną komunikację za pomocą bazy danych bądź też web serwisów.
Rejestracja modułu w kodzie.
Najprostszą metodą rejestracji modułów jest rejestracja w kodzie. Aby zarejestrować nowy moduł np. moduł Person (reprezentujący np. dane adresowe korzystających z naszego programu) możemy zarejestrować ten moduł w następujący sposób:
  1.  Nadpisujemy w bootrapperze metodę ConfigureModuleCatalog
  2.  Dodajemy za pomocą metody AddModule nowy moduł do kolekcji.
Przykład:


Code:
        protected override void ConfigureModuleCatalog()
        {
            var personModuleType = typeof(PersonModulePersonModule);
            ModuleCatalog.AddModule(new ModuleInfo
            {
                ModuleName = personModuleType.Name,
                ModuleType = personModuleType.AssemblyQualifiedName
            });
        }

W tym przypadku możemy skorzystać z instrukcji typeof(object) która pozwoli ustalić typ naszego obiektu. Jeżeli jednak nasz projekt nie będzie mieć referencji do modułu, musimy ręcznie podać nazwę modułu i jego typ. W moim przypadku były by to kolejno: PersonModule i PersonModule.PersonModule, PersonModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Rejestracja modułu W XAMLu
W tym celu dodajemy plik XAML jako zasób do naszej aplikacji. Definiujemy w nim jakie moduły mają zostać dodane i w jakiej kolejności ma się to odbyć - jest to rozwiązanie podobne do poprzedniego z tym że używamy pliku XAML a następnie wywołujemy metodę CreateFromXAML. Przykład:

<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
    <Modularity:ModuleInfo Ref="file://PersonModule.dll" ModuleName="PersonModule" ModuleType="PersonModule,PersonModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</Modularity:ModuleCatalog>

Należy tutaj pamiętać o tym, że dany moduł musi być w katalogu w którym znajduje się aplikacja inicjująca shell.
Następnie w kodzie ładujemy moduły znajdujące się w pliku w następujący sposób:

Code:
return ModuleCatalog.CreateFromXaml(new Uri("/WpfApplicationName;component/Modules.xaml", UriKind.Relative));

Rejestracja modułów za pomocą pliku konfiguracyjnego.
Zaletą tego rozwiązania jest to iż można dodawać nowe moduły bez potrzeby dekompilacji całej aplikacji (plik app.config nie jest kompilowany). Przykład:
Do naszej aplikacji dokładamy plik app.config a w nim dopisujemy następującą sekwencję:


Code:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <modules>
    <module assemblyFile="WpfApplication.PersonModule.dll"
            moduleType="WpfApplication.PersonModule, WpfApplication.PersonModule,Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="PersonModule" startupLoaded="false"/>
  </modules>
</configuration>

Następnie w metodzie tworzącej katalog modułów deklarujemy:


Code:
        protected override IModuleCatalog CreateModuleCatalog()
        {
            return new ConfigurationModuleCatalog();
        }

Automatyczne wyszukiwanie modułów w katalogu.
Aby skorzystać z tego rozwiązania należy zastosować atrybuty podczas tworzenia modułów, które specyfikują zależności pomiędzy modułami. Podczas wyszukiwania na podstawie nazw deklarowanych w atrybutach zostaną dodane odpowiednie moduły do aplikacji.
Przykład:

Code:
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog() {ModulePath = @".\Modules"};
}

Inicjalizacja modułu.
Po załadowaniu modułu do pamięci następuje jego inicjalizacja czyli stworzenie obiektu oraz wywołanie metody Initialize. W metodzie tej:
  1. Rejestrujemy widoki związane z danym modułem
  2. Subskrybujemy zdarzenia oraz serwisy potrzebne w danym module
  3. Rejestracja typów w kontenerach DI
Należy pamiętać, że jeżeli moduł B jest zależny od modułu A należy moduł B zainicjować przed modułem A. Można tutaj skorzystać z odpowiedniej inicjalizacji w kodzie bądź też wygodnej inicjalizacji za pomocą atrybutów w przypadku metody wyszukiwania modułów w katalogu:


Code:
    [Module(ModuleName = "PersonModule")]
    [ModuleDependency("BankModule")]
    public class PersonModule
    {
        ...
    }

Ładowanie modułów na życzenie
Jeżeli nie chcemy ładować niektórych modułów przy starcie aplikacji (np. wiemy że użytkownik korzysta z nich bardzo rzadko albo w ogóle) można oznaczyć dany moduł atrybutem OnDemand co sprawi że dany moduł zostanie załadowany dopiero kiedy użytkownik sobie tego jawnie zażyczy. Zależność tę można zadeklarować przykładowo w kodzie:


Code:
        protected override void ConfigureModuleCatalog()
        {
            var personModuleType = typeof(PersonModulePersonModule);
            ModuleCatalog.AddModule(new ModuleInfo
            {
                ModuleName = personModuleType.Name,
                ModuleType = personModuleType.AssemblyQualifiedName,
                InitializationMode = InitializationMode.OnDemand
            });
        }

Pozostałe metody rejestracji modułów można znaleźć w oficjalnej dokumentacji Prism.

2 komentarze:

  1. Ten komentarz został usunięty przez autora.

    OdpowiedzUsuń
  2. Nigdy nie miałem do czynienia z tym językiem programowania. Ogólnie wiem tylko, że raczej można stworzyć w nim aplikacje użytkowe jak robi to firma https://craftware.pl i to z wielkim powodzeniem. W sumie praca branży IT i programowanie aplikacji dla klientów jest dość fajnym zadaniem.

    OdpowiedzUsuń