Notki po polsku
RavenDB – najprostsza baza danych, jaką widziałeś
May 20th
RavenDB właśnie został oficjalnie opublikowany. Przez kilka ostatnich dni popołudniami i ł eksperymentowałem z tą technologią. Teraz chciałbym się z Wami podzielić moimi odczuciami.
Z początku byłem nastawiony bardzo sceptycznie — kolejna zabawka Ayende. Po Rhino DHT, Rhino PHT, Rhino Queues, Rhino ServiceBus i innych, których nie pamiętam, straciłem entuzjazm. W końcu jednak postanowiłem dać Raven’owi szanse. W końcu jest to jedyny produkt z kategorii NoSQL napisany w .NET.
Moje eksperymenty rozpocząłem od zabawy z klienckim API. Jest to coś, co wyróżnia ten produkt na tle konkurencji. Inne bazy dokumentowe albo wcale nie mają .NETowego API, albo jest ono napisane na odczepkę i po macoszemu. API klienckie Ravena jest przemyślane i dopracowane w każdej kwestii. Prawdopodobnie dlatego, że jest oparte o NHibernate. Przestawienie się z jednego na drugie było dla mnie kwestią minut: zamiast ISessionFactory — IDocumentStore, zamiast ISession — IDocumentSession.
Z naśladowaniem API NHibernate wiąże się jeszcze jedna unikatowa cecha API Ravena — implementacja wzorca Unit of Work. Oznacza to, że w ramach jednej instancji IDocumentSession każdy pobrany dokument ma dokładnie jedną reprezentację w pamięci. Jeśli pobierzemy dwukrotnie ten sam dokument, Raven automatycznie wykryje ten fakt i za drugim razem zwróci nam pobraną wcześniej instancję. Możemy więc śmiało używać operatora == na obiektach wyciągniętych z bazy.
Kolejnym miłym zaskoczeniem była dla mnie obsługa transakcji. Na poziomie protokołu HTTP Raven może przetwarzać wiele zleceń modyfikacji danych wysłanych za pomocą jednego żądania. Taka paczka jest wtedy przetwarzana transakcyjnie — albo wykonają się wszystkie modyfikacje, albo żadna. Na poziomie API klienta, Raven automatycznie łączy w paczki wszystkie modyfikacje z danej instancji IDocumentSession. Nasz unit of work jest więc także transakcją. Czy może być coś prostszego?
API Ravena wspiera także sharding, czyli technikę pozwalającą klientowi traktować wiele instancji bazy danych jako jedną wielką instancję. Co najważniejsze, wsparcie to jest zupełnie przeźroczyste dla kodu — zrealizowane jest po prostu jako inna implementacja pary interfejsów IDocumentStore oraz IDocumentSession.
Zachęcam Was wszystkich do poeksperymentowania z Ravenem. Ruch NoSQL zasługuje na odrobinę uwagi. Zrozumiałe jest dla mnie, że nie każdy od razu rzuca się na CouchDB i pisze “z palca” funkcje map-reduce w Erlangu, ale Raven to zupełnie inna para kaloszy. Raven jest napisany w .NET specjalnie dla developerów piszących w .NET! Naprawdę warto dać mu szansę.
Sześć tygodni nietrywialnego modelu
May 17th
Minęło właśnie sześć tygodni od opublikowania notki omawiającej szkic architektury systemu, nad którym właśnie pracuje. Tak, jak zakładaliśmy, wchodzimy właśnie z naszym systemem w fazę testów akceptacyjnych. Wdrożenie produkcyjne zbliża się wielkimi krokami. Przy tej okazji chciałbym Wam opowiedzieć, co zmieniło się w projekcie naszego systemu w ciągu tych pracowitych sześciu tygodni.
Architektura
Większość definicji architektury odnosi się do jej niezmienności — architektura, to ta część projektu, która jest kosztowna lub trudna do zmiany w późniejszych jego etapach. Ponieważ nie przechodziliśmy przez żaden bolesny proces przeprojektowywania, mogę z całą pewnością stwierdzić, że nasza architektura pozostała niezmieniona. Na początku projektu zdefiniowałem architekturę tak:
model zakłada istnienie anemicznych struktur danych, które są przetwarzane przez wiele następujących po sobie procesów. Każdy proces jest zaś sekwencją czynności, z których każda reaguje na konkretny rodzaj zdarzenia. Efektem wykonania czynności jest opublikowanie innego zdarzenia, aktywujące kolejną czynność.
Prawda, że minimalna ta definicja? Dobra architektura powinna być właśnie minimalna, ponieważ tylko wtedy może być stabilna. Jeśli architekt robi zbyt wiele założeń, jest więcej niż pewne, że któreś z tych założeń okaże się fałszywe w toku prac nad projektem.
Infrastruktura
Celem infrastruktury jest implementacja założeń architektury. W tym obszarze, jak w każdym innym, staram się kierować zasadą YAGNI (You Ain’t Gonna Need It). Z tego powodu infrastruktura w moim projekcie zmieniała się dosyć często.
Na początku było…
Na początku w ogóle jej nie było! Przez pierwszy tydzień lub dwa skupiliśmy się na rozwoju logiki systemu. Chcieliśmy mieć działający prototyp, który przechodzi testy “na sucho” (bez odwoływania się do bazy danych itp.). W ten sposób chcieliśmy udowodnić, że model procesowy w ogóle sprawdza się w przypadku naszego problemu. Gdyby okazało się inaczej, mielibyśmy jeszcze całkiem sporo czasu na zmianę założeń.
Pierwsza wersja
Ostatecznie przyszedł czas na pierwszą wersji infrastruktury. Napisanie jej zajęło mi jakieś 2 dni. Wersja ta obsługiwała wszystkie trzy tryby łączenia zdarzeń i czynności (natychmiastowy, z checkpointem i odroczony). Nie oferowała jedna żadnych mechanizmów kontroli stanu działania (ile zdarzeń oczekuje na przetworzenie itp.) ani historii wykonania (jakie czynności zostały wykonane w kontekście danego obiektu biznesowego). Była wystarczająca do wewnętrznych testów i o to nam chodziło.
Mechanizm był banalnie prosty. Każde zakolejkowane zdarzenie było adresowane do konkretnej docelowej czynności. W momencie publikacji odszukiwane były wszystkie zasubskrybowane czynności i do kolejki odkładane było tyle kopii zdarzenia, ile znaleziono subskrybentów. Dzięki temu różne czynności mogły subskybować to samo zdarzenie z różnym opóźnieniem.
Przetwarzanie zrealizowane było w formie pętli, która w każdym obrocie pobierała pierwsze zdarzenie z kolejki, odnajdywała jego adresata, przekazywała mu obiekt zdarzenia a następnie usuwała zdarzenie z kolejki oczekujących. Efektem takiego działania mogło być opublikowanie nowych zdarzeń. Te z nich, które były subskrybowane trwale (z checkpoint‘em) odkładane były do kolejki w bazie danych. Pozostałe kolejkowane były w pamięci do natychmiastowego przetworzenia w ramach tej samej transakcji.
Historia
Pierwszym dodatkowym wymaganiem, jakie się pojawiło, było odkładanie historii przetwarzania. “Nic prostszego!” — odpowiedziałem naszemu Product Owner‘owi. Dodanie kodu wiążącego zdarzenie z obiektem zajęło mi dosłownie chwilę. Efektem ubocznym tej poprawki była zmiana kodu usuwającego przetworzone zdarzenia na kod, który oznaczał je flagą “wykonane”. Jakie to miało konsekwencje dowiecie się niedługo.
Diagnostyka
Następna zmiana została zainicjowana przeze mnie. W pierwszej wersji, do zapisu danych związanych ze zdarzeniami, użyliśmy serializacji binarnej System.Runtime.Serialization. Dlaczego? Ponieważ ten mechanizm jest wspierany przez NHibernate out-of-the-box. Niestety ma on dwie wady. Po pierwsze, rozmiar danych po serializacji jest dosyć duży. Po drugie (i gorsze), dane te są zupełnie nieczytelne, zarówno z poziomu SQL Server Management Studio, jak technologii GUI, którą zastosowaliśmy (własne rozwiązanie 4GL). Przejście na serializację JSON (z formatowaniem) rozwiązało problem diagnostyki (wykorzystaliśmy bardzo dobrą bibliotekę JSON.NET).
Wydajność
Przetwarzanie zdarzeń oparte było o jedną tabelę w bazie danych. Zdarzenia, która są gotowe do przetworzenia wyciągane były mniej-więcej takim zapytaniem:
SELECT * FROM QueuedEvent WHERE DueDate <= @currentTime AND Processed = 0
Ten drugi warunek został dodany w momencie implementacji obsługi historii. Niestety wydajność takich zapytań, wziąwszy pod uwagę ilość zdarzeń, jakie mogą się odłożyć po roku od uruchomienia systemu, nie byłaby zapewne zadowalająca. Pierwszym rozwiązaniem, jakie się nasuwa, jest partycjonowanie. Niestety w tym wypadku zdefiniowanie funkcji partycjonującej byłoby trudne. Postanowiliśmy więc rozdzielić zdarzenia na dwie osobne tabele: jedną dla zdarzeń oczekujących i jedną dla przetworzonych. Ta pierwsza zwykle będzie zawierać nie więcej niż kilkadziesiąt rekordów, więc nie ma sensu w ogóle jej indeksować. Dzięki temu częste wstawienia i usunięcia będą tańsze.
Wydajność raz jeszcze
W końcu przyszedł czas na podejście do problemu zrównoleglenia przetwarzania. Okazało się, że czas odpowiedzi zewnętrznych systemów, z którymi się integrujemy, jest bardzo długi. Miało to bardzo niekorzystny wpływ na przepustowość naszego systemu, gdyż w podczas odczekiwania na odpowiedź dla jednego zdarzenia, inne nie były przetwarzane.
Rozwiązanie jest proste — zrównoleglijmy przetwarzanie. Prościej powiedzieć niż zrobić. Ostatecznie jednak się udało. Nasze rozwiązanie składa się z podręcznej pamięci przechowującej identyfikatory zdarzeń oczekujących na przetwarzanie i wielu wątków pobierających kolejne identyfikatory. Cache automatycznie odświeża się, gdy zostanie opróżniony.
Aby nie blokować całej tabeli ze zdarzeniami w momencie pobierania danych do cache, stosujemy tam najsłabszy poziom izolacji transakcji. Konsekwencją tego jest fakt, że nie jesteśmy pewni, czy zdarzenia, których identyfikatory pobraliśmy, są jeszcze zdatne do przetworzenia. Fakt ten jest weryfikowany przed samym przetworzeniem oraz po jego zakończeniu (optimistic concurrency).
Morał
Każda historia ma swój morał. W tym wypadku chodzi o relację architektura — design. Architektura w tym projekcie była stabilna. Z drugiej strony design (czyli głównie infrastruktura) zmieniał się praktycznie cały czas.
Stabilność architektury (zaklętej w interfejsach) pozwoliła zbudować szybko całą logikę biznesową aplikacji, bez potrzeby robienia kosztownych restrukturyzacji po każdej modyfikacji infrastruktury.
Najlepsza architektura to taka, której prawie nie widać. Najlepszy design to taki, który rozwija się wraz z projektem i jego potrzebami.
Ewolucja przyzwyczajeń DI/IoC
May 11th
Unity
Bardzo długo moim ulubionym kontenerem był Unity, mimo faktu, że dużo mądrych ludzi ze społeczności wieszało na nim psy. Dlaczego tak polubiłem Unity? Ponieważ powstawał na moich oczach. Znam go począwszy od wczesnych “zajawek” wypuszczanych przez grupę Patterns & Practices. Wcześniej nieco interesowałem się ich biblioteką ObjectBuilder, więc naturalnie moja uwaga przeszła na Unity. Byłem w stanie przeczytać i zrozumieć kod pierwszej wersji tego kontenera bez specjalnego wysiłku. Efekt tego był taki, że znam Unity jak własną kieszeń i wiem czego się po nim spodziewać.
To prawda, że pod względem ilości ficzerów Unity jest daleko w tyle za “wiodącymi” kontenerami, ale przez bardzo długi czas zupełnie mi to nie przeszkadzało. W końcu jednak zaczęło…
Dlaczego nie Unity?
W projekcie, który aktualnie realizuję w końcu (co było do przewidzenia) pojawiła się potrzeba zastosowania kontenera DI. Było to w momencie tworzenia pierwszych testów integracyjnych. Struktura projektu oparta jest na kilku kluczowych interfejsach reprezentujących najważniejsze koncepcje, takie jak: czynność, proces (sekwencja czynności), reguła walidacji, czy reguła przepisywania. Tu pojawił się problem: chciałbym, aby rejestracja implementacji była w jakiś sposób zautomatyzowana — abym nie musiał rejestrować w kontenerze każdej kolejnej klasy. Niestety Unity (sam w sobie) nie pozwala na takie rzeczy. Czyżby czas na zmianę przyzyczajeń?
Jeśli nie Unity, to co?
Potrzebowałem czegoś, co jest w stanie zarejestrować automatycznie implementacje moich interfejsów na podstawie podanych reguł — konwencji. Wiele kontenerów obsługuje dziś takie scenariusze. W szczególności rozważałem dwa: Autofac oraz Castle Windsor. Spring.NET dyskwalifikuje niedojrzałość płynnego interfejsu konfiguracji. Wybrałem ostatecznie Autofaca. Dlaczego? Powodów jest kilka.
Po pierwsze i najważniejsze, Autofac pozwala mi zrealizować moje wymaganie. Kod konfigurujący kontener wygląda mniej-więcej tak:
private void RegisterActivities()
{
RegisterAllTypes().Where(IsActivityType).AsSelf().SingleInstance();
}
private static bool IsActivityType(Type type)
{
return type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IActivity<>));
}
private void RegisterRequestValidationRules()
{
RegisterAllTypes().AssignableTo<IRequestValidationRule>().AsImplementedInterfaces();
}
Pierwsza metoda rejestruje wszystkie klasy implementujące generyczny interfejs IActivity<T> jako konkretne klasy w trybie singletonowym. Druga jest oczywiście pomocnicza. Trzecia zaś rejestruje wszystkie typy implementujace interfejs IRequestValidationRule jako imlementowane interfejsy. Prawda, że proste i eleganckie?
Po drugie, Autofac to tylko kontener. Nie posiada rozbudowanego systemu rozszerzeń (jak Castle Facilities), który, o ile aplikacja tego wymaga, może okazać się nieoceniony, jednak w moim przypadku (dosyć prosta aplikacja) tylko by zawadzał.
Po trzecie, Autofac nie posiadająca żadnych zależności. Dlaczego to takie ważne? Ponieważ mój projekt, z takich czy innych powodów, ma już podłączone (o wiele za) dużo różnych bibliotek. Nie chciałem narażać się na możliwość konfliktu wersji Castle.DynamicProxy (z którego korzystają zarówno moje biblioteki, jak i Windsor) lub log4net.
Po ostatnie (i najmniej ważne), Autofac to tylko jedna dll-ka.
Co dalej?
Zrewidowałem właśnie swoje przyzwyczajenia odnośnie kontenera. Zmieniłem Unity na Autofac. To oczywiste, ale to nie wszystko. Zmieniłem także swoje nastawienie kwestii DI w ogólności. Ta zmiana zmusiła mnie do lepszego przyjrzenia się dostępnym możliwościom, poszerzyła moje horyzonty. Wreszcie, zmiana ta uczyniła każdą następną zmianę łatwiejszą. Nowe wymaganie dla mojej aplikacji będzie można zrealizować prościej używając Castle Windsor? Proszę bardzo! Już się “przepinam”.
Nieodłączną cechą “wyzywania na pojedynek” swoich przyzwyczajeń jest uświadomienie sobie istnienia założeń, które podświadomie czynimy. W moim wypadku założeniem było korzystanie z Unity i przez kilka projektów nawet nie rozważałem innej opcji. Po “przygodzie” z Autofac, następnym razem na pewno się zastanowię. Innym przykładem nieświadomego założenia jest wykorzystanie bazy SQLowej w (entej) postaci normalnej. Czy ktoś się nad tym zastanawia? A może warto? W dzisiejszych czasach jest tyle ciekawych alternatyw…
Wydarzenia społecznościowe – Kraków
May 10th
57. spotkanie KGD.NET
W najbliższą środę, 12 maja, odbędzie się pięćdziesiąte siódme, przedostatnie w tym sezonie, spotkanie Krakowskiej Grupy Developerów .NET. Planowane są dwie prezentacje. Na początek Piotr Leszczyński opowie o dwóch bardzo “trendi” technologiach: Silverlight 4 oraz MEF oraz o tym jak można ten tandem wykorzystać do budowy pluginowej aplikacji. Następnie Marcin Książek zademonstruje nową funkcjonalność TFS 2010 — Test Lab Managamenet. TLM umożliwia budowę wirtualnych laboratoriów testerskich za pomocą kilku kliknięć.
Serdecznie zapraszam na obie prezentacje. Spotkanie odbędzie się, jak zwykle, w siedzibie ABB, przy ulicy Starowiślnej 13. Start — 18.30. Aby się zarejestrować, kliknij tutaj.
Visual Studio Community Launch
Za niecałe dwa tygodnie, w sobotę 22 maja, odbędzie się Krakowska edycja wydarzenia Visual Studio Community Launch. Co to takiego? Jest to darmowa konferencja dla developerów tworzących oprogramowania na platformy Microsoft zorganizowana z okazji wydania nowej wersji narzędzi platformy (.NET) oraz narzędzi developerskich (VisualStudio).
W ramach konferencji (trwającej od 9.00 do 19.00) będzie można wziąć udział w siedmiu prezentacjach o bardzo zróżnicowanej tematyce: począwszy od zawiłych kwestii związanych z pisaniem kodu równoległego, a skończywszy na prezentacji narzędzi dla architektów.
W ramach ciekawostek, podczas konferencji będzie można pobawić się Microsoft Surface. Wśród osób, które wytrwają do końca, rozlosowana zostanie konsola XBox 360. Już dziś zarejestruj się tutaj. Dodatkowo, możesz zarejestrować się na witrynie networkingowej VSCL tutaj. Dzięki temu jeszcze przed konferencją będziesz miał okazję poznać ludzi, którzy wezmą w niej udział, umówić się na kawę w przerwie między prezentacjami lub na piwo po konferencji.
Testowalność modelu domeny
May 4th
Większość osób zaczynając swoją przygodę z Domain-Driven Design jest mocno zafascynowanych encjami. Tak też było w moim wypadku. Prawdę mówiąc, pierwszy system, który wydawało mi się, że buduję w oparciu o zasady DDD nie miał w ogóle rozróżnienia na encje i obiekty wartości. Tych ostatnich po prostu nie było.
Jeśli ktoś zaczął czytać mojego bloga w tym miejscu, krótkie wyjaśnienie. Encja (Entity) to w DDD obiekt posiadający własną tożsamość niezależną od wartości atrybutów. Obiekt wartości (Value Object) to zaś byt tożsamości pozbawiony. Dwa obiekty wartości są uważane za tożsame, jeśli zawierają takie same dane. Dobrą praktyką jest, aby obiekt wartości był niemodyfikowalny (immutable), to znaczy nie możliwa była zmiana wartości jego atrybutów po utworzeniu (jak w klasie String).
Wracając do głównego wątku, dlaczego w ogóle o tym piszę? Czytałem ostatnio sporo postów, głownie dotyczących Entity Framework 4, ale nie tylko, sugerujących, że mapowanie O/RM tabelek w bazie danych na klasy C# (lub jakiegokolwiek innego języka) da nam w efekcie model domeny. Owszem, da — jakiś model. Ale na pewno nie będzie on zgodny z dobrymi praktykami DDD. Brakującym elementem są obiekty wartości.
Dlaczego są one notorycznie pomijane przez wszelkie poradniki dotyczące bibliotek O/RM? Tego nie wiem. Wiem jednak, że obiekty wartości mają kluczowe znaczenie, jeśli chodzi o testowalność modelu domeny. Encje są “grubymi” obiektami, które z definicji akumulują stan. Zmiany stanu encji spowodowane są oczywiście wywoływaniem ich metod. Z drugiej strony efekty wywoływania metod zależą oczywiście od stanu encji w momencie wywołania. Tu pojawiają się schody: aby przetestować obiekt należy go najpierw doprowadzić do odpowiedniego stanu. Jak to zrobić? Są na to dwie możliwości:
- ustawić wartości pól za pomocą refleksji
- wywołać odpowiednią kombinację metod
Obie są raczej półśrodkami, niż godnymi polecenia metodami. Wadą pierwszej jest bardzo wysoki stopień powiązania między testami, a implementacją, znacznie ograniczający możliwości refaktoryzacji kodu. Drugi zaś powoduje powstawanie niespecyficznych testów, które chcąc przetestować jedną metodę biznesową, muszą najpierw wywołać inne 4, aby przygotować obiekt. Kiedy błąd wkradnie się do jednej z podstawowych (wczesnych w cyklu życia obiektu) metod, złamany zostanie nie jeden test, ale kilkadziesiąt/kilkaset wykorzystujących tę metodę do przygotowania stanu.
Co mają do tego obiekty wartości? Jak już wspomniałem, powinny one być niemodyfikowalne. Jest to wspaniała cecha, jeśli chodzi o testowalność. Oznacza bowiem, że cały problem z doprowadzeniem obiektu do odpowiedniego stanu magicznie znika! Obiekty wartości otrzymują komplet informacji określających ich stan w konstruktorze. Jeśli chcesz zmodyfikować obiekt wartości — tworzysz po prostu nową instancję.
Brzmi prosto. Cały problem polega na tym, żeby tak budować model domeny, aby logika biznesowa naturalnie trafiała do obiektów wartości, natomiast encje były jedynie odpowiedzialne za koordynowanie odpowiednich wywołań tych obiektów oraz przechowywanie ich instancji jako reprezentacji własnego stanu. Rola encji w modelu powinna się ograniczać do trzech prostych zadań:
- utrzymywania unikalnej tożsamości
- utrzymywania relacji z innymi encjami
- przechowywania stanu w postaci obiektów wartości i (ostatecznie) typów prymitywnych
Wszystko ponadto, a w szczególności skomplikowana logika biznesowa, jest zbędne i może zostać zepchnięte na obiekty wartości. Nawet utrzymywanie relacji z innymi encjami, jeśli wymaga skomplikowanej logiki, może być zrealizowane przez wyspecjalizowany obiekt wartości.
Samo nasuwa się pytanie: dobrze, ale jak? Istnieje wiele sprawdzonych wzorców ułatwiających zadanie wydzielania obiektów wartości ze zbyt “grubych” encji. Przykładem wręcz klasycznym jest wzorzec Quantity/Money Fowlera. Inne obiekty wartości, które ja osobiście stosuję, to m.in. Currency (symbol waluty), Country (symbol kraju), BIC (11-to znakowy kod BIC/SWIFT banku), IBAN (międzynarodowy numer rachunku bankowego) oraz NRB (numer rachunku polskiego).
No dobrze, nawet jeśli się uda zepchnąć całą logikę do obiektów wartości, to i tak encje pozostaną i będą potrzebowały przetestowania. Jak do zrobić? Jestem zdania, że kod pozbawiony logiki warunkowej (a taki powinien być kod encji) wystarczy przetestować za pomocą rozsądnego zestawu testów integracyjnych, ale każdy może mieć swoje ulubione metody. Ważne, żeby testować:-)



