Pokazywanie postów oznaczonych etykietą cqrs. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą cqrs. Pokaż wszystkie posty

czwartek, 9 marca 2017

CQRS część 4, ostania - Validator

Hej,
ostatnią integralną częścią mojego CQRSa są walidatory. Są to klasy które dostając komendę, sprawdzają jej poprawność i możliwość wykonania. Gdy dana komenda w aktualnym stanie systemu jest niemożliwa do wykonania np nie można usunąć ula, w którym jest jakaś pszczela rodzina lub dane komendy są niepoprawne, spoza zakresu itp - to wtedy zwracamy taką informację do szyny komend, a ona za pomocą wyjątku zwraca ją dalej - użycie zostało pokazane w implementacji szyny komend w poście numer 1 dotyczącym CQRSa.
Dla walidatorów tak jak i dla komend mam ścieżkę synchroniczną i asynchroniczną. Zacznijmy od interfejsów:

public interface IValidator<TCommand> where TCommand : ICommand 
{
    ValidationResult Validate(TCommand command);
}

public interface IValidatorAsync<TCommand> where TCommand : ICommandAsync
{
    Task<ValidationResult> Validate(TCommand command); 
}

Jedyną nową rzeczą w tym kawałku kodu to klasa ValidationResult, a przedstawia się ona następująco:

public class ValidationResult
{
    public bool Result { get; set; } = true;

    public List<string> Messages { get; private set; } = new List<string>();
}

Mamy tutaj wynik walidacji określający czy wykonać komende, czy też nie, oraz listę komunikatów o problemach - jest to lista, ponieważ w ten sposób nie muszę się martwić o prawidłową konkatenację moich błędów i mam też informację o ich ilości, a gdy chcę pokazać informację o błędach robię po prostu String.join("\n", validationResult.Messages). Ostatnim elementem moich walidatorów jest customowy wyjątek, który wygląda następująco:

public class ValidationException : Exception
{
    public ValidationResult Result { get; protected set; }

    public ValidationException(ValidationResult result)
    {
        this.Result = result;
    }
}
Te kilka klas i interfejsów zamyka mi temat sprawdzania poprawności komend - oczywiście za pomocą kontenera i automatycznej rejestracji z assembly wszystko działa bardzo wygodnie. Ten post kończy serię o CQRS, po kilku tygodniach używania tego wzorca jestem bardzo zadowolony, wprawdzie ilość klas wzrosła i odczuwam pewien większy narzut czasowy pisania logiki jednak jest to niska cena za efekt, który uzyskałem. Kod jest czytelny, prosty, łatwo testowalny i dużo trudniej jest złamać zasadę jednej odpowiedzialności. Będę korzystał z CQRSa na pewno częściej w swojej pracy zawodowej.

środa, 8 marca 2017

CQRS część 3 - Event, Consumer

Hej,
wracamy do naszego CQRSa. Omówiłem już jego dwa podstawowe elementy czyli Komendy i Zapytania i na tym teoretycznie sam wzorzec poprzestaje, jednak oglądając na githubie jego różne implementacje zdecydowałem dołożyć do niego te dwa przydatne elementy. Zdarzenia są oczywiście wywoływane tylko przez handlery komend bo tylko tam zmieniamy stan systemu. Przenosząc się z Flexa do Xamarina, naturalne były dla mnie Eventy - we Flexie są bardzo dobrze zaimplementowane, a ich propagacja w systemie, propagacja po drzewie renderingu w górę do korzenia, anulowanie itp były out of the box. Zmieniłem z tego powodu trochę konwencję nazewnictwa samych zdarzeń co zobaczycie za chwilkę w snippetach. Konsument - jest to interfejs, który implementują klasy nasłuchujące na zdarzenia - zazwyczaj modele widoków. Wstęp za nami, lecimy z kodem, marker interface dla zdarzeń:

public interface IEvent
{
}

I od razu nasz interfejs szyny zdarzeń, dla mojej wygody po przesiadce z Flexa zamiast IEventBus interfejs po którym odnajdujemy w kontenerze naszą szynę nazywa się tak:

public interface IEventPublisher
{
    Task PublishAsync<TEvent>(TEvent @event) where TEvent : IEvent;
    void RegisterAsyncConsumer<TEvent>(IConsumerAsync<TEvent> consumer) where TEvent : IEvent;
    void UnregisterAsyncConsumer<TEvent>(IConsumerAsync<TEvent> consumer) where TEvent : IEvent;
}

Musicie mi wybaczyć tą zmianę konwencji nazewnictwa, sama klasa implementująca nazywa się już poprawnie :) Jak widać z samego interfejsu - wszystkie eventy obsługiwane są asynchronicznie - z początku chciałem zrobić dwie ścieżki tak jak dla komend jednak uznałem to za zbędną komplikacje i zostało mi to co widać. Nie będę zamieszczał tym razem implementacji gdyż jest trywialna (jest na githubie) i opiera się na jednym polu zawierającym typ eventa i listę instancji klas nasłuchujących, które niestety musiałem zapakować w object:

private static Dictionary<Type, List<object>> _consumersAsync = new Dictionary<Type, List<object>>();

Dla konsumenta przygotowałem interfejs:

public interface IConsumerAsync<TEvent> where TEvent : IEvent
{
    Task HandleAsync(TEvent eventMessage);
}

Ponieważ spora część moich zdarzeń do będą CRUDowe informacje na temat dodania, aktualizacji lub usunięcia elementu dodałem bardzo przydatną uniwersalną klasę generyczną:

public class Event<TData> : IEvent where TData : DataModelBase
{
    public TData Item { get; protected set; }

    public EventAction Action { get; protected set; }

    public Event(TData item, EventAction action)
    {
        this.Action = action;
        this.Item = item;
    }
}

public enum EventAction
{
    CREATE = 1,
    UPDATE= 2,
    DELETE = 3
}

Kolejna porcja mojego CQRSa za nami, jeszcze trochę do omówienia zostało ale to w kolejnych postach, do przeczytania jutro!

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.


poniedziałek, 6 marca 2017

Architektura i CQRS, część 1 - Command

Witam ponownie,
podejmując wyzwanie konkursowe, zapewne każdy stawia sobie za jeden z celów naukę nowych rzeczy - ja również taki cel mam, a jest nim m.in próba zastosowania CQRS w praktyce. O samym Command Query Responsibility Segregation pewnie wielu z was słyszało, a jeśli nie to odsyłam do wujka googla, bo nie chcę tutaj powielać treści, które są już wielokrotnie w internecie opublikowane.
Do tej pory pisząc aplikacje, w tym aplikacje mobilne starałem się trzymać architektury MVC lub dla Xamarina MVVM, która niestety była trudno testowalna i po dłuższym czasie robiło mi się zawsze spaghetti code, którego nikt oprócz mnie by szybko nie zrozumiał. Zarówno w modelach jak i kontrolerach często naruszałem zasadę jednej odpowiedzialności. Po analizie w głowie założeń CQRSa wydaje mi się, że jest receptą na czytelny i łatwo testowalny kod. Kolejną rzeczą, która dla mnie jest nowością (wstyd!) jest kontener DI. Nie twierdzę, że z samego dependency injection  nie korzystałem, ale tak wygodne narzędzie jak kontener było mi obce. No ale od czego jest DSP - kolejna rzecz do toolsetu wpadnie ;) Wybór po krótkich próbach pisania własnego kontenera dla zrozumienia jego działania oraz  krótkiej konsultacji z kolegą Norkiem padł na Autofaca i jak na razie jestem z niego zadowolony.
Ok, przejdźmy zatem do omówienia mojego CQRSa, w jego implementacji wzorowałem się głównie na dwóch źródłach : ebooku od Maćka Aniserowicza i poście Mateusza Stascha, którego prelekcje o CQRSie miałem kiedyś przyjemność posłuchać.

Zaczniemy od ogólnego omówienia. Składowe mojej implementacji cqrsa to:
- Komendy (Command)
- Zapytania (Query)
- Walidatory komend (Validator)
- Zdarzenia (Event)
- Handlery - osobno dla komend i zapytań
- Szyny - szyna komend, szyna zapytań i szyna zdarzeń

To jest core kodu CQRSa, który będę tutaj omawiał. Dodatkowo dochodzą modele danych, widoki i modele widoków, startup aplikacji wraz z rejestrowaniem klas w kontenerze itp, które to rzeczy omówię w kolejnych postach.

Komendy


Zaczniemy od komend, dla nich mam przygotowane dwa interfejsy (marker interface):


public interface ICommand
{
}

public interface ICommandAsync : ICommand
{
}

Jak widać same komendy nic nie robią. Mam dwa rodzaje komend - jedne synchroniczne, drugie asynchroniczne aczkolwiek jak widać, każda komenda asynchroniczna jest również zwykłą komendą. Fakt zastosowania przeze mnie dwóch ścieżek obsługi komend wynika z bogatego wykorzystywania w Xamarinie async i await. Handlery komend wyglądają następująco:

public interface ICommandHandler<in TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

public interface ICommandHandlerAsync<in TCommand> where TCommand : ICommandAsync
{
    Task HandleAsync(TCommand command);
}

Tutaj widać pierwsze złamanie zasady aby komenda nic nie zwracała - w przypadku obsługi asynchronicznej zwracam taska, aby móc wyłapać exceptiony podczas obsługi komendy. Polecam poczytać o stosowaniu async await i dlaczego nie powinno się stosować async void. No i na koniec mamy interfejs szyny komend:

public interface ICommandBus
{
    void SendCommand<TCommand>(TCommand cmd) where TCommand : ICommand;

    Task SendCommandAsync<TCommand>(TCommand cmd) where TCommand : ICommandAsync;
}

Zamieszczę jeszcze implementację szyny komend:


public class CommandBus : ICommandBus
{

 private readonly ILifetimeScope _resolver;
 private readonly IEventPublisher _eventPublisher;

 public CommandBus(ILifetimeScope resolver, IEventPublisher eventPublisher)
 {
  _resolver = resolver;
  _eventPublisher = eventPublisher;
 }

 
 public void SendCommand<TCommand>(TCommand cmd) where TCommand : ICommand
 {
  if (cmd == null)
  {
   throw new ArgumentNullException("command");
  }

  var commandValidator = _resolver.ResolveOptional<IValidator<TCommand>>();
  if (commandValidator != null)
  {
   var res = commandValidator.Validate(cmd);
   if (res.result == false)
   {
    throw new ValidationException(res);
   }
  }

  var commandHandler = _resolver.ResolveOptional<ICommandHandler<TCommand>>();
  if (commandHandler == null)
  {
   throw new Exception(string.Format("No handler found for command '{0}'", cmd.GetType().FullName));
  }

  commandHandler.Handle(cmd);


 }

  
 public async Task SendCommandAsync<TCommand>(TCommand cmd) where TCommand : ICommandAsync
 {

  var commandValidatorAsync = _resolver.ResolveOptional<IValidatorAsync<TCommand>>();
  if (commandValidatorAsync != null)
  {
   var res = await commandValidatorAsync.Validate(cmd);
   if (res.result == false)
   {
    throw new ValidationException(res);
   }
  }

  var commandValidator = _resolver.ResolveOptional<IValidator<TCommand>>();
  if (commandValidator != null)
  {
   var res = commandValidator.Validate(cmd);
   if (res.result == false)
   {
    throw new ValidationException(res);
   }
  }

  var commandHandler = _resolver.ResolveOptional<ICommandHandlerAsync<TCommand>>();
  if (commandHandler == null)
  {
   throw new Exception(string.Format("No handler found for command '{0}'", cmd.GetType().FullName));
  }


  await commandHandler.HandleAsync(cmd);

 }
}

Mimo że implementacja szyny komend jest trochę przydługawa, jej działanie jest bardzo proste i co najważniejsze łatwo rozszerzalne, a na razie: szukamy walidatora komendy, jak jest wykonujemy walidacje, jak walidacja się powiedzie to szukamy handlera komendy, jak go znajdujemy to wykonujemy komende. Implementacje podwaja osobna ścieżka dla asynchronicznych komend i synchronicznych. Kryje się w tym prostym kodzie na prawdę wielka moc - przechodzą przez te dwie metody wszystkie zmiany stanu naszego systemu, chciałem dołożyć walidację i proszę - troszkę kodu i mam machanizm walidacji każdej komendy, będę chciał dołozyć logowanie błędów - proszę bardzo - try catch na handle i dodajemy logowanie. Ponieważ postanowiłem podzielić posta na kilka mniejszych, osobno dla każdego elementu mojego cqrsa to pozwolę sobię jeszcze zamieścić przykładową implementację komendy i jej handlera.


public class AddApiary : ICommandAsync
{
 public Apiary apiary { get; private set; }

 public AddApiary(Apiary apiary)
 {
  this.apiary = apiary;
 }
}

public class AppApiaryHandler : ICommandHandlerAsync<AddApiary>
{
 private SQLiteConnection _conn;

 private IEventPublisher _eventBus;

 public AppApiaryHandler(SQLiteConnection conn, IEventPublisher eventBus)
 {
  _conn = conn;
  _eventBus = eventBus;
 }

 public async Task HandleAsync(AddApiary command)
 {
  _conn.Insert(command.apiary);
  await _eventBus.PublishAsync<Event<Apiary>>(new Event<Apiary>(command.apiary, EventAction.CREATE));
 }
}

Przepraszam za kiepskie wcięcia w kodzie, jak ktoś zna toola, który dobrze koloruje składnie c# i jednocześnie dobrze robi wcięcia to proszę o komentarz. Następny post - Query.