Sagas: عملية واحدة على تلات services من غير transaction واحدة

جارٍ التحميل…

01 / ملخص الدرس

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 بيشوفها.

02 / شرح الدرس مكتوبًا

نص شرح المعلم والكود الذي كتبه ونتيجة تشغيله، بنفس ترتيب الدرس.

أهلًا بيك. الـ 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.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.

saga.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
// 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.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.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)
  1. 01أهلًا بيك: يعني إيه backend، والكورس ده ماشي إزاي
  2. 02الملفات والفولدرات والـ terminal: تتحرك على جهازك زي المبرمجين
  3. 03Node وأول برنامج: تشغّل وتقرا رسالة الخطأ
  4. 04القيم والمتغيرات والأنواع: const و let و typeof
  5. 05القرارات والتكرار: if و === والـ truthy والـ loops
  6. 06الـ functions: parameters و return والـ arrow functions والـ scope
  7. 07الـ objects والـ arrays: destructuring و spread و map و filter و reduce
  8. 08JSON والنصوص والتواريخ: اللغة اللي الـ backend بيتكلم بيها
  9. 09الـ callbacks والـ closures وأول لمسة async: setTimeout
  10. 10الـ classes والـ errors: throw و try/catch و stack trace
  11. 11git: تحفَظ كل خطوة وترجعلها، وما تسرّبش أسرارك
  12. 12ESM و CommonJS و package exports: Node بيلاقي الـ modules إزاي
  13. 13الـ Promises و async/await: sequential و parallel والأخطاء والإلغاء
  14. 14الـ event loop والـ microtasks: Node بيعمل كل حاجة بـ thread واحد إزاي
  15. 15الـ Streams والـ Backpressure: ملفات أكبر من الـ memory
  16. 16الـ File System والـ paths: قراية وكتابة صح، ومن غير path traversal
  17. 17الـ Buffers والبيانات الـ binary: الـ bytes من جُوّه
  18. 18Crypto APIs والأسرار: random و hash و HMAC و AES-GCM
  19. 19الـ Worker Threads: شغل CPU تقيل من غير ما توقف الـ server
  20. 20الـ Errors على مستوى الـ process: operational و programmer و crash-only
  21. 21الـ Node test runner والـ debugging: tests من غير مكتبات
  22. 22HTTP من جُوّه: الـ methods والـ status codes والـ headers
  23. 23Express والـ Routing: الـ params والـ query والترتيب والـ Router
  24. 24Express 5: إيه اللي اتغيّر، وإزاي تنقل مشروع قديم
  25. 25الـ Middleware ودورة حياة الـ request جُوّه Express
  26. 26Validation للمدخلات بـ Zod: ما تصدّقش أي حاجة جاية من برة
  27. 27تصنيف الأخطاء و Problem Details: شكل واحد لكل الـ errors
  28. 28الـ CORS والـ Cookies: المتصفح بيحمي المستخدم إزاي
  29. 29رفع الملفات وحدود الحجم: multer من غير ثغرات
  30. 30الـ Realtime: Server-Sent Events و WebSockets
  31. 31OpenAPI والـ API Versioning: عقد مكتوب، وتغيير من غير ما تكسر حد
  32. 32PostgreSQL: الـ DDL وأنواع البيانات الصح لكل عمود
  33. 33الـ Constraints: PK و FK و UNIQUE و CHECK — الـ database بتحمي نفسها
  34. 34الـ Normalization والـ ERD: تقسّم البيانات صح من أول يوم
  35. 35الـ JOINs والتجميع: INNER و LEFT و GROUP BY و HAVING و window functions
  36. 36JSONB في PostgreSQL: إمتى تستخدمه، وإزاي تسأله وتعمله index
  37. 37الـ Indexes من جُوّه: B-tree و GIN و BRIN
  38. 38Partial و Covering indexes: indexes أصغر وأسرع
  39. 39الـ Pagination بالـ Keyset: ليه OFFSET بيبطّأ وبيكرر
  40. 40Full Text Search في Postgres: بحث بالكلمات، بالعربي والإنجليزي
  41. 41EXPLAIN ANALYZE والـ Planner: اقرا الـ plan وصلّح الـ query
  42. 42الـ Transactions بـ node-postgres: كله أو ولا حاجة
  43. 43الـ MVCC ومستويات الـ Isolation: transactions كتير في نفس الوقت
  44. 44الـ Locks والـ Deadlocks: مين مستني مين
  45. 45SKIP LOCKED و Job Leasing: Postgres كـ job queue
  46. 46Row Level Security والـ Multi-tenancy: كل عميل يشوف بياناته بس
  47. 47Migrations من غير downtime: expand و backfill و contract
  48. 48الـ Replication والـ Read Replicas: نسخ حية من الـ database
  49. 49الـ Connection Pooling و PgBouncer: connections أقل، requests أكتر
  50. 50الـ Partitioning والـ VACUUM والمراقبة: Postgres على المدى الطويل
  51. 51Redis وأنواع البيانات: strings و hashes و lists و sets و sorted sets
  52. 52Cache-aside والـ TTL: Redis قدام الـ database
  53. 53الـ Cache invalidation والـ Stampede: لما الـ key الشهير يموت
  54. 54Lua والـ Atomicity في Redis: كام أمر كأنهم أمر واحد
  55. 55الـ Rate Limiting في Redis: fixed window و sliding window و token bucket
  56. 56الـ Sessions في Redis: تسجيل دخول تقدر تلغيه في أي وقت
  57. 57Redis Streams والـ Consumer Groups: log بيتوزع على workers
  58. 58Pub/Sub و BullMQ: إشارات بين الـ instances، وطوابير شغل محترمة
  59. 59Redis لما يقع: الـ persistence (RDB و AOF) والـ replication
  60. 60Redis في الإنتاج: الـ ACLs والـ Cluster والمراقبة
  61. 61تخزين الـ Passwords بـ Argon2id: صح من أول مرة
  62. 62Session Fixation والـ Rotation: id جديد مع كل صلاحية جديدة
  63. 63الـ Cookie Flags والـ CSRF: المتصفح بيبعت الـ cookie، فلازم تتأكد مين اللي طلب
  64. 64الـ XSS والـ Content Security Policy: ما تخليش النص يتحوّل لـ code
  65. 65الـ OAuth و OpenID Connect و PKCE: login بـ Google من غير ما تشوف الـ password
  66. 66التأكد من الـ JWT صح: التوقيع والـ claims والـ scopes
  67. 67الـ BOLA و BFLA: الـ token السليم مش معناه إن الطلب مسموح
  68. 68الـ RBAC و ABAC والملكية: policy واحدة بدل ifs متفرقة
  69. 69الـ MFA والـ recovery: التاني factor اللي بجد
  70. 70الـ Passkeys و WebAuthn: login من غير password وضد الـ phishing
  71. 71الـ Threat Modeling بـ STRIDE: فكّر زي المهاجم قبل ما تكتب code
  72. 72الـ SQL Injection والـ Parameterization: الـ data ما تبقاش code
  73. 73الـ SSRF والـ Egress: ما تخليش الـ server بتاعك يزور الشبكة الداخلية
  74. 74الـ Security Headers والـ TLS: كل response يقول للـ browser يحمي المستخدم إزاي
  75. 75توقيع الـ Webhooks والـ Replay: الـ webhook ده جاي فعلًا من Stripe؟
  76. 76تغيير الأسرار والـ HMAC: إزاي تغيّر مفتاح من غير ما كل حاجة تقع
  77. 77سلسلة التوريد والـ SBOM: إنت واثق في ألف package ما تعرفهمش؟
  78. 78الـ Audit والـ Privacy Logs: سجّل مين عمل إيه، من غير ما تسجّل الأسرار
  79. 79اختبار الأمان و OWASP ASVS: الأمن جُوّه الـ CI، مش audit آخر السنة
  80. 80الـ Prototype Pollution والـ Resource DoS: request واحد يخلّي الكل admin أو يوقّع الـ server
  81. 81الـ SLI والـ SLO والـ Error Budget: إمتى تصحّي الـ on-call وإمتى تسيبه نايم
  82. 82الـ Event Loop Delay والـ ELU: لما Node نفسه يتخنق
  83. 83الـ Worker Threads والـ CPU Profiling: الشغل التقيل بره الـ event loop، والوقت راح فين
  84. 84الـ Memory والـ GC والـ Leaks: الـ process اللي بيكبر لحد ما يقع
  85. 85الـ N+1 Queries والـ DataLoader: صفحة واحدة بمِيّتين query
  86. 86الـ HTTP Caching والـ Compression: أسرع request هو اللي ما بيوصلش
  87. 87الـ Load Testing والـ Soak Testing: الـ server بيستحمل قد إيه بجد
  88. 88الـ Concurrency Limits والـ Bulkheads: dependency بطيئة ما توقعش الـ app كله
  89. 89الـ Timeouts والـ Retries والـ Circuit Breakers: إزاي تتعامل مع dependency بطيئة أو واقعة
  90. 90OpenTelemetry والـ Distributed Tracing: الـ request راح فين وضاع وقته فين
  91. 91Docker Layers والـ Build Context: ليه الـ build بياخد دقيقة وفيه الـ .env بتاعك
  92. 92Multi-stage Builds و Non-root: image صغيرة ومن غير root
  93. 93Docker Compose والـ Networks: الـ services بتلاقي بعض إزاي
  94. 94Docker Volumes: الـ data بتتحفَظ فين
  95. 95Healthcheck و Restart و Resource Limits: running مش معناها شغّال
  96. 96BuildKit والـ Build Secrets: الـ token ما يدخلش الـ image
  97. 97Reverse Proxy و TLS: اللي واقف قدام الـ app
  98. 98Multi-arch Images والـ Native Modules: اشتغلت عندي
  99. 99CI/CD و Rollback و Canary: الـ deploy يبقى ممل
  100. 100Kubernetes من الأول: Pods و Deployments و Services و Probes
  101. 101Modular Monolith و DDD: حدود واضحة من غير network
  102. 102Transactional Outbox و Inbox: الـ event ما يضيعش وما يتنفذش مرتين
  103. 103Sagas: عملية واحدة على تلات services من غير transaction واحدةانت هنا
  104. 104CQRS و Event Sourcing: الـ events هي الحقيقة
  105. 105الـ Queues والـ Brokers: BullMQ و RabbitMQ و Kafka و SQS
  106. 106Realtime على أكتر من instance: Redis pub/sub و presence و resume
  107. 107الـ Object Storage والـ Media: presigned URLs و uploads مباشرة
  108. 108التكامل مع الـ Email والـ Search: retries و webhooks و indexes
  109. 109GraphQL و gRPC في الإنتاج: contracts و DataLoader و limits و deadlines
  110. 110Kafka على أكتر من zone و region: الـ replication والـ ISR والـ acks والـ MirrorMaker والـ failover
  111. 111TypeScript و runtime boundaries: الـ types بتختفي، والـ schemas لأ
  112. 112ORM ولا SQL: query builders و repositories و migrations و transactions
  113. 113الـ Serverless من جُوّه: cold starts و freeze و timeouts و connections
  114. 114الـ Testing Pyramid: unit و integration و e2e على Postgres حقيقية
  115. 115Contract Testing و Property-Based Testing بـ fast-check
  116. 116الـ Payments: idempotency و webhooks و refunds و reconciliation
  117. 117الـ Timezones والـ Internationalization: UTC و DST والـ locales والـ plurals والفلوس
  118. 118الـ Privacy والـ Data Retention: consent و export و erasure و crypto shredding
  119. 119الـ ADRs والـ Feature Flags: القرارات المكتوبة والـ rollout من غير deploy
  120. 120Multi-region active-active: حل الـ conflicts بـ HLC و CRDTs
  121. 121Readiness و Liveness و Startup Probes: الـ pod جاهز ولا عايش؟
  122. 122Incident Runbooks: من الـ alert للـ postmortem