Галоўная / Артыкулы / Prisma для разработчаў Spring Boot: супарабоцаванне прычынак JPA з Node.js

Prisma для разработчаў Spring Boot: супарабоцаванне прычынак JPA з Node.js

Канал для разработчыкаў Java і JPA, якіе пераходзяць на Node.js: як модэлі, адносы, міграцыі і типы Prisma адпавядаюць вядомым панявам, і што застаецца для вас.

1866 слоў

Калі сервер на базе Node.js пачаткова трэбуе зберагчы данні ў PostgreSQL, вы сталквайцеся з вядомым выборам: запісваць сыры SQL, выкарыстоўваць традыцыйны ORM або інструментарый, які спачатку працуе з схемай, напрыклад Prisma. Для разработчыкаў, якія працавалі з Java, Spring Boot, JPA і Hibernate, выбор таксама стосуецца перенесення вялікай меры ўжо існуючага ментальнага модэлю, а також чыстага шару доступу да даных, які не праблекае SQL у обробніках запытанняў. У гэтым кяле як прыклад выкарыстоўваецца невялікий сервер для запісу голасу, каб паказаць, як концэпцыі Prisma адпавядаюць таму, што вы ведаеце з JPA, дзе ў яных разлікі, і якія навыкі работы з базамі даных не можа заменіць жадны ORM.

Месца Prisma ў стэкі

Prisma — это ORM і інструментарый для работы з базамі даных для Node.js і TypeScript. Ён знаходзіцца межы кодам вашага прыемліка і базай даных:

Node.js / TypeScript API
  ↓
Prisma
  ↓
PostgreSQL

PostgreSQL застаецца аслівным базой дадзеных, якая адпрацоўвае зберагачванне, обмежэння, транзакцыі і выконанне запытаў. Prisma дае аплікацыі типаваны, структураванны спосаб працы з яю.

Без ORM, калі трэба знайсці пользователя па электронной пошцы, неабходна напісаць SQL безпосередня:

SELECT *
FROM users
WHERE email = 'user@example.com';

З Prisma той жа запыт выглядае як звычны код на TypeScript. Функцыя findUnique прымеўа толькі тыя поля, якія схема пазначыла як унікальныя або як первычныы ключ, таму кампайлер ведае, што гэты запыт вернее не больш адной строчкі.

const user = await prisma.user.findUnique({
  where: {
    email: "user@example.com"
  }
});

Якщо вы вжывалі рэпазітарыя Spring Data JPA, гэты стыль падабедзіцца вам: вы вызываеце метод у спецыяльным аксесарыі для модэлю, замест таго каб самі складваць запыт.

Як гэтыя концэпцыі паўтараюцца з Spring Data JPA

Два экасыстэмы не адаптуюцца адно-аднаму, ал адпраўнасць, якую яны беруць, ёст каляўдзейная. Код, спецыяльны для базы дадзеных, не павінен прасачвацца ў кожную частку аплікацыі; яны патрабуецца ў спецыяльным шаре адчынення да дадзеных. Найбольшая структурная разліка — у тым, дзе визначаецца модэль. JPA вырахоўвае адпраўненне на аснове класаў Java з анотацыямі, тады калі Prisma выкарыстоўвае околачны файл шымату як едынага адказвальнага джерага і на яго аснове генеруе кліента.

Визначэнне модэля

У Spring Boot аб’ект-выкарыстоўнік — это клас з анотацыямі:

@Entity
public class User {

    @Id
    private Long id;

    private String email;

    private String name;
}

У Prisma адпаведны элемент знаходзіцца ў schema.prisma. Атрыбуты @id і @default(autoincrement()) выпольваюць ролю атрыбута @Id у JPA з генераваным значэнням, а @unique стае справжнім унікальным абмежэнням у базе дадзеных.

model User {
id Int @id @default(autoincrement())
email String @unique
name String }

Заўважкі: формат схемы Prisma выкалічвае, каб кожна польніца была на сваёй лініі, а замыкаючыя складанкі — на адной окремай лініі; у рэальным файле компактная версія, падобная да вышэйшай, таксама должна быць структураваная такім чынам. Гэтая схема чытваецца як інструментамі міграцый, так і створаным кліентам, таму яна стае афіцыйным описам таго, як прыстройка бачыць базу дадзеных.

Створаныя типы як абаранітэльны механізм

Першая функцыя, яку большасць людзей зазначае, — это тое, насколькі ўжоцьплана Prisma інтегруецца з TypeScript. Выконанне команды prisma generate стварае кліент, чыяя методы і типы значэнняў выводзяцца з вашых модэляў. Просты запит, падобны гэтаму:

const users = await prisma.user.findMany();

вырачвае об’екты, пра якія TypeScript знае, што ў іх точна є гэтыя польніцы:

id
email
name

У практыцы гэта значыць автадаптаванне тэксту ў редактары, пераконтроўванне даных у кожнай польцы, якую вы чытаете або фільтруячы, і памылкі часу кампайлявання, калі столбец у схеме пераяменаваны, але не ў кодзе. У большых бэкендах гэта пазбавляе ад цэлай категорыі багоў, вызваных памылкамі написання.

Стварэнне записаў

Падазроўваючы, што прыстановка для запісу аудыя трэбае зберагаць аудыязапісы. Модэль з первым ключам у формате UUID і часовым пазначэннем стварэння, якое заполняе база дадзенаў, выглядае так:

model Recording {
  id        String   @id @default(uuid())
  title     String
  audioUrl  String
  createdAt DateTime @default(now())
}

Уведзенне рэкорда тады ўжо ёсць адна вызовка. Вы пасылаете толькі поля без стандартных значэнняў, а Prisma вяртае цэлы створаны запис, укладаючы ў яго генераваныя значэння id і createdAt:

const recording = await prisma.recording.create({
data: { title: "Project Meeting",
        audioUrl: "/audio/project-meeting.mp3"
} });

Якшто робіць гэта вручную, трэба будзе напісаць каманду INSERT, прыўязаць параметры, прачытаць генераваныя значэння і перакласты рэкорд у об’ект.

Моделюванне асоціяцый

Рэальныя схемы рэдка калі складаюцца з аўтанонімных тэблей. У гэтым прыкладзе адны корыстнік володзіць многамі записямі. У Prisma адноснае звязанне прызначаецца з обох бакоў: поле-списак у User, а ў Recording — скалярны званачыцельны ключ плюс поле-адноснасць, якое называе столбцы, якія з’ѐднуюць іх.

model User {
id String @id @default(uuid())
email String @unique
name String recordings
Recording[]
}
model Recording {
id String @id @default(uuid())
title String
audioUrl String
createdAt DateTime @default(now())
userId String
user User @relation(fields: [userId], references: [id])
}

Як і у пакульшым модэлі, вышэўшая структура ў складзеным варыянце. У робочай схеме поле-списак пішацца на адной лініі як recordings Recording[], а кожна іншая змянная таксама залічваецца на адной лініі. Толькі userId стае рэальным столбцом; recordings і user — віртуальныя зменныя, якія існуюць у кліенте для навігацыі.

Калі адноснасць установлена, можна стварыць запис, які належыць конкрэтнаму корыстніку, выканаўшы прызначэнне званачыцельнага ключа безпасова:

const recording = await prisma.recording.create({
data: {
   title: "Daily Standup",
   audioUrl: "/audio/standup.mp3",
   userId
      }
});

Для разработчыкаў JPA гэта адпавядае @OneToMany на стороне пользователя і @ManyToOne на стороне записаў. Адна практычная разліка: у PostgreSQL Prisma не дадае автаматычна індэкса для столбца з іншым ключам, таму варта дадаць @@index([userId]) у модэль Recording, якщо вы часта будзеце шукаць записы па ўласніку.

Эвалюяцыя схемы за дапамой міграцый

Схемы зменяюцца. Уявіце, што першая версія табелі пользователя мае толькі гэтыя столбцы:

id
email
name

а пазней вам патрэбна будзе дадаць часовую метку:

createdAt

Рэдагаванне базы дадзеных у працоўнай сіткі вручную — гэта сама тое, чаго трэба адвакувацца. Апранаты для міграцыйяў Prisma парабяльвае вашу схему з історыяй міграцыйняў і стварае файлы SQL для кожных змян. У режыме разработкі prisma migrate dev стварае і застосоўвае гэтыя файлы; у працоўнай сіткі prisma migrate deploy застосоўвае тыя, што ўжо чакаюць, не ствараючы нічога новага. Файлы SQL знаходзяцца разам з вашым вучальным кодам, таму змяны базы дадзеных праходзяць через перагляд коду і історыю Git, як і будзь-яе інша змена, падобна да таго, што прыносяць Flyway або Liquibase у проектах Spring.

Калі чысты SQL заўсёды ёсць лепшы апранат

Чысты SQL — не вор, і розумеўце SQL заўсёды єсць неабходны. Рукапісныя запиты часта ўжо падходзяць лепей для:

  • складных аналітычных запитоў
  • сильна оптымізаваных аперацый
  • запитоў для стварэння звярнункоў
  • функцыі, спецыяльныя для PostgreSQL
  • Для стандартных операцый з прыемамі, падобных тым, што пераказана нижэй, ORM дапамагае заўсёды абмежыць колькасць павтараючагуся коду:

    Create user
    Get user
    Update recording
    Delete session
    List transcripts
    Find recording by ID
    

    Мета не ў тым, каб усунуць SQL з проекту. Цель — залічыць стандартныя операцыі CRUD простымі, адночасна застаючыся у кантакце з тым, што відбываецца на рэвэрсе базы дадзеных. Prisma таксама праследжвае $queryRaw для ситуацый, калі патрэбна работа з SQL без выходу за межы кліента.

    Чаму Prisma, а не іншыя варыянты для Node.js

    Экасістэма Node.js праследжвае многа бібліятэкаў для баз дадзеных, кожная з якіх мае свои плюсы і мінусы:

    Prisma
    Drizzle ORM
    TypeORM
    Sequelize
    Knex
    node-postgres
    

    Для развіццялара, які працаваў з Spring Boot, прыемлівасць Prisma выражаецца пераважна ў яго досвядзе разработкі і падтрымцы TypeScript першага класу. Ён таксама спрыяе шаровым падходам да рашэння проблем, якія адпавядаюць типовым прыемам у Spring-прыемах:

    Model
      ↓
    Data Access
      ↓
    Service
      ↓
    API
    

    заместо розсіювання SQL-запитоў між працэўнікамі API. Якщо вы хочаце болей шырокія порэванні вароў, уключаючы моменты, калі будувальнік запитоў є кращым выборам, адзірніце як выбраць між сырым SQL, Prisma і Drizzle.

    Інтеграцыя Prisma ў шырэйшую архітектуру

    У дапытнай працэўніку Prisma адпавядае за реляцыйныя даны, такія як:

    Users
    Recordings
    Sessions
    Transcripts
    Metadata
    Processing jobs
    

    Калі система расте, архітектура можа ператворыцца ў нешта подабнае да гэтага, з шаром служб між API і кодам для доступу да даных:

    React / Next.js Frontend
            ↓
    Node.js / TypeScript API
            ↓
    Service Layer
            ↓
    Prisma
            ↓
    PostgreSQL
    

    У пазнейшыя стадіі можа быць дадзена ўваўка іншых компанентаў:

    Object Storage
    Redis
    Message Queues
    AI Transcription Services
    Background Workers
    

    Prisma не заменяе нічога з гэтага: аудыя належыць у сховішча об’ектаў, кэшы — у Redis, а транскрыпцыя — у працэўніках, якія працуюць за принцыпам очаквання у черзі. Prisma керуе толькі реляцыйным шаром.

    Тое, што можна пераносіць: тэкучасць дадзеных

    Вивчэнне API Prisma — гэта менш важлівы элемент. Найболей значным є розумэнне таго, як дадзеныя пераводзяцца через бэкенд ад запиту да конкретнай строкі:

    HTTP Request
         ↓
    Controller / Route
         ↓
    Service
         ↓
    Repository / Prisma
         ↓
    PostgreSQL
    

    Конкрэтна, стварэнне запісу выконваецца па такой схеме:

    POST /recordings
         ↓
    Recording Controller
         ↓
    Recording Service
         ↓
    Prisma
         ↓
    INSERT INTO recordings
    

    Гэтая тэкучасць застаецца аднаковай незалежна ад таго, які ORM чы ўпрацоўваныя мова вы вжываеце, і самэ гэта якраз і дазволяе пераносіць данні.

    Што ORM не робіць для вас

    Prisma не ўзаменяе PostgreSQL, правільны дизайн схемы чы знаўства SQL. Вам все рава трэба моцныя навыкі у:

    Indexes
    Constraints
    Primary keys
    Foreign keys
    Transactions
    Joins
    Normalization
    Query performance
    Locking
    Connection pooling
    

    ORM спрыяе лёгкаму доступу да дадзеных; але ён не робіць таблицу з паслабленым індексаванням быстраю чы нехватку обмежэнняў — безпечной. Пулінацыя з’язоў выклікае ўвагу ў Node.js, адтолькі што стварэнне большой колькасці экземпляраў кліента, напрыклад праз кожную перзагрузку чы вызов без сервера, можа выкарыстаць усе з’язны PostgreSQL.

    Заключэнне

    Для бэкенду на TypeScript з базайнам PostgreSQL Prisma параджыць корыстны баланс между продуктыўнасцю і разумэнням, і ён дазваляе разработчыкам Spring Boot перадаўжваць выкарыстоўваць большасць сваіх архітэктурных навыкаў. Справжнія навыкі — гэта не запамятовваць такія калі, як гэты:

    prisma.user.findMany();
    

    Гэта разумэнне таго, як запыт праходзіць ад канцачнай точкі API да реляцыйнага базайна і зваротна. Разумны наступны крок — задаць вашы першыя рэальныя моделі, пад’язаць іх да PostgreSQL, стварыць пачатковую міграцыю і адкрыць данні через REST API.

    • Вважаць schema.prisma едынай адказной джэрамі для моделей, зв’язкаў і абмежэнняў.
    • Полагацца на створаны кліент для безпекі типаў і перзастраіваць яго ўсё час, калі змянюецца схема.
    • Выкарыстоўваць migrate dev у локальных умовах і migrate deploy у прыняцоўскіх умовах, каб кожная змяна схемы была версіявана.
  • Зберагаеце необработаны SQL-код для аналізу, стварэння звярненняў та функцый, спецыфічных для базы дадзеных.
  • Продовжайце інвеставацыі ў індэксы, обмежэння, транзакцыі та выкааноўнасц запытав; ORM не будзе гэта робіць замест вас.