Kilka słów o tabelach w obiektowym świecie – ORM czy SQL?

37

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?

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Share.

About Author

Zawodowo .NET Developer i Team Leader. Z zamiłowania rówież developer, entuzjasta chmury, bloger, prelegent i lider Białostockiej Grupy .NET oraz Microsoft Azure User Group Polska w Białymstoku. Gdy tylko mogę chwytam się wszystkiego co nowe i świeże. By się odprężyć czytam przeróżne książki, komiksy i gotuję.

37 Comments

  1. 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 :).

  2. 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ć, czasami to są monstra na parę stron… człowiek potem musi się habilitować, żeby namierzyć fragment kodu do optymalizacji.

    Historii można by mnożyć. Od lat obserwuje trend, w którym coraz mniej się troszczymy od bazy danych, a przecież jak się spier***i bazę danych to potem nawet Summit (TOP 500) nie pomoże…

    • 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.

    • Jacek Brożyna on

      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 infrastruktury, że taniej będzie zatrudnić firmę, która zoptymalizuje wszystko jak trzeba :)

  3. 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.

    • 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 :(

  4. Kamil Tkacz on

    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.

  5. 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.

  6. “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.

      • 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 pisanie SQLi w czystym ADO.NET a polecam użycie jakiegoś microORM’a np Dappera, który działa prawie tak samo szybko jak ADO a nie musimy klepać SqlReaderów i innych klas znajdujących się zdecydowanie na niższym poziomie abstrakcji.

        Wyobrażam sobie tylko jeden przypadek, gdzie architektura aplikacji powinna iść w jedną stronę (ORM lub surowy SQL) – gdy nasz zespół nie zna jednego z podejść. Jest to wg mnie jednak sytuacja marginalna, gdyż moim zdaniem każdy programista backend’owy powinien znać bardzo dobrze zarówno ORM (ogólnie, nie konkretny framework – zasady są te same), SQL i bazy danych (przynajmniej te relacyjne).

      • Dariusz Lenartowicz on

        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.

  7. 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.

    • Dariusz Lenartowicz on

      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ń.

  8. 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

  9. 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 :)

      • 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 można ludzi wprowadzić w błąd.

      • 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.

Leave A Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.