wtorek, 7 marca 2017

CQRS, część 2 - Query

Hej,
dzisiaj omówię kolejny element mojego CQRSa czyli Zapytania - Query. Nadmienię jeszcze, że sam CQRS to wzorzec projektowy, a nie architektura (dzięki Norek, za uwagę żeby to podkreślić!). Xamarin jest oczywiście stworzony do MVVM - Model, Model widoku, Widok i z takiej architektury korzystam, a CQRS jest tutaj stosowany pomocniczo.

Ok, a więc Query - czyli zapytanie, które z definicji nie zmienia stanu systemu, mam dla niego przygotowany interfejs :
public interface IQuery<TResult>
{
}
Mimo że również jest to marker interface mamy już tutaj typ generyczny, który określa jaki typ danych zapytanie zwraca. Obsługujący zapytanie handler implementuje interfejs:

public interface IQueryHandler<in TQuery,  TResult> where TQuery : IQuery<TResult>
{
    TResult Execute(TQuery query);
}

Nasz handler ma określony typ zapytania oraz typ zwracanych danych, zwracam również uwagę, że dla ścieżki zapytań nie stosuję metod async. Wynika to ze specyfiki bazy danych - SQLite mimo że ma bibliotekę w nugecie obsługującą asynchroniczne zapytania - niestety nie spełniła moich oczekiwań i pozostałem przy wersji synchronicznej. Nie ma to w moim projekcie większego znaczenia i nie widzę potencjalnych benefitów z korzystania z bazy w sposób asynchroniczny. Upraszcza to również naszą szynę zapytań, która implementuje następujący interfejs:

public interface IQueryBus
{
    TResult Process<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult>;
}

Tak jak dla komend tak i tutaj pozwolę sobie zamieścić implementację mojej szyny zapytań - kiedy pisałem swojego CQRSa brakowało mi w internetowych artykułach  implementacji - wszyscy skupiali się tylko na interfejsach, a szukać przykładu musiałem na githubie przeglądając projekty.

public class QueryBus:IQueryBus
{

    private readonly ILifetimeScope _resolver;

    public QueryBus(ILifetimeScope resolver)
    {
        _resolver = resolver;
    }

    public TResult Process<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult>
    {
        var queryHandler = _resolver.ResolveOptional<IQueryHandler<TQuery,TResult>>();
        if (queryHandler == null)
        {
             throw new Exception(string.Format("No handler found for query '{0}'", query.GetType().FullName));
        }
        return queryHandler.Execute(query);
    }
}

Implementacja szyny jest bardzo prosta - szukamy zarejestrowanego handlera i jeśli go znajdujemy to wykonujemy zapytanie i zwracamy dalej jego wynik. Żeby powielić schemat posta o komendach, tutaj również pozwolę sobie zamieścić przykładowe zapytanie i handler zapytania.

public class GetBeeHivesOnApiary :IQuery<List<BeeHive>>
{
    public int ApiaryId { get; protected set; }

    public GetBeeHivesOnApiary(int apiaryId)
    {
        this.ApiaryId = apiaryId;
    }
}

public class GetBeeHivesOnApiaryHandler :IQueryHandler<GetBeeHivesOnApiary, List<BeeHive>>
{
    protected SQLiteConnection _database;

    public GetBeeHivesOnApiaryHandler(SQLiteConnection database)
    {
        _database = database;
    }

    public List<BeeHive> Execute(GetBeeHivesOnApiary query)
    {
        return _database.Query<BeeHive>("SELECT * FROM tb_beehive WHERE bh_ap_id = " + query.ApiaryId.ToString());
    }
}

OK, mamy więc omówioną z przykładem ścieżkę jaką wędrują w systemie zapytania, do przeczytania jutro - omówimy kolejny element czyli zdarzenia - Events.


0 komentarze: