Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

22 lutego 2014

Zrób to szablonowo

Filed under: Codzienne dylematy modelarza — Tagi: , , , , — 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.

Reklamy

15 lutego 2014

Poszukiwacze kamieni szlachetnych – Początek

Filed under: Poszukiwacze kamieni szlachetnych — Tagi: , , — 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 lutego 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.

Stwórz darmową stronę albo bloga na WordPress.com.