Jakie języki programowania przewijają się najczęściej w ofertach pracy? Cokolwiek, w czym jesteś w stanie stworzyć jakiś back-end, nieśmiertelny JS i niemalże równie wszechobecny SQL tego lub innego sortu.
Na rozmowie czasem zdarzy nam się odpowiedzieć na pytanie dotyczące where’ów, selectów i innych joinów i… bywa, że jest to ostatni raz, gdy masz do czynienia w SQL-em w tej konkretnej firmie.
Słowem wstępu
Założę, że programujesz obiektowo. W takim razie niemalże na pewno zdarzyło Ci się stanąć przed wyborem bibliotek w momencie tworzenia rozwiązania lub przeglądać stos technologiczny istniejącego projektu. Myślę, że się nie pomylę, jeżeli napiszę, że w większości, jeśli nie we wszystkich przypadkach przed Twoimi oczami mignął jakiś ORM. Czasem okazuje się też, że SQL jest wymagany tylko i wyłącznie dlatego, że ten konkretny maper stoi nad tą, a nie inną technologią bazodanową.
Pozwól jednak, że nieco odejdę od tematu i się przedstawię, bo prawdopodobnie nie mieliśmy jeszcze okazji się poznać. Nazywam się Rafał Hryniewski i na co dzień pracuję z technologiami Microsoftu – głównie o nich będę Ci opowiadał. Goszczę tu po raz drugi, wliczając cykl „Wasze Historie”. Tym razem planuję zostać na dłużej i zająć się tematyką baz danych z perspektywy programisty obiektowego lub osoby, która nigdy wcześniej nie pracowała z bazą danych. Pokażę Ci, jak ogarnąć podstawy, jakie narzędzia masz do wyboru i jakie są ich mocne oraz słabe strony, jak podejrzeć i zabrać się za optymalizację tego, co wypluje z siebie Entity Framework, i wiele innych rzeczy, o których możesz nie wiedzieć, jeśli za bardzo nie wychylasz się zza używanego przez Ciebie ORM-a. Postaram się, by co jakiś czas dać Ci coś ciekawego do przeczytania niezależnie od tego, czy dopiero zaczynasz z bazami danych, czy masz już z nimi trochę doświadczenia.
Ale ja programuję obiektowo!
Wróćmy do tematu. Moim zdaniem bardzo mocno zaniedbaliśmy znajomość technologii bazodanowych. Pewnie, możemy napisać parę klasek, nadać kilka atrybutów, odpalić migrację i wszystko śmiga, baza stoi, ma nawet jakąś sensowną strukturę. To o wiele szybsze rozwiązanie niż składanie tabel do kupy ręcznie, zastanawianie się nad typami danych dla każdej kolumny i różne inne „pierdoły”. Podobnie sprawa ma się z pisaniem zapytań do naszej bazy – niby po co mielibyśmy męczyć się z czystym SQL-em w stringach, skoro możemy użyć doskonale znanego nam LINQ (w przypadku C#)?
Programujesz obiektowo? Jeśli tak, to jestem przekonany, że czujesz się o wiele bardziej komfortowo w świecie obiektów i kolekcji niż tabel i rekordów. Mogło Ci się też zdarzyć strukturyzować dane w bazie, myśląc o nich w kontekście klas, kolekcji i biorąc pod uwagę zasady pisania czystego kodu. Jak jednak mają się dobre praktyki obiektowe do dobrych praktyk bazodanowych? Najczęściej nijak i jeśli tylko wszystko będzie działać, to gdy liczba rekordów skoczy nam do kilkuset tysięcy lub więcej, możemy się obudzić z poważnymi problemami z wydajnością.
W SQL-u też należy dbać o porządek!
Wspomniałem o czystym kodzie. Niepokojącym mnie zwyczajem jest bezpośrednie mapowanie tabel na model obiektowy i działanie bezpośrednio na nim. Czy jest w tym coś złego? A nie brakuje Ci może jakiejkolwiek sensownej warstwy abstrakcji między bazą danych a DAL-em (data access layer)? Kilka drobnych zmian w bazie i dane mogą przestać się modyfikować albo odczytywać.
Co możemy zrobić? Tak samo jak w kodzie obiektowym posługujemy się np. interfejsami do wystawienia publicznych metod, tak w SQL-u możemy skorzystać z trzech różnych widoków zawierających wyłącznie dane specyficzne dla – powiedzmy – użytkownika, klienta i adresata paczki, używając w nich jednej tabeli Users. Podobnie możemy zrobić z przeróżnymi procedurami i funkcjami, a wszystko to ładnie zmapować w naszym ORM-ie.
Czy jest to bardziej pracochłonne? Tak. A co zyskujemy? Przede wszystkim to, że tak długo, jak nasz widok czy funkcja poprawnie zwraca dane w określonej strukturze, nie musimy się martwić o tym, jak to wszystko składane jest pod spodem do kupy. Mieliśmy plątaninę joinów, którą zastąpiliśmy implementacją grafów w SQL Server? Wystarczy, że przepiszemy kod naszego widoku i wszystko pozostanie w jak najlepszym porządku. Ba, możemy sobie pozwolić na całkiem spore optymalizacje, denormalizację w celu uproszczenia formy odczytywanych danych czy co jeszcze przyjdzie nam do głowy. A wiesz, co jest najpiękniejsze? Widoki, procedury i funkcje same w sobie na ogół nie przechowują danych, ich modyfikacje są błyskawiczne i nie wymagają zatrzymywania aplikacji.
Świat, którego nie znasz
Sam SQL zawiera mnóstwo narzędzi, które mogą Ci znacząco ułatwić życie. Zdarzyło Ci się kiedyś pisać skomplikowane wersjonowanie treści w kodzie obiektowym? SQL Server 2016 i wyższe mają funkcję zwaną Temporal Tables, która ogarnia wersjonowanie out of the box. Podobnie ma się sprawa z aplikacjami multi-tenant i row level security, maskowaniem danych, konstruktami znanymi z grafowych baz danych czy otwieraniem JSON-ów jako tabele. SQL potrafi zrobić naprawdę bardzo dużo z naszymi danymi, trzeba się z nim tylko troszeczkę zapoznać i przede wszystkim zgłębić jego możliwości.
O praktycznie każdym z elementów, które wymieniłem gdzieś wyżej, można stworzyć oddzielny wpis. O niektórych nawet kilka. Dlatego też nie opiszę ich wszystkich w tym miejscu – jeśli ciekawi Cię jakiś konkretny temat, daj znać w komentarzu, a postaram się napisać o tym coś więcej. Chciałem Ci tylko pokazać, że na tym poziomie istnieje naprawdę wiele rzeczy, o których można nie wiedzieć, jeśli wszystko przesłaniają Ci różowe okulary z ORM-ów. Nieco więcej na ten temat opowiadałem chociażby na swojej sesji na tegorocznym 4 Developers, nagranie możesz obejrzeć poniżej, a z prezentacją pewnie od czasu do czasu wyskoczę w Polskę, więc może nawet spotkamy się na żywo.
To w końcu używać czy nie?
Tytuł tego wpisu jest pytaniem, a ja wciąż nie udzieliłem na nie odpowiedzi. Wychwalam SQL-e pod niebiosa, płaczę, jakie te ORM-y są ubogie. Myślę, że doskonale wiesz, że odpowiedź brzmi jak zwykle: to zależy. Ja osobiście niemalże nie używam ORM-ów w swoich projektach, ale… czasem mi się zdarza i pracuję z nimi na co dzień, więc przydaje mi się wiedza o tym, co dokładnie leci do bazy danych po użyciu kilku metod w LINQ.
Używanie zarówno ORM, jak i czystego SQL ma swoje wady i zalety. Development np. z takim Entity Frameworkiem jest niesamowicie szybki, a szlifowanie procedur i widoków z każdej zbędnej milisekundy z pewnością nie jest zadaniem dla każdego (i – co ważniejsze – istotnym w każdym projekcie!) i może sprawić, że terminy realizacji staną się nierealne. Oba podejścia mają swoje wady, ale za najgorsze z nich uważam to trzecie, w którym zamykamy się na jakąś opcję dla zasady. „ORM to smutny syf i nie użyję go nawet do CRUD-a”, ”Nie dotknę SQL-a – jestem programistą obiektowym”.
Na tytułowe pytanie musisz odpowiedzieć samodzielnie. Pamiętaj jednak, że jeśli dobrze skonstruujesz swojego SQL-a, to raczej nie zaistnieje potrzeba przepisania go na jakiegokolwiek ORM-a, zwłaszcza jeśli zadbasz o odpowiednią abstrakcję po stronie bazy danych. I myślę, że to tego tematu możesz się ode mnie spodziewać w najbliższym czasie – jakich narzędzi użyć, by zbudować solidną warstwę abstrakcji między Twoją aplikacją a Twoją bazą.
To… o czym chcesz poczytać w przyszłości?
Coś czuję, że kolejne Twoje wpisy będą zdroworozsądkowo sensowne, ale z drugiej strony mięsisto techniczne. Dlaczego? Mam nadzieję, że uda Ci się spełnić takie oczekiwania :).
Wow Rafał widzisz? :) Super, znając Rafała: tak właśnie będzie!
Trochę tego, trochę tamtego, jak będzie o czym, to nawet pofilozofujemy.
Ale mięcho mogę obiecać ;)
W IT pracuje już ponad 10 lat i zawsze jak tylko przejmowaliśmy jakiś projekt to był problem z wykorzystywaniem ORM do granic możliwości byleby tylko SQLa nie pisać. Zapytania, które na bazie DEV działały 1 sekundę (mało danych) na produkcji potrafiły wykonywać się ponad 60 sekund; oczywiście nie wyszło to od razu, ponieważ początkowo produkcja tez miała mało rekordów, ale już po roku… Co więcej, jeden z największych minusów pisania zapytań tylko z poziomu ORMu – moim zdaniem – to brak możliwości prostego sprawdzenia jaki proces biznesowy w najbardziej obciąża serwer bazodanowy. Zapytania generowane przez ORM bardzo ciężko jest zdekodować,… Read more »
Ja często jeśli już korzystam z EFto na etapie developmentu wstawiam sobie ctx.Database.Log += (sql) => logger.Log(sql) i wykonuję generowany SQL z podglądem planu zapytania. W przypadku prostszych operacji ORMy dają radę, podobnie na etapie kiedy baza co chwilę się zmienia.
Ale faktycznie, znalezienie konkretnego zapytania w większym projekcie gdy ma ono kilka tysięcy linii i wszelkie nazwy aliasów nie mają sensu to horror.
Zgadzam się w 100%. Często programiści przyzwyczajają się do używania ORMów w prostych projektach, a jak przychodzi działać z bazą mającą po kilkadziesiąt milionów rekordów i wyciągać dane z kilku tabel, okazuje się, że to nie działa… Nie po to firmy rozwijają bazy danych, żeby później programista używał tylko tabel, a widoki generował za nie jakiś ORM. Niestety, ale spotkałem się z opinią, że klient dokupi droższy serwer, zrobi klaster, jak system nie będzie dawał rady, bo to tańsze niż praca programisty… Problem w tym, że przy naprawdę dużych systemach, kiepsko napisany kod obsługujący bazę danych zwiększy tak bardzo koszty… Read more »
Ja swego czasu, gdy nie umiałem jeszcze dobrze EF-a to wszystkie zapytania pisałem w SQL-u lub procedurach czy funkcjach.
Teraz większość piszę w EF-ie, ale nie które bardziej skomplikowane zapytania piszę w procedurach i wywołuje za pomocą EF-a, tudzież robię widoki w sql-u ale też wywołuje w EF-ie.
Czy to jest słuszne podejście?, nie wiem. Ja pracuje w ten sposób bo jest to dla mnie łatwiejsze.
Może dzięki Twoim artykułom dowiem się (w końcu) jak można podejść do tego zagadnienia (ORM i/lub SQL).
Czekam na więcej.
Długo nie będziesz czekał, prawdopodobnie następne w kolejce są właśnie zalety używania funkcji, widoków i procedur.
Nawet z ORMem da się wyciągnąć dzięki nim całkiem sporo, bo kontrola nad tym co leci do bazy jest o wiele, wiele większa. Jak trzeba to można tak walczyć nawet o milisekundy.
ORM or not to ORM, this the QUERY.
RESULT: not to. ;)
W praktycznie żadnym swoim pozapracowym projekcie nie używam dużych ORMów już od długiego czasu i jakoś nie mam problemu z sięgnięciem do bazy, więc można.
Inna sprawa, że coraz częsciej nie korzystam też z SQLa, a eksperymentuję z przechowywaniem danych w Azure Table Storage/Dynamo DB, a w najnowszym projekcie mam już w głowie eksperyment na azurowym blobie.
To świetnie Rafał, że Twój główny przekaz brzmi: “to zależy.” Profesjonalny twórca oprogramowania zna przynajmniej najpopularniejsze rozwiązania do persystencji danych i wie kiedy ich użyć i wie kiedy ich nie użyć. Ostrożnie podchodzi do solennych zapewnień marketingowców tej czy innej firmy. A już na pewno trzyma swój dostęp do danych za interfejsami, żeby w zależności od kaprysu raz użyć Azura, raz SQL Serwera, a innym razem płaskich plików na dysku C.
Jakoś nie wyobrażam sobie pisania szybkiego proof of concept w czystym SQLu, z wyszlifowanymi pod pojedyncze bajty zapytaniami.
Z drugiej strony nie byłbym w stanie wyobrazić sobie sytuacji kiedy ktoś dobrze pisane w SQL zapytania, przepisuje na powiedzmy Entity Framework.
Ale fakt, ładna abstrakcja pozwala na całkiem sprawną podmiankę warstwy DAL na napisaną z micro-ORMami czy nawet czystym ADO w bardzo łątwy sposób. Gorzej jak po projekcie latają pełne encje :(
W poprzedniej firmie, gdzie pracowałem ogólnie brzydzili się SQL-em i tylko ORM-o rządziło. W obecnym miejscu pracy mam naokoło takich magików od SQL-a, że EF dawno nikt nie widział. Większość operacji jest wykonywana na SQL-u a C# służy praktycznie tylko do “wyplucia” tego co procedury zmieliły. Z jednej skrajności w drugą ;)
Z perspektywy czasu, widząc efekty obu podejść sądzę, że działanie na SQL-u daje większą kontrolę i przejrzystość. Czekam na dalsze spisy ;)
Akurat logika biznesowa raczej nie pasuje mi do SQLa. Chyba, że mówimy o generowaniu raportów i operacjach, które bardzo ładnie da się napisać na zbiorach.
Z drugiej strony przy odpowiednim podejściu do bazy można funkcję wypluwającą przykładowo etykiety adresowe wzbogacić o prefiks Szanowny Pan/Szanowna Pani czy zamienić imię z nazwiskiem w przysłowiową minutkę bez żadnych restartów aplikacji.
Bardzo ciekawy artykul-moze dlatego ze moja dzialka;)
Chetnie sie dowiem:
1. jak zrobic warstwe posrednia miedzy klasa a baza danych
2. jak I czy wogole zastapic ORM SQLem [jest to moje preferowane podejscie-bo SQL jest dla mnie banalnie prosty I faktycznie cuda na kiju mozna zrobic;)]
3. jakie metody zastosujesz do walidacji danych I zapewnienie im spojnosci [np. przy kasowaniu]
Dzięki!
1. O tym będzie pewnie następnym razem.
2. Jeśli zaistnieje taka potrzeba (braki w wydajności, korzystanie z ciekawszych funkcji SQLa, wygenerowane skomplikowane zapytania etc.) to niestety, ale warto zacząć myśleć nad zastąpieniem ORMa. A jak? To już zależy od projektu, ale kiedyś pewnie poruszę ten temat.
3. Jeśli mam mix ORMa i czystego SQLa to takie rzeczy zostawiam dla ORMa bo performance najczęściej jest ważny głównie przy odczycie. Jeśli jest to sam SQL to ładuję operację w procedurę, jeśli jest taka potrzeba to w SQLu też można pisać testy, które pewne założenia sprawdzą.
ad 3)
Zależy co walidujemy :) Najlepiej mieć walidację na każdej “warstwie” aplikacji:
a) gdy sprawdzamy dane wprowadzone przez użytkownika np. czy kwota jest nieujemna (logika aplikacji)
b) gdy sprawdzamy reguły biznesowe np. czy użytkownikowi należy się rabat (logika biznesowa)
c) gdy sprawdzamy integralność i spójność danych
Najczęściej a) i b) realizuje się w kodzie aplikacji. Można skorzystać do tego z gotowych bibliotek np. FluentValidation.
c) najczęściej jest realizowane w postaci zakładania constraintów na bazie. Czasami jednak jest to niemożliwe – np. w przypadku mikroserwisów, gdy każdy serwis posiada własną bazę danych i niemożliwe jest zakładanie FK pomiędzy bazami.
“CQRS to the rescue”. Po stronie Command ORM doskonale wczytuje nam pełny agregat, nie mniej – nie więcej. Po stronie Query proste zapytania można puszczać przez ORMa, ale w sumie proste query można szybko w SQLu napisać, natomiast cięższe query już tylko czysty SQL (wydajne i skrojone na daną potrzebę zapytanie). Zakładając, że mamy do czynienia z dwoma modelami w kodzie, nawet jeśli pod spodem jest jeden model bazodanowy można osiągnąć przyzwoitą szybkość działania jak i czystość kodu.
Dokładnie – ORM jest dobrze stosować po stronie zapisu, ale często nie daje rady po stronie odczytu, wtedy dobrze zastosować SQLa.
Prawda. Ale niestety nie wszędzie damy radę wrzucić CQRS. :(
Wprowadzenie CQRS działającego na jednym modelu bazodanowym jesteśmy w stanie wprowadzić wszędzie, bo koszt takiego rozwiązania zawsze będzie niższy niż rozwiązania bez CQRS. ORM jest dobry do zapisu, bo śledzi zmiany w encjach i nie trzeba pisać cały czas podobnego (trywialnego) kodu. Dodatkowo jest dobry do prostych zapytań, dlatego świetnie się sprawdza w pobieraniu agregatów, bo są to najczęściej proste joiny do tabel po FK. Surowy SQL jest dobry do pobierania danych, bo umożliwia pełną kontrolę nad tym jak zapytanie wygląda i co zwraca, co przy większych systemach i wolumenach danych jest kluczową kwestią jeśli chodzi o wydajność. Odradzam jednak… Read more »
Tak jak napisał Kamil cqrs jest trywialny. Pooglądaj video Maćka na ten temat. :)
Ale ja wiem, że implementacja CQRS, zwłaszcza w tej najbiedniejszej wersji, to nie jest jakieś wielkie wyzwanie.
Sęk w tym, że CQRS nie jest rozwiązaniem, które rozwiąże każdy problem i nie zawsze ma sens jego implementacja jeśli wystarczy przepisać na SQLa jedno-dwa mocniej eksploatowane repozytoria.
A wiedzieć kiedy ORM jest wystarczająco dobry, a kiedy wygeneruje cuda, które nie dadzą rady na produkcji za n miesięcy to też sztuka. I niestety, ale to wymaga już dobrej znajomości i ORMa, i bazki.
@Dariusz Lenartowicz
CQRS jest trywialny tylko w takich trywialnych przykładach jak Maćka.
A kto komu zabrania mieszać w różnych miejscach aplikacji ORM z SQLem?
Może nie “ORM czy SQL” a kiedy jedno a kiedy drugie bo to się nie wyklucza. Zwłaszcza przy rozsądnej architekturze.
Wynik z SQLa i tak często (zwykle) trzeba przerobić na jakieś obiekty.
Od transformacji danych z bazy na “jakieś obiekty” są tzw. MicroORMy (nie mylić właśnie z “pełnymi” ORMami). :)
A dlaczego? Poza wydajnością ale to trzeba by sprawdzić.
Masz EF DbContext bez żadnych dbsetów
Pytasz tylko sqlem, który mapuje na obiekty. Będzie dużo wolniej niż jakimś mikro?
Jacek, ja często korzystam z dbContext.Database jeśli już korzystam z EF, faktycznie nie ma sensu używanie dodatkowego Micro ORMa jeśli już mamy EF, jest to odrobinę mniej wydaje, ale nie są to jakieś wielkie ilości czasu.
EF sam w sobie psuje wydajność change trackerem i generowaniem różnego rodzaju proxy oraz generowaniem raczej słabych zapytań.
Czekam na więcej artykułów. Ostatnio bardzo mnie interesuje temat ORMów i chętnie poczytam coś więcej na ten temat. Stosunkowo niedawno dowiedziałem się o tym. Jakiś czas temu miałem okazję poznać Entity Framework, teraz zapoznaję się z Jpa i springiem. Bardzo fajny tekst
Dzięki! Ale ode mnie spodziewaj się rzeczy raczej ze stajni Microsoftu.
Dobry artykuł, ciekawie się czyta, a i niesie ze sobą dobrą dawkę wiedzy :)
Odpowiadając na pytanie z artykułu, czym byłbym zainteresowany, to otwieraniem JSON-ów jako tabele. Brzmi ciekawie, a nie spotkałem się jeszcze z tym. Słyszałem o w spraciu dla jsonów w Postgres’ie, ale to wydaje mi się nie to :)
Ale po co?
Jacek, czasem się przydaje. Powiedzmy, że trzymasz coś w JSONie (przykład z czapki wymyślony w 5 sekund) jak na przyklad jakieś zserializowane zdarzenia i chciałbyś jako developer sobie po tym poquerować w jakims celu.
Ja po wysłaniu to się chwilę zastanawiałem o co chodzi z “otwieraniem JSONów jako tabele”.
Chyba założyłem, że chodzi o zserializowanie tabeli do JSON-a.
To co piszesz to raczej jest jakieś Object DB. Ja rozumiem sens zapisu JSONów nawet np logowania całej komunikacji przychodzącej tylko nie w tabelach i raczej bez SQL-a.
Zobaczymy co da się zrobić, ale na dłuższą metę coś takiego odradzam. Jest to mało wydajne, fajnie móc w miarę łatwo się do tego dobrać jak już musimy, ale jeśli do jakichś danych w bazie relacyjnej musimy często się dostać w ten sposób to raczej nie powinny być przechowywane w JSONie.
A dlaczego niby nie powinny być? Postgres świetnie wspiera JSON. Polecam zapoznać się z Martenem (https://github.com/JasperFx/marten). Zamienia Postgres w bazę dokumentową zachowując możliwość dalszego korzystania z uroków baz relakcyjnych (transakcyjność) jeśli trzeba oraz da się integrować z EFem. Takie podejście zmniejsza konieczność korzystania z custom sql oraz pozbywa nas większości boilerplate ORMów. Czy masz jakieś konkretne dane, że takie podejście w Postgres jest mało wydajne? Bardzo bym wdzięczny za takie dane jako użytkownik i kontrybutor Martena ;) Mała sugestia z mojej strony: przed wydaniem kategorycznych sądów, szczególnie występując z pozycji “eksperta” dobrze zbadać sprawę, a nie mówić z głowy, bo… Read more »
Nie doprecyzowałem tego w komentarzu, wybacz. Chodziło mi konkretnie o SQL Server. Tam są to operacje na nvarchar(max) i z wydajnością nie ma to zbyt wiele wspólnego, ale sama składnia jest całkiem wygodna jeśli już musimy na JSONach operować.
O Martenie i Postgresie wiem, chociaż nie miałem okazji pracować z żadnym z nich dłuzej.