Blog

Drizzle ORM, Neon Postgres, and typed publication state

Notatki inżynieryjne Naly: Drizzle ORM, Neon Postgres i typowany stan publikacji

Warstwę publikacji treści Naly można uczynić deterministyczną, zapisując każdy krok workflow w Neon Postgres za pomocą schematów Drizzle ORM. To podejście przenosi kontrolę publikacji ze stanów trzymanych w pamięci na typowane rekordy, które przetrwają ponowienia, awarie i redeploye. Dobrze współgra to z wykonaniem serverless w Next.js.

June 26, 20268 sources

Abstrakt

Ten stos daje Naly typową płaszczyznę kontroli publikacji: Drizzle ORM zapisuje artykuły, prognozy, źródła, posty społecznościowe, nagrody i rekordy cron jako encje relacyjne w Neon Postgres, zamiast rozproszonego stanu runtime. Ponieważ każdy krok workflow jest zapisywany jako jawne wiersze i enumy stanów, Naly może bezpiecznie ponownie uruchamiać potoki cron, odzyskiwać się po błędach częściowych i utrzymywać dane interfejsu edytora zgodne ze stanem widocznym w API. Na dzień 26 czerwca 2026 r. jest to operacyjnie trwały kontrakt projektu dla publikowania prognoz.

Gdzie to jest w Naly

W obecnym stosie zachowanie publikacji jest współdzielone między kilkoma ścieżkami aplikacji Next.js i zadaniami cron, które wszystkie odwołują się do tego samego kontraktu bazy danych. Zestaw pakietów, który jest już wykorzystywany—next@16.0.7, react@19.2.1, drizzle-orm@^0.44.7, @neondatabase/serverless@^1.0.2, tsx@^4.21.0, typescript@^5.9.3—odpowiada warstwie ORM o zbliżonej do serverful naturze działającej wewnątrz tras/akcji po stronie serwera i zaplanowanych workerów.

Naly przechowuje te domeny jako tabele pierwszej klasy:

  • rekordy artykułów i prognoz
  • URL źródeł i migawki pochodzenia
  • posty społecznościowe + metadane dystrybucji
  • wyniki nagród, metadane kalibracji i pola audytu
  • metadane uruchomień crona i punkty kontrolne publikacji

Wartość to nie tylko trwałość; to wspólna semantyka. Każdy renderer, worker i akcja API odczytują ten sam model stanu publikacji i mogą koordynować działanie bez ukrytych flag pomiędzy procesami.

Mechanizm techniczny

Drizzle zapewnia ścieżkę schema-first dla tego typu wspólnej semantyki. Wskazówki Drizzle pokazują kanoniczny przepływ: zdefiniuj schemat w TypeScript, skonfiguruj Neona DATABASE_URL, inicjalizację drizzle, a następnie używaj wygenerowanych typów zapytań do insertów/odczytów/aktualizacji (dokumentacja przeglądowa, samouczek Neon).

Minimalny, stylowy dla Naly, wzorzec inicjalizacji sterownika wygląda tak:

import { drizzle } from 'drizzle-orm/neon-http';
import { pgTable, text, timestamp, pgEnum, integer } from 'drizzle-orm/pg-core';

export const publicationState = pgEnum('publication_state', [
  'queued', 'draft_ready', 'published', 'failed',
]);

export const publications = pgTable('publications', {
  id: text('id').primaryKey(),
  slug: text('slug').notNull().unique(),
  state: publicationState('state').notNull(),
  stateVersion: integer('state_version').notNull().default(1),
  stateChangedAt: timestamp('state_changed_at').notNull().defaultNow(),
});

const db = drizzle(process.env.DATABASE_URL!);

To odzwierciedla dokumentację Drizzle, która obsługuje drizzle-orm/neon-http i drizzle-orm/neon-serverless jako opcje transportu dla Neon, z HTTP dla obciążeń jednorazowych oraz zachowaniem sesji przypominającym WebSocket dla interakcyjnej pracy transakcyjnej, gdy jest to potrzebne. Definicje Drizzle pg-core również umożliwiają inferencję typów ($inferInsert, $inferSelect), dzięki czemu ładunki publikacji są walidowane przez TypeScript w czasie kompilacji przy jednoczesnym zachowaniu elastycznego JSON dla mniej krytycznych metadanych.

Dla Naly kluczowy wzorzec architektoniczny to:

  1. zdefiniuj przejścia stanów (queued -> drafted -> approved -> published -> archived) jako jawne wiersze,
  2. utrzymaj logikę przejść w jednym module wyłącznie serwerowym,
  3. loguj każdą mutację z kluczami idempotentności (id zadania + hash stanu),
  4. uruchamiaj krytyczne przejścia wewnątrz transakcji bazy danych tam, gdzie ma znaczenie atomowość,
  5. generuj niemodyfikowalne artefakty (np. adresy URL blob, treści social, migawki schematu) dopiero po przesunięciu stanu.

Efekt przypomina niewielką maszynę stanów utrzymywaną w Postgresie, z rygorystycznymi kontraktami schematu zamiast domyślnie zakodowanego zachowania w workerach cron.

Co mówią źródła

Podstawowa dokumentacja przedstawia to jako praktyczny wybór projektowy dla systemów serverless:

  • Drizzle sam siebie opisuje jako SQL-like z natury i gotowy na serverless, redukując narzut abstrakcji ORM dla zespołów, które chcą bezpośredniej semantyki SQL przy zachowaniu bezpieczeństwa typów (Przegląd Drizzle).
  • Tutoriale Drizzle/Neon wyraźnie obsługują natywne kombinacje sterowników Neon HTTP/WebSocket i modelowanie schema-first z typami, w tym @neondatabase/serverless integracje i przykłady inferencji typów (Drizzle z Neon, Pierwsze kroki z Drizzle + Neon).
  • Macierz połączeń Drizzle pokazuje jawne rozdzielenie sterowników runtime, dzięki czemu zespoły mogą dopasować tryb wykonania do obciążenia i ograniczeń środowiska uruchomieniowego (Dokumentacja połączeń bazy danych).
  • Własna dokumentacja sterowników Neona i wskazówki dla edge podkreślają, że dostęp serverless/PostgreSQL jest często realizowany przez HTTP albo proxy oparte na websocketach, co właśnie uzasadnia, że decyzje wykonania na edge muszą być explicite dla każdego obciążenia (Sterownik serverless Neon, Jak korzystać z Postgresa na edge).

W obszarze schematu badania dotyczące ewolucji schematu pokazują, dlaczego ważne są jawne migracje i modele stanów. Tesseract wskazuje, że ewolucję schematu można traktować jako operację transakcyjną pierwszej klasy, a solidne systemy powinny projektowo minimalizować przestoje (online schema evolution).EvoSchema pokazuje, że zmiany schematu — zwłaszcza perturbacje na poziomie tabeli — mogą destabilizować zachowanie po stronie downstream, co jest silnym ostrzeżeniem przed przypadkowymi, ad-hoc dodatkami do tabel publikacji/stanu (EvoSchema

).

Kompromisy projektowe

  • Wybór sterownika: neon-http jest prostszy i zwykle szybszy dla operacji jednorazowych; neon-serverless jest lepszy, gdy potrzebne są sesje interakcyjne.
  • Projekt schema-first: bezpieczeństwo czasu kompilacji ogranicza błędy runtime, ale zmiany schematu wymagają planowania migracji i mogą objawić się jako zablokowane wdrożenia, jeśli testy nie obejmują przejść stanów.
  • Przenośność runtime: model sterowników Neona przyjazny edge rozszerza opcje wdrożeń, ale tryb transportu wpływa na zachowanie sesji, profil latencji i ścieżkę uwierzytelniania/TLS.
  • Wierność typów a wygoda operacyjna: jawne enuma zapobiegają niepoprawnym stanom, ale mogą spowalniać nocne hotfixy, jeśli skrypty migracji nie zostały wcześniej zatwierdzone.

Tryby awarii

  • Niewłaściwy sterownik do obciążenia: używanie jednorazowych zapytań HTTP do wieloetapowych przejść stanów może utracić gwarancje atomowości i tworzyć częściowe stany publikacji.
  • Dryf schematu między workerami i wdrożeniami: jeśli publication_state wartości zmienią się bez skoordynowanych migracji, stary kod crona może zapisać nieprawidłowe stany.
  • Nieidempotentne ponowienia: restarty crona mogą dublować zapisy do social, chyba że punkty kontrolne są idempotentne i unikalne według run-id.
  • Opóźnienie migracji: zaplanowane zadania uruchamiane na przestarzałych migawkach schematu mogą kończyć się błędami, zwłaszcza podczas rolling deploy.
  • Rozruch edge + narzut uwierzytelniania: w ograniczonych warstwach edge wielokrotna inicjalizacja połączeń może zwiększać latencję i generować fałszywe timeouty, jeśli budżety timeoutów i fanout zadań nie są odpowiednio dostrojone.

W kontekście tego tematu analiza błędów powinna traktować stan publikacji jako artefakt poprawności, a nie detal implementacyjny. Każde przejście stanu musi być bezpieczne do ponownego odtworzenia.

Uwagi implementacyjne

  • Trzymaj pliki schematu centralnie i wersjonowane; regeneruj artefakty migracji z jednego źródła prawdy.
  • Oddziel modyfikowalne tabele domenowe od tabel niezmiennych artefaktów.
  • Modeluj przejścia publikacji jako granicę transakcji i egzekwuj inwarianty (np. bez bezpośredniego queued -> published skoku).
  • Przechowuj metadane harmonogramu (last_run, next_run_at, error_count) w oddzielnej tabeli do alertowania i audytu.
  • Preferuj moduły tylko serwerowe do inicjalizacji bazy danych (DATABASE_URL z .env.local środowisk cron/runtime).
  • Używaj zapytań strukturalnych zamiast surowego SQL w większości operacji, a surowy SQL rezerwuj wyłącznie dla seedów migracyjnych lub raportowania.
  • Traktuj stateVersion i logi zdarzeń jako mosty kompatybilności, gdy ewoluuje schemat.

Referencje

Sources