Галоўная / Артыкулы / Падступы архітектуры бэкенду, якія ствараюць проблемы для команд React, якія спачатку фокусуюцца на фронтендзе.

Падступы архітектуры бэкенду, якія ствараюць проблемы для команд React, якія спачатку фокусуюцца на фронтендзе.

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

3129 слоў

Як разработчыки React, вы, верагаце, вмелі ствараць прыгожыя, адпаведныя сітуацыі інтерфейсы. Вы розумеете канкурэнтную відраслівацыю, серверныя компаненты і складныя шаблоны каліткавання стану. Але калі разговор пераходзіць да бэкенд-сістэм, багато інжынераў, якія сфокусаваны на фронтэндзе, яшчэ працуюць на рэвэліхнам рывень. Базавыя мідлваркі Express, прымітныя запыты да MongoDB і платформы для разгрупавання, якія абстрагуе інфраструктуру, ўжо давно є стандартамі. Усё працуе без проблем на localhost, і стэйджынг таксама выглядае нормальна. Але як толькі пачынаеся справжній трафік у працэйным режыме, слабасці быстра становяцца виднымі.

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

Распалох №1: Аб’ектыўнае павяржэнне толькі ў Express Middleware без розумеўця парадыгм API

Знайомая настройка бэкенду для разработчыкаў React — это аплікацыя Express, якая складаецца з серыі вызваў app.use(). У яй насталяваныя элементы cors, body-parser, morgan, дадаюцца кальчыкі для обработкі маршрутаў, і вяртаецца JSON. Гэты падход здаецца зручным, таму што ён адпавядае тым жа шаблонам JavaScript, якія вы вжываеце ў фронтэндзе. Проблема заключаецца ў тым, што такі падход ігнаруе болей фундаментальны вопыт: якая самэўсёлкаваная парадыгма API насправды падходзіць да дадзеных, якія вы аддаёте?

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

Глыбокая тэхнічная рашытка

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

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

GraphQL запобегае чрэзмернаму запрашоўванню, дазваляючы кліенту точна указаць тыя поля, якія ён хоча. Аднойчы ж усё гэта стварае проблему на бакэндзе, вядомую як прыбліжна N+1 запитоў. Прыпустім, рэзалвер вытягвае спіс корыстувачоў, а потым запускае окрэмы запит для кожнага з іх, каб вытягнуць ўсі яныя замовленні — адна вхідная запчатка ператвараецца на сто раунд-трыпов да базы дадзеных. Без такіх рашэнняй, як DataLoader чы батчаванне на рэвэлі поляў, сервер GraphQL не зможа вытрымаць рэальны трафік.

gRPC выкарыстоўвае Protocol Buffers замест JSON, чым дае бінарныя пакеты даных, якія ў дзесяць разоў меньшыя і значна быстрэй парсуюцца. Ён не прызначаны для API, якія выкарыстоўваюцься браузерамі — браузеры не можаюць натычна обрабляць HTTP/2 trailer без проксі паўзу — але ён ідеальна падходзіць для внутрашняя пераказы даных межа службамі. Калі гейтвэй Node.js павінен канектавацца, скажамо, з аналітычным сервісам на Python чы сервісам аутэнтыкацыі на Go, gRPC з protobuf значна пераважае REST-over-JSON па эфектывнасці ў внутрашняй сеті.

Што зрабіць у замене

Для API, якое выкорыстоўваецца з боку корыстніка і задае даныя фронтэнду на базе React, зазвычай найкраща падходзіць сумешаная стратэгія. Выкорыстоўваюце REST для простых операцый CRUD, калі важна кэшаванне. Корыстаюцеся GraphQL, калі трэбаванні да дадзеных ўскладненыя та глыбокая, але паў’язваюце яго з DataLoader, ўбачымаючы вызывы базы дадзеных у пакетах і усунуўчы дублікаціі. Заказваюце gRPC для внутраняга абмену між сервісамі, якія знаходзяцца за вашым гейтвэйем.

Нижчэ прыводзіцца прыклад рашэйвера GraphQL, який групавае запиты за дапамогою DataLoader:

// userLoader.js
const DataLoader = require('dataloader');
const batchUsers = async (ids) => {
  // Single query for all IDs
  const users = await db.user.findMany({
    where: { id: { in: ids } }
  });

  // Return in the same order as the keys
  const userMap = new Map(users.map(u => [u.id, u]));
  return ids.map(id => userMap.get(id) || null);
};
const userLoader = new DataLoader(batchUsers);
// Resolver
const resolvers = {
  Order: {
    user: (parent) => userLoader.load(parent.userId),
  }
};

Якшчы не выкарыстоўваць гэты loader, адпаведна да рашэйвараў ста замовленняў будзе выканана сто окремых запытаванняў SELECT. Якшчы яго выкарыстоўваць, усе гэтыя запытанні з’едынаюцца ў адны запыт SELECT ... WHERE id IN (...). Гэта і є разлік межа адпаведзення за 50 мс і запытання, якое таймаутуе за тры секунды.

Памылка №2: Безпосередній выканання запытанняў да базы дадзеных для кожнага запытання

Калі кожна апдэйтаванне сторонкі спрычынае новы запит да базы дадзэнняў, тады тое, што у вас ёсць, на самай працоўнай адзе не ўжо архітектура — гэта проста канэкцыя. Базы дадзэнняў такія як MongoDB і PostgreSQL ўжо шырокі, але не без меры. Пад адночасным навантажэнням запасы канектацый выкараняюцца, запиты накапліваюцца ў черге, а час адпаведзення, які раней становіў 20 мілісекунд, можа вырастаць да 5 сэкунд.

У развіцелів React часта бывае так, што яны ставяцца да базы дадзэнняў як да ўсё толькі іншага об’екта JavaScript у памяці. Запит Mongoose або вызов Prisma праходзіць безпосередна да обробніка маршрута, рэзультат вяртаецца, і на гэтым усё. Гэта працуе добра, пакуль продукт не начынае прымаць рэальных корыстувачоў. У такі момент выкараняецца максымальнае выкарыстанне CPU базы дадзэнняў, графік затрымкаў вашага API пачынае выглядаць як края прыпаю, а корыстувачы стаяць і сакліваюцца, калі спінеры так і не завяршаюць свою роботу.

Глыбокая тэхнічная рашытка

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

Пачніце з патэрна Cache-Aside. Калі прыходзіць запит, спачатку шукайце яго ў Redis. Якщо значэнне існуе і не выйшла за термін дзейнароўкі, адразу ж вярніце яго, без жадных запытоў да базы дадзэння. Якщо яго няма, значыць адбылася неудача кэшавання: запытайце галоўную базу дадзэння, запісайце рэзультат у Redis з пазначаным терміном дзейнароўкі, а пасля вярніце яго. Кожны наступны запыт да тых сабе ж дадзэнняў будзе адпраўляцца з памяці за менш чым мілісэкунду.

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

Ось як выглядае гэты патэрн у кодзе:

// cache.js
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
async function getCachedOrFetch(key, fetchFn, ttlSeconds = 300) {
  // 1. Check cache
  const cached = await redis.get(key);
  if (cached) {
    return JSON.parse(cached);
  }

  // 2. Cache miss: fetch from database
  const data = await fetchFn();

  // 3. Store in Redis with TTL
  await redis.setex(key, ttlSeconds, JSON.stringify(data));

  return data;
}
// Route handler
app.get('/api/products/:id', async (req, res) => {
  const { id } = req.params;

  const product = await getCachedOrFetch(
    `product:${id}`,
    () => db.product.findById(id), // Only runs on cache miss
    600 // 10 minutes
  );

  if (!product) return res.status(404).json({ error: 'Not found' });
  res.json(product);
});

А ось крок анулювання, який выконваецца кожны раз, калі апдейтуўаецца запис продукту:

async function updateProduct(id, updates) {
  const updated = await db.product.update(id, updates);
  await redis.del(`product:${id}`); // Invalidate
  return updated;
}

Існуе таксама рашынка ў аспекте модэлювання, якую варта прыміць свядома: трэба знати, калі PostgreSQL ўжо кращы выбар чым MongoDB. Якщо ваш фронтэнд на React патрабуе атрыбутаваць дашборды, якія выкарстаныя для з’еднання дадзеных, агрэгацыі і аналізу тайм-серый, то правильна настроўаная база дадзеных PostgreSQL завжды будзе кращая за MongoDB. MongoDB ўжо хорашы выбар для дадзеных у формате дакументаў, якія не маюць большых зв’язкаў. PostgreSQL становіцься кращым выбарам, калі вашы дадзеныя маюць чыстую структуру, а запиты залежнаць ад JOIN.

Памылка №3: Стварэнне сінхронных монолітаў

Уявіце, што корыстнік завантажае фота высокай раздзялованасці через вашу аплікацыю на React. Сервер Express прыме файл, перадае яго па пяць разных размераў, стыскае кожную версію, выкладзе ўсе яны у S3, апдэйтуе рядок у базе дадзенаў, і толькі пасля таго, калі ўсё гэта завершыцца, адправіце код 200 OK. Корыстнік будзе працягваць спаглядаць індыкатор загрузкі прыблізна дванаццаць секунд. А якщо наэтапе перадачы размераў выйдзе проблема, весь запит будзе зупінены, і яму даведзяцца завантажыць файл зноў.

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

Глыбокая тэхнічная адпаведзь

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

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

Ось той жа прыем практыкуецца з BullMQ і Redis:

// api.js — The HTTP layer
const { Queue } = require('bullmq');
const imageQueue = new Queue('image-processing', { connection: redis });
app.post('/api/upload', upload.single('image'), async (req, res) => {
  const job = await imageQueue.add('process-image', {
    filePath: req.file.path,
    userId: req.user.id,
  }, {
    attempts: 3,
    backoff: { type: 'exponential', delay: 2000 }
  });

  // Return immediately. Work happens elsewhere.
  res.status(202).json({ jobId: job.id, status: 'processing' });
});
// worker.js - The background processor
const { Worker } = require('bullmq');
const sharp = require('sharp');
const imageWorker = new Worker('image-processing', async (job) => {
  const { filePath, userId } = job.data;

  // Heavy work happens here, not in the API thread
  const sizes = [1200, 800, 400, 200];
  const uploads = sizes.map(async (size) => {
    const buffer = await sharp(filePath)
      .resize(size)
      .jpeg({ quality: 85 })
      .toBuffer();

    return s3.upload({
      Bucket: 'my-bucket',
      Key: `users/${userId}/image-${size}.jpg`,
      Body: buffer,
    }).promise();
  });

  await Promise.all(uploads);
  await db.user.update(userId, { imageProcessed: true });

  // Notify frontend via WebSocket or push notification
  await notifyUser(userId, { type: 'IMAGE_READY' });

}, { connection: redis, concurrency: 5 });

Едыная задача шару API — обробка HTTP-запытоў; едыная задача шару працоўнікаў — выкорыстанне CPU. Яны масштабуюцца на разных асах. Дзесяць тысяч заваношэнняў можа значыць запуск дзесяці экземпляраў API разам з пяцьдзюцятьма працоўнікамі. Самэ гэта раздзеленне і є справжняя архітэктура.

Памылка №4: Падазрэнне, што бэкэнд — гэта проста ўсё той жа JavaScript

Калі ваша аплікацыя на React выклікае кашлюнку 500, нейкразныя дзіяння — гэта падбіраць яе, паказаць спавящую панель і перадаць проблему таму, хто адпавядае за бэкенд. Але ў большасці рэальных систем фронтэнд і бэкенд не аддзельваюцца чыста за дапамою мовы програмавання. API-шлюз, з якім вы вярбуецеся, можа працаваць на Node.js, тады як основная бізнес-логіка знаходзіцца ў сервісе на Java, аутентыкацыя выканана на Go, а механізм рэкамендацый напісаны на Python.

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

Глубокая тэхнічная парадка

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

След лептаса Java расказвае сваю історію знізу дагоры — асалодны прычынны факт зазвычай знаходзіцца бліжэй да верху, напрыклад у вачынку NullPointerException, ConnectionPoolTimeoutException чыў HeapSpaceError. Калі вы бачыце Caused by: java.sql.SQLException: Connection pool exhausted, гэта адразу паведамляе вас, што база дадзеных перагружана адночаснымі запытамі. Рашэнне зовсім не ў шаре Java — яго трэба шукать у налаштаваннях пулу з’яўленняў чы ў оптымізацыі асновных запытаў.

Паніка ў програмах зазвычай выражаецца болей прымітна. У такіх случаях паказваецца точная горутайн, файл і рядок, дзе выйшла праця. Паведамленне на кшталт panic: runtime error: invalid memory address or nil pointer dereference означае, што якая-небудзь структура была выкорыстана раней, чым яе было ініціялізавана.

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

  • Выкаранне з’ўязкама з базай дадзеных: шукайце ў логах словы timeout, pool, connection refused або too many clients. Гэта зазвычай указвае на неабходнасць кращага пулінгу з’ўязкаў у бэкэндзе або дадаць реплікі для чытання.
  • Выкаранне памяці: шукайце ў логах контейнера записы HeapSpace, OOM або Killed. Рашэнняя зазвычай включаюць оптымізацыю запитоў, дадаць пагінацыю або падняць ліміты памяці контейнера.
  • Адыяварэннія серыявацькага процесу: стараннае адзірванне паведамленняў у дакладзе JSON parse error або cannot serialize. Гэта па-практычна завжды значыць, што формат дадзеных, які адправляе фронтэнд, больш не падходзіць таму, калі прыемлівае ўсё гэта бэкэнд.
  • Якщо ваша арганізацыя выкарыстоўвае цэнтралізаваную платформу для логавання, такую як Datadog, Splunk або ELK stack, выдзяліце час на тое, каб навучыцца правільна яе выкарыстоўваць. Паравняйце часовы пазнак з адыяварэнняў фронтэнда з записамі логаў бэкэнду, якія з’являюцца прыбліжна ў той жа момент, і следзіце за ID запиту, калі ён пераходзіць з адной службы на іншую. Інжынер фронтэнда, які можа праследаваць адзіны запит праз усю структуру, ёст калішнім тым, хто нарэшце вярнуў рэальныя спасабы адлагоджэння, а не проста падае заявку на ўсуненне бяга.

    Памылка №5: Развёртанне на адной падставе „Усё працавало добра локальна“

    Платформы такія як Heroku і Vercel гадзіны прыводзілі ў таёмніцу ад разработчыкаў проблемы, зв’язаныя з інфраструктурай. Вы падаўалі свой код, і ён проста запускаўся. Гэта чудова для стварэння пратотыпаў і вивучэння базавых аспектаў, але гэта стварае сярозны прыём у розумеенні таго, як насправды функцыонуюць системы для рэальнай експлуатаціі. Калі ў чымсь выходзіла проблема, у вас не было жадных ведамаў пра ОС, шар сетявання чы то, як керуецца контейнерамі — а відтворыць баг у локальных умовах было немагчыма, таму што ваша локальная наладка зовсім не нагадвала рэальную среду.

    Глыбокая тэхнічная адпаведнасць

    Затрачыце час на Docker, Kubernetes і CI/CD — не на такі глубокі рэвень, як у спецыяліста з інфраструктуры, але достатна, каб могцы рассуждать як архітектар. Вы должны ведаць, што насправды выканаеся з вашым кодам у той момент, калі вы яго падаўаете.

    Ценна якосць Docker заключаецца ў можлівасці повтарнага стварэння аплякацыі: Dockerfile точна паказвае, якія ОС, залежнасці і средства выканання патрэбны для правильнага запуску вашай аплякацыі. Іспользованне багатастадийнага процеса стварэння дапамагае залічыць канечны продакшн-образ лёгкім і болей безпечным, адколькі воно раздзеляе інструменты, неабяжныя для стварэння аплякацыі, і тое, што патрэбна для ўласнага яе выканання.

    Нижчэй паказан прыклад Dockerfile багатастадийнага типу, падходячага для продакшн-выкарыстання, для бэкенду на Node.js:

    # Stage 1: Build
    FROM node:20-alpine AS builder
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --only=production && npm cache clean --force
    COPY . .
    RUN npm run build
    # Stage 2: Production
    FROM node:20-alpine
    WORKDIR /app
    ENV NODE_ENV=production
    # Create non-root user for security
    RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
    USER nodejs
    # Copy only necessary files from builder
    COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
    COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
    COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
    EXPOSE 3000
    CMD ["node", "dist/main.js"]
    

    Результуючы образ залишаецца меншым за 150 МБ, адколькі ў яму не ўключаюцца компайляры TypeScript, інструменты для стварэння та карты выхідных кодаў. Ён запускаецца пад не-root аднойчынам і містіць толькі тое, што абавязкова патрэбна для выканання.

    Kubernetes абсалютная частка ў кераванні гэтымі контейнерамі у великых масштабах. Рэсурс Deployment паказвае, скількі копій вашага API трэба запускаць адразу. Рэсурс Service керуе распадзелам навантажэння між гэтымі копіямі. HorizontalPodAutoscaler аўтаматычна дадае новыя пады, калі відсотак выкарыстоўвання CPU перасягае, напрыклад, 70 процэнтоў, і зменшае ўжо існуючыя пады, калі патрабавання зменшыцца.

    Ось як выглядае гэтая наладка на практыцы:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: api-backend
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: api-backend
      template:
        metadata:
          labels:
            app: api-backend
        spec:
          containers:
          - name: api
            image: my-registry/api:latest
            ports:
            - containerPort: 3000
            env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: url
            resources:
              requests:
                memory: "256Mi"
                cpu: "250m"
              limits:
                memory: "512Mi"
                cpu: "500m"
    ---
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: api-backend-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: api-backend
      minReplicas: 3
      maxReplicas: 20
      metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization
            averageUtilization: 70
    

    Калі навантажэння расте, Kubernetes аўтаматычна запускае больш падоў; калі яно зменшыцца, ён іх знову закрывае. Ваш API не ламаецца пад такім навантажэнням — ён масштабуецца, каб яго падтрымаць.

    Мэтась CI/CD-пайплайна — гарантуваць, што тое, што вы пераканалі перед з’еднанням, будзе абсолютна тая ж версія, яка запрацюе ў працоўнай средзе. Якісна пайплайн-система выкарыстоўвае автаматызаваныя тэсты на рывень елементаў і інтеграцыі, адбывае сканаванне на абяцоўку безпекі, і толькі пасля чаго стварае адпаведны образ контейнера, усё гэта перш чым ўсё гэта будзе дазволенае для викорыстання ў рэальных умовах. Неудача на будзь-якім з этых этапаў немеджча зупіняе розгортанне — гэта той механізм, які не дазволяе бракавальным змянам дасягнуць рэальных корыстнікаў.

    Вывад

    Пераход з ролі разработчыка фронтэнду ў інжынера full-stack — гэта не проста вывучэнне новай сынтаксі чы замена React на Node.js. Гэта разумеў, як фактычна пераходзяць даны праз систему — як яны кэшуюцца, як ўсьлед за тым яны обрабоўваюцца асінхронна, і як яны розгортаюцца та масштабуюцца.

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

    Спадневаная літаратура

  • Дзесяць архітектурных прычын, якія дапамагаюць падтрымваць кодавы базы фронтэнду працездатнымі гадзінамі — Характарызуецца структурнымі прычынамі, такімі як оптымаўванне для можлівасці выдалення, чысты тэкучы дадзейнаў і ізоляція бізнес-логікі, якія спакойваюць кодавы базы працездатнымі праз гады змян.
  • Адказы на запитанні пад час абавесцяў пра React і JavaScript, якія паказваюць справжню глыбіну — Рассматрываецца моцнейшыя, более дзеярожныя адказы на распашчастыя запитанні пад час абавесцяў пра React і JavaScript, ад Віртуальнага DOM да дызайна системы, якія падтверджваюць глыбэйшую інжынерную кваліфікацыю.
  • REST vs GraphQL: Асалівыя праблемы, якія рашуеюць REST і GraphQL, их внутрэшняя працэўнасць, і скрытые праблемы, якія трэба взважыць пры выборе аднаго з іх для вашай API. — Пасвячана конкрэтным проблемам, якія рашуеюць REST і GraphQL, их внутрэшняй механізм работы, а таксама скрытым праблемам, якія трэба взважыць пры выборе аднаго з іх для вашай API.
  • Асалівыя прынцыпы кэшавання ў Redis: шаблоны, праблемы і канцэпты для спраўк — Дзеясоўваецца, як кэшавання ў Redis працуе ў дапытках на Node.js, ад методаў кэшавання і тайма-тоўкі до захоплення ад кэша, правіла вывядзення дадзенаў і распашчытных пытанняў для спраўк.
  • Проектаванне бэкенд-сістэмы на адказах: ад скарычвальніка URL да е-камерцыі — падход, які спачатку фокусуецца на трэбаваннях, для праектавання бэкенд-сістэм на Node.js: калі трэба дадзіць балансэры навантажэння, Redis, реплікі, кяухі і ліміты частоты, а такса кожнага з іх.