Галоўная / Артыкулы

REST протык GraphQL: Рэальныя компрэсіі, якія існуюць за кожной архітэктурай

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

Выдавец: REACTAPP.TOP10 хв чытання2224 слоў

Крыніцы: REST vs GraphQL: Which Problem Does Each One Actually Solve?

Калі вы прымаетесь спланаваць, як кліент і сервер павінны аб’едначацца, майже ўсё час вылупляецца адна запытанне: REST чы GraphQL?

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

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

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

Ёнклівна дыячоўская адпаведнасць даходзіць тады, калі адкласіць усю шуму і разглядаць асновныя мотывацыі: што прагледзела вырашыць кожная падход, як ён функціонуе внутршная адзінклівасць і якія компрэсіі з’яўляюцца ўнаследке.

Какія проблемы рашыў REST?

Да таго часу, калі REST стаў стандартам, веб-сэрвісы сильна завышаліся на протаколах такіх як SOAP і XML-RPC. Яны выкалікалі патрэбу ў строгіх XML-шыматах, длігіх пакетах паведамленняў і складнай настройцы. Кліентам было неабходна знать точныя назвы даўнях процедураў, толькі каб атрымаць даныя.

Рой Філдінг у 2000 годзе адкрыў REST (Representational State Transfer).

У сваім сэрцы REST запраўдаваў простую, але эфектывную ідею: выкарыстоўваць сабе архітэктуру веба.

У працы з серверамі замест таго, каб спрыяваць як нечытаемы механізм выконання функцый, REST адобразвае систему як скупчанне рэсурсаў, кожны з якіх можна адзначыць за дапамой чыстаг URL. Дзеяння над гэтымі рэсурсамі выражаюцца за дапамой стандартных HTTP-дзеянняў (GET, POST, PUT, DELETE), а рэзультат кожнага запиту паведамляецца за дапамой звычных кодаў статусу HTTP, такіх як 200 OK для успеху, 404 Not Found, калі рэсурс не знайдзены, або 500 Server Error, калі на бэкенде выйшла проблема.

Resource-Oriented Architecture (REST)
Client ─── GET /api/users/12 ──────────> Server (Find user #12)
Client <── 200 OK [ { user data } ] ──── Server
Client ─── DELETE /api/posts/99 ───────> Server (Delete post #99)
Client <── 204 No Content ───────────── Server

Чаму REST мае значэнне

  • Простата і прыемлівасць: Калі вы зрозумеете, што /api/products аднасаецца да скупчання продуктав, а /api/products/42 — да конкрэтнага элемента, інтерфейс становіцца самазрозумелым.
  • Вбудованая кэшаванне HTTP: Паколькі запыты GET ўзьўязваныя і перадаюцца праз унікальныя URL-адрэсы, інфраструктура на кшталт браузераў, CDN-сервісаў (напрыклад, Cloudflare) і зворачных проксі (такіх як Nginx) можа кэшаваць адпаведзі без дадатковай логіки прыкладнага програмавання.
  • Раз’ўязаныя ціклы жыцця: Команды, якія працуюць з бэкендам, можу вольна перастраіваць внутрашнія схемы базы дадзеных, як толькі публічны HTTP-контракт застаецца аднаковым.
  • Гэтыя падходы давалі розробнікам хорашыя рэзультаты працы ў больш часу. Але калі кліентскія прыкладнення сталаўліся болей складнымі, пачалі выявляцца адзінаковыя проблемы.

    Новыя проблемы: чрэзмернае і недастатковае запрашанне дадзеных

    Уявіце мобільныя прыкладнення, якія атрыбутуюць экран з прафілем пользователя. Што неабходна на гэтым экране — тры рэчы:

    • Імя пользователя
    • URL, які вядзе да аватара пользователя
    • Загаловкі троха пасяжных абаз гэтаго пользователя

    За дапамогай традыцыйнага REST API адпаведнае выкананне гэтай простай патрабоўкі часта вызывае два разныя прыблуды.

    1. Чрэзмернае запрашанне дадзеных

    Кліент запрашвае GET /api/users/12.

    Сервер адпавядае цэлым рэкордам пользователя:

    {
      "id": 12,
      "name": "Sarah Connor",
      "avatarUrl": "https://cdn.example.com/sarah.jpg",
      "email": "sarah@example.com",
      "address": { "street": "...", "zip": "...", "country": "..." },
      "billingHistory": [ ... ],
      "preferences": { ... },
      "createdAt": "2021-04-12T08:14:00Z"
    }
    

    На экраны былі патрэбны толькі name і avatarUrl, але ў рэзультате былы запрашаны некалякія дакументы пра адресу, інформацыя па рахунку та настройкі прывычак. Праз спадзялую мобайльную з’ўязку перадача непатрэбных байтов выкорыстоўвае батарэю і спамічае адрасаванне контента. Гэты сценарый называецца чрэзмерным запрашаннем дадзеных.

    2. Недастаткова колькасць запрашаных дадзеных (проблема N+1 у сеті)

    Кліент все ўсё патрэбуе абазы Сары, але адпаведнае на /api/users/12 запрашанне іх не ўключала.

    Тады выконваецца другі запыт: GET /api/users/12/posts.

    Падазрóўцем, што канечная тэмплятка вяртае толькі просты список ідэнтыфікатараў паспэў. У такім случыку кліенту можа знадобіцца выконваць тры адзелёныя дадатковыя запыты: першы — для загрузкі /api/posts/101, другі — для загрузкі /api/posts/102, і трэці — для загрузкі /api/posts/103.

    На гэты момент прыстрой выконаў калькольве парадных сеансаў з’ёднання з сетю толькі каб адобразыць адну экранаў. Такі патэрн называецца недастатковай загрузкай.

    Какую проблему на самае справе рашыў GraphQL?

    Facebook стаў супакоўвацца самэй гэтай прычыне блокавання адносна 2012 года, калі пераключаў свой флагманскі дапыт з мобайльных веб-версый на натыўныя дапыты. Мобайльные з’ёднання былі ненадзеянымі і повольнымі, а інтэрфейс Facebook складаўся з тычуцца адзін да другога дадзенняў — корыстувальнікі, друзья, каментары, лайкі, рэакцыі.

    Створэнне спецыяльнага REST-канца для кожнага экрана, на прымер /api/mobile-profile-screen, быстра ператварылася ў неконтрольаваны патэран.

    Ёжы ўзьмяць гэта пад контроль, Facebook створыў GraphQL.

    GraphQL — гэта не техналогія базы дадзенаў. Це мова запитоў для API, якая выкорыстоўваецца разам з серверным рантаймам, які выконвае запиты на адповідачы, заданыя вам у системе типаў.

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

    Падход GraphQL

    Ось як той жа экран прызначэння можа запрашваць свае дадзеныя за дапамогою GraphQL:

    GraphQL

    query GetUserProfile {
      user(id: "12") {
        name
        avatarUrl
        posts(limit: 3) {
          title
        }
      }
    }
    

    А сервер вяртае самэ тое, пра што было запитана, нічога больш:

    JSON

    {
      "data": {
        "user": {
          "name": "Sarah Connor",
          "avatarUrl": "https://cdn.example.com/sarah.jpg",
          "posts": [
            { "title": "Building Reliable Systems" },
            { "title": "Understanding Memory Leaks" },
            { "title": "A Guide to Clean Architecture" }
          ]
        }
      }
    }
    

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

    Разгляд усіх аспектаў

    Ёнколы ёсць можлівасць адоўжна зрозумець усі плюсы і мінусы, калі розглядаецца, як кожны падход адбываецца на стороне сервера.

    Модэль REST на аднойчынных канцах

    У такім фрэймворку, як Express.js, маршруты безпасяродна супарабатваюць з функцыямі-обраОавальнікамі згодна з прыйнятымі URL:

    JavaScript

    // Express.js REST route
    app.get('/api/users/:id', async (req, res) => {
      const user = await db.users.findById(req.params.id);
      if (!user) return res.status(404).json({ error: 'User not found' });
      res.json(user);
    });
    

    Разработчык бэкенду мае цалы кантроль над логікаю запытоў, вызовамі базы дадзеных і форматам адпаведзення. Налагоджэнне працысоў у гэтым падходзе ёсць простым: дасылаюцца запыты безпасяродна да відпаведнага обрабоўчыка канца і там оптымізуецца SQL-запыт чы дадаёцца кэшаванне.

    Модэль GraphQL на схеме і рашытчыку

    GraphQL створаны на адмініструецькай схеме Schema, а таксама на окалічных функцыях, які называюцца Resolvers:

    JavaScript

    // Type definitions
    const typeDefs = `
      type Post {
        id: ID!
        title: String!
      }
    
      type User {
        id: ID!
        name: String!
        avatarUrl: String!
        posts: [Post]
      }
    
      type Query {
        user(id: ID!): User
      }
    `;
    
    // Resolvers map fields to actual data sources
    const resolvers = {
      Query: {
        user: async (_, { id }) => {
          return await db.users.findById(id);
        },
      },
      User: {
        posts: async (parentUser) => {
          // Runs when the client query includes the 'posts' field
          return await db.posts.findByAuthorId(parentUser.id);
        },
      },
    };
    

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

    Гэта даёт командам фронтэнду вялікую свабоду у формаванні іх запытоў. Але ёсць адна умова, якую варта згадаць: кліент выбірае формат запыту, але самэй серверу трэба несці выкарыстаныя для яго адпаведзення ресурсы.

    Якія затраты несе GraphQL у тылу

    Гэтая гнучкасць не даёцца безкоштовна. Пераход з REST на GraphQL фактычна пераносіць складнасць з кліента на сервер.

    Проблема N+1 запытоў на рэвэрсе базы дадзеных

    Зноў розглянем размяшчанне рэзалвера, паказанага вышэй. Што будзе, як кліент запытаеся пра спіс з 20 корыстнікаў разам з пасункамі кожнага з іх?

    GraphQL

    query {
      users {
        name
        posts {
          title
        }
      }
    }
    
    • Рэзалвер users выконваецца аднае раз, запрашаючы ўсі 20 рэкордаў заодно (SELECT * FROM users).
    • Потым рэзалвер posts выконваецца окрема для кожнага з адзінаковых корыстнікаў, якія былі вярнуты.
    • Канечны рэзультат: ваша база дадзенаў выконвае 1 запит для спісу корыстнікаў плюс 20 дадатковых запитоў для пасунков — адзначна 21 запіт.

    У REST-сервісах зазвычай гэта адбываецца за дапамогай аднаго SQL-з’еднання ў коде канцэнтраў ( SELECT * FROM users JOIN posts... ). У GraphQL для аб’еднання такіх розрозненых запытоў і ўтрамбавання іх перш чым яны дасягнуць базы дадзенаў патрэбныі інструменты на кшталт DataLoader.

    Проблемы з кэшаваннем HTTP

    Разглянемы календзе REST на прыкладзе:

    GET /api/articles/42
    

    CDN можа зберагчыць такую адпаведзь у кэшы, скажамо, на 10 хвілін. Якщу за гэты час 5 000 корыстнікаў запытае /api/articles/42, ваш сервер-выканавальнік павінен обрабацваць толькі першаго — рэст працюе через CDN.

    GraphQL наражае гэты патэрн, таму што майже кожны запыт адправляецца як POST да ўзела /graphql.

    Паколькі сам запыт знаходзіцца ў тэле запыту, а не ў URL, звычныя кэшы HTTP, проксі і CDN не можу распазнаць, што два запыты запрашваюць ідэнтычныя даны. Чырага для гэтага трэба альбо сложны кэш на кліенте (напрыклад, Apollo Client або Relay), альбо спецыяльны кэшаваючы шлюз.

    Глыбіна запыту як вектар атаки

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

    Аднак у GraphQL зловольны аб’ект могаў бы запрацаваць ў такі спосаб:

    GraphQL

    query EvilQuery {
      user {
        posts {
          author {
            posts {
              author {
                posts {
                  title
                }
              }
            }
          }
        }
      }
    }
    

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

    REST і GraphQL падыеднакава

    ФічыRESTGraphQLЗялэнне дадзейнаўВерне фіксаваны формат дадзейнаў для кожнага URLВерне толькі тыя поля, якія запрашвае кліентСетевыя вызывыЧаста патрабуе кальколька раунд-трыповЗазвычай вырашваецца ў аднам запитеHTTP-кэшаваннеВбудованае, простае і эфектываеТрудная задача; патрабуе дадатковых інструментоў на кліяне або гэйтвейзеКрываючая крываяПростая, спакоюецца на знайомых HTTP-стандартахБолей высокая, трэба разумець схемы і рэзалверыАдчытанне абохвотКанвэнцыйныя коды статусу HTTP (404, 500, 401)Практычна завжды 200 OK, пры чым проблэмы паказаны ў поле errorsЗалічэнне файлоўПроста праз multipart form dataПатрабуе спецыяльнай логікі мутацый або празвісаных URL-адрэсаў для залічэнняІдеальныя сцэнарыіСервісы у стыле CRUD, мікросервісы, адкрытыя веб-APIЯркія UI і дапыткі, якія працуюць на кальколька платформ

    Вызыв: як выбраць

    Калі REST ўжо лепшы выбар

    • Ваша модэль рычоўнасці простая. Для типовых продактаваў, якія апыраюцца на механізмы CRUD, такіх як панелі адарбніка, дашборды чыста базовыя вітріны, тэхналогія REST дапамагае шырокаць продукт быстрэй.
    • Для вас важна кэшаванне. Для публічнага контэнту, які часта чытаецца, такога як блогі, старонкі новасцей чыста каталагі, натыўнае HTTP-кэшаванне через CDN не мае суперніка па швальнасці чыста цэне.
    • Вы публікуеце API для сторонніх корыстувачаў. Староннія разработчыкі вядома можу выкарыстоўваць канцэнтры REST з curl чыста fetch, не выучваючы спецыяльную бібліятэку кліента.

    Калі GraphQL ўжо лепей падходзіць

    • Вы падтрымліваеце многа разных типаў кліентаў. Уявіце сабе продакт з кліентамі для iOS, Android, смартгаджэтаў чыста веб-кліентамі, кожны з якіх патрэбуе разнага сабсету тых самых дадзенняў.
  • Ваш домэн — это жорсткая, взаімнасвязаная сетка. Гэта стосуецца кадры, дзе такія элементы, як корыстнікі, групы, каментары, правыя на доступ і медыя-матэрыялы, сталоўна адна да другойя павязаны.
  • Фронтэнд і бэкэнд працуюць па разных графіках. У большых камандах GraphQL выступае як прыемнай шар: калі ўжо визначены рэзалверы і типы, інжынеры фронтэнду можаць ствараць новыя відображэння без патрэбы чакаць, калі каманда бэкэнду ў кожны цыкл дадае новыя канцэнтры.
  • Заключэнне

    Ні адна з гэтых тэхналогій не стала застарелай заводзе з іншай.

    • REST задаў патрэбу ў аднаковым, масштабаваным і спрыяючым кэшу способе выкладвання ресурсаў у інтэрнете.
    • GraphQL задаў патрэбу, каб кліенты самі формавалі свае запиты да дадзэнняў, чым зменшаліся марныя перадзваненні праз спакойныя або ненадзеяныя мобільныя з’ѐеднанні.

    Выбір между нимі не стосуецца следавання традыцыям, а спрямованы на розумеўце рэальных працоўскіх труднасцей, з якімі сталкаецца ваша система. Якщо вашыя найбольшыя проблемы — це продуктывасць у мобільных працоўках і постаўляючыся довгі список канцэнтраў, спецыфічных для фронтэнду, то GraphQL можа стаць эфектываю рашэнням. А якщо для вас найважлівейшыя простата, надзеяны кэшаванне і фронтэнд, які лёгка ў тымчасовай падтрымцы, то REST застаецца надзеяным і перавераным у практыцы варыянтом.