ທຸກແອັບທີ່ໃຫ້ຄວາມຮູ້ສຶກວ່າໄວ ແລະ ເຊື່ອຖືໄດ້ ລ້ວນມີ Backend ທີ່ອອກແບບມາຢ່າງດີຢູ່ເບື້ອງຫຼັງ. Backend ເປັນຜູ້ດູແລກົດທາງທຸລະກິດ, ປົກປ້ອງຂໍ້ມູນ ແລະ ຕອບຄຳຮ້ອງຂໍຫຼາຍພັນເທື່ອຕໍ່ວິນາທີ ໂດຍທີ່ບໍ່ມີໃຜສັງເກດເຫັນ. ການວາງສະຖາປັດຕະຍະກຳໃຫ້ຖືກຕັ້ງແຕ່ຕົ້ນຊ່ວຍປະຢັດເວລາແກ້ວຽກຄືນໄດ້ຫຼາຍເດືອນ, ສ່ວນການວາງຜິດຈະສະແດງອອກມາເປັນລະບົບລົ່ມ, ໜ້າເວັບຊ້າ ແລະ ຟີເຈີທີ່ໃຊ້ເວລາພັດທະນາດົນເກີນຄວນ.
ບົດຄວາມນີ້ພາໄປເບິ່ງການຕັດສິນໃຈທີ່ສຳຄັນທີ່ສຸດ: ການອອກແບບ API, ການຈັດໂຄງສ້າງບໍລິການ, ການອອກແບບຂໍ້ມູນ ແລະ ການເຮັດໃຫ້ທຸກຢ່າງເຮັດວຽກໄດ້ລຽບງ່າຍເມື່ອຜູ້ໃຊ້ເພີ່ມຂຶ້ນ.
ອອກແບບ REST API ທີ່ນັກພັດທະນາມັກໃຊ້
REST ເບິ່ງລະບົບເປັນຊັບພະຍາກອນ (resource) ເຊິ່ງເປັນຄຳນາມ ທີ່ client ຈັດການດ້ວຍ method HTTP ມາດຕະຖານເຊິ່ງເປັນຄຳກິລິຍາ. ຫົວໃຈແມ່ນຄວາມສະໝ່ຳສະເໝີ: ເມື່ອນັກພັດທະນາຮຽນຮູ້ endpoint ໜຶ່ງແລ້ວ ກໍຄວນເດົາ endpoint ທີ່ເຫຼືອໄດ້.
GET /v1/ordersສະແດງລາຍການຄຳສັ່ງຊື້ ພ້ອມການກັ່ນຕອງ ແລະ ແບ່ງໜ້າຜ່ານ query parameter.GET /v1/orders/{id}ສົ່ງຄືນຄຳສັ່ງຊື້ໜຶ່ງລາຍການ.POST /v1/ordersສ້າງຄຳສັ່ງຊື້ ແລ້ວຕອບກັບດ້ວຍ201 Createdແລະ headerLocation.PATCH /v1/orders/{id}ອັບເດດຄຳສັ່ງຊື້ບາງສ່ວນ;PUTແທນທີ່ທັງໝົດ.DELETE /v1/orders/{id}ລຶບຄຳສັ່ງຊື້ ແລ້ວຕອບກັບດ້ວຍ204 No Content.
Status code ເປັນສ່ວນໜຶ່ງຂອງສັນຍາ API, ຈຶ່ງຄວນໃຊ້ໃຫ້ແມ່ນຍຳ:
200 OK,201 Createdແລະ204 No Contentສຳລັບຄວາມສຳເລັດ.400 Bad Requestສຳລັບຄຳຮ້ອງຂໍທີ່ຮູບແບບຜິດ ແລະ422 Unprocessable Contentສຳລັບຄຳຮ້ອງຂໍທີ່ຮູບແບບຖືກແຕ່ບໍ່ຜ່ານການກວດສອບ.401 Unauthorizedເມື່ອຜູ້ເອີ້ນຍັງບໍ່ໄດ້ຢືນຢັນຕົວຕົນ, ແລະ403 Forbiddenເມື່ອຢືນຢັນຕົວຕົນແລ້ວແຕ່ບໍ່ມີສິດ.404 Not Found,409 Conflictສຳລັບສະຖານະທີ່ຂັດແຍ່ງກັນ ເຊັ່ນ ຂໍ້ມູນຊ້ຳ, ແລະ429 Too Many Requestsເມື່ອຖືກຈຳກັດອັດຕາການເອີ້ນ.5xxໃຊ້ສະເພາະຄວາມຜິດພາດຝັ່ງເຊີບເວີເທົ່ານັ້ນ, ຫ້າມໃຊ້ກັບຂໍ້ມູນທີ່ສົ່ງເຂົ້າມາຜິດ.
ນີ້ແມ່ນ endpoint ພາສາ TypeScript ທີ່ໃຊ້ Express ເຊິ່ງກວດສອບຂໍ້ມູນທີ່ສົ່ງເຂົ້າມາຕັ້ງແຕ່ຂອບເຂດຂອງລະບົບ ແລະ ຕອບກັບດ້ວຍ status code ທີ່ຖືກຕ້ອງ:
import express from 'express'
import { z } from 'zod'
import { orders } from './orders'
const app = express()
app.use(express.json())
const CreateOrder = z.object({
customerId: z.string().uuid(),
items: z.array(z.object({ sku: z.string().min(1), quantity: z.number().int().positive() })).min(1),
})
app.post('/v1/orders', async (req, res) => {
const parsed = CreateOrder.safeParse(req.body)
if (!parsed.success) {
return res.status(422).json({
type: 'https://api.example.com/problems/validation',
title: 'The request body is invalid',
status: 422,
errors: parsed.error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message })),
})
}
const order = await orders.create(parsed.data)
res.status(201).location(`/v1/orders/${order.id}`).json(order)
})ຂໍ້ຜິດພາດທີ່ສອດຄ່ອງກັນ, ການແບ່ງໜ້າ ແລະ ການຈັດການເວີຊັນ
ສົ່ງຂໍ້ຜິດພາດທຸກແບບໃນໂຄງສ້າງດຽວກັນ ເພື່ອໃຫ້ client ຈັດການໄດ້ແບບທົ່ວໄປ. ຮູບແບບ Problem Details (RFC 9457) ແມ່ນມາດຕະຖານທີ່ຮອງຮັບຢ່າງກວ້າງຂວາງ:
{
"type": "https://api.example.com/problems/out-of-stock",
"title": "Item is out of stock",
"status": 409,
"detail": "SKU TS-1042 has 0 units available; 3 were requested.",
"instance": "/v1/orders"
}- ການແບ່ງໜ້າ: ໃຊ້ການແບ່ງໜ້າແບບ cursor (
?after=ord_123&limit=50) ສຳລັບຊຸດຂໍ້ມູນຂະໜາດໃຫຍ່ ຫຼື ປ່ຽນແປງໄວ. ການແບ່ງໜ້າແບບ offset ຈະຂ້າມ ຫຼື ສະແດງລາຍການຊ້ຳ ເມື່ອມີແຖວໃໝ່ຖືກເພີ່ມໃນຂະນະທີ່ client ກຳລັງເລື່ອນໜ້າ. - ການຈັດການເວີຊັນ: ໃສ່ເວີຊັນຫຼັກໄວ້ໃນ path (
/v1/) ແລະ ເພີ່ມໄດ້ສະເພາະຟີລດ໌ໃໝ່ພາຍໃນເວີຊັນດຽວກັນ. ການລຶບ ຫຼື ປ່ຽນຊື່ຟີລດ໌ຖືເປັນການປ່ຽນແປງທີ່ເຮັດໃຫ້ໃຊ້ຮ່ວມກັນບໍ່ໄດ້. - Idempotency: ໃຫ້ client ສົ່ງ header
Idempotency-Keyມາກັບຄຳຮ້ອງຂໍPOSTເຊິ່ງເປັນຮູບແບບທີ່ API ດ້ານການຊຳລະເງິນເຮັດໃຫ້ແຜ່ຫຼາຍ, ເພື່ອໃຫ້ຄຳຮ້ອງຂໍທີ່ສົ່ງຊ້ຳບໍ່ສ້າງຄຳສັ່ງຊື້ ຫຼື ການຕັດເງິນຊ້ຳ.
Monolith ຫຼື Microservices?
Monolith ແມ່ນແອັບດຽວທີ່ deploy ທັງກ້ອນ. Microservices ແບ່ງລະບົບເປັນບໍລິການນ້ອຍໆ ທີ່ແຕ່ລະບໍລິການເປັນເຈົ້າຂອງຂໍ້ມູນຂອງຕົນເອງ ແລະ deploy ແຍກກັນ. Microservices ມັກຖືກນຳສະເໜີວ່າເປັນທາງເລືອກມາດຕະຖານສະໄໝໃໝ່, ແຕ່ຕົວຈິງແລ້ວມັນແລກຄວາມຊັບຊ້ອນຂອງໂຄດກັບຄວາມຊັບຊ້ອນໃນການດູແລລະບົບ:
- ສິ່ງທີ່ໄດ້: deploy ແຍກກັນໄດ້, ຂະຫຍາຍສະເພາະຈຸດທີ່ຮັບພາລະໜັກ, ແລະ ທີມຕ່າງໆເຮັດວຽກໄດ້ໂດຍບໍ່ກ້າວກ່າຍກັນ.
- ສິ່ງທີ່ຕ້ອງຈ່າຍ: ການເອີ້ນຜ່ານເຄືອຂ່າຍທີ່ອາດລົ້ມເຫຼວ ຫຼື ຊ້າ, ຂໍ້ມູນກະຈາຍຢູ່ຫຼາຍຖານຂໍ້ມູນໂດຍບໍ່ມີ transaction ຮ່ວມກັນ, ການ trace ແລະ debug ແບບກະຈາຍ, ແລະ ໂຄງສ້າງພື້ນຖານທີ່ຕ້ອງດູແລຫຼາຍຂຶ້ນອີກ.
ສຳລັບຜະລິດຕະພັນສ່ວນໃຫຍ່, ຈຸດເລີ່ມຕົ້ນທີ່ຖືກຕ້ອງແມ່ນ Modular Monolith: deploy ເປັນກ້ອນດຽວ ແຕ່ມີຂອບເຂດພາຍໃນທີ່ເຂັ້ມງວດ (ຄຳສັ່ງຊື້, ການເກັບເງິນ, ລາຍການສິນຄ້າ) ເຊິ່ງສື່ສານກັນຜ່ານ interface ທີ່ກຳນົດໄວ້ເທົ່ານັ້ນ. ຖ້າມື້ໜຶ່ງໂມດູນໃດຕ້ອງຂະຫຍາຍ ຫຼື ອອກເວີຊັນແຍກ, ຂອບເຂດຂອງມັນກໍສະອາດພໍທີ່ຈະແຍກອອກມາເປັນບໍລິການໄດ້.
ແຍກລະບົບຕາມຂອບເຂດທາງທຸລະກິດ ເມື່ອທີມງານ ຫຼື ຄວາມຕ້ອງການດ້ານການຂະຫຍາຍລະບົບບັງຄັບໃຫ້ຕ້ອງເຮັດ, ບໍ່ແມ່ນເພາະແຜນຜັງສະຖາປັດຕະຍະກຳເບິ່ງງາມກວ່າ.
ເມື່ອບໍລິການຕ້ອງສື່ສານກັນ, ໃຫ້ໃຊ້ການເອີ້ນແບບຊິງໂຄຣນັສ (REST ຫຼື gRPC) ເມື່ອຜູ້ເອີ້ນຕ້ອງການຄຳຕອບທັນທີ, ແລະ ໃຊ້ອີເວັນແບບອະຊິງໂຄຣນັສຜ່ານ message broker ເມື່ອບໍລິການອື່ນພຽງແຕ່ຕ້ອງຕອບສະໜອງ ເຊັ່ນ: "ມີຄຳສັ່ງຊື້ໃໝ່" ຫຼື "ໄດ້ຮັບການຊຳລະເງິນແລ້ວ". ເພື່ອສົ່ງອີເວັນຢ່າງເຊື່ອຖືໄດ້, ໃຫ້ຂຽນອີເວັນລົງຕາຕະລາງ outbox ພາຍໃນ transaction ດຽວກັນກັບການປ່ຽນແປງທາງທຸລະກິດ, ແລ້ວໃຫ້ process ແຍກຕ່າງຫາກສົ່ງຕໍ່ໄປຫາ broker. ບໍ່ດັ່ງນັ້ນ, ຖ້າລະບົບລົ່ມລະຫວ່າງສອງຂັ້ນຕອນ ຂໍ້ມູນ ແລະ ອີເວັນຈະບໍ່ກົງກັນ.
ອອກແບບຖານຂໍ້ມູນໃຫ້ຂະຫຍາຍໄດ້
ບັນຫາດ້ານປະສິດທິພາບສ່ວນໃຫຍ່ແມ່ນບັນຫາຂອງຖານຂໍ້ມູນ. ຫຼັກການບໍ່ເທົ່າໃດຂໍ້ຊ່ວຍໄດ້ຫຼາຍ:
- ອອກແບບຕາມໂດເມນກ່ອນ ແລ້ວຈຶ່ງຄິດເຖິງ query. ເລີ່ມຈາກແບບ normalize ໃຫ້ແຕ່ລະຂໍ້ເທັດຈິງຢູ່ບ່ອນດຽວ, ແລ້ວຈຶ່ງ denormalize ຢ່າງຕັ້ງໃຈສະເພາະບ່ອນທີ່ query ທີ່ວັດແທກແລ້ວຕ້ອງການ.
- ໃຫ້ຖານຂໍ້ມູນບັງຄັບໃຊ້ກົດ: constraint ເຊັ່ນ
NOT NULL, foreign key,UNIQUEແລະCHECKຈັບບັກທີ່ໂຄດຂອງແອັບພາດໄປ. - ເກັບຈຳນວນເງິນເປັນຈຳນວນເຕັມ (ໜ່ວຍນ້ອຍທີ່ສຸດຂອງສະກຸນເງິນ) ຫຼື ຊະນິດ decimal, ຫ້າມໃຊ້ຕົວເລກທົດສະນິຍົມແບບ floating-point.
- ສ້າງ index ສຳລັບ query ທີ່ໃຊ້ແທ້ ແລະ ກວດສອບດ້ວຍ
EXPLAIN. ທຸກ index ຊ່ວຍໃຫ້ອ່ານໄວຂຶ້ນ ແຕ່ເຮັດໃຫ້ຂຽນຊ້າລົງ. - ປ່ຽນ schema ຜ່ານ migration ທີ່ມີເວີຊັນ ເຊິ່ງເຮັດວຽກອັດຕະໂນມັດ, ຫ້າມແກ້ດ້ວຍມືໃນ production.
CREATE TABLE orders (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers (id),
status TEXT NOT NULL CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled')),
total_cents BIGINT NOT NULL CHECK (total_cents >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Serves "latest orders for a customer" without scanning the whole table
CREATE INDEX orders_customer_created_idx ON orders (customer_id, created_at DESC);ລະວັງບັນຫາ N+1 query: ການໂຫຼດຄຳສັ່ງຊື້ 50 ລາຍການ ແລ້ວເອີ້ນ query ເພີ່ມອີກໜຶ່ງເທື່ອຕໍ່ຄຳສັ່ງຊື້ເພື່ອດຶງຂໍ້ມູນລູກຄ້າ. ໃຫ້ດຶງຂໍ້ມູນທີ່ກ່ຽວຂ້ອງໃນ query ດຽວດ້ວຍ join ຫຼື WHERE id IN (…) ແບບເປັນຊຸດແທນ. ແລະ ໃຊ້ connection pool ນຳ, ເພາະການເປີດການເຊື່ອມຕໍ່ຖານຂໍ້ມູນໃໝ່ທຸກຄຳຮ້ອງຂໍນັ້ນຊ້າ ແລະ ເຮັດໃຫ້ຊັບພະຍາກອນເຊີບເວີໝົດໄວ.
ຮູບແບບການຂະຫຍາຍລະບົບທີ່ໄດ້ຜົນ
- ເຮັດໃຫ້ເຊີບເວີແອັບບໍ່ມີສະຖານະ (stateless) (ເກັບ session ໃນຖານຂໍ້ມູນ ຫຼື ແຄດ, ເກັບໄຟລ໌ໃນ object storage) ເພື່ອໃຫ້ແລ່ນຈັກສຳເນົາກໍໄດ້ຢູ່ຫຼັງ load balancer.
- ເກັບແຄດການອ່ານທີ່ມີຕົ້ນທຶນສູງດ້ວຍອາຍຸສັ້ນໆ, ແລະ ຕັດສິນໃຈຕັ້ງແຕ່ຕົ້ນວ່າແຕ່ລະລາຍການໃນແຄດຈະຖືກລ້າງແນວໃດເມື່ອຂໍ້ມູນປ່ຽນ.
- ຍ້າຍວຽກທີ່ຊ້າໄປໄວ້ໃນຄິວເບື້ອງຫຼັງ: ອີເມວ, ລາຍງານ, ການປະມວນຜົນຮູບພາບ ແລະ ການເອີ້ນບໍລິການພາຍນອກ. API ຕອບກັບທັນທີ ແລ້ວ worker ຈະເຮັດວຽກຕາມຫຼັງ.
- ເພີ່ມ read replica ເມື່ອການອ່ານມີສັດສ່ວນສູງ, ແລະ ສົ່ງ query ສຳລັບລາຍງານໄປທີ່ນັ້ນແທນຖານຂໍ້ມູນຫຼັກ.
- ຈຳກັດອັດຕາການເອີ້ນ endpoint ສາທາລະນະ ເພື່ອປົກປ້ອງລະບົບຈາກການໃຊ້ງານໃນທາງທີ່ຜິດ ແລະ client ທີ່ເຮັດວຽກຜິດພາດ.
- ແບ່ງ partition ຫຼື shard ຂໍ້ມູນເປັນຂັ້ນຕອນສຸດທ້າຍ ເມື່ອຖານຂໍ້ມູນດຽວທີ່ປັບແຕ່ງດີແລ້ວຮັບບໍ່ໄຫວແທ້ໆເທົ່ານັ້ນ.
ຄວາມໝັ້ນຄົງ ແລະ ການສັງເກດການລະບົບ
ລະບົບແບບກະຈາຍລົ້ມເຫຼວໃນແບບທີ່ເກີດພຽງບາງສ່ວນ ແລະ ຊວນໃຫ້ສັບສົນ, ຈຶ່ງຕ້ອງອອກແບບໄວ້ລ່ວງໜ້າ. ຕັ້ງ timeout ໃຫ້ທຸກການເອີ້ນອອກໄປພາຍນອກ. ລອງໃໝ່ (retry) ສະເພາະການເຮັດວຽກແບບ idempotent ໂດຍໃຊ້ exponential backoff ແລະ ການໜ່ວງເວລາແບບສຸ່ມ (jitter) ເພື່ອບໍ່ໃຫ້ທຸກ client ລອງໃໝ່ພ້ອມກັນ. ໃຊ້ circuit breaker ເພື່ອຢຸດເອີ້ນບໍລິການທີ່ເຫັນຊັດວ່າລົ່ມຢູ່.
ຈາກນັ້ນເຮັດໃຫ້ລະບົບສັງເກດການໄດ້: log ແບບມີໂຄງສ້າງ ທີ່ມີ request ID, metric ສຳລັບຄວາມໜ່ວງ, ປະລິມານການໃຊ້ງານ, ຂໍ້ຜິດພາດ ແລະ ລະດັບການໃຊ້ຊັບພະຍາກອນ, ແລະ distributed tracing ເຊັ່ນ ດ້ວຍ OpenTelemetry ທີ່ສະແດງວ່າເວລາຖືກໃຊ້ໄປຢູ່ໃສລະຫວ່າງບໍລິການຕ່າງໆ. ແຈ້ງເຕືອນຕາມອາການທີ່ຜູ້ໃຊ້ຮູ້ສຶກໄດ້ ເຊັ່ນ ອັດຕາຂໍ້ຜິດພາດ ແລະ ຄວາມໜ່ວງ, ແທນທີ່ຈະແຈ້ງທຸກເທື່ອທີ່ CPU ພຸ່ງຂຶ້ນ.
ປະເດັນສຳຄັນ
- ອອກແບບ API ອ້ອມຮອບຊັບພະຍາກອນ, ໃຊ້ status code ຢ່າງແມ່ນຍຳ ແລະ ມີຮູບແບບຂໍ້ຜິດພາດດຽວທີ່ສອດຄ່ອງກັນ.
- ເລີ່ມຈາກ Modular Monolith; ແຍກບໍລິການຕາມຂອບເຂດທາງທຸລະກິດເມື່ອມີຄວາມຈຳເປັນແທ້.
- ໃຫ້ຖານຂໍ້ມູນຮັກສາຄວາມຖືກຕ້ອງຂອງຂໍ້ມູນ, ສ້າງ index ຕາມ query ທີ່ໃຊ້ແທ້ ແລະ ຫຼີກລ່ຽງ N+1 query.
- ຂະຫຍາຍລະບົບດ້ວຍເຊີບເວີແບບ stateless, ແຄດ ແລະ ຄິວ, ພ້ອມວາງ timeout, retry ແລະ ການສັງເກດການລະບົບໄວ້ຕັ້ງແຕ່ຕົ້ນ.
ສະຫຼຸບ
Backend ທີ່ຂະຫຍາຍໄດ້ບໍ່ໄດ້ມາຈາກເທັກໂນໂລຊີແປກໃໝ່, ແຕ່ມາຈາກການຕັດສິນໃຈທຳມະດາທີ່ເຮັດຢ່າງສະໝ່ຳສະເໝີທຸກຈຸດ: ສັນຍາ API ທີ່ຊັດເຈນ, ຂອບເຂດທີ່ສະອາດລະຫວ່າງສ່ວນຕ່າງໆຂອງລະບົບ, ຖານຂໍ້ມູນທີ່ອອກແບບມາຢ່າງດີ ແລະ ນິໄສໃນການດູແລລະບົບທີ່ເຮັດໃຫ້ເຫັນບັນຫາໄດ້ແຕ່ເນີ່ນໆ. ເຮັດສິ່ງເຫຼົ່ານີ້ໃຫ້ຖືກ, ແລ້ວການເພີ່ມກຳລັງຮອງຮັບຈະເປັນພຽງການປ່ຽນການຕັ້ງຄ່າ ບໍ່ແມ່ນການຂຽນລະບົບໃໝ່.
ອ່ານເພີ່ມເຕີມ
- #Architecture
- #Databases
- #Microservices
- #REST API
- #TypeScript




