19/01/2012

RavenDB (cz. 1)

Home

Raven DB to dokumentowa baza danych, przedstawiciel trendu NoSQL, opracowana przez Ayende i jego zespół. Całkiem niedawno bo w październiku 2011 Ayende był zresztą w Polsce i opowiadał o swoim dziecku. Kilka miesięcy temu w jednym ze swoich pet project postanowiłem zmienić technologię dostępu do danych i wybór padł właśnie na Raven DB.

Kilka słów wstępu

Projekt ten to program LanguageTrainer wspomagający naukę słówek. Pomysł jego napisania narodził się ponad rok temu kiedy "ponownie" rozpocząłem naukę języka niemieckiego. W sieci nie znalazłem programu, który spełniałby moje oczekiwania. Zdecydowałem więc, że napiszę coś szytego na miarę. Początkowo rozpoczęło się niewinnie: listę słówek i ich tłumaczeń trzymałem w pliku. Dokładniej mówiąc był to po prostu wynik serializacji XML'owej. Podejście to przestało się sprawdzać kiedy do głowy zaczęły mi przychodzić kolejne pomysły: A może dodać bardziej zaawansowane wyszukiwanie, jakieś statystyki itd.

Początkowo pomyślałem o przejściu na bazę relacyjną, ale stwierdziłem, że tutaj nie nauczę się niczego nowego. Pomyślałem, że fajnie będzie wypróbować coś zupełnie nowego, z czym nie miałem jeszcze do czynienia, a ponieważ wcześniej czytałem trochę o Raven DB i wiedziałem, że ma .NET'owe API, wybrałem właśnie ją.

Z bazą tą już trochę pracuję, z braku czasu niezbyt intensywnie, ale zebrałem już trochę doświadczeń i postanowiłem sie nimi podzielić. Planuję serię postów, w których opiszę jak pracuje się z Raven DB na przykładzie swojego pet project. Nie będzie to jednak typowy tutorial omawiający wszystkie zagadnienia. Poruszę tylko te, z którymi miałem okazję się zapoznać.

Zaczynamy

LanguageTrainer to typowa aplikacja grubego klienta (WPF + MVVM). Nie chciałem jednak uzależniać się od połączenia z serwerem. Dlatego zdecydowałem się na hostowanie Raven DB w procesie aplikacji. Po ściągnięciu paczki z binariami na swoje potrzeby skopiowałem więc katalog EmbeddedClient. W tym momencie zaznaczę jeszcze, że warstwę dostępu do danych mam ukrytą za dobrze zdefiniowanym interfejsem, a więc aplikacja nie wie z jakim źródłem danych pracuje. Poniżej fragment kodu z klasy implementującej ten interfejs, odpowiedzialny za zainicjowanie Raven DB.

public EmbeddableDocumentStore Store { get; private set; }

...

public void Init(string dir)
{
 Store = new EmbeddableDocumentStore
 {
  DataDirectory = dir,
  UseEmbeddedHttpServer = true,
 };

 Store.Initialize();
}
 


Instancja EmbeddableDocumentStore posłuży nam później do otwierania sesji pracy z Raven DB, wykonywania zapytań itd. W kodzie tym ustawiam tylko dwie właściwości. DataDirectory wskazuje katalog roboczy, w którym Raven DB utworzy odpowiednią strukturę katalogów i gdzie będzie trzymał dane. Za pierwszym razem inicjalizacja zajmie więc trochę więcej czasu.

Istotna jest też właściwość UseEmbeddedHttpServer. Dzięki ustawieniu jej na true mam dostęp do napisanej w Silverlight aplikacji Raven Studio do zarządzania dokumentami oraz, co mniej ważne z mojej perspektywy, dostęp REST'owy do dokumentów. Oczywiście ponieważ Raven DB jest hostowany przez mój program, aplikacja Raven Studio będzie dostępna tylko wtedy, kiedy uruchomiony jest LanguageTrainer.

Numer portu z jakiego będzie korzystał serwer HTTP ustawiamy w pliku konfiguracyjnym aplikacji dodając następujący wpis:
<appSettings>
    <add key="Raven/Port" value="8888" />
</appSettings>

To jednak nie wszystko. Jeśli w tym momencie spróbujemy uruchomić Raven Studio otrzymamy od serwera HTTP taki błąd:

Could not find file Raven.Studio.xap, which contains the Raven DB Studio functionality. Please copy the Raven.Studio.xap file to the base directory of RavenDB and try again.

Plik Raven.Studio.xap znajdziemy w dystrybucji Raven DB i musimy umieścić go w katalogu z jakiego uruchamiamy aplikację. Ja zrobiłem to w ten sposób, że dodałem go do projektu, Build Action ustawiłem na Content, a Copy to Output Directory na Copy if newer. Testowałem w przeglądarkach Chrome, Firefox oraz IE i działa.

Podsumowanie

Tyle na dzisiaj. Podsumujmy co już mamy/umiemy:
  • Bazę Raven DB osadzoną w aplikacji hostującej.
  • Kod inicjalizujący Raven DB.
  • Dostęp do Raven Studio i API REST'owego.
W następnej części opiszę jak wykonywać podstawowe operacji na dokumentach.

24/12/2011

Życzenia świąteczne

Home



Z okazji Świąt Bożego Narodzenia życzę czytelnikom i czytelniczkom wszystkiego dobrego, żeby najbliższe dni spędzili w wymarzony sobie sposób, z bliskimi sobie ludźmi, a w nowym roku pomyślności i wielu ciekawych wpisów na tym blogu :)

Serdecznie pozdrawiam,
Michał Komorowski

01/12/2011

Wczytywanie podzespołów do domeny aplikacyjnej

Home

Platforma .NET, dzięki mechanizmowi refleksji, pozwala na dynamiczne wczytywanie do programu podzespołów (ang. assembly). Pozwala to w łatwy sposób pisać rozszerzane przy pomocy pluginów aplikacje i na wiele innych rzeczy. Ostatnio potrzebowałem wykorzystać ten mechanizm do własnych celów. Aby zwiększyć bezpieczeństwo, postanowiłem ładować podzespoły do odzielnych domen aplikacyjnych. W ten sposób, jeśli po załadowaniu podzespołu i wykonaniu jego kodu pojawi się błąd, główna domena aplikacyjna pozostaje nienaruszona.

Użycie osobnej domeny aplikacyjnej przydaje się również kiedy chcemy wczytywać i usuwać załadowane assembly z pamięci. Problem polega na tym, że po załadowaniu podzespołu do domeny nie ma możliwości aby go z niej usunąć. Można jednak osiągnąć podobny rezultat ładując podzespoły do oddzielnych "roboczych" domen, a potem skorzystać z metody AppDomain.Unload, która usuwa z pamięci domenę i wszystkie wczytane do niej podzespoły. (To pewne uproszczenie. Jeśli assembly zostało załadowane do kilku domen to zostanie usunięte dopiero jeśli usuniemy wszystkie domeny ją używające.)

Jak to zrobić? W sieci można znaleźć kilka podejść, ja użyłem w uproszczeniu następującego sposobu:
public static class SeperateDomainAssemblyLoader
{
  [Serializable]
  private class InternalLoader
  {
    public void LoadAndProcess(string assemblyPath)
    {
      Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);

      var assembly = Assembly.LoadFrom(assemblyPath);
      //...
    }
  }

  private static AppDomain _domain = AppDomain.CreateDomain("SeperateDomainAssemblyLoader");

  public static void LoadAndProcess(string assemblyPath)
  {
    InternalLoader internalLoader = (InternalLoader)(_domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InternalLoader).FullName));
    internalLoader.LoadAndProcess(assemblyPath);
   }
}
i kod testujący:
...
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
SeperateDomainAssemblyLoader.LoadAndProcess(somePath);
...
Niestety ku mojemu zdziwieniu program wypisał na ekran dwa razy tą samą nazwę domeny. Jak to możliwe, przecież jak wół stoi, że instancja klasy InternalLoader została stworzona w osobnej domenie. Uważni czytelnicy już pewnie widzą błąd. Ja też go znalazłem, ale chwilę zajęło mi uzmysłowienie sobie, co robię nie tak.

Zapomniałem o tym, że obiekty pomiędzy domenami aplikacyjnymi przekazywana są domyślnie przez wartość. Co z tego, że utworzyłem obiekt w osobnej domenie, skoro i tak pracowałem z jego kopią. Jeśli InternalLoader dziedziczyłby z MarshalByRefObject to pracowałabym nie z prawdziwym obiektem ale z proxy i wszystko byłoby dobrze. Poprawka jest więc bardzo prosta:
...
private class InternalLoader : MarshalByRefObject
{
  ...
}
...

Problem z półką

Home

Jakiś czas temu próbując wykonać operację merge w TFS napotkałem na bardzo irytujący problem pod tytułem:

TF203015 The Item '' has an incompatible pending change.

Nie robiłem nic bardzo skomplikowanego. Najpierw pobrałem do gałęzi A zmiany umieszczone na półce (ang. shelve). Następnie, przy pomocy polecenia merge, chciałem do nich dodać zmiany z changeset'a z gałęzi B i w tym momencie pojawił się powyższy komunikat. Sprawdziłem też odwrotną kolejność czyli najpierw merge z gałęzi B do A, a potem pobranie kodu z półki ale błąd również wystąpił. Innymi słowy, zamiast zgłosić konflikt i umożliwić jego rozwiązanie TFS wypiął się i rzucił błędem.

Nie chciałem wykonywać "ręcznego" łączenia plików ponieważ to błędogenne i niewygodne. Zacząłem szukać rozwiązania i znalazłem sposób na obejście problemu. Daleki od ideału, ale lepszy rydz niż nic. Postąpiłem w następujący sposób:
  • Zainstalowałem Team Foundation Server Power Tools
  • Najpierw wykonałem operację merge z gałęzi B do gałęzi A.
  • Uruchomiłem Visual Studio 2010 Command Prompt.
  • Przeszedłem do katalogu z gałęzią A.
  • Wpisałem komendę tfpt unshelve
  • Wybrałem swoja półkę.
  • Rozwiązałem konflikty.
Jak widać jeśli korzystamy z komendy tfpt to zamiast otrzymać błąd dostaniemy listę wykrytych konfliktów i możliwość ich rozwiązania. Można? Ano można.

16/11/2011

Hawkeye

Home

Hawkeye .NET Runtime Object Editor to program, który znalazłem w sieci dobre dwa lata temu. W tym czasie wielokrotnie mi się przysłużył, a jest przydatny w szczególności tym, którzy pracują z technologią Windows Forms. W skrócie, pozwala modyfikować UI działającej aplikacji. Jego użycie jest proste. Wskazujemy myszką interesujący nas fragment aplikacji, a Hawkeye oznacza wybraną kontrolkę przy pomocy czerwonej ramki i wyświetla listę właściwości i prywatnych pól klasy, których wartości możemy modyfikować. Jak by tego było mało, Hawkeye pozwala również dynamicznie wywoływać metody dla wybranych obiektów.

Zastanawiamy się jak nasza aplikacja będzie wyglądać z różowym tłem? Chcemy przesunąć na próbę kontrolkę o 2 piksele w prawo? A może chcemy nacisnąć przycisk, który jest nieaktywny? Te i inne rzeczy osiągamy zmieniając wartości odpowiednich właściwości, w tym przypadku odpowiednio: BackColor, Location, Enabled. W trzecim wypadku możemy też wywołać dynamicznie metodę PerformClick. Pozwala to w łatwy i przyjemny sposób zobaczyć, jak przy danych ustawieniach, będzie w runtime'ie będzie wyglądała nasza aplikacja, bez potrzeby jej rekompilacji.

Hawkeye pozwala również poruszać się w hierarchii kontrolek tworzących interfejs i sprawdzić kto jest rodzicem interesującej nas kontrolki. Podaje też pełną nazwę klasy dla aktualnie wybranego obiektu. Przydaje się to, kiedy pracujemy z dużą, skomplikowaną aplikacją i chcemy dowiedzieć się gdzie u licha znajduje się kod obsługujący aktualnie widoczną kontrolkę. Przykład z życia. Ostatnio kumpel potrzebował znaleźć kod odpowiedzialny za pewną część UI. W tym celu chciał wyświetlić designera dla interesującego go okna i na tej podstawie dojść do kontrolki. Niestety Visual Studio odmówiło posłuszeństwa i przy próbie wyświetlenia designera raportowało błąd. Hawkeye rozwiązał problem w kilka minut.

Uwaga! Na dzień dzisiejszy, na stronie programu znajdują się jego dwie wersje do pobrania. Jedna działa tylko z aplikacjami skompilowanymi na platformę .NET 4, a druga z aplikacjami skompilowanymi na starsze wersje platformy.