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.

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).

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.

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

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:

[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".

[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.