Contract Testing و Property-Based Testing بـ fast-check
الـ orders team غيّرت اسم field، والـ tests بتاعتهم وبتاعتنا عدّت، والـ checkout وقع في الـ production، لأن الـ stub اللي بنختبر عليه كاتبينه بإيدينا من سنة. والـ staging المشترك بقى زحمة ومحدش بيعتمد عليه. والـ split بتاع الفلوس عدّى الـ test الوحيد بتاعه، وبيضيّع قروش. بنفهم الـ consumer-driven contracts: الـ consumer يكتب محتاج إيه بالظبط بـ matchers، ويختبر على mock من الـ contract، والـ provider يتحقق منه قبل كل deploy، والـ provider states، والـ broker و can-i-deploy، والـ tolerant reader.
نص شرح المعلم والكود الذي كتبه ونتيجة تشغيله، بنفس ترتيب الدرس.
أهلًا بيك. عندنا دلوقتي أكتر من service. الـ checkout بيكلّم الـ orders service. بص على checkout dot naive dot test dot js. الـ test بيبدّل fetch بـ stub، بيرجّع order شكله زي ما احنا فاكرين. وتحت، splitMoney، بتقسم مبلغ على أكتر من جزء، وليها test. الـ orders team غيّرت qty لـ quantity. الـ tests بتاعتهم عدّت، لأنهم غيّروها. والـ tests بتاعتنا عدّت، لأن الـ stub لسه بيرجّع qty. وأول ما عملوا deploy، الـ checkout وقع. الـ stub بيختبر إننا متفقين مع نفسنا، مش مع الـ service الحقيقية. والحل التاني، staging فيه كل الـ services: بطيء، ودايمًا فيه service مش على الـ version الصح، والـ tests بتقع لأسباب مالهاش علاقة. و splitMoney عدّت الـ test الوحيد بتاعها، تسعين على تلاتة. بس واحد على اتنين بترجّع واحد وواحد، يعني اتنين. ومِيّة على تلاتة بترجّع تلاتة وتلاتين تلات مرات، يعني قرش ضاع. الـ test بيختبر الحالة اللي احنا فكرنا فيها، والـ bugs في الحالات اللي ما فكرناش فيها.
نبدأ بالـ contracts. الفكرة اسمها consumer-driven contract testing، وأشهر أداة ليها اسمها Pact. الـ consumer، اللي هو الـ checkout، بيكتب contract: أنا هبعت GET على orders اتنين وأربعين، ومستني status ميتين، و body فيه id و status و total_cents و items، كل item فيه sku و qty. بس كده. مش كل الـ response، اللي أنا بستخدمه بس. الـ contract ده بيتستخدم مرتين: عند الـ consumer: بيعمل منه mock server، والـ client بتاع الـ checkout يتختبر عليه. فالـ mock مش مكتوب بالإيد، ده طالع من الـ contract. عند الـ provider: الـ orders service بيشغّل نفسه بجد، وأداة بتبعتله كل request في الـ contract، وتتأكد إن الـ response مطابق. لو مش مطابق، الـ build بتاع الـ provider يفشل، قبل الـ deploy.
الـ matchers. لو الـ contract قال total_cents لازم يبقى أحداشر ألف وأربعمية بالضبط، الـ provider الحقيقي هيرجّع order تاني بسعر تاني، ويفشل. فاحنا مش عايزين القيمة، عايزين الشكل. like: نفس النوع، رقم يعني رقم، و string يعني string، مش نفس القيمة. eachLike: array، فيه على الأقل item واحد، وكل item شكله كده. integer: رقم صحيح، مش أي رقم. regex: string مطابق لـ pattern، زي الـ status لازم يبقى pending أو paid أو shipped. و iso8601 للتواريخ. وكل matcher معاه example. الـ example ده اللي الـ mock بيرجّعه للـ consumer، والـ matcher هو اللي بيتطابق مع الـ provider. وفيه قاعدة مهمة اسمها tolerant reader. الـ consumer يقرا اللي هو محتاجه بس، ويتجاهل أي حاجة زيادة. والـ contract بيتطابق بنفس الطريقة: لو الـ provider زوّد field جديد، عادي. ولو شال field محدش من الـ consumers كاتبه في الـ contract، عادي برضه. ودي أحلى حاجة في الـ contracts: الـ provider يعرف بالضبط مين بيستخدم إيه. لو عايز يشيل internal_note، يبص في الـ contracts، يلاقي محدش بيستخدمه، فيشيله وهو مطمّن. أما لو غيّر اسم qty، أو غيّر نوع total من رقم لـ string، الـ verify يفشل، ويقوله بالضبط: items صفر qty missing. وساعتها يا يتكلم مع الـ checkout team، يا يعمل الـ field الجديد جنب القديم لفترة، زي ما اتكلمنا في درس الـ API versioning.
الـ provider states. الـ contract بيقول: GET على orders اتنين وأربعين، ميتين. طب الـ provider لما يتعمله verify، منين هيجيب order اتنين وأربعين؟ كل interaction معاها state، جملة زي order اتنين وأربعين موجود، أو user خمسة موجود. والـ provider بيكتب function، قبل كل interaction، تجهّز البيانات دي في الـ database بتاعته، زي الـ factories اللي عملناها الدرس اللي فات. كده الـ verify بيتعمل على الـ code الحقيقي والـ database الحقيقية، بس ببيانات معروفة. والـ contracts بتتحفظ في مكان مشترك اسمه broker، زي Pact Broker. كل consumer بيرفع الـ contract بتاعه بالـ version، وكل provider بيرفع نتيجة الـ verify. وقبل أي deploy فيه سؤال اسمه can I deploy: الـ version ده متوافق مع الـ versions اللي شغالة في الـ production من كل الناحيتين؟
إمتى الـ contracts مناسبة؟ لما يكون عندك services انت أو الـ teams بتاعتك اللي بتكتبهم، وبيكلّموا بعض بـ HTTP أو messages. الـ contract بيحل محل جزء كبير من الـ e2e tests بين الـ services، وهو أسرع بكتير، لأن كل service بيتختبر لوحده. بس مش بيختبر كل حاجة: مش بيختبر الـ business logic جُوّه الـ provider، ده دور الـ unit والـ integration. ومع API عامة ليها آلاف الـ clients انت مش عارفهم، الأحسن OpenAPI schema و versioning. ولو بتستخدم GraphQL أو gRPC، الـ schema نفسها contract، وفيه أدوات بتقارن الـ schema القديمة بالجديدة وتقولك فيه breaking change ولا لأ.
ننتقل للنوع التاني: الـ property-based testing. الـ test العادي: input معين، و output معين. تسعين على تلاتة يبقى تلاتين وتلاتين وتلاتين. والمشكلة إنك بتختار الحالات بنفسك، وبتختار اللي انت فكرت فيه. الـ property: قاعدة لازم تفضل صح لأي input. أي مبلغ على أي عدد أجزاء: المجموع لازم يبقى هو المبلغ بالضبط. والفرق بين أكبر جزء وأصغر جزء مش أكتر من قرش. وعدد الأجزاء صح. وبعدين أداة زي fast-check بتولّد مِيّة حالة عشوائية، أو ألف، وتجرّب القاعدة على كل واحدة. ولو لقت حالة بتفشل، تعمل shrinking: تصغّرها لحد ما توصل لأبسط حالة بتفشل. فبدل ما تقولك تلاتين ألف على اتنين بايزة، تقولك واحد على اتنين بايزة.
طب ألاقي الـ properties إزاي؟ فيه أنواع بتتكرر: roundtrip: حاجة وعكسها ترجّعك مكانك. format وبعدين parse، أو encode وبعدين decode، أو serialize وبعدين deserialize. invariants: حاجة لازم تفضل صح، زي المجموع في الـ split، أو الـ list بعد الـ sort نفس الـ items ومترتبة. قوانين جبرية: commutative يعني الترتيب مش مهم، زي دمج cart مع cart. و associative يعني التجميع مش مهم. و identity يعني فيه حاجة لما تدمجها ما بتغيّرش، زي cart فاضي. و idempotent يعني لو عملتها مرتين زي مرة. oracle: تقارن بـ implementation تاني بسيط وبطيء بس انت متأكد منه. ومن غير crash: أي input، الـ function ما ترميش غير الـ errors المتوقعة. و fast-check عنده arbitraries لكل حاجة: integers و strings و records و arrays، وتقدر تبني منهم arbitrary لـ order كامل مطابق للـ contract، وتستخدمه في tests الـ API.
التحدي. عندك provider dot js، الـ orders service بـ Express، بتلات versions، وفيه setState. و contract dot js، الـ contract اللي الـ checkout كاتبه. و checkout dash client dot js، الـ client. و lab dot js و test dot js. والمطلوب contracts dot js. كل التفاصيل في الكومنت: الـ matchers، و matches بالترتيب والرسايل بالضبط، و exampleOf، و startMockProvider، و verifyProvider، و splitMoney و formatMoney و parseMoney و mergeCarts، و orderArb. اقرا الكومنت كويس، ونشغّل test dot js لحد ما تبقى أربعتاشر من أربعتاشر.
اكتب contracts.js: like و eachLike و integer و regex و iso8601، و matches recursive بالـ loose والـ paths، و exampleOf، و startMockProvider بالـ unmatched و missing، و verifyProvider بالـ states، و splitMoney و formatMoney و parseMoney من غير floats، و mergeCarts، و orderArb بـ fast-check. كل التفاصيل في الكومنت، وشغّل node test.js.
الحل. matches: الـ missing الأول، وبعدين الـ matchers، وبعدين الـ arrays والـ objects، وفي الآخر الـ literals. والـ loose بينزل مع الـ recursion. الـ mock: أول interaction الـ method والـ path والـ body بتاعها مطابقين، والـ response بـ exampleOf. الـ verify: الـ state، وبعدين الـ request بـ exampleOf، وبعدين الـ status والـ body. والـ split: نقسم الـ absolute، والباقي على الأوائل، والإشارة في الآخر. والـ parse بالـ regex والـ integers من غير parseFloat. نشغّل lab dot js.
// contracts.js: consumer-driven contracts بـ matchers و mock provider و verifyProvider، و functions متختبرة بـ properties
// lab.js و test.js بيعملوا import من هنا: like و eachLike و integer و regex و iso8601 و matches و exampleOf و startMockProvider و verifyProvider و splitMoney و formatMoney و parseMoney و mergeCarts و orderArb
import http from "node:http";
import fc from "fast-check";
const M = "$matcher";
const J = (x) => JSON.stringify(x);
const typeOf = (x) => (x === null ? "null" : Array.isArray(x) ? "array" : typeof x);
const isMatcher = (x) => x !== null && typeof x === "object" && M in x;
export const like = (value) => ({ [M]: "like", value });
export const eachLike = (value, { min = 1 } = {}) => ({ [M]: "eachLike", value, min });
export const integer = (example = 1) => ({ [M]: "integer", example });
export const regex = (pattern, example) => ({ [M]: "regex", pattern: pattern instanceof RegExp ? pattern.source : pattern, example });
export const iso8601 = (example = "2026-01-01T00:00:00.000Z") => regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/, example);
export function matches(expected, actual, path = "$", loose = false) {
if (actual === undefined) return [`${path}: missing`];
if (isMatcher(expected)) {
const kind = expected[M];
if (kind === "like") return matches(expected.value, actual, path, true);
if (kind === "integer") return Number.isInteger(actual) ? [] : [`${path}: expected integer, got ${J(actual)}`];
if (kind === "regex") return typeof actual === "string" && new RegExp(expected.pattern).test(actual) ? [] : [`${path}: expected to match /${expected.pattern}/, got ${J(actual)}`];
if (kind === "eachLike") {
if (!Array.isArray(actual)) return [`${path}: expected array, got ${typeOf(actual)}`];
if (actual.length < expected.min) return [`${path}: expected at least ${expected.min} items, got ${actual.length}`];
return actual.flatMap((item, i) => matches(expected.value, item, `${path}[${i}]`, true));
}
}
if (Array.isArray(expected)) {
if (!Array.isArray(actual)) return [`${path}: expected array, got ${typeOf(actual)}`];
if (loose) return expected.length ? actual.flatMap((item, i) => matches(expected[0], item, `${path}[${i}]`, true)) : [];
if (actual.length !== expected.length) return [`${path}: expected ${expected.length} items, got ${actual.length}`];
return expected.flatMap((e, i) => matches(e, actual[i], `${path}[${i}]`, false));
}
if (typeOf(expected) === "object") {
if (typeOf(actual) !== "object") return [`${path}: expected object, got ${typeOf(actual)}`];
return Object.keys(expected).flatMap((k) => matches(expected[k], actual[k], `${path}.${k}`, loose));
}
if (loose) return typeOf(expected) === typeOf(actual) ? [] : [`${path}: expected ${typeOf(expected)}, got ${typeOf(actual)}`];
return expected === actual ? [] : [`${path}: expected ${J(expected)}, got ${J(actual)}`];
}
export function exampleOf(x) {
if (isMatche
…المخرجات
$ node lab.js
--- 1) الـ consumer: الـ checkout بيختبر الـ client بتاعه على mock من الـ contract ---
getOrder(42) ← {"id":42,"status":"paid","totalCents":11400,"lines":["2 × BOK-001"],"placedAt":"2026-04-01T10:00:00.000Z"}
getOrder(7) ← null
placeOrder ← {"id":1,"totalCents":5700}
interactions ما اتجرّبتش: []
الـ contract اتكتب في pacts/checkout-web-orders-service.json، وبيتبعت للـ provider (أو لـ broker)
--- 2) الـ provider: كل version بيتحقق قصاد الـ contract قبل الـ deploy ---
v1 (الحالي): ✔ can deploy
v2 (شال internal_note وزوّد currency): ✔ can deploy
v3 (غيّر qty لـ quantity و total_cents لـ total string): ✘ can't deploy
get existing order: $.total_cents: missing | $.items[0].qty: missing
place order: $.total_cents: expected integer, got "57"
--- 3) property: الـ split القديم بـ Math.round ---
fast-check لقى حالة بعد 2 محاولة، وصغّرها 5 مرة لـ: total=1 parts=2
naiveSplit(1, 2) = [1,1] ← المجموع مش 1
splitMoney: ألف حالة عدّت، ومثال: splitMoney(100, 3) = [34,33,33]
--- 4) property: parse بـ parseFloat ---
أصغر حالة: 29 cents ← "0.29" ← parseFloat × مِيّة = 28.999999999999996 ← 28
--- 5) orderArb: orders عشوائية مطابقة للـ contract ---
…الأول: الـ client اتختبر على mock طالع من الـ contract، وكل الـ interactions اتجرّبت، والـ contract اتكتب في file. التاني: version واحد عدّى. واتنين شال internal_note وزوّد currency، وعدّى برضه، لأن محدش بيستخدمهم. وتلاتة غيّر الأسماء والـ types، والـ verify قاله بالضبط إيه اللي اتكسر، وده قبل ما يعمل deploy. التالت: fast-check لقى bug في الـ split القديم، وصغّره لـ واحد على اتنين. و splitMoney عدّت ألف حالة. الرابع: parseFloat ضيّع قرش في تسعة وعشرين قرش، لأن صفر وتسعة وعشرين في مِيّة مش تسعة وعشرين بالضبط في الـ floats. والخامس: orders عشوائية، كلها مطابقة للـ contract. ونشغّل test dot js.
المخرجات
$ node test.js
✅ literals بالقيمة، و like بالنوع بس ["$.id: expected number, got string"]
✅ integer و regex و iso8601 بالـ path في رسالة الـ error ["$.qty: expected integer, got 1.5","$.sku: expected to match /^[A-Z]+$/, got \"ab\"","$.at: expected to match /^\\d{4}-/d{2}-/d{2}T/d{2}:\\d{2}:\\d{2}(\\./d+)?(Z|[+-]\\d{2}:\\d{2})$/, got \"2026-04-01\""]
✅ eachLike: array و min، وكل item بالنوع، والـ path فيه الـ index ["$: expected at least 2 items, got 1"]
✅ الـ fields الزيادة مسموحة (tolerant reader)، والناقصة error، والـ literal arrays بالطول ["$.a.c: missing"]
✅ exampleOf بيعمل body من الـ matchers، ومطابق ليهم {"id":42,"status":"paid","total_cents":11400,"items":[{"sku":"BOK-001","qty":2}],"created_at":"2026-04-01T10:00:00.000Z"}
✅ mock provider: الـ consumer test بيشتغل على الـ contract من غير الـ provider الحقيقي {"id":42,"status":"paid","totalCents":11400,"lines":["2 × BOK-001"],"placedAt":"2026-04-01T10:00:00.000Z"}
✅ mock provider: request مش في الـ contract ← 500 و unmatched، و missing بيقول مين ما اتجرّبش ["place order"]
✅ mock provider بيطابق الـ request body بالـ matchers {"id":1,"totalCents":5700}
✅ verifyProvider: الـ provider الحقيقي بيحقق الـ contract، والـ states ات
…كله نجح. جبنا سيرة الفلوس كتير. الدرس الجاي: الـ payments بجد. الـ checkout مع payment provider، والـ webhooks اللي بتوصل متأخرة ومكررة وبترتيب غلط، والـ idempotency في كل خطوة، والـ refunds، والـ reconciliation.
// checkout.naive.test.js: الـ tests بتاعة الـ checkout قبل كده
import { test } from "node:test";
import assert from "node:assert/strict";
import { createOrdersClient } from "./checkout-client.js";
// stub كتبناه بإيدينا من سنة، على حسب ما كنا فاكرين الـ orders service بيرجّع
globalThis.fetch = async () => new Response(JSON.stringify({ id: 42, status: "paid", total_cents: 11400, items: [{ sku: "BOK-001", qty: 2 }], created_at: "2026-04-01T10:00:00Z" }));
test("getOrder", async () => {
const o = await createOrdersClient("http://orders").getOrder(42);
assert.equal(o.totalCents, 11400); // بيعدّي دايمًا، حتى لو الـ orders service غيّر كل حاجة
});
// الحل التاني اللي جربناه: staging فيه الـ services كلها شغالة مع بعض
// كل team بتستنى الـ staging يبقى فاضي وكل الـ services فيه على الـ versions الصح، والـ tests بتقع لأسباب مالهاش علاقة
function splitMoney(total, parts) {
return Array.from({ length: parts }, () => Math.round(total / parts));
}
test("splitMoney", () => {
assert.deepEqual(splitMoney(90, 3), [30, 30, 30]); // الحالة الوحيدة اللي فكرنا فيها
});// provider.js: الـ orders service (الـ provider). version بيغيّر شكل الـ response زي ما بيحصل في الحقيقة مع الوقت
import express from "express";
export function createProvider({ version = 1 } = {}) {
const orders = new Map();
const users = new Set();
let nextId = 100;
const app = express();
app.use(express.json());
const view = (o) => {
const base = { id: o.id, status: o.status, total_cents: o.total, items: o.items, created_at: o.createdAt, internal_note: "vip" };
if (version === 2) { delete base.internal_note; base.currency = "EGP"; } // شلنا field محدش بيستخدمه وزودنا واحد جديد
if (version === 3) { base.items = o.items.map(({ sku, qty }) => ({ sku, quantity: qty })); base.total = String(o.total / 100); delete base.total_cents; } // غيّرنا الأسماء والـ types
return base;
};
app.get("/orders/:id", (req, res) => {
const o = orders.get(Number(req.params.id));
if (!o) return res.status(404).json({ error: "NOT_FOUND" });
res.json(view(o));
});
app.post("/orders", (req, res) => {
const { userId, items } = req.body ?? {};
if (!users.has(userId)) return res.status(422).json({ error: "UNKNOWN_USER" });
if (!Array.isArray(items) || items.length === 0) return res.status(422).json({ error: "EMPTY_ORDER" });
const o = { id: nextId++, status: "pending", items: items.map(({ sku, qty }) => ({ sku, qty })), total: items.reduce((s, i) => s + i.qty * 5700, 0), createdAt: new Date().toISOString() };
orders.set(o.id, o);
const v = view(o);
res.status(201).json({ id: v.id, total_cents: v.total_cents ?? v.total });
});
// provider states: الـ verifier بينادي دي قبل كل interaction علشان يجهّز البيانات
async function setState(state) {
orders.clear(); users.clear();
if (state === "order 42 exists") orders.set(42, { id: 42, status: "paid", total: 11400, items: [{ sku: "BOK-001", qty: 2 }], createdAt: "2026-04-01T10:00:00.000Z" });
else if (state === "user 5 exists") users.add(5);
else if (state === "order 7 does not exist") {}
else throw new Error(`unknown provider state: ${state}`);
}
return { app, setState };
}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