Sagas: عملية واحدة على تلات services من غير transaction واحدة
الـ checkout بيحجز stock، ويخصم من المحفظة، ويحجز شحن، وكل واحدة service ليها database. الشحن فشل فالـ stock محجوز والفلوس اتخصمت، والـ process وقعت في النص فمحدش فاكر، والـ retry خصم مرتين. بنفهم ليه مفيش transaction بتجمعهم، وليه الـ two phase commit مش الحل، والـ saga بالـ compensations، والـ orchestration قصاد الـ choreography، والـ state المحفوظ والـ recovery، والـ idempotency keys، والـ stuck sagas، والـ pending states اللي الـ user بيشوفها.
نص شرح المعلم والكود الذي كتبه ونتيجة تشغيله، بنفس ترتيب الدرس.
أهلًا بيك. الـ outbox حلّ مشكلة event واحد. النهارده المشكلة أكبر: عملية واحدة بالنسبة للـ user، الـ checkout، بس هي في الحقيقة تلات عمليات، على تلات services، وكل service ليها database لوحدها. بص على checkout dot naive dot js. لو الشحن فشل، الـ stock محجوز والفلوس اتخصمت. والـ user شايف error، ومش فاهم ليه فلوسه اتخصمت. لو الـ process وقعت بعد الـ debit، مفيش أي حتة مكتوب فيها إن فيه checkout كان شغّال. محدش هيكمّله، ومحدش هيرجّعه. ولو الـ debit رجّع timeout، مش عارف هو اتعمل ولا لأ. لو عدته من غير idempotency key، ممكن يتخصم مرتين. طب ليه مش transaction واحدة؟ لأن كل service ليها database، ومفيش BEGIN بيجمعهم. فيه حاجة اسمها two phase commit، بتعمل كده، بس محتاجة كل الـ databases تدعمها، وأي واحد بطيء بيقفل الكل، والـ coordinator لو وقع في النص، الـ locks بتفضل. علشان كده أغلب الـ systems النهارده مش بتستخدمها بين الـ services.
الحل اسمه saga. سلسلة من الـ local transactions، كل واحدة في service لوحدها، وكل واحدة ليها compensation: عملية تانية بتلغي اللي هي عملته. الحجز ليه release، والخصم ليه refund، وحجز الشحن ليه cancel. ولو step فشلت، بنعمل الـ compensations للي خلص، بالعكس: آخر واحدة الأول. وخلّي بالك: الـ compensation مش rollback. الـ refund مش بيمسح الخصم من التاريخ، هو عملية جديدة بترجّع الفلوس. والـ user ممكن يكون شاف الخصم في التطبيق. علشان كده الـ saga مش atomic زي الـ transaction. ده اسمه eventual consistency، والـ UI لازم يعبّر عنه: الـ order حالته pending لحد ما الـ saga تخلص.
وفيه طريقتين تمشّي بيهم الـ saga. choreography: مفيش مدير. كل service بتسمع events وتطلّع events. الـ inventory حجز وطلّع StockReserved، والـ wallet سمعه وخصم وطلّع WalletDebited، وتكمّل بنفس الطريقة. بسيطة في الأول، بس مع خمس أو ست steps، محدش يقدر يقولك الـ flow فين، والـ logic متوزّع على كل الـ services. orchestration: فيه orchestrator واحد بيقول لكل service تعمل إيه، وبيستنى الرد، وبيقرّر الخطوة الجاية أو الـ compensation. الـ flow كله في مكان واحد، تقدر تقراه وتعمله test. في الدرس ده هنعمل orchestrator. والـ orchestrator ده لازم يحفظ الـ state بتاعه بعد كل خطوة، علشان لو وقع، حد تاني يكمّل.
والقواعد اللي بتخلّي الـ saga تشتغل في الحقيقة: كل action وكل compensation لازم idempotent. لأن الـ orchestrator ممكن يقع بعد ما الـ service عملت الحاجة، وقبل ما يسجّل إنها اتعملت. فلما يقوم، هيعيدها. علشان كده كل step ليها idempotency key ما بيتغيرش، مبني من الـ saga id واسم الـ step، والـ service لو شافت نفس الـ key، بترجّع نفس الرد من غير ما تعمل حاجة. الـ retry للـ errors المؤقتة بس، زي خمسمية وتلاتة أو timeout. أما الرصيد مش كفاية، أعيده ليه؟ والـ compensation ممكن تفشل هي كمان. بعد كام محاولة، الـ saga بتبقى stuck، ودي لازم alert وحد يبصلها. فلوس عميل محجوزة مش حاجة نسيبها لـ retry للأبد. التحدي. عندك services dot js: تلات services حقيقية على loopback، inventory و wallet و shipping، وكل واحدة بتحترم الـ Idempotency-Key header. و checkout dot js فيه تعريف الـ saga، ولو شغّلته لوحده بيبقى orchestrator بيعمل process.exit بعد step معيّنة، يعني crash حقيقي. و lab dot js بيشغّل ست سيناريوهات. والمطلوب saga dot js. كل التفاصيل في الكومنت: defineSaga، والـ stores، و runSaga والـ drive بالـ retries والـ compensations، و resumeSaga و recoverAll. اقرا الكومنت كويس، وخصوصًا مكان الـ put بالنسبة للـ try، ونشغّل test dot js لحد ما تبقى أربعتاشر من أربعتاشر.
اكتب saga.js: defineSaga، و createMemoryStore و createFileStore، و runSaga بالـ retries والـ compensations والـ stuck، و resumeSaga و recoverAll. كل التفاصيل في الكومنت، وشغّل node test.js.
الحل. الـ drive هي القلب: جزء للـ running، وجزء للـ compensating، والاتنين بيكمّلوا من اللي متسجّل في الـ record. فالـ run والـ resume هما نفس الـ code. الـ put بعد كل تغيير، وبره الـ try بتاع الـ action، علشان crash في الـ store ما يتفهمش إنه فشل للـ step. والـ file store بيكتب في file مؤقت وبعدين rename، والـ rename على نفس الـ disk بيحصل مرة واحدة، فالـ file يا القديم يا الجديد، عمره ما يبقى نص. نشغّل lab dot js.
// saga.js: saga orchestrator بالـ steps والـ compensations، والـ retries، و state محفوظ يقدر يكمّل بعد crash
import { readFileSync, writeFileSync, renameSync, existsSync } from "node:fs";
export function defineSaga(name, steps) {
if (!Array.isArray(steps) || !steps.length) throw new Error("الـ saga محتاجة step واحدة على الأقل");
const seen = new Set();
for (const s of steps) {
if (!s?.name || seen.has(s.name)) throw new Error(`اسم step فاضي أو متكرر: ${s?.name}`);
if (typeof s.action !== "function") throw new Error(`${s.name}: مفيش action`);
if (s.compensate != null && typeof s.compensate !== "function") throw new Error(`${s.name}: الـ compensate لازم function`);
seen.add(s.name);
}
return Object.freeze({ name, steps: Object.freeze([...steps]) });
}
export function createMemoryStore() {
const m = new Map();
return {
get: (id) => (m.has(id) ? structuredClone(m.get(id)) : null),
put: (r) => { m.set(r.sagaId, structuredClone(r)); },
list: () => [...m.values()].map((r) => structuredClone(r)),
};
}
export function createFileStore(path) {
const read = () => (existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {});
return {
get: (id) => read()[id] ?? null,
put: (r) => {
const all = read();
all[r.sagaId] = r;
writeFileSync(path + ".tmp", JSON.stringify(all, null, 2));
renameSync(path + ".tmp", path);
},
list: () => Object.values(read()),
};
}
const summary = (r) => ({
sagaId: r.sagaId, status: r.status, failedStep: r.failedStep, error: r.error, compensationError: r.compensationError,
completed: [...r.completed], compensated: [...r.compensated], results: structuredClone(r.results),
});
const ctxFor = (r, step, attempt) => ({
sagaId: r.sagaId, input: structuredClone(r.input), results: structuredClone(r.results),
idempotencyKey: `${r.sagaId}:${step.name}`, attempt,
});
async function drive(def, r, store, { compensationAttempts = 3 } = {}) {
if (r.status === "running") {
for (const step of def.steps) {
if (r.completed.includes(step.name)) continue;
let attempt = r.attempts[step.name] ?? 0;
for (;;) {
let out, failure = null;
try { out = await step.action(ctxFor(r, step, attempt)); }
catch (e) { failure = e instanceof Error ? e : new Error(String(e)); }
// الـ put بره الـ try: لو الـ store نفسه وقع، ده crash مش فشل للـ step
if (!failure) {
r.results[step.name] = out ?? null;
r.completed.push(step.name);
store.put(r);
break;
}
if (failure.retryable && attempt < (step.retries ?? 0)) {
r.attempts[step.name] = ++attempt;
store.put(r);
continue;
}
r.status = "compensating"; r.failedStep = step.name; r.error = failure.message;
store.put(r);
break;
}
if (r.status !== "running") break;
}
if (r.status === "running") { r.status = "completed"; store.put(r); ret
…المخرجات
$ node lab.js البداية: stock mug 5 | balances mahmoud 1000، sara 300، youssef 800، karim 500 | bookings 0 s1: محمود، اتنين mug بأربعمية، للقاهرة: completed | completed [reserve,debit,ship] | compensated [] stock mug 3 | balances mahmoud 600، sara 300، youssef 800، karim 500 | bookings 1 s2: سارة، الرصيد مش كفاية: compensated عند debit | completed [reserve] | compensated [reserve] error: /wallet/debit 402: الرصيد مش كفاية stock mug 3 | balances mahmoud 600، sara 300، youssef 800، karim 500 | bookings 1 s3: يوسف لسيوة، والشحن مش بيوصل هناك: compensated عند ship | completed [reserve,debit] | compensated [debit,reserve] error: /shipping/book 422: مفيش شحن لـ siwa stock mug 3 | balances mahmoud 600، sara 300، youssef 800، karim 500 | bookings 1 s4: كريم، والمحفظة بترجّع 503 مرتين: completed | completed [reserve,debit,ship] | compensated [] calls على debit في s4: 3 و stock mug 2 | balances mahmoud 600، sara 300، youssef 800، karim 400 | bookings 2 s5: الشحن فشل، والـ refund نفسه واقع: stuck عند ship | completed [reserve,debit] | compensated [] error: /shipping/book 422: مفيش شحن لـ siwa compensationError: debit: /wallet/refund 503: service unav …
s1: التلات steps نجحوا. s2: سارة رصيدها مش كفاية. الـ debit فشل بأربعمية واتنين، مفيش retry، والـ reserve اترجع، والـ stock رجع زي ما كان. s3: سيوة. الشحن فشل، فالـ refund الأول وبعدين الـ release، بالعكس. ورصيد يوسف رجع تمانمية. s4: المحفظة رجّعت خمسمية وتلاتة مرتين، والـ saga عملت retry ونجحت في التالتة. s5: الشحن فشل، والـ refund نفسه واقع. بعد تلات محاولات، stuck، وفلوس يوسف لسه مخصومة. والـ recoverAll ما لمسهاش، لأنها محتاجة إنسان. ولما الـ refund رجع، الـ admin رجّعها compensating، والـ resume كمّلها. s6: أهم واحد. الـ orchestrator خرج بـ exit code مِيّة سبعة وتلاتين بعد الـ debit، والـ store لسه شايف reserve بس. orchestrator جديد قام، وعمل recoverAll، وعاد الـ debit بنفس الـ key، والـ wallet رجّعت نفس الـ txId. ومحمود اتخصم مرة واحدة بس. ونشغّل test dot js.
المخرجات
$ node test.js ✅ defineSaga: validation ✅ createMemoryStore: نسخ مش references، و list، و null ✅ createFileStore: بيكتب على الـ disk، و store جديد بيقرا نفس الحاجة، ومفيش tmp فاضل ✅ runSaga: كله نجح، والـ ctx فيه sagaId و idempotencyKey و attempt والـ results اللي قبله، والـ input نسخة ✅ فشل مش retryable: compensations بالعكس للي خلص بس، وبالـ results بتاعته ✅ retries: retryable بس، والـ attempt بيزيد، وبيتسجّل في الـ store ✅ compensation فاشلة: بعد تلات محاولات ← stuck، و compensationError، والـ error الأصلي فاضل، و recoverAll ما بيلمسهاش ✅ stuck بعد ما حد صلّح: compensating و resume ← compensated، و compensationError بيتمسح ✅ الـ codes: SAGA_EXISTS و SAGA_NOT_FOUND و SAGA_MISMATCH ✅ crash بعد ما الـ action نجح وقبل ما يتسجّل: الـ resume بيعيده بنفس الـ key، والـ effect مرة واحدة ✅ crash في نص الـ compensation: الـ resume بيكمّل، واللي ما اتسجّلش بيتعاد، علشان كده الـ compensate لازم idempotent ✅ recoverAll: running و compensating بتاعة نفس الـ saga بس ✅ resume لـ saga خلصت: ولا action، وبيرجّع الـ summary زي ما هو ✅ crash مع file store: process جديدة بـ store جديد على نفس الـ file بتكمّل 14/14
كله نجح. لحد دلوقتي الـ database بتاعة كل service هي نفس الـ model للقراية والكتابة. بس فيه systems الكتابة فيها محتاجة rules كتير، والقراية محتاجة شكل مختلف خالص، dashboards و search. الدرس الجاي: CQRS، إنك تفصل الـ write model عن الـ read model، و Event Sourcing، إنك تحفظ الـ events نفسها بدل الـ state، وتبني منها أي شكل محتاجه.
// checkout.naive.js: تلات services، وكل واحدة ليها database، والـ code بيكلّمهم ورا بعض وخلاص
export async function checkout(order) {
const { reservationId } = await post("/inventory/reserve", { sku: order.sku, qty: order.qty });
const { txId } = await post("/wallet/debit", { customer: order.customer, amount: order.amount });
const { bookingId } = await post("/shipping/book", { city: order.city });
// ← لو الشحن فشل: الـ stock محجوز والفلوس اتخصمت، ومحدش هيرجّعهم
// ← لو الـ process وقعت بعد الـ debit: محدش فاكر إن فيه checkout كان شغّال أصلًا
// ← ولو الـ debit رجّع timeout، تعيده؟ ممكن العميل يتخصم مرتين
return { reservationId, txId, bookingId };
}
const post = async (path, body) => (await fetch("http://services" + path, { method: "POST", body: JSON.stringify(body) })).json();// services.js: تلات services حقيقية على 127.0.0.1: inventory و wallet و shipping، وكل واحدة بتحترم الـ Idempotency-Key
import http from "node:http";
export async function startServices({ stock, balances, cities = ["cairo", "alex", "giza"] }) {
const s = { stock: { ...stock }, balances: { ...balances }, reservations: {}, txs: {}, bookings: {}, calls: {}, fail: {} };
const replay = new Map();
let seq = 0;
const id = (p) => `${p}${++seq}`;
const routes = {
"/inventory/reserve": ({ sku, qty }) => {
if ((s.stock[sku] ?? 0) < qty) return [409, { error: `مفيش stock كفاية من ${sku}` }];
s.stock[sku] -= qty;
const r = id("r"); s.reservations[r] = { sku, qty };
return [200, { reservationId: r }];
},
"/inventory/release": ({ reservationId }) => {
const r = s.reservations[reservationId];
if (r) { s.stock[r.sku] += r.qty; delete s.reservations[reservationId]; }
return [200, { released: !!r }];
},
"/wallet/debit": ({ customer, amount }) => {
if ((s.balances[customer] ?? 0) < amount) return [402, { error: "الرصيد مش كفاية" }];
s.balances[customer] -= amount;
const t = id("t"); s.txs[t] = { customer, amount };
return [200, { txId: t }];
},
"/wallet/refund": ({ txId }) => {
const t = s.txs[txId];
if (t) { s.balances[t.customer] += t.amount; delete s.txs[txId]; }
return [200, { refunded: !!t }];
},
"/shipping/book": ({ city }) => {
if (!cities.includes(city)) return [422, { error: `مفيش شحن لـ ${city}` }];
const b = id("b"); s.bookings[b] = { city };
return [200, { bookingId: b }];
},
"/shipping/cancel": ({ bookingId }) => { const had = !!s.bookings[bookingId]; delete s.bookings[bookingId]; return [200, { cancelled: had }]; },
};
const server = http.createServer(async (req, res) => {
let body = ""; for await (const c of req) body += c;
const send = (code, obj) => res.writeHead(code, { "content-type": "application/json" }).end(JSON.stringify(obj));
if (req.url === "/state") return send(200, s);
const route = routes[req.url];
if (!route) return send(404, { error: "not found" });
s.calls[req.url] = (s.calls[req.url] ?? 0) + 1;
if (s.fail[req.url] > 0) { s.fail[req.url]--; return send(503, { error: "service unavailable" }); }
const key = req.headers["idempotency-key"];
const k = `${req.url}:${key}`;
if (key && replay.has(k)) return send(...replay.get(k));
const out = route(JSON.parse(body || "{}"));
if (key) replay.set(k, out);
send(...out);
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const url = `http://127.0.0.1:${server.address().port}`;
return {
url,
state: () => structuredClone(s),
failNext: (path, n) => { s.fail[path] = n; },
close: () => new Promise((r) => server.close(r)),
};
}// checkout.js: الـ checkout saga: احجز stock، واخصم من المحفظة، واحجز شحن. ولو اتشغّل لوحده بيبقى orchestrator ممكن يقع
import { fileURLToPath } from "node:url";
import { defineSaga, runSaga, createFileStore } from "./saga.js";
export function checkoutSaga(baseUrl) {
const call = async (path, body, key) => {
const res = await fetch(baseUrl + path, {
method: "POST",
headers: { "content-type": "application/json", ...(key && { "idempotency-key": key }) },
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) throw Object.assign(new Error(`${path} ${res.status}: ${data.error}`), { retryable: res.status >= 500 });
return data;
};
return defineSaga("checkout", [
{
name: "reserve", retries: 2,
action: ({ input, idempotencyKey }) => call("/inventory/reserve", { sku: input.sku, qty: input.qty }, idempotencyKey),
compensate: ({ results, idempotencyKey }) => call("/inventory/release", { reservationId: results.reserve.reservationId }, idempotencyKey + ":undo"),
},
{
name: "debit", retries: 3,
action: ({ input, idempotencyKey }) => call("/wallet/debit", { customer: input.customer, amount: input.amount }, idempotencyKey),
compensate: ({ results, idempotencyKey }) => call("/wallet/refund", { txId: results.debit.txId }, idempotencyKey + ":undo"),
},
{
name: "ship", retries: 2,
action: ({ input, idempotencyKey }) => call("/shipping/book", { city: input.city }, idempotencyKey),
compensate: ({ results, idempotencyKey }) => call("/shipping/cancel", { bookingId: results.ship.bookingId }, idempotencyKey + ":undo"),
},
]);
}
// node checkout.js <url> <store.json> <sagaId> <input json> <crashAfter>
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const [url, file, sagaId, input, crashAfter] = process.argv.slice(2);
const def = checkoutSaga(url);
const crashing = { ...def, steps: def.steps.map((s) => ({ ...s, action: async (ctx) => { const out = await s.action(ctx); if (s.name === crashAfter) process.exit(137); return out; } })) };
console.log(JSON.stringify(await runSaga(crashing, JSON.parse(input), { store: createFileStore(file), sagaId })));
}03 / دروس المسار (122)
- 01أهلًا بيك: يعني إيه backend، والكورس ده ماشي إزاي
- 02الملفات والفولدرات والـ terminal: تتحرك على جهازك زي المبرمجين
- 03Node وأول برنامج: تشغّل وتقرا رسالة الخطأ
- 04القيم والمتغيرات والأنواع: const و let و typeof
- 05القرارات والتكرار: if و === والـ truthy والـ loops
- 06الـ functions: parameters و return والـ arrow functions والـ scope
- 07الـ objects والـ arrays: destructuring و spread و map و filter و reduce
- 08JSON والنصوص والتواريخ: اللغة اللي الـ backend بيتكلم بيها
- 09الـ callbacks والـ closures وأول لمسة async: setTimeout
- 10الـ classes والـ errors: throw و try/catch و stack trace
- 11git: تحفَظ كل خطوة وترجعلها، وما تسرّبش أسرارك
- 12ESM و CommonJS و package exports: Node بيلاقي الـ modules إزاي
- 13الـ Promises و async/await: sequential و parallel والأخطاء والإلغاء
- 14الـ event loop والـ microtasks: Node بيعمل كل حاجة بـ thread واحد إزاي
- 15الـ Streams والـ Backpressure: ملفات أكبر من الـ memory
- 16الـ File System والـ paths: قراية وكتابة صح، ومن غير path traversal
- 17الـ Buffers والبيانات الـ binary: الـ bytes من جُوّه
- 18Crypto APIs والأسرار: random و hash و HMAC و AES-GCM
- 19الـ Worker Threads: شغل CPU تقيل من غير ما توقف الـ server
- 20الـ Errors على مستوى الـ process: operational و programmer و crash-only
- 21الـ Node test runner والـ debugging: tests من غير مكتبات
- 22HTTP من جُوّه: الـ methods والـ status codes والـ headers
- 23Express والـ Routing: الـ params والـ query والترتيب والـ Router
- 24Express 5: إيه اللي اتغيّر، وإزاي تنقل مشروع قديم
- 25الـ Middleware ودورة حياة الـ request جُوّه Express
- 26Validation للمدخلات بـ Zod: ما تصدّقش أي حاجة جاية من برة
- 27تصنيف الأخطاء و Problem Details: شكل واحد لكل الـ errors
- 28الـ CORS والـ Cookies: المتصفح بيحمي المستخدم إزاي
- 29رفع الملفات وحدود الحجم: multer من غير ثغرات
- 30الـ Realtime: Server-Sent Events و WebSockets
- 31OpenAPI والـ API Versioning: عقد مكتوب، وتغيير من غير ما تكسر حد
- 32PostgreSQL: الـ DDL وأنواع البيانات الصح لكل عمود
- 33الـ Constraints: PK و FK و UNIQUE و CHECK — الـ database بتحمي نفسها
- 34الـ Normalization والـ ERD: تقسّم البيانات صح من أول يوم
- 35الـ JOINs والتجميع: INNER و LEFT و GROUP BY و HAVING و window functions
- 36JSONB في PostgreSQL: إمتى تستخدمه، وإزاي تسأله وتعمله index
- 37الـ Indexes من جُوّه: B-tree و GIN و BRIN
- 38Partial و Covering indexes: indexes أصغر وأسرع
- 39الـ Pagination بالـ Keyset: ليه OFFSET بيبطّأ وبيكرر
- 40Full Text Search في Postgres: بحث بالكلمات، بالعربي والإنجليزي
- 41EXPLAIN ANALYZE والـ Planner: اقرا الـ plan وصلّح الـ query
- 42الـ Transactions بـ node-postgres: كله أو ولا حاجة
- 43الـ MVCC ومستويات الـ Isolation: transactions كتير في نفس الوقت
- 44الـ Locks والـ Deadlocks: مين مستني مين
- 45SKIP LOCKED و Job Leasing: Postgres كـ job queue
- 46Row Level Security والـ Multi-tenancy: كل عميل يشوف بياناته بس
- 47Migrations من غير downtime: expand و backfill و contract
- 48الـ Replication والـ Read Replicas: نسخ حية من الـ database
- 49الـ Connection Pooling و PgBouncer: connections أقل، requests أكتر
- 50الـ Partitioning والـ VACUUM والمراقبة: Postgres على المدى الطويل
- 51Redis وأنواع البيانات: strings و hashes و lists و sets و sorted sets
- 52Cache-aside والـ TTL: Redis قدام الـ database
- 53الـ Cache invalidation والـ Stampede: لما الـ key الشهير يموت
- 54Lua والـ Atomicity في Redis: كام أمر كأنهم أمر واحد
- 55الـ Rate Limiting في Redis: fixed window و sliding window و token bucket
- 56الـ Sessions في Redis: تسجيل دخول تقدر تلغيه في أي وقت
- 57Redis Streams والـ Consumer Groups: log بيتوزع على workers
- 58Pub/Sub و BullMQ: إشارات بين الـ instances، وطوابير شغل محترمة
- 59Redis لما يقع: الـ persistence (RDB و AOF) والـ replication
- 60Redis في الإنتاج: الـ ACLs والـ Cluster والمراقبة
- 61تخزين الـ Passwords بـ Argon2id: صح من أول مرة
- 62Session Fixation والـ Rotation: id جديد مع كل صلاحية جديدة
- 63الـ Cookie Flags والـ CSRF: المتصفح بيبعت الـ cookie، فلازم تتأكد مين اللي طلب
- 64الـ XSS والـ Content Security Policy: ما تخليش النص يتحوّل لـ code
- 65الـ OAuth و OpenID Connect و PKCE: login بـ Google من غير ما تشوف الـ password
- 66التأكد من الـ JWT صح: التوقيع والـ claims والـ scopes
- 67الـ BOLA و BFLA: الـ token السليم مش معناه إن الطلب مسموح
- 68الـ RBAC و ABAC والملكية: policy واحدة بدل ifs متفرقة
- 69الـ MFA والـ recovery: التاني factor اللي بجد
- 70الـ Passkeys و WebAuthn: login من غير password وضد الـ phishing
- 71الـ Threat Modeling بـ STRIDE: فكّر زي المهاجم قبل ما تكتب code
- 72الـ SQL Injection والـ Parameterization: الـ data ما تبقاش code
- 73الـ SSRF والـ Egress: ما تخليش الـ server بتاعك يزور الشبكة الداخلية
- 74الـ Security Headers والـ TLS: كل response يقول للـ browser يحمي المستخدم إزاي
- 75توقيع الـ Webhooks والـ Replay: الـ webhook ده جاي فعلًا من Stripe؟
- 76تغيير الأسرار والـ HMAC: إزاي تغيّر مفتاح من غير ما كل حاجة تقع
- 77سلسلة التوريد والـ SBOM: إنت واثق في ألف package ما تعرفهمش؟
- 78الـ Audit والـ Privacy Logs: سجّل مين عمل إيه، من غير ما تسجّل الأسرار
- 79اختبار الأمان و OWASP ASVS: الأمن جُوّه الـ CI، مش audit آخر السنة
- 80الـ Prototype Pollution والـ Resource DoS: request واحد يخلّي الكل admin أو يوقّع الـ server
- 81الـ SLI والـ SLO والـ Error Budget: إمتى تصحّي الـ on-call وإمتى تسيبه نايم
- 82الـ Event Loop Delay والـ ELU: لما Node نفسه يتخنق
- 83الـ Worker Threads والـ CPU Profiling: الشغل التقيل بره الـ event loop، والوقت راح فين
- 84الـ Memory والـ GC والـ Leaks: الـ process اللي بيكبر لحد ما يقع
- 85الـ N+1 Queries والـ DataLoader: صفحة واحدة بمِيّتين query
- 86الـ HTTP Caching والـ Compression: أسرع request هو اللي ما بيوصلش
- 87الـ Load Testing والـ Soak Testing: الـ server بيستحمل قد إيه بجد
- 88الـ Concurrency Limits والـ Bulkheads: dependency بطيئة ما توقعش الـ app كله
- 89الـ Timeouts والـ Retries والـ Circuit Breakers: إزاي تتعامل مع dependency بطيئة أو واقعة
- 90OpenTelemetry والـ Distributed Tracing: الـ request راح فين وضاع وقته فين
- 91Docker Layers والـ Build Context: ليه الـ build بياخد دقيقة وفيه الـ .env بتاعك
- 92Multi-stage Builds و Non-root: image صغيرة ومن غير root
- 93Docker Compose والـ Networks: الـ services بتلاقي بعض إزاي
- 94Docker Volumes: الـ data بتتحفَظ فين
- 95Healthcheck و Restart و Resource Limits: running مش معناها شغّال
- 96BuildKit والـ Build Secrets: الـ token ما يدخلش الـ image
- 97Reverse Proxy و TLS: اللي واقف قدام الـ app
- 98Multi-arch Images والـ Native Modules: اشتغلت عندي
- 99CI/CD و Rollback و Canary: الـ deploy يبقى ممل
- 100Kubernetes من الأول: Pods و Deployments و Services و Probes
- 101Modular Monolith و DDD: حدود واضحة من غير network
- 102Transactional Outbox و Inbox: الـ event ما يضيعش وما يتنفذش مرتين
- 103Sagas: عملية واحدة على تلات services من غير transaction واحدةانت هنا
- 104CQRS و Event Sourcing: الـ events هي الحقيقة
- 105الـ Queues والـ Brokers: BullMQ و RabbitMQ و Kafka و SQS
- 106Realtime على أكتر من instance: Redis pub/sub و presence و resume
- 107الـ Object Storage والـ Media: presigned URLs و uploads مباشرة
- 108التكامل مع الـ Email والـ Search: retries و webhooks و indexes
- 109GraphQL و gRPC في الإنتاج: contracts و DataLoader و limits و deadlines
- 110Kafka على أكتر من zone و region: الـ replication والـ ISR والـ acks والـ MirrorMaker والـ failover
- 111TypeScript و runtime boundaries: الـ types بتختفي، والـ schemas لأ
- 112ORM ولا SQL: query builders و repositories و migrations و transactions
- 113الـ Serverless من جُوّه: cold starts و freeze و timeouts و connections
- 114الـ Testing Pyramid: unit و integration و e2e على Postgres حقيقية
- 115Contract Testing و Property-Based Testing بـ fast-check
- 116الـ Payments: idempotency و webhooks و refunds و reconciliation
- 117الـ Timezones والـ Internationalization: UTC و DST والـ locales والـ plurals والفلوس
- 118الـ Privacy والـ Data Retention: consent و export و erasure و crypto shredding
- 119الـ ADRs والـ Feature Flags: القرارات المكتوبة والـ rollout من غير deploy
- 120Multi-region active-active: حل الـ conflicts بـ HLC و CRDTs
- 121Readiness و Liveness و Startup Probes: الـ pod جاهز ولا عايش؟
- 122Incident Runbooks: من الـ alert للـ postmortem