ORM do użycia w GO: GORM, sqlc, Ent czy Bun?

GORM vs sqlc vs Ent vs Bun ```

Page content

Ecosystem Go oferuje szeroki wybór narzędzi ORM (Object-Relational Mapping) oraz bibliotek baz danych, każdy z własną filozofią. Oto szczegółowe porównanie czterech głównych rozwiązań dla używania PostgreSQL w Go: GORM, sqlc, Ent i Bun.

Oceńmy je pod kątem wydajności, doświadczenia dewelopera, popularności oraz funkcji/rozszerzalności, z przykładami kodu pokazującymi podstawowe operacje CRUD na modelu User. Średniozaawansowani i zaawansowani deweloperzy Go uzyskają wgląd w权衡 każdego narzędzia i to, które może najlepiej odpowiadać ich potrzebom.

ekran laptopa z pewnym kodem

Przegląd ORM

  • GORM – bogaty w funkcje ORM stylu Active Record. GORM pozwala zdefiniować struktury Go jako modele i oferuje szeroki API do wyszukiwania i manipulowania danymi za pomocą łańcuchów metod.
    Istnieje od lat i jest jednym z najbardziej popularnych ORM w Go (prawie 39 tys. gwiazdek na GitHubie).
    Wartość GORM polega na jego solidnym zestawie funkcji (automatyczne migracje, obsługa relacji, ładowanie wsteczne, transakcje, hooki i wiele więcej), które umożliwiają czysty kod Go z minimalnym użyciem surowego SQL.
    Jednak wprowadza własne wzorce i powoduje narzut czasowy z powodu refleksji i użycia interfejsów, co może wpływać na wydajność w dużych operacjach.

  • sqlc – nie jest tradycyjnym ORM, ale generatorem kodu SQL. Z sqlc piszesz surowe zapytania SQL (w plikach .sql), a narzędzie generuje bezpieczny dla typów kod Go (funkcje DAO), które wykonują te zapytania.
    Oznacza to, że interakcja z bazą danych odbywa się poprzez wywoływanie wygenerowanych funkcji Go (np. CreateUser, GetUser) i otrzymujesz wyniki z typowym dopasowaniem bez ręcznego skanowania wierszy. sqlc pozwala używać surowego SQL z bezpieczeństwem kompilacji — nie ma warstwy budowania zapytań ani refleksji w czasie wykonywania. Wkrótce zyskał popularność (~16k gwiazdek) dzięki prostocie i wydajności: unika całkowicie narzutu czasowego, korzystając z przygotowanych zapytań SQL, co czyni go tak szybkim jak użycie database/sql bezpośrednio.
    Wymianą jest to, że musisz pisać (i utrzymywać) instrukcje SQL dla swoich zapytań, a dynamiczna logika zapytań może być mniej intuicyjna w porównaniu do ORM, które buduje zapytania za Ciebie.

  • Ent (Entgo)ORM oparty na kodzie z użyciem generowania kodu, aby stworzyć bezpieczny dla typów API dla modelu danych. Definiujesz swój schemat w Go (korzystając z DSL Ent do pól, krawędzi/relacji, ograniczeń itp.), a następnie Ent generuje pakiety Go dla modeli i metod zapytań. Wygenerowany kod używa płynnego API do budowania zapytań, z pełnym sprawdzaniem typów w czasie kompilacji. Ent jest stosunkowo nowy (popierany przez Linux Foundation i opracowany przez Ariga).
    Skupia się na doświadczeniu dewelopera i poprawności — zapytania są tworzone za pomocą łańcuchowych metod zamiast surowych ciągów, co ułatwia ich kompozycję i zmniejsza ryzyko błędów. Metody Ent generują bardzo zoptymalizowane zapytania SQL i nawet cacheją wyniki zapytań, aby zmniejszyć powtarzające się wizyty w bazie danych.
    Został zaprojektowany z myślą o skalowalności, więc radzi sobie z złożonymi schematami i relacjami skutecznie. Jednak użycie Ent dodaje krok budowania (uruchamianie generowania kodu) i wiąże Cię z jego ekosystemem. Obecnie obsługuje PostgreSQL, MySQL, SQLite (oraz eksperymentalną obsługę innych baz danych), podczas gdy GORM obsługuje szerszy zakres baz danych (w tym SQL Server i ClickHouse).

  • Bun – nowszy ORM z “podejściem SQL-first”. Bun był inspirowany Go database/sql i starszą biblioteką go-pg, mając na celu bycie lekkim i szybkim z naciskiem na funkcje PostgreSQL.
    Zamiast wzorca Active Record, Bun oferuje płynny budowniczy zapytań: zaczynasz od db.NewSelect() / NewInsert() / itp., a budujesz je metodami, które są bardzo zbliżone do SQL (możesz nawet osadzić fragmenty surowego SQL). Mapuje wyniki zapytań na struktury Go za pomocą tagów podobnych do GORM. Bun preferuje jawność i pozwala łatwo wrócić do surowego SQL, kiedy to konieczne.
    Nie uruchamia automatycznie migracji (piszesz migracje lub używasz pakietu migrate Bun ręcznie), co niektórzy uważają za plus w kwestii kontroli. Bun jest pochwalany za eleganckie obsługuje złożone zapytania i obsługuje zaawansowane typy Postgres (tablice, JSON itp.) domyślnie.
    W praktyce, Bun niesie znacznie mniejszy narzut czasowy niż GORM (brak ciężkiej refleksji w każdym zapytaniu), co przekłada się na lepszą przepustowość w scenariuszach wysokiej obciążalności.
    Jego społeczność jest mniejsza (~4k gwiazdek), a jego zestaw funkcji, choć solidny, nie jest tak rozbudowany jak u GORM. Bun może wymagać trochę więcej wiedzy na temat SQL, by być skutecznie używany, ale w zamian oferuje wydajność i elastyczność.

Wydajność i testy porównawcze

Kiedy chodzi o wydajność (opóźnienie zapytań i przepustowość), istnieją znaczne różnice w zachowaniu tych narzędzi, zwłaszcza przy wzroście obciążenia. Ostatnie testy i doświadczenia użytkowników wyróżniają kilka kluczowych punktów:

  • GORM: Wygoda GORM kosztuje pewną wydajność. Dla prostych operacji lub małych zestawów wyników, GORM może być bardzo szybki — faktycznie, jeden test pokazał, że GORM miał najkrótszy czas wykonania dla bardzo małych zapytań (np. pobranie 1 lub 10 rekordów).
    Jednak, gdy liczba rekordów rośnie, narzut GORM powoduje, że znacznie spada. W testach pobierania 15 000 wierszy, GORM był około 2× wolniejszy niż sqlc, a nawet niż surowy database/sql (59,3 ms dla GORM vs ~31,7 ms dla sqlc i 32,0 ms dla database/sql w tym scenariuszu).
    Projekt oparty na refleksji i abstrakcje budowania zapytań dodają opóźnienia, a GORM może wysyłać wiele zapytań dla złożonych obciążeń (np. jedno zapytanie na każdą powiązaną encję, gdy używany jest Preload domyślnie), co może powiększyć opóźnienie. Podsumowanie: GORM jest zwykle dobrej jakości dla umiarkowanych obciążeń, ale w scenariuszach wysokiej przepustowości lub dużych operacjach masowych, jego narzut może stać się wąskim gardłem.

  • sqlc: Ponieważ wywołania sqlc są w zasadzie ręcznie napisanym SQL, jego wydajność jest porównywalna z bezpośrednim użyciem standardowej biblioteki. Testy zawsze umieszczają sqlc wśród najbardziej szybkich opcji, często tylko marginalnie wolniejszy lub nawet lekko szybszy niż surowy database/sql dla porównywalnych operacji.
    Na przykład, przy 10k+ rekordach, sqlc był obserwowany jako lekko przewyższający zwykły database/sql w przepustowości (prawdopodobnie z powodu unikania niektórych boilerplate skanowania i użycia wydajnego kodu generowanego). Z sqlc nie ma żadnego budowniczego zapytań w czasie wykonywania — Twoje zapytanie wykonuje się jako przygotowane instrukcja z minimalnym narzutem. Kluczem jest to, że już wcześniej wykonałeś pracę napisania optymalnego zapytania SQL; sqlc po prostu oszczędza Ci wysiłku skanowania wierszy do typów Go. W praktyce to oznacza wysoką skalowalność — sqlc będzie obsługiwał duże obciążenia danych tak samo dobrze jak surowy SQL, czyniąc go najlepszym wyborem, gdy krytyczna jest niska opóźnienie.

  • Ent: Wydajność Ent znajduje się gdzieś między podejściami surowego SQL i tradycyjnymi ORM. Ponieważ Ent generuje kod bezpieczny dla typów, unika refleksji i większości kosztów kompozycji zapytań w czasie wykonywania. SQL, który generuje, jest bardzo zoptymalizowany (masz kontrolę nad napisaniem efektywnych zapytań za pomocą API Ent), a Ent zawiera wewnętrzny warstwę cache, aby ponownie używać planów wykonania zapytań/wyników w niektórych przypadkach.
    Może to zmniejszyć powtarzające się wizyty w bazie danych w złożonych przepływach. Choć konkretne testy Ent w porównaniu do innych się różnią, wielu deweloperów raportuje, że Ent przewyższa GORM w równoważnych operacjach, zwłaszcza w miarę wzrostu złożoności.
    Jednym z powodów jest to, że GORM może generować suboptymalne zapytania (np. N+1 wyborów, jeśli nie starannie używasz łączeń/załadowania wsteczne), podczas gdy Ent zachęca do jawnej, wstecznej ładowania relacji i będzie łączyć dane w mniejszej liczbie zapytań. Ent również tendencja do alokowania mniej pamięci na operację niż GORM, co może poprawić przepustowość, zmniejszając presję na zbieracz śmieci Go. W ogólności, Ent jest zbudowany do wysokiej wydajności i dużych schematów — jego narzut jest niski, a może obsłużyć złożone zapytania skutecznie — ale może nie dopasować się do surowej przepustowości ręcznie napisanego SQL (sqlc) w każdym scenariuszu. Jest to silny kandydat, jeśli chcesz zarówno szybkość, jak i bezpieczeństwo warstwy ORM.

  • Bun: Bun został stworzony z myślą o wydajności i często jest wymieniany jako szybszy alternatywa dla GORM.
    Używa płynnego API do budowania zapytań SQL, ale te budownicze są lekkie. Bun nie ukrywa SQL od Ciebie — jest to bardziej cienka warstwa nad database/sql, co oznacza, że ponosisz bardzo mały narzut poza tym, co robi standardowa biblioteka Go. Użytkownicy zauważali znaczne poprawy po przejściu z GORM do Bun w dużych projektach: na przykład, jeden raport wspominał, że GORM był “bardzo wolny” dla ich skali, a zastąpienie go Bunem (i pewnymi surowymi zapytaniami SQL) rozwiązało ich problemy z wydajnością.
    W testach takich jak go-orm-benchmarks, Bun często jest bliski wierzchu w szybkości dla większości operacji (często w granicach 1,5× surowego sqlx/sql) i znacznie wyprzedza GORM w przepustowości. Wspiera również batches insertów, ręczne kontrolowanie łączeń vs osobnych zapytań i inne optymalizacje, które deweloperzy mogą wykorzystać. Podsumowanie: Bun dostarcza wydajności zbliżonej do niskopoziomowego sprzętu, a jest świetnym wyborem, gdy chcesz wygody ORM, ale nie możesz poświęcić prędkości. Jego natura SQL-first zapewnia, że zawsze możesz zoptymalizować zapytania, jeśli wygenerowane nie są optymalne.

Podsumowanie wydajności: Jeśli celem jest maksymalna wydajność (np. aplikacje intensywne w zakresie danych, mikroserwisy pod wysokim obciążeniem), sqlc lub nawet ręczne wywołania database/sql są zwycięzcami.
Mają praktycznie zero kosztu abstrakcji i skalują liniowo. Ent i Bun również działają bardzo dobrze i mogą obsłużyć złożone zapytania skutecznie — osiągają równowagę między szybkością a abstrakcją. GORM oferuje najwięcej funkcji, ale kosztuje narzut; jest idealny dla wielu aplikacji, ale należy być świadomy jego wpływu, jeśli oczekujesz obsługi dużych ilości danych lub potrzebujesz ultra-niskiego opóźnienia. (W takich przypadkach możesz ograniczyć koszt GORM, starannie używając Joins zamiast Preload, aby zmniejszyć liczbę zapytań, lub mieszając surowy SQL w krytycznych ścieżkach, ale to dodaje złożoność.)

Doświadczenie dewelopera i łatwość użycia

Doświadczenie dewelopera może być subiektywne, ale obejmuje krzywą uczenia się, przejrzystość API i jak produktywny możesz być w codziennym programowaniu. Oto jak nasze cztery konkurencyjne narzędzia porównują się pod względem łatwości użycia i przepływu pracy dewelopera:

  • GORM – bogaty w funkcje, ale krzywa uczenia się: GORM jest często pierwszym ORM, którego próbują nowi użytkownicy Go, ponieważ obiecuje pełną automatyzację (definiujesz struktury i możesz natychmiast tworzyć/znajdować bez pisania SQL). Dokumentacja jest bardzo szczegółowa z wieloma przewodnikami, co pomaga. Jednak GORM ma własny idiomaticzny sposób działania (“code-based approach” do interakcji z bazą danych) i może wydawać się nieprzyjemny początkowo, jeśli jesteś przyzwyczajony do surowego SQL. Wiele typowych operacji jest prosta (np. db.Find(&objs)), ale kiedy wchodzisz w asocjacje, relacje polimorficzne lub zaawansowane zapytania, musisz uczyć się konwencji GORM (tagi struktury, Preload vs Joins itp.). Dlatego często wspomina się o wysokiej początkowej krzywej uczenia się.
    Po pokonaniu tej krzywej, GORM może być bardzo produktywny — mniej czasu spędzisz na pisaniu powtarzalnego SQL i więcej w logice Go. W rzeczywistości, po opanowaniu go, wielu znajduje, że mogą rozwijać funkcje szybciej z GORM niż z niższowarstwowych bibliotek.
    Krótko mówiąc, doświadczenie dewelopera z GORM: wysoka początkowa złożoność, ale z czasem się stabilizuje. Duża społeczność oznacza wiele przykładów i odpowiedzi na StackOverflow, a bogaty ekosystem wtyczek może dalej uprościć zadania (np. wtyczki do usuwania danych, audytu itp.). Głównym zastrzeżeniem jest to, że musisz być świadomy tego, co GORM robi pod spodem, aby uniknąć pułapek (np. przypadkowe N+1 zapytania). Ale dla dewelopera, który inwestuje czas w GORM, rzeczywiście “czuje się” jak potężne, zintegrowane rozwiązanie, które wiele robi za Ciebie.

  • Ent – oparty na schemacie i bezpieczny dla typów: Ent został jawnie zaprojektowany, aby poprawić doświadczenie dewelopera w ORM. Opisujesz swój schemat w jednym miejscu (kod Go) i otrzymujesz silnie typowany API do pracy — to oznacza brak zapytań typu string, a wiele błędów jest wykrywanych w czasie kompilacji. Na przykład, jeśli spróbujesz odwołać się do pola lub krawędzi, która nie istnieje, Twój kod po prostu nie skompiluje się. To bezpieczeństwo to ogromny plus w doświadczeniu dewelopera dla dużych projektów. API Ent do budowania zapytań jest płynne i intuicyjne: łańcuch metod takich jak .Where(user.EmailEQ("alice@example.com")) czyta się prawie jak angielski. Deweloperzy z ORM w innych językach często znajdują podejście Ent naturalne (jest to trochę podobne do Entity Framework lub Prisma, ale w Go). Nauczenie się Ent wymaga zrozumienia jego procesu generowania kodu. Musisz uruchomić entc generate (lub użyć go generate), kiedykolwiek zmienisz swój schemat. To dodatkowy krok, ale zwykle częścią procesu budowania. Krzywa uczenia się Ent jest umiarkowana — jeśli znasz Go i podstawy SQL, koncepcje Ent (Pola, Krawędzie itp.) są proste. W rzeczywistości, wielu znajduje Ent łatwiejszym do zrozumienia niż GORM, ponieważ nie musisz się zastanawiać, jaki SQL jest generowany; często możesz przewidzieć to z nazw metod, a jeśli potrzebujesz, możesz wypisać zapytanie do debugowania. Dokumentacja i przykłady Ent są bardzo dobre, a wspierane przez firmę gwarantuje, że jest aktywnie utrzymywane. W ogólności doświadczenie dewelopera z Ent: bardzo przyjazne dla tych, którzy chcą jasności i bezpieczeństwa. Kod, który piszesz, jest bardziej rozbudowany niż surowy SQL, ale samodokumentujący się. Refaktoryzacja jest bezpieczniejsza (zmiana nazwy pola w schemacie i ponowne generowanie zaktualizuje wszystkie użycia w zapytaniach). Wadą może być to, że jesteś pewnym stopniem ograniczony przez to, co oferuje generowanie kodu Ent — jeśli potrzebujesz bardzo niestandardowego zapytania, możesz albo napisać je za pomocą API Ent (co obsługuje złożone łączenia, ale czasami znajdziesz przypadek, który łatwiej byłby napisać w surowym SQL). Ent pozwala na fragmenty surowego SQL, kiedy to konieczne, ale jeśli często się do tego odnosisz, możesz zapytać, czy ORM jest właściwym wyborem. Podsumowując, Ent oferuje gładkie doświadczenie dewelopera dla większości logiki CRUD i zapytań z minimalnymi niespodziankami, o ile jesteś zgodny z uruchamianiem kroku generowania kodu i przestrzeganiem wzorców, które Ent wymusza.

  • Bun – bliższy SQL, mniej magii: Użycie Bun czuje się inaczej niż użycie GORM lub Ent. Filozofia Bun to nie ukrywanie SQL — jeśli znasz SQL, już znasz większość sposobów użycia Bun. Na przykład, aby zapytać użytkowników, możesz napisać: db.NewSelect().Model(&users).Where("name = ?", name).Scan(ctx). To jest płynne API, ale bardzo bliskie strukturze rzeczywistego instrukcji SELECT. Krzywa uczenia się Bun to ogólnie niska, jeśli jesteś komfortowy z samym SQL.
    Mniej “magii ORM” do nauki; głównie uczysz się nazw metod do budowania zapytań i konwencji tagów struktury. To czyni Bun bardzo dostępny dla doświadczonych deweloperów, którzy używali database/sql lub innych budowniczych zapytań, takich jak sqlx. W przeciwieństwie do początkującego, który nie jest pewny, jak pisać SQL, może znaleźć Bun mniej pomocny niż GORM — Bun nie generuje automatycznie złożonych zapytań za Ciebie za pośrednictwem relacji, chyba że je określiłeś. Jednak Bun tak obsługuje relacje i ładowanie wsteczne (Relation() metodę do łączenia tabel) — tylko robi to jawnie, co niektórzy deweloperzy preferują dla przejrzystości. Doświadczenie dewelopera z Bun można opisać jako lekkie i przewidywalne. Piszesz nieco więcej kodu niż z GORM dla niektórych operacji (ponieważ Bun często prosi Cię, aby jawnie określić kolumny lub łączenia), ale w zamian masz większą kontrolę i przejrzystość. Ma minimalną magię wewnętrzna, więc debugowanie jest łatwiejsze (zwykle możesz zalogować łańcuch zapytań, który zbudował Bun). Dokumentacja Bun (przewodnik uptrace.dev) jest szczegółowa i zawiera wzorce dla migracji, transakcji itp., choć mniejsza społeczność oznacza mniej tutoriali trzecich stron. Innym aspektem doświadczenia dewelopera jest dostępność narzędzi: Bun, będąc rozszerzeniem database/sql, oznacza, że każde narzędzie działające z sql.DB (np. proxy debugowania, logi zapytań) działa łatwo z Bun. Podsumowując, Bun oferuje bezpieczne doświadczenie: czuje się jak pisanie strukturalnego SQL w Go. To świetne dla deweloperów, którzy cenią kontrolę i wydajność, ale może nie pomagać tak bardzo mniej doświadczonym deweloperom, jak coś takiego jak GORM. Konsensus to, że Bun łatwia proste rzeczy i umożliwia złożone (jak surowy SQL), bez nakładania wiele ramów na Ciebie.

  • sqlc – pisz SQL, otrzymuj kod Go: podejście sqlc odwraca typowe narracje ORM. Zamiast pisać kod Go, który generuje SQL, piszesz SQL i otrzymujesz kod Go. Dla deweloperów, którzy lubią SQL, to fantastyczne — możesz użyć całej mocy SQL (złożone łączenia, CTE, funkcje okien itp.) z zero ograniczeń ORM. Krzywa uczenia się sqlc sama w sobie jest bardzo mała. Jeśli wiesz, jak napisać SELECT/INSERT/UPDATE w SQL, już masz trudną część za sobą. Musisz jednak nauczyć się, jak adnotować zapytania komentarzami -- name: Name :one/many/exec i ustawić plik konfiguracyjny, ale to trywialne.
    Wygenerowany kod będzie prostymi definicjami funkcji, które wywołujesz jak każdą funkcję Go. Zalety doświadczenia dewelopera: nie ma API ORM do nauki, nie ma niespodzianek — zapytania wykonują się dokładnie tak, jak zostały napisane. Unikasz całej klasy problemów z ORM (np. ustalanie, dlaczego ORM wygenerował pewne łączenie lub jak dostosować zapytanie). Ponadto, Twoje recenzje kodu mogą obejmować recenzowanie samego SQL, co często jest bardziej przejrzyste dla złożonej logiki. Inna duża zaleta doświadczenia dewelopera: bezpieczeństwo typów i wsparcie IDE — wygenerowane metody i struktury można przeskakiwać w edytorze, narzędzia do refaktoryzacji działają na nich itp., w przeciwieństwie do surowych zapytań w formacie ciągu, które są nieprzejrzyste dla IDE. Na minusie, sqlc wymaga od Ciebie zarządzania skryptami SQL. Oznacza to, że jeśli Twój schemat lub wymagania się zmienią, musisz ręcznie aktualizować lub dodawać odpowiednie SQL i ponownie uruchomić generowanie kodu. Nie jest trudne, ale to więcej ręcznej pracy niż po prostu wywołanie metody ORM. Ponadto, dynamiczne zapytania (gdzie części SQL są warunkowe) mogą być uciążliwe — albo piszesz wiele wariantów SQL albo używasz sztuczek składni SQL. Niektórzy deweloperzy wspominają, że to ograniczenie podejścia sqlc.
    W praktyce, często możesz strukturalnie zaprojektować dostęp do danych tak, że nie potrzebujesz zbyt dynamicznego SQL, albo możesz wywołać surowy database/sql dla tych przypadków. Ale to jest rozważenie: sqlc jest świetny dla dobrze zdefiniowanych zapytań, mniej dla budowania zapytań ad-hoc. Podsumowując, dla dewelopera, który jest biegły w SQL, użycie sqlc czuje się naturalnie i bardzo wydajnie. Ma bardzo mało nowego do nauki, a usuwa powtarzalny boilerplate Go. Dla dewelopera, który nie jest tak pewny SQL, sqlc może być początkowo wolniejszy do pracy (w porównaniu do ORM, który np. automatycznie generuje zapytania dla podstawowych CRUD). Jednak wielu deweloperów Go uzna sqlc za konieczność, ponieważ trafia w idealny punkt: kontrola ręczna z wysokim bezpieczeństwem i bez kosztu czasowego w czasie wykonywania.

Popularność i wsparcie ekosystemu

Adopcja i wsparcie społeczności mogą wpłynąć na Twoje wyboru – popularna biblioteka oznacza większą liczbę wkładów społeczności, lepsze utrzymanie, a także więcej zasobów do nauki.

  • GORM: Jako najstarsza i najbardziej dojrzała z czterech, GORM ma znacznie największą bazę użytkowników i ekosystem. Obecnie jest najbardziej gwiazdowana biblioteka ORM w Go na GitHub (ponad 38k gwiazdek) i jest używana w niezliczonych projektach produkcyjnych. Właściciele projektu są aktywni, a projekt regularnie aktualizowany (GORM v2 był znacznym przebudowaniem poprawiającym wydajność i architekturę). Dużą zaletą popularności GORM jest duża liczba rozszerzeń i integracji. Istnieją oficjalne i nieoficjalne wtyczki dla takich rzeczy jak sterowniki bazy danych (np. dla PostgreSQL, MySQL, SQLite, SQL Server, ClickHouse), gotowe do użycia. GORM obsługuje szeroki zakres przypadków użycia: migracje, automatyczne generowanie schematu, usuwanie miękkie, pola JSON (z gorm.io/datatypes), pełnotekstowe wyszukiwanie, itp., często poprzez wbudowane funkcje lub dodatki. Społeczność stworzyła różne narzędzia takie jak gormt (do generowania definicji struktur z istniejącej bazy danych), oraz wiele tutoriów i przykładów. Jeśli natkniesz się na problem, szybki wyszukiwarka prawdopodobnie znajdzie problem lub pytanie na Stack Overflow zadane przez kogoś innego. Podsumowanie ekosystemu: GORM jest bardzo dobrze wspierany. Jest to “domyślny” wybór ORM dla wielu, co oznacza, że znajdziesz go w frameworkach i szablonach. Odwrotna strona monety to to, że jego wielkość może sprawiać, że będzie się czuł ciężki, ale dla wielu to godna cena za głębokość społeczności.

  • Ent: Mimo że jest nowszy (otwarcie źródeł około 2019), Ent zyskał silną społeczność. Z około 16k gwiazdkami i wsparciem od Fundacji Linux, nie jest to projekt na marginesie. Duże firmy zaczęły używać Ent ze względu na jego zalety związane z schematami, a utrzymanie (przez Ariga) oferuje wsparcie dla firm krytycznych, co jest znakiem zaufania. Ekosystem wokół Ent rośnie: są rozszerzenia Ent (ent/go) dla rzeczy takich jak integracja OpenAPI/GraphQL, integracja gRPC, a nawet narzędzie do migracji SQL, które działa z schematami Ent. Ponieważ Ent generuje kod, niektóre wzorce ekosystemu są inne – na przykład, jeśli chcesz zintegrować się z GraphQL, możesz użyć generowania kodu Ent do tworzenia rozwiązywaczy GraphQL. Zasoby naukowe dla Ent są dobre (oficjalna dokumentacja i projekt przykładowy pokrywający prostą aplikację). Forum społecznościowe i dyskusje na GitHub są aktywne z pytaniami dotyczącymi projektowania schematów i wskazówkami. W kwestii wsparcia społeczności, Ent jest pewnie poza fazą “wcześniejszych adopterów” i jest uważany za gotowy do produkcji i niezawodny. Może nie mieć tylu odpowiedzi na Stack Overflow jak GORM, ale szybko nadąża. Jedną rzeczą, którą warto zauważyć: ponieważ Ent jest trochę bardziej opiniotwórczy (np. chce zarządzać schematem), prawdopodobnie używasz go samodzielnie, a nie razem z innym ORM. Nie ma “wtyczek” w tym samym sensie jak GORM, ale możesz napisać własne szablony, aby rozszerzyć generowanie kodu lub włączyć się do zdarzeń cyklu życia (Ent ma wsparcie dla hooków/middleware dla wygenerowanych klientów). Wsparcie od fundacji wskazuje na długoterminowe wsparcie, więc wybór Ent to bezpieczna opcja, jeśli jego model pasuje do Twoich potrzeb.

  • Bun: Bun (część otwartej wersji Uptrace) zyskuje popularność, szczególnie wśród tych, którzy byli fanami teraz nieobsługiwanej biblioteki go-pg. Z około 4,3k gwiazdkami, jest to najmniejsza społeczność w tej porównawczych, ale to bardzo aktywny projekt. Utrzymanie jest reaktywne i szybko dodaje funkcje. Społeczność Bun jest entuzjastyczna co do jego wydajności. Znajdziesz dyskusje na forach Go i Reddit od deweloperów, którzy przechodzili na Bun z powodu szybkości. Jednak ponieważ baza użytkowników jest mniejsza, czasami nie znajdziesz odpowiedzi na pytania o niszowe tematy – czasami musisz przeczytać dokumentację/źródła lub zadać pytanie w Bun GitHub lub Discord (Uptrace oferuje chat społecznościowy). Ekosystem rozszerzeń jest bardziej ograniczony: Bun ma własną bibliotekę migracji, ładowarkę fixture, a także łączy się z narzędziami obserwacji Uptrace, ale nie znajdziesz liczby wtyczek, jaką ma GORM. Mimo to, Bun jest kompatybilny z sql.DB, więc możesz mieszać i dopasowywać – na przykład, używając github.com/jackc/pgx jako sterownik pod spodem lub integracji z innymi pakietami, które oczekują *sql.DB. Bun nie zamyka Cię w żadnym miejscu. W kwestii wsparcia, bycie nowsze oznacza, że dokumentacja jest aktualna, a przykłady są nowoczesne (często pokazują użycie z kontekstem itp.). Oficjalne dokumenty porównują Bun bezpośrednio z GORM i Ent, co jest pomocne. Jeśli rozmiar społeczności jest problemem, jedną strategią mogłoby być przyjęcie Bun za jego zalety, ale utrzymanie jego użycia na stosunkowo niskim poziomie (np. możesz zamienić go na inne rozwiązanie, jeśli będzie potrzebne, ponieważ nie nakłada ciężkiego abstrakcji). W każdym razie, trajektoria Bun jest w górę, a wypełnia ona konkretny niszowy obszar (ORM skupiony na wydajności), co daje mu siłę utrzymania.

  • sqlc: sqlc jest bardzo popularny w społeczności Go, co potwierdzają około 15,9k gwiazdki i wielu zwolenników, szczególnie w kręgach skupionych na wydajności. Często jest rekomendowany w dyskusjach o “unikaniu ORM”, ponieważ osiąga dobre pośrednie równowagi. Narzędzie jest utrzymywane przez współpracowników (w tym oryginalnego autora, który jest aktywny w jego poprawianiu). Będąc bardziej kompilatorem niż biblioteką uruchomieniową, jego ekosystem opiera się na integracjach: na przykład, edytory/IDE mogą mieć podświetlenie składni dla plików .sql i uruchamiasz sqlc generate jako część swojego budowania lub potoku CI pipeline. Społeczność stworzyła szablony i przykładowe repozytoria, jak organizować kod z sqlc (często łącząc je z narzędziem do migracji, takim jak Flyway lub Golang-Migrate, dla wersjonowania schematu, ponieważ sqlc sam nie zarządza schematem). Istnieje oficjalny Slack/Discord dla sqlc, gdzie możesz zadać pytania, a problemy na GitHub są zwykle zauważane. Wiele typowych wzorców (jak obsługa wartości nullowych lub pól JSON) są dokumentowane w sqlc docs lub mają posty społecznościowe. Jedną rzeczą, którą warto zaznaczyć: sqlc nie jest specyficzny dla Go – może generować kod w innych językach (takich jak TypeScript, Python). To rozszerza jego społeczność poza Go, ale w Go konkretnie, jest szeroko szanowany. Jeśli wybierzesz sqlc, jesteś w dobrych rękach: jest używany w produkcji przez wiele startupów i dużych firm (zgodnie z pokazami społeczności i sponsorach wymienionych w repozytorium). Kluczowe rozważanie ekosystemu to to, że sqlc nie oferuje funkcji uruchomieniowych, takich jak ORM, więc możesz potrzebować innych bibliotek do rzeczy takich jak transakcje (choć możesz łatwo używać sql.Tx z sqlc) lub może lekkiego wrappera DAL. W praktyce, większość używa sqlc wraz z biblioteką standardową (dla transakcji, anulowania kontekstu itp., używasz bezpośrednio idiomów database/sql w swoim kodzie). To oznacza mniejsze zablokowanie – odjazd od sqlc oznaczałby napisanie własnej warstwy danych, co jest tak trudne jak napisanie SQL (którego już zrobiłeś). W ogólności, wsparcie i społeczność sqlc są silne, a wielu rekomenduje go jako “muszycy” dla projektów Go interakcji z bazami danych SQL z powodu jego prostoty i niezawodności.

Zestaw funkcji i rozszerzalność

Każde z tych narzędzi oferuje inną grupę funkcji. Oto porównanie ich możliwości i jak są one rozszerzalne w zaawansowanych przypadkach użycia:

  • Funkcje GORM: GORM ma na celu być pełnoprawnym ORM. Lista jego funkcji jest bardzo szeroka:
  • Wiele baz danych: Pierwszorzędną obsługę PostgreSQL, MySQL, SQLite, SQL Server i więcej. Przełączanie baz danych jest zwykle tak samo łatwe jak zmiana sterownika połączenia.
  • Migracje: Możesz automatycznie migrować swój schemat z modeli (db.AutoMigrate(&User{}) utworzy lub zmodyfikuje tabelę users). Choć wygodne, ostrożnie używaj migracji automatycznej w produkcji – wielu jej używa w środowisku deweloperskim, a migracje w produkcji są bardziej kontrolowane.
  • Relacje: Tagi struktury GORM (gorm:"foreignKey:...,references:...") pozwalają zdefiniować jedno do wielu, wielu do wielu itp. Może obsługiwać tabele łączące dla wielu do wielu i ma Preload do szybkiego ładowania relacji. GORM domyślnie ładowa się opóźnionie (tj. oddzielne zapytania), ale możesz użyć Preload lub Joins, aby dostosować to. Nowsze wersje mają również API oparte na generics dla łatwiejszych zapytań związanych.
  • Transakcje: GORM ma łatwą do użycia obudowę transakcji (db.Transaction(func(tx *gorm.DB) error { ... })) i nawet wsparcie dla zagnieżdżonych transakcji (punkty zapisu).
  • Hooki i callbacki: Możesz zdefiniować metody takie jak BeforeCreate, AfterUpdate na strukturach modelu, lub zarejestrować globalne callbacki, które GORM wywoła w określonych zdarzeniach cyklu życia. To świetnie nadaje się do rzeczy takich jak automatyczne ustawianie czasów lub zachowania usuwania miękkiego.
  • Rozszerzalność: System wtyczek GORM (gorm.Plugin interface) pozwala na rozszerzanie jego funkcjonalności. Przykłady: pakiet gorm-gen generuje bezpieczne pod względem typów metody zapytań (jeśli preferujesz sprawdzanie zapytań w czasie kompilacji), lub wtyczki społecznościowe do audytu, wielodostępności itp. Możesz również cofnąć się do surowego SQL w dowolnym momencie za pomocą db.Raw("SELECT ...", params).Scan(&result).
  • Inne przyjemności: GORM obsługuje klucze główne złożone, osadzanie modeli, relacje polymorficzne, a nawet rozdzielacz schematu do użycia wielu baz danych (dla replik odczytu, fragmentacji itp.).

Wszystkim razem, GORM jest bardzo rozszerzalny i bogaty w funkcje. Prawie każda funkcja ORM, której możesz chcieć, ma albo wbudowany mechanizm, albo opisany wzorzec w GORM. Kosztem tej szerokości jest złożoność i pewna sztywność (często musisz przystosować się do sposobu działania GORM). Ale jeśli potrzebujesz jednoznacznego rozwiązania, GORM dostarcza.

  • Funkcje Ent: Filozofia Ent opiera się na schemacie jako kodzie. Kluczowe funkcje obejmują:
  • Definicja schematu: Możesz zdefiniować pola z ograniczeniami (unikalne, wartości domyślne, enumy itp.), a także krawędzie (relacje) z kardynalnością (jedno do wielu itp.). Ent używa tego do generowania kodu i może również generować SQL migracji dla Ciebie (istnieje komponent ent/migrate, który może generować różnicowe SQL między Twoim bieżącym schematem a schematem docelowym).
  • Bezpieczeństwo typów i walidacja: Ponieważ pola są silnie typowane, nie możesz przypadkowo ustawić pola liczbowego na ciąg znaków. Ent również pozwala na własne typy pól (np. możesz zintegrować się z sql.Scanner/driver.Valuer dla pól JSONB lub innych złożonych typów).
  • Konstruktor zapytań: Wygenerowany interfejs zapytań Ent obejmuje większość konstrukcji SQL: możesz wykonywać wybory, filtry, sortowanie, ograniczenia, agregacje, łączenia po krawędziach, a nawet podzapytania. Jest wyrażony – np. możesz napisać client.User.Query().WithOrders(func(q *ent.OrderQuery) { q.Limit(5) }).Where(user.StatusEQ(user.StatusActive)).All(ctx) aby uzyskać aktywnych użytkowników z ich pierwszymi 5 zamówieniami, w jednym ruchu.
  • Transakcje: Klient Ent obsługuje transakcje, eksponując wersję transakcyjną klienta (przez tx, err := client.Tx(ctx), która daje ent.Tx, który możesz użyć do wykonania wielu operacji i następnie potwierdzić lub wycofać).
  • Hooki i middleware: Ent pozwala na rejestrowanie hooków na operacjach tworzenia/aktualizowania/usuwania – są to jak拦截器, gdzie możesz np. automatycznie wypełnić pole lub zastosować własne reguły. Istnieje również middleware dla klienta, aby opakować operacje (przydatne do logowania, instrumentacji).
  • Rozszerzalność: Choć Ent nie ma “wtyczek” per se, jego generowanie kodu jest szablonowane i możesz napisać własne szablony, jeśli potrzebujesz rozszerzenia wygenerowanego kodu. Wiele zaawansowanych funkcji została zaimplementowana w ten sposób: na przykład, integracja z OpenTelemetry do śledzenia wywołań bazy danych, lub generowanie rozwiązywaczy GraphQL z schematu Ent, itp. Ent również pozwala na mieszanie surowego SQL, kiedy to konieczne, przez pakiet entsql lub pobierając sterownik podstawowy. Możliwość generowania dodatkowego kodu oznacza, że zespoły mogą używać Ent jako podstawy i warstwy własnych wzorców, jeśli to konieczne.
  • Integracja GraphQL/REST: Dużą zaletą podejścia Ent opartego na grafie jest to, że dobrze nadaje się do GraphQL – Twój schemat Ent może być prawie bezpośrednio mapowany na schemat GraphQL. Istnieją narzędzia, które automatyzują wiele z tego. Może to być wygoda produkcyjna, jeśli tworzysz serwer API.
  • Optymalizacje wydajności: Ent, na przykład, może ładować w sposób zbiorczy krawędzie powiązane, aby uniknąć N+1 zapytań (ma API ładowania wstępnego .With<EdgeName>()). Wewnętrznie będzie używał JOINów lub dodatkowych zapytań w zoptymalizowany sposób. Ponadto, można włączyć buforowanie wyników zapytań (w pamięci), aby uniknąć uderzania w bazę danych dla identycznych zapytań w krótkim czasie.

Podsumowując, zestaw funkcji Ent jest skierowany ku dużym, utrzymywalnym projektom. Może brakować niektórych z domyślnych korzyści GORM (np. automatycznych migracji schematu GORM vs generowanych skryptów migracji Ent – Ent wybiera, aby oddzielić tę kwestię), ale nadaje się w zamian za potężne narzędzia deweloperskie i bezpieczeństwo typów. Rozszerzalność w Ent polega na generowaniu tego, czego potrzebujesz – jest bardzo elastyczna, jeśli jesteś gotowy, aby zanurzyć się w tym, jak działa entc (generator kodu).

  • Funkcje Bun: Bun skupia się na byciu potężnym narzędziem dla tych, którzy znają SQL. Niektóre funkcje i punkty rozszerzalności:
  • Konstruktor zapytań: W centrum Bun znajduje się płynny konstruktor zapytań, który obsługuje większość konstrukcji SQL (SELECT z łączeniem, CTE, podzapytania, oraz INSERT, UPDATE z wsparciem dla operacji masowych). Możesz rozpocząć zapytanie i głęboko je dostosować, nawet wstrzykując surowy SQL (.Where("some_condition (?)", value)).
  • Funkcje specyficzne dla PostgreSQL: Bun ma wbudowaną obsługę typów tablic Postgres, JSON/JSONB (mapowanie do []<type> lub map[string]interface{} lub typów niestandardowych), a także obsługuje skanowanie do typów złożonych. Na przykład, jeśli masz kolumnę JSONB, możesz ją mapować do struktury Go z tagami json:, a Bun obsłuży dla Ciebie serializację. To jest zaleta w porównaniu do niektórych ORM, które traktują JSONB jako nieprzejrzysty.
  • Relacje/Ładowanie wsteczne: Jak pokazano wcześniej, Bun pozwala zdefiniować tagi struktury dla relacji, a następnie możesz użyć .Relation("FieldName") w zapytaniach, aby łączyć i ładować powiązane jednostki. Domyślnie, .Relation używa LEFT JOIN (możesz symulować wewnętrzne łączenia lub inne typy dodając warunki). To daje Ci subtelny kontrolę nad tym, jak są pobierane dane powiązane (w jednym zapytaniu vs wielu). Jeśli wolisz ręczne zapytania, zawsze możesz napisać łączenie w konstruktorze SQL Bun bezpośrednio. W przeciwieństwie do GORM, Bun nigdy nie ładowa relacji automatycznie, chyba że poprosisz, co unika przypadkowych problemów N+1.
  • Migracje: Bun oferuje oddzielne narzędzie do migracji (w github.com/uptrace/bun/migrate). Migracje są zdefiniowane w Go (lub jako ciągi SQL) i wersjonowane. Nie jest tak automatyczne jak auto-migracja GORM, ale jest solidne i dobrze integruje się z biblioteką. To świetne do utrzymania jawnych zmian schematu.
  • Rozszerzalność: Bun jest zbudowany na interfejsach podobnych do database/sql – nawet opakowuje *sql.DB. Możesz więc używać dowolnego narzędzia na niższym poziomie (np. możesz wykonać zapytanie *pgx.Conn, jeśli to konieczne, i nadal skanować do modeli Bun). Bun pozwala na niestandardowe skanery wartości, więc jeśli masz złożony typ, który chcesz przechowywać, możesz zaimplementować sql.Scanner/driver.Valuer, a Bun go użyje. Aby rozszerzyć funkcjonalność Bun, ponieważ jest stosunkowo prosty, wielu ludzi po prostu pisze dodatkowe funkcje pomocnicze na podstawie API Bun w swoich projektach, zamiast oddzielnych wtyczek. Samo biblioteka ewoluuje, więc nowe funkcje (np. dodatkowe pomocniki zapytań lub integracje) są dodawane przez utrzymujących.
  • Inne funkcje: Bun obsługuje anulowanie kontekstu (wszystkie zapytania akceptują ctx), ma pulę połączeń przez database/sql (konfigurowalna tam), a także obsługuje buforowanie zapytań przygotowanych (przez sterownik, jeśli używasz pgx). Istnieje również przyjemna funkcja, w której Bun może skanować do wskaźników struktur lub prostych list łatwo (np. wybierając jedną kolumnę do []string bezpośrednio). Małe wygody takie jak te sprawiają, że Bun jest przyjemny dla tych, którzy nienawidzą powtarzalnego kodu skanowania.

Podsumowując, zestaw funkcji Bun pokrywa 90% potrzeb ORM, ale z naciskiem na przejrzystość i wydajność. Może nie mieć wszystkich funkcji (np. nie robi automatycznych aktualizacji schematu ani nie ma wzorca aktywnego rekordu), ale dostarcza bloków budujących, aby zaimplementować wszystko, czego potrzebujesz. Ponieważ jest młody, oczekuj, że jego zestaw funkcji będzie się dalej rozwijać, kierowany przez rzeczywiste przypadki użycia (utrzymujący często dodają funkcje, gdy użytkownicy je żądają).

  • Funkcje sqlc: “Funkcje” sqlc są bardzo różne, ponieważ to generator:
  • Pełna obsługa SQL: Największą funkcją jest po prostu to, że używasz własnych funkcji PostgreSQL. Jeśli Postgres obsługuje to, możesz to użyć w sqlc. To obejmuje CTE, funkcje okien, operatory JSON, zapytania przestrzenne (PostGIS) itp. Nie ma potrzeby, aby sama biblioteka implementowała coś specjalnego, aby użyć tych funkcji.
  • Mapowanie typów: sqlc jest mądry w mapowaniu typów SQL na typy Go. Obsługuje typy standardowe i pozwala skonfigurować lub rozszerzyć mapowania dla typów niestandardowych lub enumów. Na przykład, typ Postgres UUID może mapować się do typu github.com/google/uuid, jeśli tego chcesz, lub typ domeny może mapować się do podstawowego typu Go.
  • Obsługa wartości nullowych: Może generować sql.NullString lub wskaźniki dla kolumn nullowych, w zależności od Twojej preferencji, więc nie musisz walczyć z skanowaniem wartości nullowych.
  • Operacje wsadowe: Choć sqlc sam w sobie nie oferuje wysokiego poziomu API do operacji wsadowych, możesz pewnie napisać SQL do wsadowego wstawiania i wygenerować kod dla niego. Albo wywołać procedurę przechowywaną – to kolejna funkcja: ponieważ to SQL, możesz wykorzystać procedury przechowywane lub funkcje bazy danych, a sqlc wygeneruje wrapper w Go.
  • Zapytania wieloinstrukcyjne: Możesz wstawić wiele instrukcji SQL w jednym zapytaniu nazwanym, a sqlc wykona je w transakcji (jeśli sterownik to obsługuje), zwracając wyniki ostatniego zapytania lub tego, co określiłeś. To sposób, aby np. zrobić coś takiego jak “stwórz i następnie wybierz” w jednym wywołaniu.
  • Rozszerzalność: Będąc kompilatorem, rozszerzalność przychodzi w formie wtyczek dla nowych języków lub społecznościowych wкладów do obsługi nowych konstrukcji SQL. Na przykład, jeśli pojawi się nowy typ danych PostgreSQL, sqlc może zostać zaktualizowany, aby obsługiwał jego mapowanie. W Twojej aplikacji, możesz rozszerzyć sqlc, pisząc funkcje pomocnicze. Ponieważ wygenerowany kod jest pod Twoją kontrolą, możesz go zmodyfikować – choć normalnie nie robisz tego, tylko dostosowujesz SQL i ponownie generujesz. Jeśli to konieczne, zawsze możesz mieszać surowe wywołania database/sql z sqlc w swoim kodzie (nie ma konfliktu, ponieważ sqlc po prostu generuje pewien kod Go).
  • Minimalne zależności uruchomieniowe: Kod generowany przez sqlc zwykle zależy tylko od standardowego database/sql (i konkretnego sterownika, takiego jak pgx). Nie ma ciężkiego uruchomieniowego biblioteki; to tylko pewne typy pomocnicze. To oznacza zero nadmiarowości w produkcji z bazy danych – wszystka praca odbywa się w czasie kompilacji. Oznacza to również, że nie otrzymasz funkcji takich jak cache obiektów lub mapa identyfikatorów (jak niektóre ORMs), jeśli potrzebujesz cache, zaimplementujesz to w warstwie usług lub użyjesz osobnej biblioteki.

W praktyce, “funkcja” sqlc to to, że zwiększa ona przestrzeń między Twoją bazą danych SQL a Twoim kodem Go, bez dodawania niczego w międzyczasie. Jest to specjalistyczne narzędzie – nie robi migracji, nie śledzi stanu obiektu itp., zgodnie z projektem. Te kwestie pozostawia się innym narzędziami lub deweloperowi. To jest przyjemne, jeśli chcesz cienką warstwę danych, ale jeśli szukasz jednoznacznego rozwiązania, które obsługuje wszystko od schematu po zapytania po relacje w kodzie, sqlc sam nie jest – połączyłby się z innymi wzorcami lub narzędziami.

Podsumowując ten rozdział, GORM to silny zestaw funkcji i jasny wybór, jeśli potrzebujesz dojrzałego, rozszerzalnego ORM, który można dostosować za pomocą wtyczek. Ent oferuje nowoczesne, bezpieczne pod względem typów podejście do funkcji, priorytetyzując poprawność i utrzymanie (z takimi rzeczami jak generowanie kodu, hooki i integracje z warstwami API). Bun oferuje podstawy i opiera się na znajomości SQL, co ułatwia rozszerzenie, pisząc więcej SQL lub lekkich wrapperów. sqlc usuwa funkcje do podstaw – jest ono w zasadzie tak bogate w funkcje, jak samo SQL, ale wszystko poza tym (cache itp.) zależy od Ciebie, aby je dodać.

Przykład kodu: Operacje CRUD w każdej bibliotece ORM

Nic lepiej nie ilustruje różnic niż zobaczenie, jak każdy narzędzie obsługuje podstawowe operacje CRUD dla prostego modelu. Załóżmy, że mamy model User z polami ID, Name i Email. Poniżej znajdują się przykłady kodu obok siebie dla tworzenia, odczytywania, aktualizowania i usuwania użytkownika w każdej bibliotece, korzystając z PostgreSQL jako bazy danych.

Uwaga: W wszystkich przypadkach załóżmy, że mamy już nawiązane połączenie z bazą danych (np. db lub client), a obsługa błędów została pominięta dla uproszczenia. Celem jest porównanie API i złożoności każdego podejścia.

GORM (styl Active Record)

    // Definicja modelu
    type User struct {
        ID    uint   `gorm:"primaryKey"`
        Name  string
        Email string
    }

    // Tworzenie nowego użytkownika
    user := User{Name: "Alice", Email: "alice@example.com"}
    db.Create(&user)                      // INSERT INTO users (name,email) VALUES ('Alice','alice@example.com')

    // Odczyt (szukanie po kluczu głównym)
    var u User
    db.First(&u, user.ID)                // SELECT * FROM users WHERE id = X LIMIT 1

    // Aktualizacja (jedno pole)
    db.Model(&u).Update("Email", "alice_new@example.com")
    // (generuje: UPDATE users SET email='alice_new@example.com' WHERE id = X)

    // Usunięcie 
    db.Delete(&User{}, u.ID)             // DELETE FROM users WHERE id = X

Ent (Codegen, fluent API)

    // (Schemat Ent jest zdefiniowany gdzie indziej, a kod jest generowany. Załóżmy, że `client` to ent.Client)

    // Tworzenie nowego użytkownika
    u, err := client.User.
        Create().
        SetName("Alice").
        SetEmail("alice@example.com").
        Save(ctx)
    // SQL: INSERT INTO users (name, email) VALUES ('Alice','alice@example.com') RETURNING id, name, email

    // Odczyt (po ID)
    u2, err := client.User.Get(ctx, u.ID)
    // SQL: SELECT * FROM users WHERE id = X

    // Aktualizacja (jedno pole)
    u3, err := client.User.
        UpdateOneID(u.ID).
        SetEmail("alice_new@example.com").
        Save(ctx)
    // SQL: UPDATE users SET email='alice_new@example.com' WHERE id = X RETURNING id, name, email

    // Usunięcie 
    err = client.User.DeleteOneID(u.ID).Exec(ctx)
    // SQL: DELETE FROM users WHERE id = X

Bun

    // Definicja modelu
    type User struct {
        bun.BaseModel `bun:"table:users"` 
        ID   int64    `bun:",pk,autoincrement"`
        Name string   `bun:",notnull"`
        Email string  `bun:",unique,notnull"`
    }

    // Tworzenie nowego użytkownika
    user := &User{Name: "Alice", Email: "alice@example.com"}
    _, err := db.NewInsert().Model(user).Exec(ctx)
    // INSERT INTO users (name, email) VALUES ('Alice','alice@example.com')

    // Odczyt (szukanie po ID)
    var u User
    err = db.NewSelect().Model(&u).
          Where("id = ?", user.ID).
          Scan(ctx)
    // SELECT * FROM users WHERE id = X

    // Aktualizacja (jedno pole)
    _, err = db.NewUpdate().Model(&u).
          Set("email = ?", "alice_new@example.com").
          Where("id = ?", u.ID).
          Exec(ctx)
    // UPDATE users SET email='alice_new@example.com' WHERE id = X

    // Usunięcie 
    _, err = db.NewDelete().Model((*User)(nil)).
          Where("id = ?", u.ID).
          Exec(ctx)
    // DELETE FROM users WHERE id = X

sqlc


    // Załóżmy, że napisaliśmy SQL w plikach, a sqlc wygenerował strukturę Queries z metodami.
    queries := New(db)  // New przyjmuje *sql.DB (lub pgx.Conn) i zwraca wygenerowane Queries

    // Tworzenie nowego użytkownika (wygenerowana metoda wykonuje INSERT i skanuje wynik)
    newUser, err := queries.CreateUser(ctx, "Alice", "alice@example.com")
    // SQL w queries.sql -> INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email

    // Odczyt (po ID)
    user, err := queries.GetUser(ctx, newUser.ID)
    // SQL -> SELECT id, name, email FROM users WHERE id = $1

    // Aktualizacja (e-mail po ID)
    updatedUser, err := queries.UpdateUserEmail(ctx, newUser.ID, "alice_new@example.com")
    // SQL -> UPDATE users SET email=$2 WHERE id = $1 RETURNING id, name, email

    // Usunięcie 
    err = queries.DeleteUser(ctx, newUser.ID)
    // SQL -> DELETE FROM users WHERE id = $1

Jak pokazuje powyższy kod, każdy z podejść ma inny charakter:

  • GORM korzysta z metod struktury i fluent chaining na db.Model(&obj) lub bezpośrednio na obiekcie db. Wypełnia strukturę wartościami zwróconymi (np. po Create, user.ID jest ustawiony). Ukrywa również szczegóły SQL przez projekt – zazwyczaj nie widzisz zapytania, chyba że debugujesz.

  • Ent korzysta z wygenerowanego fluent API. Zwróć uwagę, jak metody takie jak Create().SetX().Save(ctx) lub UpdateOneID(id).SetX().Save(ctx) wyraźnie oddzielają fazę budowania od fazy wykonania. Ent zwraca obiekty typu ent.Type (które odpowiadają wierszom) i błędy, podobnie jak zapytanie SQL zwraca wyniki lub błąd.

  • Bun wymaga bardziej jawnej specyfikacji (np. użycie Set("email = ?", ...) do aktualizacji), co jest bardzo podobne do pisania SQL, ale z użyciem składni Go. Po wstawieniu, struktura user nie jest automatycznie wypełniana nowym ID, chyba że dodasz klauzulę RETURNING (Bun obsługuje .Returning() jeśli jest potrzebne). Przykład powyżej zachowuje rzeczy proste.

  • sqlc wygląda jak wywoływanie dowolnej funkcji Go. Wywołujemy queries.CreateUser, itp., a pod spodem to są wykonywane przygotowane instrukcje. SQL jest pisany w plikach zewnętrznych, więc choć nie widzimy go w kodzie Go, mamy pełną kontrolę nad nim. Zwracane obiekty (np. newUser) to zwykłe struktury Go wygenerowane przez sqlc, które modelują dane.

Można zauważyć różnice w liczbie znaków (GORM jest bardzo skrótowy; Bun i sqlc wymagają więcej pisania w kodzie lub SQL) oraz różnice w stylu (Ent i GORM oferują wyższy poziom abstrakcji, podczas gdy Bun i sqlc są bliższe surowym zapytaniami). W zależności od Twoich preferencji, możesz preferować jawność nad skrótowym zapisem lub odwrotnie.


TL;DR

Wybór “prawidłowej” biblioteki ORM lub bazy danych w Go zależy od potrzeb Twojej aplikacji i preferencji Twojej drużyny:

  • GORM to świetny wybór, jeśli chcesz użyć sprawdzonego ORM, które wiele dla Ciebie robi. Świetnie sprawdza się przy szybkim tworzeniu aplikacji CRUD, gdzie wygoda i bogata funkcjonalność są ważniejsze niż maksymalna wydajność. Wsparcie społeczności i dokumentacja są bardzo dobre, co może ułatwić przezwyciężenie trudnych momentów nauki. Uważaj, by odpowiednio używać jego funkcji (np. użyj Preload lub Joins, aby uniknąć pułapek ładowania opóźnionego) i oczekuj pewnego narzutu. W zamian otrzymujesz produktywność i jedno miejsce, gdzie znajdziesz rozwiązanie większości problemów. Jeśli potrzebujesz rzeczy takich jak wsparcie dla wielu baz danych lub rozbudowaną ekosystemę wtyczek, GORM ma na to odpowiedź.

  • Ent odpowiada tym, którzy priorytetyzują bezpieczeństwo typów, przejrzystość i utrzymanie kodu. Jest dobrze dopasowany do dużych baz kodu, gdzie zmiany schematu są częste, a chcesz, by kompilator pomagał Ci wykrywać błędy. Ent może wymagać więcej projektowania na wstępie (definiowanie schematów, uruchamianie generowania), ale zapłaci się to, gdy projekt rośnie – Twój kod pozostanie solidny i łatwy do refaktoryzacji. W zakresie wydajności, może obsługiwać ciężkie obciążenia i złożone zapytania skutecznie, często lepiej niż ORMs stylu active-record, dzięki zoptymalizowanej generacji SQL i cache’owi. Ent jest trochę mniej “plug-and-play” dla szybkich skryptów, ale dla długotrwałych usług oferuje solidną, skalowalną podstawę. Jego nowoczesne podejście (i aktywne rozwijanie) czyni z niego przyszłościowy wybór.

  • Bun to idealny wybór dla deweloperów, którzy mówią: „Znam SQL i chcę tylko lekkiego pomocy wokół niego”. Odrzuca pewne magie, aby dać Ci kontrolę i szybkość. Jeśli tworzysz usługę wrażliwą na wydajność i nie boisz się być jawny w swoim kodzie dostępu do danych, Bun to bardzo przekonujący wybór. Jest też dobrym pośrednikiem, jeśli migrujesz z surowego database/sql+sqlx i chcesz dodać strukturę bez poświęcania zbyt wielu efektywności. W zamian za to, mniejsza społeczność i mniej wysokopoziomowych abstrakcji – napiszesz trochę więcej kodu ręcznie. Ale jak mówi dokumentacja Bun, nie przeszkadza Ci w pracy. Używaj Bun, jeśli chcesz ORM, które czuje się jak bezpośrednie użycie SQL, szczególnie w projektach skupionych na PostgreSQL, gdzie możesz wykorzystać funkcje specyficzne dla bazy danych.

  • sqlc jest w kategorii samej siebie. To idealne rozwiązanie dla zespołu Go, który mówi: „nie chcemy ORM, chcemy gwarancje kompilacji dla naszego SQL”. Jeśli masz silne umiejętności w SQL i preferujesz zarządzanie zapytaniami i schematami w SQL (może masz administratorów baz danych lub po prostu lubisz tworzenie wydajnego SQL), sqlc prawdopodobnie zwiększy Twoją produktywność i pewność siebie. Wydajność jest praktycznie optymalna, ponieważ nic nie stoi między Twoim zapytaniem a bazą danych. Jedyne powody, by nie używać sqlc, to jeśli naprawdę nie lubisz pisać SQL lub jeśli Twoje zapytania są tak dynamiczne, że pisanie wielu wersji staje się obciążające. Nawet wtedy możesz użyć sqlc dla większości zapytań statycznych i obsłużyć kilka dynamicznych przypadków innym sposobem. Sqlc również dobrze współgra z innymi – nie wyklucza użycia ORM w częściach projektu (np. niektóre projekty używają GORM do prostych rzeczy i sqlc do krytycznych ścieżek). Krótko mówiąc, wybierz sqlc, jeśli wartościsz jawność, zero narzutu i bezpieczny SQL w kategorii typów – to potężne narzędzie do posiadania.

W końcu warto zauważyć, że te narzędzia nie są wzajemnie wykluczające się w ekosystemie. Każde ma swoje zalety i wady, a w duchu praktycznego Go wiele zespołów ocenia trade-offy przypadkowo. Nie jest rzadko, by zaczynać od ORM takiego jak GORM lub Ent dla szybkości tworzenia, a następnie używać sqlc lub Bun dla konkretnych ścieżek, które wymagają maksymalnej wydajności. Wszystkie cztery rozwiązania są aktywnie utrzymywane i szeroko wykorzystywane, więc nie ma „zły” wyboru – chodzi o odpowiedni wybór dla Twojej sytuacji. Mam nadzieję, że to porównanie dał Ci jasniejszy obraz tego, jak GORM, Ent, Bun i sqlc się różnią, i pomoże Ci podjąć świadome decyzje dla Twojego następnego projektu w Go.

Przydatne linki