Archiwa tagu: Design

ShopHelper – Pierwsze kroki i trochę o MVVM

Poprzedni wpis na temat projektu ShopHelper: Start

W tym artykule chciałbym zaprezentować pierwszą implementację komponentów UI. W poprzednim poście zakończyliśmy na założeniu solucji i dodaniu domyślnych komponentów z użyciem frameworka Xamarin.Forms. Naszym celem na tym etapie jest:

  • stworzenie podstawowych wspólnych komponentów z użyciem wzorca MVVM (Model-View-ViewModel)
  • zapewnienie podstawowej nawigacji
  • wyświetlenie prostej listy zakupów z możliwością dodawania nowych elementów.

Zaczynamy od stworzenia dwóch klas i jednego interfejsu, które pomogą nam modelować komponenty według wzorca MVVM, oczywiście nie jest to wymagane ale bardzo uspójni architekturę i implementację przyszłych komponentów aplikacji. Tak więc tworzymy interfejs IView:

Interfejs ten jak widać powyżej zawiera tylko jedną właściwość BindingContext, która zapewni  „połączenie” pomiędzy widokiem, a ViewModel. Generalnie interfejs ten powinny implementować wszystkie widoki naszej aplikacji. Czym są widoki? To nic innego jak graficzne komponenty, które udostępnia nam framework Xamarin.Forms, może to być m.in. ContentPage, ContentView lub inne kontrolki… Następnie dodajmy klasę abstrakcyjną ViewModel oraz generyczny ViewModel:

Przedstawiony ViewModel posłuży nam do implementacji bazowego mechanizmu eksponującego model dla widoków. Mówiąc inaczej to w tym miejscu (w klasach pochodnych) znajdą się właściwości, które chcemy udostępnić do warstwy prezentacji. ViewModel implementuje interfejs INotifyPropertyChanged, za pomocą tego interfejsu możemy powiadomić widok, że jakaś wartość jednej z naszych właściwości uległa zmianie. Dzięki takiemu rozwiązaniu otrzymujemy rozdzielenie warstwy prezentacji (widoków) od modelu / logiki. I tak zadaniem widoku jest wyłącznie prezentować dane, nie wykonuje on żadnej logiki.

Przejdźmy teraz do stworzenia podstawowych elementów naszej aplikacji. Każda aplikacji musi mieć jakiś główny komponent, nazwę go po prostu Main. Tworzę więc katalog Main i umieszczam w nim klasę MainComponent, to tu umieszczę logikę związaną z tym komponentem, myślę tu głownie o nawigacji do innych komponentów aplikacji. W tym samym katalogu tworzę MainView.xaml używając szablonu oferowanego przez Visual Studio.

Tak jak wspomniałem wcześniej każdy widok powinien implementować interfejs IView:

Dzięki temu, że ContentPage dziedziczy po BindableObject, w którym już znajduje się właściwość o tej samej nazwie nic więcej nie musimy implementować. W konstruktorze naszego bazowego ViewModel wykona się cała „magia” MVVM – ViewModel zostanie powiązany z widokiem. Następnie utworzyłem klasę MainViewModel, która dziedziczy po generycznym ViewModel. Zwróćmy uwagę jak ViewModel udostępnia informacje do widoku, zobaczmy na właściwości Title oraz ShowShoppingListCommand udostępniane przez nasz MainViewModel:

Jak widać powyżej w widoku mamy odwołanie do tych właściwości poprzez wykorzystanie mechanizmu Binding (wiązania). Dodałem w MainView również jakąś prostą nawigację, wykorzystałem do tego kontrolkę StackLayout i trzy przykładowe przyciski, pod jeden z nich podpinam właśnie wspomniany wcześniej ShowShoppingListCommand, to nic innego jak implementacja System.Windows.Input.ICommand – komendy, która zapewnia przekazanie wykonania do wskazanej metody. Właściwość ShowShoppingListCommand korzysta z metody udostępnianej przez bazowy ViewModel SetProperty. Zadaniem tej metody jest ułatwić wykorzystanie notyfikacji INotifyPropertyChanged i skrócić kod przypisania nowej wartości do jednej linii. Dzięki takiemu mechanizmowi przypisując wartość naszej właściwości powiadamiamy widok o zmianie.

Nową instancję komponentu Main tworzymy w klasie App zaraz po uruchomieniu aplikacji, do metody Run przekazujemy obiekt Application. W metodzie Run uruchomimy nawigację oferowaną przez Xamarin.Forms dodając nasz widok do globalnej nawigacji. W skrócie MainComponent rozpoczyna nawigację globalną dla całej aplikacji oraz tworzy polecenia definiując metody do nawigowania do innych komponentów.

Teraz czas na komponent ShoppingList czyli naszą na razie prostą listę zakupów. Tworzę katalog ShoppingList, a w nim umieszczam kolejno ShoppingListView, ShoppingListViewModel oraz ShoppingListComponent. Widok listy zakupów przedstawia się następująco:

Dzięki prostemu zastosowaniu kontrolek oferowanych przez Xamarin.Forms umieściłem wszystkie komponenty w StackLayout co pozwoli na rozmieszczenie elementów pod sobą na całym ekranie. Dodałem listę, a pod nią kontrolkę Entry, w której możliwe będzie wprowadzanie nowych pozycji. Obok umieściłem przycisk, którym użytkownik będzie mógł dodawać pozycje do listy. Na razie jest bardzo prosto ale tak miało być :). ShoppingViewModel ma za zadanie eksponować właściwości dla widoku i tak też robi, nic innego się tam nie dzieje:

Tu można zauważyć, że pojawiła się jeszcze jedna klasa, jest to ShoppingListItem, na razie bardzo prosty model. ViewModel udostępnia kolekcję ObservableCollection<ShoppingListItem>, dzięki czemu po każdym dodaniu, usunięciu elementów z kolekcji widok zostanie automatycznie powiadomiony o zmianach.

Zadaniem komponentu ShoppingList jak na razie jest stworzyć wszelkie potrzebne dla widoku powiązania i zapewnić obsługę listy zakupów. Jak na razie obsługujemy tylko dodawanie nowych elementów do listy, dzieje się to w metodzie AddShoppingListItem, która została podpięta do AddShoppingListItemCommand. Tak więc zbieramy wprowadzoną przez użytkownika wartość pola NewShoppingListItemValue, jeśli nie jest pusta to dodajemy nowy element do kolekcji i czyścimy właściwość, czyszcząc w ten sposób kontrolkę Entry.

Na początek to chyba tyle 🙂 wychodzę z założenia, aby iść małymi krokami. Dziś chciałem też napisać trochę o MVVM ponieważ korzystając z Xamarin.Forms po prostu nie wypada inaczej, a dzięki zachowaniu prostych zasad tego wzorca nasza aplikacja będzie dobrze napisana. Cały kod znajdziecie oczywiście na GitHubie.

 

CQRS w prostym wydaniu

Czy CQRS to tylko pattern dla wielkich projektów? Oczywiście jeśli chcielibyśmy wdrożyć wszystkie komponenty, zastosować DDD razem z agregatami, sagami oraz dołożyć do tego jeszcze Event Sourcing to tak, być może byłoby to zbyt dużym skomplikowaniem w szczególności jeśli nasz system to głównie CRUD-y.

Jednak zastanówmy się, w wielu systemach można spotkać wykorzystanie wzorca Repository, który właśnie świetnie sprawdza się przy CRUD-ach. Czy przechowujesz implementację dostępu do encji właśnie w repozytorium? Czy w metodach wykonujących zapytania do bazy wykorzystujesz wiele encji, robisz złączenia itp? Jeśli tak może warto zastanowić się na rozdzieleniem operacji zapisu (Create, Update, Delete) od operacji odczytu? Ile widzieliśmy już metod gdzie LINQ ciągnie się przez kilkadziesiąt linii tworząc niepotrzebne skomplikowanie w naszym kodzie (widziałem też LINQ na 300 linii ale wolałbym o nim nie pamiętać :)). Niejednokrotnie patrząc w profilerze jak wygląda zapytanie wygenerowane przez Entity Framework można się złapać za głowę. Tak jak bardzo lubię EF, tak jestem zdania, że Framework ten nie służy kompletnie do robienia zapytań do bazy danych, owszem jeśli chcemy pobrać dane jednej encji jasne jest to świetne narzędzie ale w innych przypadkach nie jest to odpowiednie rozwiązanie.

Tak więc dlaczego w tak wielu projektach wykorzystuje się Entity Framework również do operacji odczytu danych z bazy. Myślę, że jest to kwestia podejścia albo dla wielu osób może się to wydawać po prostu naturalnym rozwiązaniem. Jednak czy poprawnym? Zróbmy prostą rzecz podzielmy nasze Repository na dwa obiekty, jeden wykorzystujący EntityFramework do zapisu, drugi wykorzystujący czystego SQL-a do odczytu z metodami zwracającymi DTO. Wiem, że SQL przestał być ostatnio w modzie ale spróbujmy moim zdaniem warto :).

Testową implementację wykonam w ASP .NET Core. Aby nie odstraszać do końca czytelników nie będę robił ręcznie mapowań z SQL, wykorzystam do tego małą i przyjemną bibliotekę Dapper. Jest to Micro ORM, czy też jak kto woli Object Mapper. Dapper po dodaniu referencji do naszego projektu rozszerza nam System.Data.IDbConnection o dodatkowe metody, które możemy wykorzystać do wykonywania zapytań do DB. Dzięki czemu w łatwy sposób integruje się z generyczną warstwą dostępu do danych oferowaną przez .NET. Przejdźmy więc do implementacji :).

Na początek tworzę nową klasę, nazwałem ją  testowo ProductQueryService, jako, że na potrzeby artykułu będziemy robić zapytania do jakiś testowych produktów w mojej bazie danych:

W kodzie widać, że wstrzykiwany jest delegat Func, pod którym ukryta jest fabryka połączeń IDbConnection. Oczywiście jeśli chcesz możesz tu wstrzykiwać konkretną fabrykę lub też konkretne połączenie, np. SqlConnection. Tak wygląda konfiguracja w kontenerze Dependency Injection:

Idąc dalej mamy metodę, która zwraca nam kolekcję DTO produktu, a w niej wywołanie widoku SQL z użyciem metody Query dostarczanej przez bibliotekę Dapper, który to automatycznie wykonana za nas mapowanie na DTO, warunek jest jeden nazwy właściwości muszę być identyczne jak nazwy kolumn zwracane z SQL. Jeśli jest taka potrzeba możemy dodać aliasy do kolumn bezpośrednio w SQL-u. Oczywiście trzymanie SQL-a bezpośrednio w kodzie nie jest dobrym pomysłem ale na potrzeby testowe wystarczy, w przyszłości postaram się dodać post o dobrym sposobie trzymanie SQL-i w projekcie :). Widok GetProducts w bazie jest bardzo prosty:

Natomiast samo wywołanie naszego serwisu Query w kontrolerze wygląda tak:

Jak widać wynik zapytania trzeba jeszcze zmapować do odpowiedniej postaci. Jako, że naszym celem jest zwrócić listę produktów wraz z nazwami kategorii dla każdego z produktów trzeba wykonać kilka dodatkowych kroków. Jest to oczywiście pewna niedogodność w porównaniu z np. Entity Framework. Jednak największą zaletą Dappera jest wydajność, według strony https://github.com/StackExchange/Dapper jest prawie tak szybki jak za pomocą SqlDataReader:

Na koniec chciałbym jeszcze pokazać możliwość parametryzacji zapytań. Dodałem przykładową metodę, która na podstawie ID wybierze z naszego widoku tylko rekordy o podanym ID:

Klasa GetProductsById to proste DTO zawierające jedną właściwość. Jak widać powyżej Dapper pozwala na przekazanie obiektu z parametrami, automatycznie przypisując wartości właściwości do parametrów w SQL, warunek jest ten sam nazwy muszą się zgadzać! W SQL-u mamy parametr @Id, który odpowiada właściwości w DTO:

Na sam koniec wywołanie z kontrolera, jak widać nasze DTO jest zmapowane z parametrem podanym w URL:

Oczywiście przedstawiony przykład jest bardzo prosty i ktoś mógłby powiedzieć, że to samo można osiągnąć w EF robiąc dwa razy Include. Jednak to nie o to chodzi. Po pierwsze rozdzielając repozytorium na dwa obiekty rozdzielamy odpowiedzialności. Dzięki czemu np. jeśli będzie potrzeba dodania kolumny nie musimy nic zmieniać w samym modelu Entity. Model warstwy odczytu może żyć niezależnie, być rozwijany przez inny zespół czy też być optymalizowany tylko pod konkretne zapytania. Czy też jeśli będzie taka potrzeba możemy korzystać nawet z osobnej bazy danych!

Myślę, że w każdym nawet małym projekcie warto zastanowić się nad rozdzieleniem warstwy DAL na Command i Query, w przedstawionym przeze mnie przykładzie starałem się pokazać, że nie wymaga to aż takiego dużego nakładu pracy, a dzięki temu zachowujemy porządek w kodzie, jeśli nasz projekt w przyszłości się rozrośnie nic nie stoi na przeszkodzie aby zastosować kolejne z technik CQRS. Przykładowy projekt znajdziecie na GitHubie.