Pierwsze kroki z Dapper’em oraz moje doświadczenia z ORM

Dzisiaj chciałem w skrócie opowiedzieć o tym, jak wprowadziłem do projektu nową bibliotekę. Słyszałem o niej już od jakiegoś czasu, ale nie miałem okazji z niej skorzystać. Kiedy lepiej niż teraz?! Tak więc zapraszam do krótkiego wpisu dotyczącego biblioteki Dapper.

Moja historia z ORM i nie tylko

W projektach .NETowych wykorzystywałem kilka sposobów dostępu do bazy danych. Na początku były to DataSety. Do dziś pamiętam ten dramat gdy trzeba było coś zmieniać w zapytaniach, lub wszystkie razy kiedy kod designera się wykrzaczył i trzeba było kombinować dlaczego.

Później przyszła pora na SqlCommand z ADO.NET. Ręczne mapowanie rezultatów zapytań, kolejny raz poprawianie kodu w wielu miejscach przy zmianach. Też nie było kolorowo, ale jednak nie trzeba było utrzymywać DataSetów. No i wydajność była wyśmienita. To już coś.

Następnie pojawił się EntityFramework w podejściu DatabaseFirst. Kolejny raz pojawił się designer, dodawanie tabelek, ale tutaj już nie trzeba było pisać żadnych procedur składowanych w SQL. Szybkie Linq i gotowe. Coś pięknego. Czar prysł dosyć szybko z kilku powodów. Pierwszy z nich to taki, że nie potrafiliśmy z niego dobrze korzystać. Globalny context na cały projekt? Dlaczego nie! Ogromne zapytania w Linq, które pięknie działają lokalnie, ale na produkcji już nie koniecznie. Oj tak! To było tylko kilka grzechów głównych, które popełniliśmy na początku. Kwestie wydajności można było zastąpić procedurami, ale przy takim podejściu w sumie ta praca wygląda jak ta z DataSetami, bleee. Tutaj pojawia się kolejny problem, czyli designer. Jak już wspomniałem, DataSety przebrane w kostium EF, a do tego z gorszą wydajnością. Nie będę tutaj dalej ciągnął tego wątku, bo to worek bez dna.

Następnie pojawił się EntityFramework, ale już taki „prawdziwy” przy podejściu CodeFirst. Kwestie problemów z wydajnością zapytań pozostają, ale nie mamy już tego przeklętego designera. Chociaż tyle. W tym momencie miałem już dużo większe doświadczenie i wiedziałem, że nie wszystko złoto co się świeci. Do prostych zapytań, jak najbardziej! Praca jest wtedy szybka, nie przejmujemy się za bardzo warstwą bazy danych. Sprawdzamy jedynie, czy EF nie wypluwa jakiegoś kosmicznego zapytania do BD (zawsze jest ono kosmiczne, ale chodzi o to, żeby dobrze działało) i voilà. Jest pięknie. Gdy mamy jakieś skomplikowane zapytanie, nie bójmy się napisać procedury, to nie jest takie złe jeżeli nie musimy tego robić za każdym razem. Na temat EF jest wiele artykułów, wiele z nich twierdzi wręcz, że ta technologia nie nadaje się do niczego. Moim zdaniem w większości przypadków takie podejście może być wystarczające (tak jak w przypadku mojego projektu), ale nie zawsze. Można sprawić, że takie rozwiązanie będzie działało bardzo szybko, ale stracimy wtedy ostatnie zalety, jakie daje EF, np. automatyczne sprawdzanie zmian. Jeżeli zależy nam na wydajności bez kompromisów, trzeba sięgnąć po coś innego.

Dapper

No i tak oto przechodzimy do Dappera. Jak można przeczytać na jego stronie, Dapper to prosty mapper obiektów dla .NET, który rozszerza interfejs IDbConnection. Jako jego główne zalety wymienić można wydajność oraz to, że nie jest ograniczany w żaden sposób przez typ bazy danych z jakiego korzystamy. Wszystkie typy, obsługiwane w .NET ADO np. SqlLite, Firebird, Oracle, MySQL, PosgreSQL czy SQL Server.

Po dwóch dniach odkąd zacząłem z nim pracować, muszę przyznać, że jestem mile zaskoczony. Prawda, trzeba pisać kod SQL, ale samo użycie biblioteki jest bajecznie proste, co zaraz zademonstruje na przykładach. Mamy tutaj zalety EF, gdzie nie trzeba mapować obiektów ręcznie, oraz czystego ADO.NET, gdzie nie trzeba utrzymywać żadnych designerów czy wielkich klas DbContextu. W moim przypadku nie jest to do końca prawda, bo EF służy mi do zarządzania użytkownikami w ASP.NET Identity oraz do wykonywania migracji. Tak więc nie pozbędę się EF z projektu. Prawda jest taka, że na moje potrzeby wystarcza on całkowicie, chodzi tutaj bardziej o naukę.

Ten prosty kawałek dodaje nowy wiersz i jednocześnie wyciąga dane z bazy danych. Najpierw mapujemy model z danymi gry na encje, dodajemy datę zapisu (moglibyśmy to zrobić na poziomie zapytania, ale wolę takiej logiki nie wydzielać z systemu). Później otwieramy połączenie. Tutaj wykorzystuję fabrykę, która pobiera z konfiguracji connection string do bazy. Na interfejsie IDbConnection wykonujemy metodę QueryFirstAsync, która pobiera i mapuje pierwszy wynik zapytania na wskazany typ obiektu i go zwraca. Nie ma tutaj niczego skomplikowanego.

Powyżej jeszcze zapytanie które jest wykonywane. Od razu pobierane są dane, które chce zwrócić na początek. Wszystko wykonane w jednym odwołaniu do bazy danych. Bardzo miło.

Do dyspozycji mamy jeszcze inne metody, które działają bardzo podobnie, np. Execute, która nie zwraca danych z bazy. Możemy przekazać obiekt transakcji, dzięki czemu możemy wykonać rollback zapytań w razie potrzeby. Taka dygresja, tutaj przekonałem się o tym, że .NET Core jeszcze raczkuje, ponieważ nie posiada w obecnej wersji TransactionScope, znanego z poprzednich wersji frameworka. Kolejna ciekawa opcja to możliwość wykonywania wielu zapytań w jednym odwołaniu do bazy poprzez QueryMultiple. Nie będę rozpisywał się za dużo o przykładach, ponieważ pełno ich w sieci, między innymi nas tronie którą podałem na początku tego posta.

Podsumowanie

Moje doświadczenia z Dapperem są na razie bardzo pozytywne. Nie wiem, czy będę do wykorzystywał do wszystkich zapytań w moim projekcie, ale na pewno do tych bardziej skomplikowanych sięgnę właśnie po niego. Zachęcam Was do zapoznania się z tą biblioteką.

Postęp prac nad projektem WordHunt

Cześć, dziś chciałem podzielić się tym, co udało mi się zrobić do tej pory. Zauważyłem ostatnio, że robię wszystko żeby nie ukończyć tego projektu na czas… Rzeczy które powinienem zostawić na koniec, lub inne mniej ważne elementy programu zostały po części zrobione, a o samej rozgrywce ani widu, ani słychu. Skoro już zdałem sobie z tego sprawę, może teraz uda się pójść w dobrym kierunku. No, ale do rzeczy.

Model bazy danych

Zaczynam od rzeczy która jest akurat bardzo ważna. Dane oczywiście muszą być gdzieś przechowywane. Tutaj nie wymyślałem niczego interesującego, używam Microsoft SQL Server. Użytkownicy przechowywani są w tabelach, które generowane są automatycznie gdy używamy ASP.NET Core Identity. Wszystko byłoby pięknie, ale gdy projektowałem własne tabele, jako identyfikatora użyłem typu danych bigint, czyli long w C#. Domyślnym identyfikatorem użytkownika generowanym przez system był string, a w bazie danych nvarchar(450). Nie podobało mi się to, że w systemie będę miał dwa typy identyfikatora. Musiałem więc to zmienić. Zadanie to nie jest specjalnie trudne, ale zajęło mi trochę więcej czasu niż się spodziewałem. Myślę że napiszę o tym osobny post, dlatego nie będę się rozpisywał na ten temat tutaj. Tabele zostały zmienione, wszystko gra. Możemy przejść dalej. W systemie naturalnie muszą się znaleźć także moje tabele, nie tylko te domyślne. Tak więc usiadłem i zacząłem je rozpisywać. Zmieniałem je kilkukrotnie, ale mniej więcej udało mi się ustalić jak będą wyglądały, przynajmniej te podstawowe dotyczące rozgrywki.

Schemat tabel bazy danych związanych z rozgrywką

Tabele związane z rozgrywką.

Jak widać, wszystko sprowadza się do powiązania z tabelą Games, gdzie będą przechowane ustawienia gry. GameStatuses odpowiadać będzie za aktualny stan rozgrywki, czyja tura aktualnie trwa, czy została już zakończona etc. GameTeams, jak nazwa wskazuje, przechowuje dane drużyn które biorą udział w grze. W przypadku, gdy grą steruje tylko jedna osoba, nie jest ona powiązana z użytkownikiem. GameFields to pola na których znajdują się słowa, ich rodzaj, czy są przypisane do któregoś zespołu i czy zostało one odkryte. GameMoves to historia wszystkich ruchów, a GameClients to urządzenia które są połączone z rozgrywką. Jestem pewny że tabel przybędzie, ale na razie tyle powinno wystarczyć, wyjdzie w praniu.

Kolejne tabele to takie przechowujące słowa. W grze chce mieć możliwość wyboru języka, więc dodałem tabelę z językami, oraz kategorie żeby łatwiej było nimi zarządzać. Tabele te są dosyć proste, nic szczególnego.

Schemat tabel powiązanych ze słowami.

Tabele związane ze słowami.

WebAPI do zarządzania słowami

Tak, bardzo ważna rzecz w grze która jeszcze nie działa. Dodałem możliwość zarządzania językami, kategoriami i słowami. Jest ono zabezpieczone w taki sposób, że jedynie użytkownicy będący administratorami mają do nich dostęp. Tabele ze słowami są pomocne od początku, ale zarządzanie nimi mogło poczekać… Już się zabierałem za robienie interfejsu użytkownika do tego, ale na szczęście się powstrzymałem. Dalszy rozwój tej części aplikacji odkładam na później.

Routing aplikacji webowej

W poprzednich postach opisywałem routing w Angularze. Na tej samej zasadzie zrobiłem go w mojej aplikacji. Na stronę główną mają dostęp wszyscy, ale żeby zobaczyć coś więcej trzeba się zalogować. Do panelu administracyjnego dostęp mają tylko administratorzy. Wykorzystałem tutaj mechanizm o którym nie wspomniałem w tamtym poście, RouteGuards. Na pewno opiszę go w drugiej części posta dotyczącej routingu. A poniżej przykład:

Podsumowanie

Rozwój aplikacji nie przebiegał ostatnio w najlepszym tempie. Wydaje mi się jednak że zrobiłem dużo nudnych rzeczy, które i tak trzeba by było prędzej czy później dodać (może nawet bardzo później…). Dzięki temu teraz będę mógł przejść do konkretów i development przyśpieszy. Kolejne krotki to dodanie możliwości uruchomienia gry, czyli strona z wyborem ustawień, następnie zapisanie ich do bazy danych. Później przyjdzie czas na system rozgłaszania wykonanego ruchu przez użytkownika do innych klientów. Ale o tym w kolejnych postach.

Poznajmy Postman’a!

Podczas rozwijania API chcielibyśmy w łatwy sposób przetestować, czy działa ono jak należy i zwraca wartości w takiej formie, w jakiej powinno. Chciałem dzisiaj opisać pokrótce narzędzie, które nadaje się to tego celu idealnie. Znam je od dawna, ale dopiero niedawno poznałem większość z jego sporych możliwości. Mowa tutaj o Postman’ie. Jest to darmowa aplikacja, która pozwala na wykonywanie zapytań oraz przechowywanie ich historii. Zapraszam na krótkie wprowadzenie.

Logo narzędzia Postman

Instalacja

Tutaj sprawa jest bardzo prosta, jeżeli mamy przeglądarkę Chrome, wystarczy że dodamy rozszerzenie, które można pobrać tutaj. Drugą opcją jest zainstalowanie natywnej aplikacji na nasz system. Ją z kolei możemy pobrać w tym miejscu. Natywna aplikacja omija kilka ograniczeń wersji z dostępnej w przeglądarce. Następnie zakładamy konto i logujemy się do aplikacji. Możemy przejść dalej!

Podstawowe funkcje

Aplikacja moim zdaniem ma bardzo przyjemny dla oka interfejs, ale łatwo przeoczyć tutaj kilka ciekawych funkcji. Na początek zajmijmy się jednak kilkoma podstawowymi rzeczami. W menu bocznym aplikacji możemy dodawać kolekcje, które będą przechowywały nasze zapytania. Możemy tutaj także zobaczyć historię ostatnio wykonanych requestów.

Kolekcje w postmanie

Kolekcje oraz historia

Kolekcje przechowują nasze zapytania. Dodajmy więc nowy request. Wybieramy typ zapytania z listy oraz adres który będziemy odpytywać.

Nowe zapytanie

Możemy wybrać typ zabezpieczeń który jest zaimplementowany w API, dodać nagłówki oraz parametry naszego zapytania. Następnie klikamy na przycisk Send i gotowe. W odpowiedzi dostaniemy status naszego zapytania, czas wykonania, rozmiar odpowiedzi, body, ciasteczka, nagłówki. Nasze zapytanie możemy zapisać w kolekcji i szybko wykonać je ponownie później.

Animacja pokazująca wykonanie zaptania

Wykonanie zapytania

Environments

Bardzo przydatną opcją Postmana jest możliwość tworzenia profili środowiska. Jest to niesamowicie pomocne gdy chcemy w szybki sposób testować naszą aplikację lokalnie, a zaraz potem już na produkcji lub środowisku testowym. Dzięki temu nie musimy dodawać wielokrotnie tego samego zapytania lub ciągle edytować tych już istniejących.

Zarządzanie środowiskami

Definiujemy tutaj adres naszego API, istniejący token, nazwę użytkownika, hasło lub cokolwiek innego. Dodajemy takie opcje zarówno dla środowiska dev jak i produkcyjnego.

Edycja środowiska

Edycja środowiska

Następnie, tam gdzie nasze dane będą zależne od środowiska, zamiast podawać te wartości, wstawiamy zmienne środowiskowe. Zostaną one automatycznie zastąpione wartościami zdefiniowanymi w aktywnym środowisku. W tym celu używamy podwójnych nawiasów klamrowych, na przykład {{url}}.

Użycie zmiennych środowiskowych

Użycie zmiennych

Tesy oraz skrypty

Kolejną przydatną rzeczą są testy oraz skrypty wykonywane przed zapytaniem. Przed wykonaniem zapytania możemy np. ustawić obecną godzinę do zmiennej. Podobnie do skryptów działają testy.
Służą one do kilku celów. Pierwszym i oczywistym jest możliwość sprawdzenia czy wynik który otrzymaliśmy jest poprawy, posiada wszystkie pola, odpowiedni status etc. Podobnie jak w przypadku skryptów, możemy ustawić tutaj zmienną, ale tym razem mamy dostęp do tego, co zwróciło nasze zapytanie. Możemy więc wysłać zapytanie np. o token, a następnie przypisać go do zmiennej.

Taka automatyzacja może nam bardzo przyśpieszyć pracę. Nie będziemy musieli sprawdzać wszystkiego samemu, co może być problemem przy większej ilości zapytań.

Test zapytania

Testy zapytania

Wykonywanie wielu zapytań

Ostatnią rzeczą którą chciałem pokazać jest wykonywanie wielu zapytań jednocześnie, co umożliwia nam Collection Runner. Przy jego pomocy możemy wykorzystać to, o czym pisałem w poprzednim akapicie. Przykładowo, jako pierwsze zapytanie w kolekcji dodajemy takie, które odpyta nasze API o token uwierzytelniający. Zapytanie te zapisze nasz token do zmiennej,
z której później będą korzystały inne zapytania. W ten sposób możemy automatyzować wykonywanie i testowanie bardzo dużej ilości zapytań.

Podsumowanie

Mam nadzieje że tym, którzy nie znali jeszcze tego narzędzia pokazałem, jak w łatwy sposób z niego korzystać i ułatwić sobie życie. Po więcej informacji dotyczących Postmana odsyłam na jego oficjalną stronę, gdzie można znaleźć więcej przykładów i dokładny opis funkcji o których tutaj nie wspomniałem.

Zmienne środowiskowe w Angular

Często, gdy pracujemy nad naszą aplikacją musimy korzystać z innych adresów dla naszego API na produkcji, a innego lokalnie. Między innymi w takich przypadkach przychodzą nam z pomocą zmienne środowiskowe aplikacji. Dzisiaj chciałem pokazać jak w szybki sposób dodać możliwość obsługi zmiennych środowiskowych w naszej aplikacji wykorzystującej Angulara. Myślałem że będzie to coś, co jest dostępne out of the box, jednak nie zawsze tak jest. Jeżeli korzystacie z Angular-CLI, macie dostęp do nich w katalogu environments. Ja pokażę jak łatwo coś takiego zaimplementować w sytuacji, gdy nie mamy takiego udogodnienia. Mój przykład opiera się o aplikację, która powstała z szablonu dostępnego w dotnet CLI o którym pisałem w poprzednich postach. Moja aplikacja wykorzystuje Webpacka.

Implementacja serwisu z konfiguracją

Moje rozwiązanie nie jest idealne, ponieważ obsługuje jedynie dwie wersje aplikacji: lokalną czyli developerską, oraz produkcyjną. Jeżeli chcielibyśmy mieć możliwość obsługi innego środowiska, należy dodać kilka dodatkowych elementów, np. task w Gulpie, który podmieniałby nasze konfiguracje. Ja nie będę się tutaj rozpisywał na ten temat. Rozwiązanie składa się z kilku elementów. Na początek dodajemy plik app.config.ts, który przechowuje nasze zmienne.

Plik ten zawiera dwie klasy, które implementują interfejs IConfig. Dzięki temu nigdy nie przeoczymy żadnej zmiennej podczas dodawania konfiguracji. Poniżej przykład takiego intefejsu.

Następnie dodajemy serwis, który będzie przekazywany wewnątrz aplikacji.

Tutaj, w zależności od tego, czy uruchomiliśmy aplikację lokalnie, czy na produkcji, tworzymy instancję klasy zawierającej nasze konfiguracje. Funkcja isDevMode sprawdza na jakim środowisku jesteśmy. W moim przypadku to Webpack uruchamia aplikację w odpowiednim trybie (podczas publishu na produkcję dodawana jest opcja env.prod, o tym więcej w dokumentacji dotnet CLI). W serwisie definiujemy tyle pól, ile potrzebujemy. Ja użyłem tutaj składni Typescipt’u, która pozwala na dodawanie getterów. Możemy taką klasę podzielić na mniejsze, jeżeli nie chcemy mieć jednej dużej klasy z całą konfiguracją. Taki serwis możemy wstrzykiwać w inne serwisy lub komponenty naszej aplikacji, oraz podmieniać ją na inną podczas testów.

Podsumowanie

Jest to rozwiązanie, które można jeszcze rozwinąć, ale obecnie spełnia moje wszystkie wymagania. Dodatkowo jest bardzo proste w implementacji i działa automatycznie, bez konieczności wykonywania innych operacji podczas rozwoju aplikacji, takich jak uruchamianie skryptów. Mam nadzieję, że komuś się przyda!

Routing w Angular2 – pierwsze kroki

Dzisiaj chce przedstawić jak w szybki sposób dodać routing do naszej aplikacji z frameworkiem Angular. Podczas tego procesu można natknąć się na kilka problemów, o których tutaj wspomnę. Do dzieła!

Routing krok po kroku

Moje przykłady podaję na projekcie, który powstał przy użyciu dotnet CLI. Po zainstalowaniu .NET Core SDK, w konsoli wpisujemy dotnet new --install Microsoft.AspNetCore.SpaTemplates::*. Daje nam to dostęp do wielu szablonów projektów. Następnie wpisujemy dotnet new angular i mamy gotowy projekt z którym można pracować! Po więcej informacji na ten temat odsyłam na bloga jednego z autorów tych szablonów, Steve’a Sandersona. Opisał to wszystko ładnie w tym poście.

Wracając do routingu, na początek definiujemy moduł, w którym powiemy aplikacji jak ma się zachowywać, gdy użytkownik będzie chciał wejść w podany przez niego adres na naszej stronie. Później zaimportujemy go do głównego modułu, który jest uruchamiany jako pierwszy. Moduł routingu składa się z kilku części. Na początek importujemy potrzebne komponenty:

Dekorator NgModule potrzebny jest do zdefiniowania modułu. Routes oraz RouterModule potrzebne są już do napisania kodu, który pozwoli nam na nawigację wewnątrz aplikacji. Następnie importowaliśmy komponenty, które sami dodaliśmy i chcemy wyświetlić. Następnie pora na przekazanie aplikacji jak ma się zachowywać, czyli definicję ścieżek.

Import RouterModule.forRoot(routes) załatwi nam sprawę routingu.

Dzięki takiej definicji, po otwarciu aplikacji, od razu zobaczymy komponent CatComponent. Został on jawnie zaimportowany na starcie aplikacji. W tym przykładzie pokażę także jeszcze jedną ważną sprawę na którą trzeba zwrócić uwagę, w momencie gdy dodajemy routing do naszej aplikacji. CatComponent został pobrany od razu gdy weszliśmy na stronę aplikacji, mogło to wpłynąć na czas otwarcia aplikacji. Takie podejście nazywamy EagerLoading. Jeżeli byłby on duży, mogłoby to w znaczący sposób wpłynąć na to, jak użytkownik będzie odbierał naszą aplikację. Z kolei DogModule zostanie załadowany dopiero wtedy, gdy będziemy chcieli wejść do niego. Będzie wtedy można zauważyć małe opóźnienie przed otwarciem tej strony. Ta metoda z kolei nazywana jest LazyLoading. Dzięki temu, że komponent załadowany będzie później, cała strona będzie ładować się szybciej. Coś za coś, idziemy tutaj na kompromis. Jednak nie musi tak być. Wcześniej w imporcie komponentów z @angular/router zaimportowałem jeszcze klasę PreloadAllModules. Jeżeli definicję modułu zmienimy na następującą:

Moduł DogModule , oraz wszystkie inne które tak dodamy do routingu pobrane zostaną w tle po uruchomieniu aplikacji. Dzięki temu zyskujemy zarówno szybkość ładowania całej aplikacji, jak i responsywność podczas przechodzenia pomiędzy stronami. Ważna uwaga: żeby LazyLoading działał w projekcie wykorzystującym Webpacka, musimy zainstalować paczkę angular-router-loader, a następnie użyć ją w pliku webpack.config.js podczas konfiguracji plików .ts.

Kolejnym istotnym punktem jest stała routableComponents którą eksportujemy razem z modułem. Dzięki temu że tutaj dodajemy moduły, nie trzeba będzie ich na nowo importować w głównym module.Należy pamiętać, że każdy komponent który jest używany musi być dodany do declarations.

Teraz pozostaje tylko dodać moduł DogModule:

Oraz moduł routingu DogRoutingModule:

Dla takich małych modułów może się wydawać, że to spory narzut, jeżeli chodzi o ilość pracy. Ale jeżeli nasze komponenty są bardziej rozbudowane i mają swoje ścieżki podrzędne, dzięki temu nasz główny komponent nie rozrośnie się do niebotycznych rozmiarów. Dodajemy także separację do logiki naszej aplikacji, co wpływa na przejrzystość kodu.

Na koniec dodajemy nasz MainComponent, czyli główną stronę aplikacji:

Oraz szablon dla tego komponentu:

W szablonie możemy zobaczyć dwa ważne elementy: routerLink oraz router-outlet. routerLink to odpowiednik ng-href z pierwszego Angulara, czyli po prostu doda nam odpowiedni adres dla odnośnika na podstawie podanej nazwy ścieżki. router-outlet to najważniejszy element tutaj, czyli miejsce gdzie wstawione zostaną nasze komponenty, w poprzedniej wersji ng-view. Dodatkowo należy pamiętać także o dodaniu do znacznika head wartość <base href="/">, bez tego routing nie zadziała.

Podsumowanie

Przedstawiłem tutaj jedynie część możliwości jakie daje nam Angular jeżeli chodzi o routing. W kolejnych postach przedstawię bardziej skomplikowane przypadki: przekazywanie parametrów w adresie scieżki, RouteGuards, czyli mechanizm pozwalający na np. blokowanie wejścia do niektórych fragmentów aplikacji oraz wykorzystanie więcej niż jednego router-outlet.