czwartek, 12 września 2013
[FullTextSearch] ElasticSearch: Wprowadzenie
ElasticSearch to open sourcowy projekt serwera wspierającego wyszukiwanie pełnotekstowe. Zapoczątkowany został w 2010 roku w oparciu o popularne rozwiązanie Apache Lucene. Obecnie jest jednym z najpopularniejszych engine'ów do Full Text Search obok Solr i Lucene. Komunikacja z ES odbywa się poprzez REST. Niektórzy wykorzystują go także jako dokumentową bazę danych, zapewniającą podobnie jak MongoDB dobrą skalowalność i możliwość rozproszenia danych przez mechanizm shardingu.
Podstawowe pojęcia związane z ElasticSearch:
Indeks
W indeksach trzymane są dane, może on być interpretowany jako odpowiednik SQL-owej tabeli, z tą różnicą, że indeks jest zoptymalizowany pod kątem szybkiego przeszukiwania i pobierania danych.
Dokument
Dokument to pojedyncza encja danych, odpowiednik rekordu z SQL. Dokumenty składają się z pól odpowiadających kolumnom. Każde pole jest typowane (numer, string, data itd). Należy pamiętać, że dokumenty nie muszą zachowywać takiej samej struktury w obrębie indeksu.
Typ dokumentu
Pozwala rozróżniać rodzaje dokumentów trzymane w obrębie jednego indeksu.
Node'y i klastry
Umożliwiają rozproszenie serwera ElasticSearch na wiele fizycznych maszyn. Grupa takich maszyn jest nazywana klastrem, natomiast każdy z nich jest nazywany nodem.
Shard
Porcja danych. Pojedynczy shard jest osobnym indeksem Lucene i może być umieszczony na osobnym serwerze, ElasticSearch otrzymując zapytanie wie, do którego sharda je przekierować. Przykładowo nazwiska można pogrupować alfabetycznie tak, jak kilkutomową encyklopedię. Jeżeli zapytanie zaczyna się od A to trafia do pierwszego sharda, jeżeli od np. J to do drugiego.
Replica
Replika to kopia danych z głównego sharda, która musi być z nim synchronizowana. Kiedy główny shard padnie, jego replika będzie promowana do bycia głównym shardem i do niej zostaną przekierowane zapytania. Rozwiązanie takie zapewnia niezawodność danych.
Aby skorzystać z ElasticSearch wystarczy pobrać plik .zip ze strony http://www.elasticsearch.org/ , a następnie rozpakować go w dowolnym miejscu na dysku. Serwer uruchamiany z command line poleceniem elasticsearch.bat wywołanym z poziomu folderu bin. Budowane indeksy są przechowywane w folderze data. ElasticSearch domyślnie działa na porcie 9200.
Do poprawnego działania ElasticSearch potrzebuje zainstalowany Java Development Kit, oraz ustawioną ścieżkę do JAVA_HOME. Instrukcja na stronie Oracle.
sobota, 7 września 2013
[C#|Visual Studio] ServiceStack: Caching i Walidacja
Cache'ować dane można w ServiceStacku na różne sposoby. W rozwiązaniach linuksowych wykorzystuje się Redis - bazę "InMemory". Można korzystać także z MemoryCacheClient, którego konfigurujemy w pliku Global.asax.
Cache można ustawiać i resetować w RESTowych endpointach. Ważne jest, aby każda cache'owana wartość miała swój unikalmy klucz, który możemy utworzyć za pomocą klasy UrnId.
Wyliczenie cache'owanej wartości odbywa się poprzez lambda expression.
ServiceStack w genialny sposób wspiera tworzenie reguł walidacyjnych za pomocą FluentValidation. Reguły tworzymy w konstruktorach odpowiednich klas. Walidację należy najpierw zarejestrować.
Przykładowa klasa walidacyjna:
container.Register<ICacheClient>(new MemoryCacheClient());
Cache można ustawiać i resetować w RESTowych endpointach. Ważne jest, aby każda cache'owana wartość miała swój unikalmy klucz, który możemy utworzyć za pomocą klasy UrnId.
public object Post(Entry entry) { var cacheKey = UrnId.Create<Entry>(entry.Date.ToShortDateString()); RequestContext.RemoveFromCache(base.Cache, cacheKey); CounterService.Add(entry.Count); return new EntryResponse() {Total = CounterService.Print()}; } public object Get(Entry entry) { var cacheKey = UrnId.Create<Entry>(entry.Date.ToShortDateString()); return RequestContext.ToOptimizedResultUsingCache(base.Cache, cacheKey, new TimeSpan(0,0,0,5), () => (object)CounterService.Print()); }
Wyliczenie cache'owanej wartości odbywa się poprzez lambda expression.
ServiceStack w genialny sposób wspiera tworzenie reguł walidacyjnych za pomocą FluentValidation. Reguły tworzymy w konstruktorach odpowiednich klas. Walidację należy najpierw zarejestrować.
public class MyEntryAppHost : AppHostBase { public override void Configure(Funq.Container container) { Plugins.Add(new ValidationFeature()); container.RegisterValidators(typeof(EntryService).Assembly); } }
Przykładowa klasa walidacyjna:
public class EntryValidator : AbstractValidator<Entry> { public EntryValidator() { RuleFor(e => e.Count).GreaterThanOrEqualTo(0).WithMessage("Koniecznie nieujemna"); RuleFor(e => e.Date).LessThanOrEqualTo(DateTime.Now); } }
piątek, 6 września 2013
[C#|Visual Studio] ServiceStack: IoC i ORM
Domyślnym kontenerem IoC w ServiceStacku jest Funq. Jest to prosty kontener zoptymalizowany pod kątem szybkości i lekkości. Domyślnie rejestrując w nim zależności rejestrujemy je jako Singletony. ServiceStack automatycznie wykona wstrzyknięcie zależności do publicznego property w klasach dziedziczących po Service. Przykład konfiguracji Funq (w pliku Global.asax i metodzie Configure).
Zależności możemy rejestrować w trybie AutoWired, gdzie przeważnie wykonuje się mapowanie klasa na interfejs lub klasa w samą siebie. Do ręcznego ustawiania zależności wykorzystuje się z kolei metodę Register, natomiast do pobierania metodę Resolve. Jeżeli chcemy, by za każdym razem kontener zwracał nowy obiekt, musimy dopisać instrukcję ReusedWithin z parametrem None.
Mechanizmem do mapowania obiektowo - relacyjnego jest w ServiceStacku OrmLite. Jest to mapper prosty w użyciu i lekki, a zarazem bardzo wydajny, Kolejną jego zaletą jest multiplatformowość - działa z wieloma systemami, co widać przy próbie instalacji NuGetem.
OrmLite typy złożone serializuje i zapisuje jako BLOBy. Można natomiast zakładać constrainty poprzez użycie odpowiednich atrybutów. Do tworzenia połączeń służy typ IDbConnectionFactory, który można skonfigurować w kontenerze IoC.
Operacje na bazie przeprowadza się w bardzo wygodny sposób
public override void Configure(Funq.Container container) { container.RegisterAutoWired<CounterService>(); container.RegisterAutoWiredAs<CounterService, ICounterService>().ReusedWithin(ReuseScope.None); container.Register<ICounterService>(new CounterService()); var cs = container.Resolve<ICounterService>(); Console.WriteLine("Registered type is {0}", cs.GetType()); }
Zależności możemy rejestrować w trybie AutoWired, gdzie przeważnie wykonuje się mapowanie klasa na interfejs lub klasa w samą siebie. Do ręcznego ustawiania zależności wykorzystuje się z kolei metodę Register, natomiast do pobierania metodę Resolve. Jeżeli chcemy, by za każdym razem kontener zwracał nowy obiekt, musimy dopisać instrukcję ReusedWithin z parametrem None.
Mechanizmem do mapowania obiektowo - relacyjnego jest w ServiceStacku OrmLite. Jest to mapper prosty w użyciu i lekki, a zarazem bardzo wydajny, Kolejną jego zaletą jest multiplatformowość - działa z wieloma systemami, co widać przy próbie instalacji NuGetem.
Do tworzenia tabel wykorzystuje się obiekty POCO, które mogą być tymi samymi obiektami co DTO. Mamy do dyspozycji także znane z EntityFramework DataAnnotations. Przykład obiektu POCO:
[Route("/entry")] [Authenticate] [RequiredRole("User")] public class Entry : IReturn<EntryResponse> { [AutoIncrement] public int Id { get; set; } public DateTime Date { get; set; } public int Count { get; set; } }
OrmLite typy złożone serializuje i zapisuje jako BLOBy. Można natomiast zakładać constrainty poprzez użycie odpowiednich atrybutów. Do tworzenia połączeń służy typ IDbConnectionFactory, który można skonfigurować w kontenerze IoC.
public override void Configure(Funq.Container container) { IDbConnectionFactory factory = new OrmLiteConnectionFactory(HttpContext.Current.Server.MapPath("~/App_Data/data.txt"), SqliteDialect.Provider); container.Register<IDbConnectionFactory>(factory); container.RegisterAutoWiredAs<CounterService, ICounterService>(); }
Operacje na bazie przeprowadza się w bardzo wygodny sposób
public class CounterService : ICounterService { public IDbConnectionFactory DbConnectionFactory { get; set; } private int _counter; public void Add(int i) { _counter += i; using (var dbConn = DbConnectionFactory.CreateDbConnection()) { dbConn.Open(); dbConn.CreateTable<Entry>(); dbConn.Insert<Entry>(new Entry() { Count = i, Date = DateTime.Now }); } } public int Print() { using (var dbConn = DbConnectionFactory.CreateDbConnection()) { //WHERE - lambda expression lub SQL jako string dbConn.Open(); return dbConn.Select<Entry>().Sum(x => x.Count); } } }
czwartek, 29 sierpnia 2013
[C#|Visual Studio] ServiceStack: Filtry
Filtrami w ServiceStacku nazywane są fragmenty kodu wywoływane przed lub po każdym zapytaniu. Można ustawiać im priorytety, które odpowiadają później kolejności, w jakiej filtry się wykonują. Aby stworzyć filtr wystarczy dziedziczyć po klasie RequestFilterAttribute lub ResponseFilterAttribute.
Użycie filtra sprowadza się do dodania atrybutu nad DTO lub serwisem.
public class IpRecordFilter : RequestFilterAttribute { public ICacheClient Cache { get; set; } public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto) { Cache.Add(req.UserHostAddress, req.RemoteIp); } }
Użycie filtra sprowadza się do dodania atrybutu nad DTO lub serwisem.
public class EntryService : Service { public int Sum { get; set; } [IpRecordFilter] public object Post(Entry entry) { Sum += entry.Count; return new EntryResponse() {Total = Sum}; } }
środa, 28 sierpnia 2013
[C#|Visual Studio] ServiceStack: Autentykacja i autoryzacja
Proces autentykacji ma na celu potwierdzenie tożsamości użytkownika, natomiast autoryzacja ma na celu wertfikację uprawnień użytkownika. W Service Stacku obie funkcjonalności zostały zaimplementowane w prosty sposób. Zaczynamy od modyfikacji w pliku Global.asax
Skonfigurowany został jeden z gotowych providerów, BasicProvider. Programiści mają też możliwość definiowania własnych providerów. RegistrationFeature umożliwi REST-owe definiowanie ról. Repozytorium pacjentów trzymane będzie w pamięci. W tym samym miejscu możemy dodać pierwszego użytkownika z rolą Admin
Wymagane role i uprawnienia a także konieczność autentykacji deklarujemy atrybutami.
Logowanie i przydzielanie ról użytkownikom (w szczególnym przypadku samemu sobie) wykonuje się w poniższy sposób
public override void Configure(Funq.Container container) { Plugins.Add(new AuthFeature( () => new AuthUserSession(), new IAuthProvider[] { new BasicAuthProvider() })); Plugins.Add(new RegistrationFeature()); container.Register<ICacheClient>(new MemoryCacheClient()); var userRepository = new InMemoryAuthRepository(); container.Register<IUserAuthRepository>(userRepository); }
Skonfigurowany został jeden z gotowych providerów, BasicProvider. Programiści mają też możliwość definiowania własnych providerów. RegistrationFeature umożliwi REST-owe definiowanie ról. Repozytorium pacjentów trzymane będzie w pamięci. W tym samym miejscu możemy dodać pierwszego użytkownika z rolą Admin
string hash, salt; new SaltedHash().GetHashAndSaltString("password", out hash, out salt); userRepository.CreateUserAuth(new UserAuth() { Id = 1, FirstName = "John", Email = "jdoe@gmail.com", PasswordHash = hash, UserName = "jdoe", LastName = "Doe", Salt = salt, Roles = new List<string>(){RoleNames.Admin}, Permissions = new List<string>(){"GetEntry"} }, "password");
Wymagane role i uprawnienia a także konieczność autentykacji deklarujemy atrybutami.
[Route("/entry")] [Authenticate] [RequiredRole("User")] public class Entry : IReturn<EntryResponse> { public DateTime Date { get; set; } public int Count { get; set; } }
Logowanie i przydzielanie ról użytkownikom (w szczególnym przypadku samemu sobie) wykonuje się w poniższy sposób
var client = new JsonServiceClient("http://localhost:51270"){UserName = "jdoe", Password = "password"}; client.Send<AssignRolesResponse>(new AssignRoles() { UserName = "jdoe", Roles = new List<string>(){"User"}, Permissions = new List<string>(){"GetEntry"} });
niedziela, 25 sierpnia 2013
[C#|Visual Studio] ServiceStack: Klient C#
Restowe API można łatwo odpytywać z kodu w C#. Wystarczy dodać nowy projekt w Visual Studio, który będzie zawierał referencję do projektu serwerowego. W klienckim projekcie należy też doinstalować .dll ServiceStack.Text. Jeżeli klientem ma być np. aplikacja konsolowa, to trzeba także przełączyć wersję .NET na np. 4.0 (bez Client Profile).
Z uwagi na wygodę po stronie klienckiej, obiekty DTO należy pisać w poniższy sposób:
a więc tak, aby implementować interfejs IReturn<T> oraz w obiektach typu T zamieszczać ResponseStatus, gdzie automatycznie zostanie wstawiony kod błędu.
Samo wywołanie serwisu po stronie klienckiej można wykonać synchronicznie lub asynchronicznie, np.
Odpowiedź zostanie zdeserializowana do odpowiedniego typu.w przypadku obu wywołań. Nie trzeba podawać większej ilości parametrów, gdyż wszystkie są pobierane z obiektów DTO (route i typ zwracany). JsonServiceClient można wymienić na inne typy klienckie, takie jak: JsvServiceClient czy CsvServiceClient
Z uwagi na wygodę po stronie klienckiej, obiekty DTO należy pisać w poniższy sposób:
[Route("/entry")] public class Entry : IReturn<EntryResponse> { public DateTime Date { get; set; } public int Count { get; set; } } public class EntryResponse { public int Total { get; set; } public ResponseStatus ResponseStatus { get; set; } }
a więc tak, aby implementować interfejs IReturn<T> oraz w obiektach typu T zamieszczać ResponseStatus, gdzie automatycznie zostanie wstawiony kod błędu.
Samo wywołanie serwisu po stronie klienckiej można wykonać synchronicznie lub asynchronicznie, np.
var client = new JsonServiceClient("http://localhost:51270"); var response = client.Send(new Entry() {Count = 12}); Console.WriteLine(response.Total); client.SendAsync(new Entry() { Count = 24 }, resp => Console.WriteLine(resp.Total), (err,exc) => Console.WriteLine("error"));
Odpowiedź zostanie zdeserializowana do odpowiedniego typu.w przypadku obu wywołań. Nie trzeba podawać większej ilości parametrów, gdyż wszystkie są pobierane z obiektów DTO (route i typ zwracany). JsonServiceClient można wymienić na inne typy klienckie, takie jak: JsvServiceClient czy CsvServiceClient
piątek, 23 sierpnia 2013
[C#|Visual Studio] ServiceStack: Tworzenie WebSerwisów
W tym poście zostanie opisane, jak utworzyć prosty projekt z RESTowymi serwisami w oparciu o ServiceStacka. Zaczynamy od stworzenia w Visual Studio pustego projektu ASP .NET.
Następnie za pomocą NuGeta instalujemy dllki ServiceStack.
Następnie tworzymy proste obiekty DTO, do których serializowane i deserializowane będą responsy i requesty REST-owe. Dobrą praktyką jest, aby obiekty DTO były symetryczne i przez konwencję do nazwy obiektu zwrotnego dodajemy "Response".
Poprzez atrybuty definiujemy routingi, w których fragmenty ścieżek można bindować bezpośrednio do properties DTO. Następnie dodajemy klasę serwisu, która dziedziczy po bazowej klasie Service z SeriveStack. W klasie dodajemy metody o takich nazwach jak czasowniki HTTP, gdzie parametrem będzie zdefiniowane wcześniej DTO.
Aby wystartować serwis, wystarczy skorzystać z pliku .asax.
Metoda Configure posłuży jako bootstrapper, gdzie będzie można konfigurować np. kontener IoC. Ostatnim etapem jest skonfigurowanie pliku Web.config, gdzie dodajemy odpowiedni handler.
Po uruchomieniu aplikacji mamy możliwość podglądu i testowania naszych endpointów.
Następnie tworzymy proste obiekty DTO, do których serializowane i deserializowane będą responsy i requesty REST-owe. Dobrą praktyką jest, aby obiekty DTO były symetryczne i przez konwencję do nazwy obiektu zwrotnego dodajemy "Response".
[Route("/entry/{Date}", "GET")] public class Entry { public DateTime Date { get; set; } public int Count { get; set; } } public class EntryResponse { public int Total { get; set; } }
Poprzez atrybuty definiujemy routingi, w których fragmenty ścieżek można bindować bezpośrednio do properties DTO. Następnie dodajemy klasę serwisu, która dziedziczy po bazowej klasie Service z SeriveStack. W klasie dodajemy metody o takich nazwach jak czasowniki HTTP, gdzie parametrem będzie zdefiniowane wcześniej DTO.
public class EntryService : Service { public int Sum { get; set; } public object Post(Entry entry) { Sum += entry.Count; return new EntryResponse() {Total = Sum}; } }
Aby wystartować serwis, wystarczy skorzystać z pliku .asax.
public class Global : System.Web.HttpApplication { public class MyEntryAppHost : AppHostBase { public MyEntryAppHost() : base("MyEntryService", typeof(EntryService).Assembly) { } public override void Configure(Funq.Container container) { } } protected void Application_Start(object sender, EventArgs e) { new MyEntryAppHost().Init(); } //... }
Metoda Configure posłuży jako bootstrapper, gdzie będzie można konfigurować np. kontener IoC. Ostatnim etapem jest skonfigurowanie pliku Web.config, gdzie dodajemy odpowiedni handler.
<configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> </system.web> <system.web> <httpHandlers> <add path="*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*"/> </httpHandlers> </system.web> </configuration>
Po uruchomieniu aplikacji mamy możliwość podglądu i testowania naszych endpointów.
Subskrybuj:
Posty (Atom)