Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

26 Marzec 2015

Messaging, czyli pierwsi będą pierwszymi

Filed under: Codzienne dylematy modelarza — Tags: , , , , — Beniamin Zaborski @ 22:24

Ostatnio zrobiłem przegląd rozwiązań „messagingowych” dla .NET pod kątem użycia w CQRS + ES. Nie wypadało nie zacząć od „rodzimego” MSMQ. Tu niewiele się zmieniło od mojego ostatniego kontaktu z tą technologią. Ostatecznie moją uwagę najbardziej przykuło jedno rozwiązanie: RabbitMQ. Dlaczego? Zalety: otwarte, darmowe, stosunkowo proste w użyciu, multiplatformowe, z client API dla wielu języków w tym oczywiście i dla C#. I najważniejsze – to działa!;). Z tym client API to może, yyy … nie jest najfajniejsze, ale … od czego jest EasyNetQ?
Kolejna zaleta jest taka, że RabbitMQ jest już trochę na rynku i wydaje się mieć na nim ugruntowaną pozycję. Dla mnie bomba, zobaczymy dalej;).

17 Marzec 2015

T-SQL – czy transakcje są atomowe?

Filed under: Sikłelowo — Tags: , , , — Beniamin Zaborski @ 22:19

W myśl zasad ACID, które SQL Server spełnia, tak! transakcje są atomowe. A co z poniższym przykładem?


USE tempdb
GO

CREATE TABLE dbo.UZYTKOWNICY (
ID_UZYTKOWNIKA UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
LOGIN VARCHAR(15) NOT NULL UNIQUE
);
GO

BEGIN TRANSACTION
INSERT INTO dbo.UZYTKOWNICY(ID_UZYTKOWNIKA, LOGIN) VALUES(NEWID(), 'Uzytkownik1'); -- OK
INSERT INTO dbo.UZYTKOWNICY(ID_UZYTKOWNIKA, LOGIN) VALUES(NEWID(), 'BardzoDlugiLoginUzytkownika'); -- ERROR
COMMIT

SELECT * FROM dbo.UZYTKOWNICY;
GO

W ramach transakcji wykonuję dwie operacje INSERT. Pierwsza jest OK, natomiast druga operacja próbuje dodać rekord, gdzie wartość wstawiana do kolumny LOGIN jest dłuższa od maksymalnej dopuszczalnej.

Czy transakcja się powiedzie? Atomowość – pamiętacie?

Tak powiedzie się, ale częściowo! SELECT zwróci tylko jeden rekord (ten pierwszy z transakcji). WTF?! Zerknijmy do dokumentacji, a ona na to: „in some cases only the Transact-SQL statement that raised the error is rolled back and the transaction continues processing”.

Można sobie z tym poradzić wykonując SET XACT_ABORT ON przed rozpoczęciem transakcji. Teraz cała nasza transakcja będzie wycofana po napotkaniu błędu. Czyż nie tego oczekiwaliśmy?

11 Styczeń 2015

Jak new ProxyGenerator() z Castle.DynamicProxy może zabić maszynę

Filed under: How Can I Take ... — Tags: , , — Beniamin Zaborski @ 18:55

Castle.DynamicProxy to fajna biblioteka do dynamicznego generowania obiektów proxy. Zwykle obiekt proxy przydaje się nam gdy chcemy wykonać dodatkową operację przed, po czy na wystąpienie błędu wykonania właściwej metody. Najbardziej trywialny przykład to logowanie błędów, ale można wyobrazić sobie także bardziej wysublimowane przykłady jak choćby zarządzanie transakcjami.
Przykładowy kod tworzący obiekt proxy dla naszego obiektu implementującego interfejs ISomeBusinessComponent (z metodą DoMagic) i aplikujący jeden prosty interceptor SomeInterceptor mógłby wyglądać tak:

ProxyGenerator proxyGenerator = new ProxyGenerator();
var componentProxy = proxyGenerator.CreateInterfaceProxyWithTarget(component, new SomeInterceptor());
componentProxy.DoMagic();

Pierwszy krok to utworzenie instancji ProxyGenerator. Następnie możemy wygenerować obiekt proxy i wykonać na nim metodę DoMagic. Nic wyszukanego – prosty przykład. Ale co jeśli chcielibyśmy intensywnie i w wielu miejscach aplikacji tworzyć obiekty proxy. Hmm … po prostu powyższy kod wstawilibyśmy w te miejsca gdzie powinien być wywołany. Co sprytniejsi ubraliby go nawet w jakąś metodę utilową. Zasymulujmy taki przypadek za pomocą pętli:

foreach (var component in components)
{
  ProxyGenerator proxyGenerator = new ProxyGenerator();
  var componentProxy = proxyGenerator.CreateInterfaceProxyWithTarget(component, new SomeInterceptor());
  componentProxy.DoMagic();
}

Kod na pierwszy rzut oka może i wygląda OK (wiem można przenieść instancjonowanie ProxyGenerator poza pętlę – to symulacja). W rzeczywistości przy każdym wywołaniu przebiegu pętli będziemy tworzyć nową instancję ProxyGenerator, a jest to kosztowna operacja jeśli chodzi o pamięć (tworzenie m.in. Assembly).
Rozwiązanie? W tej symulacji po prostu należałoby wynieść tworzenie obiektu proxyGenerator poza pętlę. Inaczej mówiąc prosi się tu o singleton dla ProxyGeneratora.

foreach (var component in components)
{
  ProxyGenerator proxyGenerator = ProxyGeneratorSingleton.Instance;
  var componentProxy = proxyGenerator.CreateInterfaceProxyWithTarget(component, new SomeInterceptor());
  componentProxy.DoMagic();
}

Odpowiednio częste wykonywanie tworzenia instancji ProxyGenerator potrafi skutecznie skonsumować całą dostępną pamieć operacyjną na naszej maszynie.

Działający przykład zamieszczam tutaj.

28 Grudzień 2014

SQL Server – optymalne rozmieszczenie plików

Filed under: How Can I Take ... — Tags: , , , — Beniamin Zaborski @ 22:10

Utworzenie nowej bazy danych w SQL Server zapewni nam poniższe polecenie języka T-SQL:

CREATE DATABASE ENT_PROJECT
GO

Co tak naprawdę się stanie po wykonaniu tego polecenia? Zanim coś więcej o tym, to wcześniej należy wspomnieć o sposobie przechowywania danych na dysku przez SQL Server.

SQL Server mapuje bazy danych na zestaw plików przechowywanych w systemie plików systemu operacyjnego. Zapisywane są dwa rodzaje danych: właściwe dane oraz log transakcji. Dane i log nigdy nie są mieszane w jednym pliku – zawsze znajdują się w osobnych plikach. Jeden plik zawsze odnosi się tylko do jednej bazy danych – nigdy nie zawiera danych czy informacji z kilku baz.

SQL Server posiada zasadniczo trzy typy plików:

Primary data files – zawiera dane oraz wskazania na inne pliki bazy danych, istnieje tylko jeden taki plik na bazę danych, posiada rozszerzenie mdf.
Secondary data files – zawiera dane, może istnieć wiele plików tego typu na bazę danych, posiada rozszerzenie ndf.
Log files – zawiera log transakcji potrzebny do odtworzenia bazy danych, musi być przynajmniej jeden taki plik na bazę danych ale może być ich więcej, posiada rozszerzenie ldf.

Wskazane rozszerzania plików są jedynie rozszerzeniami sugerowanymi i możemy użyć innych jeśli mamy taką potrzebę. Oprócz samych plików SQL Server wprowadza także pojęcie filegroups, które są nazwanymi grupami plików i są tworzone w celu ułatwienia zarządzania bazą danych. Możemy wyróżnić dwa rodzaje grup plików:

Primary – zawiera pliki typu primary data files
User-definied – opcjonalnie utworzone przez użytkownika, zawierają dodatkowe pliki danych utworzone przez użytkownika.

Grupy plików zawierają a raczej grupują jedynie pliki danych. Pliki logów transakcji nie są przechowywane w grupach plików. Dodać należy, że w grupie Primary będą przechowywane wszystkie obiekty systemowe bazy danych (oczywiście pośrednio, gdyż de facto znajdują się one w plikach danych).

Wróćmy do przykładu polecenia T-SQL tworzącego bazę danych. Nie wyspecyfikowałem w nim żadnych informacji odnośnie plików czy grup plików podczas tworzenia bazy danych, więc został przyjęty model domyślny. Model domyślny oznacza jedną grupę plików PRIMARY z jednym plikiem danych oraz jeden plik logów (oczywiście poza jakąkolwiek grupą). Wszystkie moje dane jak i dane systemowe będą przechowywane w jednym pliku danych – primary.

W niewielkich bazach danych czy w bazach które nie są zbyt intensywnie wykorzystywane (niewielka ilość transakcji na sekundę) taka konfiguracja może być OK. Co innego gdy mamy do czynienia z dużym dedykowanym systemem klasy enterprise. W takim wypadku wskazane jest utworzyć kilka grup plików. Nasuwają się pytania: ile utworzyć grup plików, jak je optymalnie rozmieścić? Istnieje kilka dobrych praktyk i postaram się te znane mi przytoczyć.

Po pierwsze musimy być świadomi ilości dysków/kontrolerów dyskowych naszego serwera, a także ilości rdzeni CPU. Jeśli mamy już taką wiedzę weźmy pod uwagę poniższe:

– Pliki logów powinny być na osobnym dysku, szybkim dysku, gdyż do nich odbywa się intensywny zapis. Dostęp do plików logów jest sekwencyjny więc nie ma większego sensu tworzyć więcej niż jednego pliku logów. Jedynym powodem do tego może być sytuacja gdy skończy się przestrzeń na dysku.

– Warto utworzyć tyle plików danych/grup plików ile dysków pomniejszając liczbę o dyski na pliki logów (w najprostszym przypadku o 1).

– Dobrym pomysłem jest odseparowanie danych systemowych od całej reszty. Uzyskamy to poprzez oznaczenie którejś z grup plików utworzonej przez użytkownika jako domyślnej (innej niż primary która standardowo jest domyślną).

– Pozytywnie na wydajność wpłynie utworzenie kilku plików dla bazy tempdb która jest intensywnie wykorzystywana. Jedna z zasad mówi, że plików danych powinno być tyle ile rdzeni CPU naszego serwera, a plików logów tyle ile fizycznych dysków (co stanowi wyjątek od tego co napisałem w punkcie pierwszym).

Utworzenie odpowiedniej ilości plików danych niejako determinuje nam konfiguracja sprzętowa, ale to tylko jedna strona medalu. Jeśli utworzyliśmy wiele grup plików to musimy odpowiednio rozmieścić nasze obiekty bazodanowe we właściwych grupach. Służy nam do tego słowo kluczowe ON filegroup. Zatem np. w poleceniu T-SQL do tworzenia tabeli CREATE TABLE możemy wskazać w której grupie plików nasza tabela będzie przechowywana. Jeśli nie wskażemy tej lokalizacji to będzie przyjęta domyślna czyli grupa plików z ustawionym znacznikiem DEFAULT. Optymalne rozmieszczenie tabel czy indeksów w określonych grupach plików zależy już mocno od specyfiki samego systemu jaki tworzymy. Na pewno nie jest to łatwe zadanie, ale kierować się można poniższymi wskazówkami:

– Warto myśleć w kategoriach zrównoleglenia wykonywanych operacji IO przez system bazodanowy versus obszary funkcjonalne naszej aplikacji.

– Dobrym pomysłem będzie umieszczenie indeksów tabeli jak i samej tabeli w różnych grupach plików. Inne podejście jest jeszcze bardziej radykalne i mówi o odseparowaniu od siebie także indeksów klastrowych i nieklastrowych.

 

Uzbrojeni w tę wiedzę, dysponując serwerem z pięcioma dyskami (C, D, E, F, G) i jednym cztero-rdzeniowym procesorem moglibyśmy utworzyć bazę danych np. w taki sposób:

CREATE DATABASE ENT_PROJECT
ON PRIMARY
(
NAME = 'ENT_PROJECT0_dat',
FILENAME = 'C:\DBs\ENT_PROJECT0_dat.mdf'
),
FILEGROUP G1
(
NAME = 'ENT_PROJECT1_dat',
FILENAME = 'E:\DBs\ENT_PROJECT1_dat.ndf'
),
FILEGROUP G2
(
NAME = 'ENT_PROJECT2_dat',
FILENAME = 'F:\DBs\ENT_PROJECT2_dat.ndf'
),
FILEGROUP G3
(
NAME = 'ENT_PROJECT3_dat',
FILENAME = 'G:\DBs\ENT_PROJECT3_dat.ndf'
)
LOG ON
(
NAME = 'ENT_PROJECT_log',
FILENAME = 'D:\DBs\ENT_PROJECT_log.ldf'
)
GO

ALTER DATABASE ENT_PROJECT
MODIFY FILEGROUP G1 DEFAULT
GO

ALTER DATABASE tempdb 
ADD FILE 
(
	NAME = 'tempdev1',
	FILENAME = 'D:\DBs\tempdb1.ndf'
),
(
	NAME = 'tempdev2',
	FILENAME = 'E:\DBs\tempdb2.ndf'
),
(
	NAME = 'tempdev3',
	FILENAME = 'F:\DBs\tempdb3.ndf'
)
GO

ALTER DATABASE tempdb 
ADD LOG FILE 
(
	NAME = 'templog1',
	FILENAME = 'D:\DBs\templog1.ldf'
),
(
	NAME = 'templog2',
	FILENAME = 'E:\DBs\templog2.ldf'
),
(
	NAME = 'templog3',
	FILENAME = 'F:\DBs\templog3.ldf'
),
(
	NAME = 'templog4',
	FILENAME = 'G:\DBs\templog4.ldf'
)
GO

Przykład zakłada że w każdej z grup istnieje tylko jeden plik danych. Jednakże spełnia nasze założenia, tj. każdy plik jest na osobnym dysku, grupa primary nie jest domyślną grupą, baza tempdb posiada kilka plików. Celowo pominąłem w przykładzie T-SQL kwestię rozmiaru plików bazy danych, aby skupić się tylko na samym rozmieszczeniu plików na dyskach. Nie oznacza, to że jest to mało istotna rzecz z punktu widzenia wydajności naszej bazy danych. Wręcz przeciwnie. Warto oszacować rozmiar naszej bazy i jej miesięczny przyrost. SQL Server potrafi zarezerwować w systemie plików wskazaną przestrzeń na dane by nie walczyć o nią gdy jej zabraknie. Z drugiej strony, dzięki funkcji autogrowth, SQL Server może powiększyć „zaalokowaną” przestrzeń dyskową gdy zacznie jej brakować. Należy jednak pamiętać, że to dość kosztowna operacja i nie powinna być wykonywana za często, gdyż wtedy potrafi rozłożyć całą maszynę na łopatki.

Teraz możemy utworzyć tabelę oraz indeks we wskazanej grupie plików:

CREATE TABLE USERS (
USER_ID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
LOGIN VARCHAR(20) NOT NULL,
IS_ACTIVE BIT NOT NULL DEFAULT(1)
)
ON G2
GO

CREATE UNIQUE NONCLUSTERED INDEX IX_USERS_LOGIN
ON USERS(LOGIN ASC)
ON G3
GO

Jak widać tabela zostanie utworzona w grupie G2, a indeks nieklastrowy na tej tabeli zostanie utworzony w grupie G3.

Rozmieszczenie tabel w poszczególnych grupach plików należy szczególnie przemyśleć, gdyż po utworzeniu nie da się ich przenieść do innych grup plików.

Przedstawiłem kilka znanych mi reguł, ale pamiętajmy że są to jedynie suche reguły! Przed zaaplikowaniem ich na własnym środowisku należy spojrzeć na nie przez pryzmat własnego systemu.

19 Grudzień 2014

Castle Windsor – Transient Memory Leak

Filed under: How Can I Take ... — Tags: , , , , , — Beniamin Zaborski @ 22:20

Od jakiegoś czasu Castle Windsor to mój ulubiony kontener IoC. Jutro co prawda może się nim stać Ninject lub Autofac, ale nie dlatego że mi źle z Windsor, a raczej z czystej ciekawości. Przejdźmy do rzeczy.
Jak generalnie wyobrażamy sobie pracę z kontenerem IoC? Z reguły są dwa kroki do wykonania: rejestracja typu i pobranie instancji tego typu. W Windsorze w najprostszej formie wygląda to tak:

Rejestracja:

container.Register(
   Component.For<ISecurityService>()
   .ImplementedBy<SecurityService>()
);

Pobranie:

ISecurityService securityService = container.Resolve<ISecurityService>();

Niby wszystko jest OK i jest to jakaś alternatywa dla tego:

ISecurityService securityService = new SecurityService();

No tak, ale ktoś mógłby spytać o długość życia obiektu securityService. I w tym momencie dotykamy bardzo ważnej kwestii kontenerów IoC – LifeStyle. Każdy kontener IoC, Windsor także, posiada kilka wbudowanych elementarnych LifeStyle’i tj.: Singleton, Transient, Scope, PerWebRequest, itd.
LifeStyle określa zasięg obiektów instancjonowanych przez kontener oraz mówi kiedy będą niszczone. Singleton jest domyślnym lifestyle’m w Windsorze. Oznacza to, że będzie wytworzona jedna instancja przy pierwszym wywołaniu Resolve i reużywana przy kolejnych wywołaniach Resolve.
Zupełnym przeciwieństwem do Singletona jest Transient – przy każdym wywołaniu Resolve kontener zwraca nową instancję.

Rejestracja naszego serwisu dla Transient wyglądałaby tak:

container.Register(
   Component.For<ISecurityService>()
  .ImplementedBy<SecurityService>()
   .LifeStyle.Transient
);

Pobieramy tak zarejestrowany serwis, robimy z nim co mamy do zrobienia i zapominamy … Garbage Collector go zniszczy. Fałsz! Mina! Memory leak! Nic takiego nie nastąpi dopóki nie zniszczymy naszego kontenera IoC (jeśli w ogóle to robimy?). To jest dość często spotykana mina na którą się można nadziać. Windsor może trzymać referencję do obiektu transient, a więc Garbage Collector nie będzie w stanie zwolnić zasobów. Dla wszystkich obiektów transient najwyższego rzędu które zostaną przez nas jawnie pobrane z kontenera IoC musimy wykonać Release. Poprawna sekwencja powinna wyglądać tak:

Rejestracja:

container.Register(
   Component.For<ISecurityService>()
   .ImplementedBy<SecurityService>()
   .LifeStyle.Transient
);

Pobranie:

ISecurityService securityService = container.Resolve<ISecurityService>();

Praca z serwisem:

securityService.Authenticate(authDTO);

Zwolnienie:

container.Release(securityService);

I tak sobie myślę, że potencjalnie wbrew pozorom najbardziej zagrożeni nadzianiem się na tę minę nie są początkujący użytkownicy kontenerów IoC (jest szansa że oni uważnie czytają dokumentację), a raczej tacy którzy przesiadają się na Windsora z jakiś „starszych” rozwiązań, myśląc że to tylko kolejny kontener z nieco innym API. Mea culpa, przyznaję się, kiedyś sam się nadziałem, ale widzę że nie jestem sam ;).

4 Marzec 2014

Poszukiwacze kamieni szlachetnych – lambda

Filed under: Poszukiwacze kamieni szlachetnych — Tags: , , , — Beniamin Zaborski @ 20:08

W C# mamy wyrażenia lambda, które są anonimowymi funkcjami za pomocą których można tworzyć np. delegaty. Raczej powinny być znane każdemu programiście C# szczególnie, że dość intensywnie używamy ich w LINQ. Spójrzmy na przykład:

delegate bool AreEqualDelegate(int a, int b);
AreEqualDelegate AreEqual = (a, b) => a == b;
bool result = AreEqual(7, 4);

Wywołanie AreEqual z parametrami 7 i 4 zwróci nam oczywiście wartość false.

Jak się sprawa ma w Ruby? A no jak się domyślacie w Ruby także są wyrażenia lambda. Spróbujmy napisać analogiczny do powyższego kod w Ruby:

are_equal = lambda {|a, b| a == b }
result = are_equal.call(7, 4)

are_equal jest tutaj odpowiednikiem delegatu AreEqualDelegate w C# i jak wszystko w Ruby jest obiektem. Ale jakim obiektem? Sprawdźmy to:

are_equal.class

metoda class zwróci nam Proc. Proc to klasa reprezentująca blok kodu w Ruby, gdyż to {|a, b| a == b } to jest właśnie blok kodu.

Do czego nam te lambdy? A no właściwie możemy ich używać w takim samym zakresie jak w C#. Dzięki wyrażeniom lambda możemy np. wprowadzić pewien dodatkowy poziom abstrakcji w naszym kodzie. Spójrzmy na przykład:

class Customer
 attr_accessor :name, :number

 def validate(validator)
   validator.call(self)
 end
end
c = Customer.new
c.name = "Rubers Company"
c.number = "CUST000001"
validator = lambda {|customer| customer.name.length < 50 }
result = c.validate(validator)

Mamy tutaj prostą klasę Customer posiadającą dwie właściwości name i number oraz metodę validate. Metoda ta sama w sobie nie określa jak ma wyglądać walidacja, a jest ona dostarczana z zewnątrz właśnie dzięki wyrażeniom lambda/blokom kodu. W dalszej części widać walidator który weryfikuje długość pola name w klasie Customer i jest on przekazany do metody validate.

Tu muszę wspomnieć o ciekawym rozwiązaniu w Ruby, a mianowicie yield. Dzięki yield metoda validate w klasie Customer może wyglądać tak:

def validate
  yield(self)
end

a wywołanie metody validate tak:

result = c.validate {|customer| customer.name.length < 50 }

Jak widzimy yield wywołuje blok kodu przekazany z zewnątrz w sposób mniej jawny dzięki czemu mogliśmy się pozbyć parametru metody validate i bezpośredniej referencji do walidatora. Oba przedstawione warianty kodu z validator.call i yield są równoważne. Podkreślić należy jednak to, że wersja z yield zawsze będzie wydajniejsza.

Na koniec dodam, że istnieje alternatywny zapis bloku kodu w Ruby do {} z użyciem słów kluczowych do … end. Oto przykład:

are_equal = lambda do |a, b|
  a == b
end

1 Marzec 2014

Poszukiwacze kamieni szlachetnych – wszystko jest obiektem

Filed under: Poszukiwacze kamieni szlachetnych — Tags: , , — Beniamin Zaborski @ 17:48

Ruby to język obiektowy, bardzo obiektowy:). Ruby jest tak bardzo obiektowy, że dosłownie wszystko tutaj jest obiektem, nawet liczba 1. Przyjrzyjmy się temu bliżej. Skoro 1 jest obiektem to można przypuszczać, że posiada jakieś metody. A i owszem. Nasza jedynka potrafi nawet nam powiedzieć jakie posiada metody. Spróbujmy czegoś takiego (najlepiej w IRB):

1.methods

Otrzymamy listę wszystkich metod naszego obiektu. methods to nic innego jak metoda, która zwraca listę dostępnych metod na obiekcie. Tak wygląda wywołanie metody bez parametrów w Ruby.

Na liście metod możemy znaleźć np. metodę o nazwie equal?. Ten znak zapytania może wygląda nieco dziwnie dla programisty C#, ale to tylko część nazwy metody. Taka jest konwencja nazewnicza dla metod zwracających wartość typu logicznego true/false. Wywołajmy zatem tą metodę z parametrem:

1.equal?(1)

W rezultacie dostaniemy true. Jest OK, lecimy dalej.

Może się zastanawiacie czemu nazwy metod są poprzedzone znakiem „:” ? Nie zastanawiajcie się i na razie przyjmijmy dla uproszczenia że tak musi być (dla ciekawskich rzucam hasło symbole).

Na liście metod zwróconych przez metodę methods zapewne zauważyliście kilka dziwnie wyglądających i może nie do końca tam pasujących, np. +, -, [], itp. Wszystko jest OK. Ruby to język bardzo obiektowy, pamiętacie? „+” to metoda realizująca dodawanie. Użyjmy jej:

1.+(2)

Na pierwszy rzut oka (dla programisty C#) wygląda dziwacznie, ale „+” to tylko specyficzna nazwa metody, równie dobra jak add.

Dzięki cukrowi syntaktycznemu można powyższe zapisać także następująco:

1+2

Dotyczy to nie tylko metody „+”, ale także kilku innych.

Ciągi znaków w Ruby to także obiekty. Zdefiniować je możemy na kilka sposobów, np.:

"Z Ruby jest przednia zabawa"
'Z Ruby jest przednia zabawa'

Obie powyższe instrukcje spowodują utworzenie obiektu string. Jest jednak między nimi pewna różnica. Ogólnie można powiedzieć, że wersja z apostrofami jest nieco uboższa, tj. nie obsługuje escape-owania i interpolacji. W wersji z cudzysłowami możemy zatem zrobić tak:

name = 'Jan'
"Cześć #{name}"

Jak to wygląda w C#? C# to także język obiektowy, ale np. typ int (będący referencją do System.Int32) jest de facto strukturą, która nie posiada tylu metod co klasy Fixnum czy Integer w Ruby. Listę metod dostępną dla danego obiektu można w C# pobrać za pomocą mechanizmu refleksji, a typ string w C# także jest obiektem jak w Ruby.

Oczywiście C# nie jest wcale „gorszym” językiem programowania, gdyż wszystko to co w Ruby da się zrealizować w C# tylko po prostu czasami trochę inaczej. Jeśli miałbym to jakoś podsumować, to chyba nie rozminę się zbytnio z prawdą jak powiem że Ruby koncepcyjnie jest nieco „bardziej obiektowy” niż C#.

22 Luty 2014

Zrób to szablonowo

Filed under: Codzienne dylematy modelarza — Tags: , , , , — Beniamin Zaborski @ 14:23

Z zawodem programisty związane są często prace powtarzalne i nudne. W sumie to mogą one stanowić znaczący ułamek całości. Nie zawsze są to wyzwania:). Po prostu robimy coś rutynowo, szablonowo, po raz n-ty. Oczywiście bezmyślne klepanie może nas kiedyś zgubić bo nie trudno o błąd będąc w stanie transu, szczególnie przed poranną kawą.

Rozwiązaniem przynajmniej części tych problemów są generatory tekstu oparte na szablonach – text templating. Takie hasło programistom aplikacji web-owych zapewne się kojarzy z generowaniem widoków na podstawie szablonów, np. Razor.

Jednak widoki w aplikacji web to nie wszystko co możemy generować. Chciałbym spojrzeć na to zagadnienie dużo szerzej. Efekt naszej pracy to kod źródłowy, który jest przecież tekstem. Czemu nie wykorzystać jakiegoś generatora opartego na szablonach do generowania kodu źródłowego. Szczególne pole do manewru jest w tych obszarach aplikacji które są powtarzalne i nudne? Tu liczę na Waszą fantazję.

Ja np. chciałbym aby jak za dotknięciem magicznej różdżki wygenerował mi się obiekt Repository dla wskazanej encji. Wyobrażam sobie, że raz tworzę szablon i … mam więcej czasu na rozkoszowanie się aromatem świeżo palonej brazylijskiej arabiki 100%:).

Visual Studio dostarcza (a dokładniej to chyba VS SDK) generator oparty na szablonach T4. Ja jednak w tym wpisie pokażę alternatywne rozwiązanie, a w zasadzie dwa, moje ulubione StringTemplate oraz NVelocity.

Zanim przejdę do konkretnego przykładu spójrzmy na poniższy diagram jak to w ogóle działa.

Text templating diagram

gdzie:

Templates – szablony przygotowane zgodnie ze składnią danego generatora.

Variables – zmienne przekazane do silnika które zostaną użyte do zastąpienia ciągów w szablonie.

Text Output – wyjściowy plik generatora.

Text Template Engine – silnik generatora.

Na pierwszy ogień przyjrzyjmy się rozwiązaniu StringTemplate. Instalacja jest łatwa, gdyż istnieje odpowiedni pakiet dla NuGet-a, a zatem:

install-package Antlr4.StringTemplate

Przejdźmy teraz do utworzenia szablonu obiektu typu repository. Powiedzmy, że mój prosty szablon będzie wyglądał tak:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Biz.TemplateApp
{
 public class <entity.Name>Repository : BaseEFRepository\<<entity.Name>>
 {
 public class <entity.Name>Repository(IDbContext context) : base(context) {}
 }
}

Widzimy kawałek kodu przedstawiający prostą klasę i chcemy, aby w odpowiednich miejscach została wstawiona nazwa encji. W tym celu użyliśmy składni <entity.Name>. W ten sposób silnik generatora zastąpi ciąg znaków wartością przekazanej zmiennej. Jak widać zmienną nie musi być prosty string. W tym wypadku przekazujemy całą encję a w szablonie bierzemy z niej tylko nazwę. W związku z tym, że używam typu generycznego stąd znak \ który escape-uje specjalny znak < w szablonie.

Spójrzmy jak wywołać generator:

var customerEntity = new { Name = "Customer" };
string templateContent = File.ReadAllText(@"Templates\EntityRepository.tpl");
Template generator = new Template(templateContent);
generator.Add("entity",customerEntity);
string outputCode = generator.Render();

Najpierw wczytujemy z pliku nasz szablon (treść pokazana na poprzednim listingu). W następnej kolejności instancjonujemy nasz generator i dodajemy zmienną o nazwie „entity” i wartości customerEntity. Na koniec wywołujemy metodę Render generatora i uzyskujemy wynik:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Biz.TemplateApp
{
 public class CustomerRepository : BaseEFRepository<Customer>
 {
   public class CustomerRepository(IDbContext context) : base(context) {}
 }
}

Gotowe! Oczywiście to bardzo prosty przykład, a sam StringTemplate ma dużo większe możliwości jak: szablony grupowe, instrukcje warunkowe w szablonach, formatowanie, agregacja, regiony, itp. Po szczegóły odsyłam do dokumentacji. Jak widać nawet tak trywialny przykład jak przedstawiłem może być użyteczny. Możliwości są spore i w zasadzie ograniczone tylko Waszą fantazją.

Zapytacie co dalej z tym zrobić? Jak zintegrować to z Visual Studio? Rozwiązań jest kilka począwszy od custom tooli, a kończąc na dedykowanym pakiecie VS, ale to już temat na osobny post.

Na koniec dla porównania pokaże analogiczne rozwiązanie oparte na NVelocity. Jest odpowiedni pakiet dla NuGet-a, podobnie jak w poprzednim przypadku, a zatem instalacja jest prosta:

install-package NVelocity

NVelocity używa nieco innej składni szablonów – posiada własny język szablonów VTL. VTL używa referencji, aby zawrzeć w kodzie szablonu dynamiczną treść. Jedną z typów referencji jest zmienna. Wartość tej zmiennej może być przekazana z zewnątrz jak i ustawiona w samym szablonie. Generalna zasada VTL-a to: dyrektywa # oznacza że chcemy coś wykonać w szablonie, a dyrektywa $ że chcemy pobrać wartość. W naszym prostym przykładzie używamy jedynie pobierania wartości zmiennych przekazanych z zewnątrz. Oczywiście nie będę opisywał tu całej składni VTL-a, bo nie ma sensu powielanie dokumentacji.

Tak wyglądałby szablon naszego repository dla NVelocity:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Biz.TemplateApp
{
 public class ${entity.Name}Repository : BaseEFRepository<${entity.Name}>
 {
   public class ${entity.Name}Repository(IDbContext context) : base(context) {}
 }
}

NVelocity ewaluuje zmienne za pomocą ${enity.Name}. A teraz wywołanie generatora:

var customerEntity = new { Name = "Customer" };
Velocity.Init();
VelocityContext velocityContext = new VelocityContext();
string templateContent = File.ReadAllText(@"Templates\EntityRepository.vtp");
velocityContext.Put("entity", customerEntity);
StringBuilder outputCode = new StringBuilder();
Velocity.Evaluate(
   velocityContext,
   new StringWriter(outputCode),
   "repo template",
   new StringReader(templateContent)
 );

Najpierw mamy zainicjowanie NVelocity i utworzenie kontekstu, a następnie przekazanie zmiennych za pomocą metody put i wywołanie generatora za pomocą Evaluate.
NVelocity to port java-owego Velocity na .NET i ma naprawdę spore możliwości. To co tu pokazałem to tylko promil jego możliwości.

15 Luty 2014

Poszukiwacze kamieni szlachetnych – Początek

Filed under: Poszukiwacze kamieni szlachetnych — Tags: , , — Beniamin Zaborski @ 18:05

W ramach samorozwoju jak i dywersyfikacji skillsów – czytaj uniezależnienia się od M$ – postanowiłem spróbować czegoś z innej bajki … innej niż .NET. W związku z tym, że nikt mnie do tego nie przymusza, a wręcz przeciwnie robię to w ramach hobby więc podstawowe założenie jest proste – ma być FUN! Duuuużo FUN-u!

Padło na RUBY!

Tak Ruby! A czemu nie?

Postanowiłem napisać cykl postów o Ruby. Ale żeby nie powielać masy informacji jakie są na temat tego języka/środowiska/platformy* (*niepotrzebne skreślić) w sieci moim założeniem jest pokazanie go z perspektywy programisty C#/.NET.

Ruby to język programowania, ale wszystkim od razu na myśl przychodzi framework do tworzenia aplikacji Web-owych Ruby on Rails. Jest wiele analogii pomiędzy C# a Rubym, a także chociażby pomiędzy ASP.NET MVC a Rails-ami. Właśnie to będę się starał przedstawić w serii moich wpisów.

Zacznijmy od razu, już teraz … oczywiście od podstaw.

Język Ruby został stworzony w 1995 roku (czyli dość dawno) przez Yukihiro Matsumoto znanego też jako Matz. Jak twierdzi autor „Ruby to dynamiczny język programowania skupiający się na prostocie i wydajności”. Ruby jest językiem interpretowanym w odróżnieniu do kompilowanego do IL języka C# na platformie .NET. Podobnie jak C# Ruby jest językiem obiektowym. Składnia Ryby-ego jest zwięzła i oparta na słowach języka angielskiego.

Jeśli to dla Was za mało i potrzebujecie intensywniejszej motywacji to fani tego języka nazywają go „pięknym i pełnym sztuki”. Chyba coś w tym jest, a może to tylko wstępne zauroczenie (?).

Jak zacząć?

Najlepiej pobrać Ruby-ego i użyć interaktywnego interpretera IRB.

Co dalej? No oczywiście nie może zabraknąć HelloWorld:). Zacznijmy od C#:

public class HelloWorld
{
  public const string TypeOfHello = "Hello";

  public string SayHello(string name)
  {
    return TypeOfHello + string.Format(" {0}", name)
  }
}

a teraz to samo w Ruby:

class HelloWorld
  TYPE_OF_HELLO = 'Hello'
  def say_hello(name)
    TYPE_OF_HELLO + " #{name}"
 end
end

Porównajmy oba listingi kodu:

1. Składnia i notacja nazewnicza

Pierwsze co się rzuca w oczy to oczywiście różnice w składni. Kod Ruby-ego jest bardziej zwięzły brak tu wydaje się zbędnych znaków np. średników na końcu linii. Preferowana notacja nazewnicza dla klas to CamelCase, dla stałych (które nie do końca są stałymi, ale o tym może innym razem) wielkie litery z podkreśleniami, a dla pozostałych składników małe listery z podkreśleniami.  Ciągi znaków w Ruby możemy zapisywać z użyciem znaków cudzysłów jak i apostrof,  jednak są pewne różnice między tymi dwoma zapisami (o tym innym razem).

2. Hermetyzacja

W Ruby klasa i jej metody domyślnie są publiczne. Wyjątkiem od tej zasady są metody initialize (coś a’la konstruktor) oraz globalne metody zdefiniowane dla klasy Object. Jest to istotna różnica w porównaniu do C# gdyż tutaj klasa domyślnie jest internal, a jej „elementy składowe” są domyślnie prywatne.

3. Typy danych

W Ruby nie określa się jawnie typów danych – interpreter języka robi to za nas. Tu nie mogę nie wspomnieć o bardzo ważnej cesze języka Ruby, mianowicie Duck Typing. Jest to specyficzny sposób rozpoznawania typu obiektu na podstawie badania metod udostępnianych przez obiekt. Skąd taka nazwa? Pochodzi ona od stwierdzenia „jeśli chodzi jak kaczka i kwacze jak kaczka, to musi być kaczką”. Każdy obiekt w Ruby posiada metodę respond_to? która pozwoli nam określić czy obiekt posiada daną metodę, np. „Tekst”.respond_to?(„downcase”). W tym wypadku otrzymamy wynik true, a downcase to po prostu metoda zamieniająca znaki ciągu na małe litery.

4. Konkatenacja ciągów znaków

Jest taka jak w C#, tj. za pomocą znaku „+”. Inna ciekawa funkcjonalność to użycie ” #{name}” co oznacza, że ciąg #{name} zostanie zastąpiony wartością zmiennej o nazwie name. Zbliżone nieco do wywołania string.Format w C#, ale dużo bardziej zwięzłe.

5. Zwracanie wyniku metody

Hmm brak jakiegokolwiek return czy coś w tym rodzaju. Bo nie jest potrzebne! Aczkolwiek można użyć słowa kluczowego return opcjonalnie.

Jak  teraz instancjonować naszą klasę i wywołać metodę? W C# po prostu:

HelloWorld hw = new HelloWorld();
hw.SayHello("Bill Gates");

a w Ruby:

hw = HelloWorld.new
hw.say_hello('Yukihiro Matsumoto')

W tym wypadku też jest nieco zwięźlej.

Tyle na początek i do następnego razu z Ruby.

5 Luty 2014

Transakcje rozproszone – Wszystko albo nic

Dzisiaj trochę o transakcjach. Wiemy, że transakcja to niepodzielna operacja wykonana na jakimś zasobie lub zasobach. Wiemy, także że transakcję charakteryzują cztery litery (nie, to nie te :)), mianowicie ACID. W języku polskim oznaczają one odpowiednio: Atomowość, Spójność, Izolacja, Trwałość.

Większości z nas pojęcie transakcji kojarzy się przeważnie z bazami danych … właśnie, bazy danych. Celowo na początku napisałem, że transakcja to niepodzielna operacja na jakimś zasobie lub zasobach. Tym zasobem może być baza danych, ale także zupełnie coś innego, np. system plików, serwis WCF, obiekt COM+, itd.

Interesuje mnie aspekt transakcyjności w kontekście liczby zasobów większej niż jeden. Powiedzmy, że moja niepodzielna operacja powinna składać się z: dodania rekordu do tabeli bazy danych i utworzenia pliku w systemie plików. Oznacza to że jeśli któraś z tych dwóch operacji nie powiedzie się całość zostanie wycofana w myśl zasady „wszystko albo nic”. Jeśli kodujemy w .NET przed wersją 2.0, to rzeczywiście możemy mieć lekki problem. Na szczęście w wersji 2.0 frameworka .NET pojawił się nowy mechanizm obsługi transakcji zwany TransactionScope.

Zauważyłem, że TransactionScope jest bardzo powszechnie używany przez programistów .NET. No i nie ma w tym nic złego, ale niestety jest on używany bardzo nieświadomie. Standardowe użycie zna chyba każdy:

using (TransactionScope transaction = new TransactionScope())
{
  SaveDataToDB();
  transaction.Complete();
}

I co tu się dzieje? A no transakcja się dzieje!

Metoda SaveDataToDB zawiera operację zapisu danych do jednej bazy danych. Wszystko działa, ale musimy mieć świadomość, że zadziała się magia i TransactionScope użył tu tzw. Lightweight Transaction Manager (LTM). Praktycznie można powiedzieć, że nasza transakcja przełożyła się bezpośrednio na transakcję bazodanową. Pod kątem wydajności LTM jest zbliżona do natywnej transakcji ADO.NET/bazy danych.

Rozważmy teraz przykład który przytoczyłem na początku, tj.:

using (TransactionScope transaction = new TransactionScope())
{  
  SaveDataToDB();
  CreateFile();
  transaction.Complete();
}

Teraz transakcja obejmuje dwa różne zasoby i znów zadzieje się magia i TransactionScope użyje rozproszonego menadżera transakcji opartego na MSDTC. Trochę teraz skłamałem, gdyż standardowo tworząc plik w .NET np. poprzez File.WriteAllText nie jest obsługiwana transakcyjność. Rozwiązaniem jest zastosowanie zewnętrznej biblioteki do transakcyjnego dostępu do systemu plików NTFS (np. Transactional NTFS Managed Wrapper) lub obsłużenie jej samemu. Obsługa jest dość prosta i wymaga zaimplementowania interfejsu IEnlistmentNotification. Trywialny przykład obsługi mógłby wyglądać tak:

public class TransactionalFileCreator : IEnlistmentNotification
{
  private string path;
  private string content;
  public TransactionalFileCreator(string path, string content)
  {
   this.path = path;
   this.content = content;
   Transaction.Current.EnlistDurable(Guid.NewGuid(), this, EnlistmentOptions.None);
  }
  public void Commit(Enlistment enlistment)
  {
    File.WriteAllText(path, content);
  }
  public void InDoubt(Enlistment enlistment)
  {
    enlistment.Done();
  }
  public void Prepare(PreparingEnlistment preparingEnlistment)
  {
    preparingEnlistment.Prepared();
  }
  public void Rollback(Enlistment enlistment)
  {
    if (File.Exists(path))
      File.Delete(path);
  }
}

Bardzo ważna jest, na powyższym listingu, linia Transaction.Current.EnlistDurable(Guid.NewGuid(), this, EnlistmentOptions.None). To ona jest warunkiem promowania wstępnie zarządzanej transakcji przez LTM do DTC. Według dokumentacji jeśli w ramach transakcji użyjemy co najmniej dwóch zasobów „durable” tj. takich obsługujących promowanie transackji do DTC to takie promowanie się odbędzie. Koszt transakcji zarządzanej przez DTC jest oczywiście znacznie większy niż tej opartej na LTM.

Uruchomienie w transakcji promowanej do DTC naszego kodu wyglądałoby teraz tak:

using (TransactionScope transaction = new TransactionScope())
{  
  SaveDataToDB();
  TransactionalFileCreator fileCreator = new TransactionalFileCreator(path, content);
  var dtIdentifier = Transaction.Current.TransactionInformation.DistributedIdentifier;
  transaction.Complete();
}

Transaction.Current.TransactionInformation.DistributedIdentifier to pobranie identyfikatora transakcji z rozproszonego koordynatora transakcji MSDTC. Naszą transakcję możemy także podejrzeć w „Koordynatorze transakcji rozproszonych” w „Panel sterowania” -> „Usługi składowe” -> „Lokalna usługa DTC” -> „Lista transakcji”:

DTC_tran

Zachęcam do zgłębienia wiedzy na temat TransactionScope, a można zacząć np. tutaj. Zdobycie wiedzy o tym jaki mechanizm zarządza naszą transakcją może nas uratować od niezłych kłopotów.

Older Posts »

Blog na WordPress.com.