الـ Testing Pyramid: unit و integration و e2e على Postgres حقيقية
الـ tests عندنا بتاخد ربع ساعة، وبتفشل كل يوم الصبح لحد ما حد يعيد تشغيلها، وبتلمس الـ database بتاعة الـ dev، وكل test معتمد على اللي قبله، والـ mocks بتعدّي والـ production بيقع. بنفهم الـ testing pyramid: إيه اللي يتختبر unit وإيه integration وإيه e2e وليه، والـ mocks والـ fakes وإمتى تبطّلهم، والـ integration على Postgres حقيقية مش sqlite، والعزل: database من template، و transaction بتترجع، و truncate، والـ factories، والوقت المزيّف، والـ flaky tests وأسبابها، و node:test.
نص شرح المعلم والكود الذي كتبه ونتيجة تشغيله، بنفس ترتيب الدرس.
أهلًا بيك. بص على orders dot naive dot test dot js. دي الـ tests اللي كانت عندنا لـ service الـ orders. فيه tests، وده كويس. بس تعالى نشوف إيه اللي بيحصل لما نشغّلها. الـ tests بتكلّم الـ database بتاعة الـ dev، اللي الناس بتشتغل عليها. فـ orders count بيستنى واحد، ولقى ألف وميتين، لأن حد عمل orders امبارح. get order معتمد إن create order اتشغّل قبله ونجح، ومحطوط في global. شغّله لوحده، أو بترتيب تاني، يفشل. الـ coupon test بيعمل sleep دقيقة كاملة. الـ server على port تلات آلاف. اتنين في الـ team شغّلوا الـ tests في نفس الوقت على الـ CI، واحد فيهم فشل. test بيعمل spy على Math dot round، فلو حد غيّر الـ implementation من غير ما يغيّر النتيجة، الـ test يفشل. وعشرين test للـ pricing كلهم عن طريق HTTP و database، كل واحد بياخد وقت، والـ suite بقت ربع ساعة. النتيجة: الناس بقت تعيد تشغيل الـ CI لحد ما يخضّر، ومحدش بيعتمد على النتيجة.
نبدأ من الأول: الـ tests بتعمل إيه؟ بتديك confidence إنك تغيّر الـ code من غير ما تكسر حاجة. وعلشان كده محتاجة تلات حاجات: تكون سريعة، علشان تشغّلها كل شوية. وتكون deterministic، يعني نفس الـ code يدي نفس النتيجة كل مرة. وتكون بتختبر حاجة حقيقية، يعني لو عدّت، يبقى الحاجة شغالة بجد. والمشكلة إن الحاجات دي بتتعارض. كل ما الـ test يقرّب من الحقيقة، database حقيقية و HTTP حقيقي، كل ما يبقى أبطأ وأصعب يبقى deterministic. وكل ما يبعد عن الحقيقة، بيبقى سريع، بس ممكن يعدّي والـ production يقع.
وده اللي الـ testing pyramid بيحلّه. تحت خالص، كتير أوي: unit tests. بتختبر logic صافي، function بتاخد inputs وترجّع output، من غير database ولا network ولا وقت حقيقي. كل واحد بياخد جزء من الـ millisecond، فتقدر يبقى عندك آلاف. في النص، أقل: integration tests. بتختبر الـ code بتاعك مع حاجة حقيقية برّه: الـ SQL مع Postgres حقيقية، أو الـ cache مع Redis. هنا بتكتشف الـ constraints والـ transactions والـ queries الغلط. فوق، قليل أوي: e2e. الـ app كله شغال، وبتبعت HTTP request حقيقي، وبتشوف النتيجة في الـ database. بتختبر رحلات مهمة بس، زي تعمل order من أوله لآخره. والعكس اسمه ice cream cone: e2e كتير، و unit قليل. وده اللي كان عندنا: بطيء و flaky، ولما يفشل ما تعرفش المشكلة فين.
علشان يبقى عندك unit tests كتير، الـ code لازم يبقى قابل للاختبار. بص على priceOrder في orders dot js. مش بتكلّم database، ومش بتقرا Date dot now. الـ coupon داخل كـ argument، والوقت داخل كـ argument. فالـ test بيديها اللي هو عايزه ويشوف النتيجة. ده اسمه dependency injection، بس بشكل بسيط: أي حاجة برّه، زي الوقت والـ random والـ database، تدخل من برّه. والـ repository بياخد db، أي حاجة فيها query. فتديله pool، أو client جُوّه transaction. والـ app بياخد pool و now. والقاعدة: اعمل test للسلوك، مش الـ implementation. الـ test يقول: الـ coupon اللي خلص ما بيتطبقش. مش: priceOrder نادت Math dot round مرتين. علشان لما تعمل refactor، الـ tests تفضل خضرا لو السلوك ما اتغيرش.
طب والـ mocks؟ الـ mock إنك تبدّل حاجة حقيقية بحاجة مزيّفة. وفيه أنواع: stub: بيرجّع قيمة جاهزة. fake: implementation بسيط شغال بجد، زي store في الـ memory، أو clock مزيّف. mock أو spy: بيسجّل مين نادى عليه وبإيه. الـ fakes حلوة للحاجات اللي برّه الـ system بتاعك خالص: الـ payment provider، والـ email، والوقت. بس ما تعملش mock للـ database بتاعتك. لو عملت mock لـ pool dot query وقلت لما ييجي insert رجّع id واحد، انت كده بتختبر إن الـ code بيبعت الـ string اللي انت كاتبه. مش بتختبر إن الـ SQL صح، ولا إن الـ constraint موجود، ولا إن الـ transaction بتعمل rollback. وده بالضبط اللي بيقع في الـ production. وكمان ما تستبدلش Postgres بـ sqlite في الـ tests. السلوك مختلف: الـ types والـ constraints والـ locks والـ JSON. استخدم نفس الـ database بنفس الـ version.
الـ integration tests على Postgres حقيقية. أول سؤال: Postgres دي جاية منين؟ على جهازك، Docker container. وفي الـ CI، service container في GitHub Actions، أو مكتبة زي Testcontainers بتشغّل container لكل test run. تاني سؤال، وده الأهم: العزل. كل test لازم يبدأ من state معروف، وما يشوفش بيانات test تاني. وفيه تلات أساليب، وهنستخدمهم التلاتة: database لكل test run أو لكل worker: بتعمل الـ migrations مرة واحدة في database اسمها template، وبعدين create database بـ template، وده بينسخ الـ files على طول، أسرع بكتير من إنك تعيد الـ migrations. transaction لكل test: begin، والـ test يعمل اللي هو عايزه، وفي الآخر rollback. أسرع حاجة، ومفيش تنضيف. truncate بعد كل test: للحاجات اللي ما تنفعش جُوّه transaction، زي الـ e2e، لأن الـ app نفسه بيعمل commit.
البيانات اللي الـ test محتاجها. الأسلوب القديم: fixtures، file كبير فيه users و orders، بيتحط في الـ database في الأول. والمشكلة إن كل الـ tests بتعتمد عليه، وأي تغيير فيه بيكسر tests مالهاش علاقة، ولما تقرا الـ test مش عارف البيانات فين. الأحسن: factories. كل test يعمل البيانات اللي هو محتاجها بس، جُوّه الـ test نفسه. الـ factory عنده defaults معقولة، و sequence علشان الـ emails تبقى unique، وانت تغيّر اللي يهمك بس. فلما تقرا الـ test تشوف: user، و coupon بخمسين في المِيّة، والنتيجة خمسة آلاف وسبعمية. كل حاجة قدامك.
الـ flaky tests: test بينجح مرة ويفشل مرة، والـ code ما اتغيرش. ودي أخطر حاجة، لأن الناس بتتعلم تتجاهل اللون الأحمر. وأسبابها معروفة: state مشترك: database أو global أو file بين tests. الترتيب: test معتمد إن تاني اتشغّل قبله. الوقت: Date dot now، أو test بيعدّي نص الليل، أو timezone. sleep: تستنى مِيّة millisecond وتتمنى الحاجة تخلص. port واحد متحدد، و network حقيقي لـ services برّه. والـ random من غير seed. والحل مش retry تلات مرات. الحل تلاقي السبب: نشغّل الـ test عشرين مرة لوحده، وبترتيب random، وبالتوازي، وشوف. وبعدين: clock مزيّف بدل الـ sleep، و port صفر علشان الـ OS يديك port فاضي، وكل test يعمل بياناته، واستنى حاجة معينة تحصل بدل ما تستنى وقت.
الأدوات: Node فيه test runner جاهز، اسمه node colon test، من غير ما تنزّل حاجة. describe بتجمع tests، و test فيها الـ test نفسه، و before و after و beforeEach و afterEach للتجهيز والتنضيف، و assert strict للمقارنات، زي deepEqual و throws و rejects. وبتشغّله بـ node dash dash test. وفيه dash dash watch، و dash dash test dash concurrency، و coverage. وفي الـ CI: الـ unit tests في كل commit، وبتخلص في تواني. والـ integration والـ e2e في كل pull request مع Postgres حقيقية. والـ coverage: رقم مفيد علشان تشوف أماكن مفيهاش tests خالص. بس ما تخليهوش هدف. coverage مِيّة في المِيّة بـ tests مش بتعمل assert على حاجة، ما تسواش حاجة. التحدي. عندك orders dot js، الـ service اللي بنختبره: priceOrder صافية، و repository على Postgres، و Express app. وعندك orders dot test dot js، suite حقيقية بـ node colon test فيها التلات طبقات، وهي مستنية testkit dot js علشان تشتغل. والمطلوب testkit dot js. كل التفاصيل في الكومنت: dbUrl، و prepareTemplate، و createTestDatabase بالـ drop، و withRollback، و truncateAll، و defineFactory، و fakeClock، و startServer، و pyramid، و findFlaky. اقرا الكومنت كويس، ونشغّل test dot js لحد ما تبقى أربعتاشر من أربعتاشر.
اكتب testkit.js: dbUrl، و prepareTemplate و createTestDatabase بـ drop with force، و withRollback بالـ rollback والـ release في finally، و truncateAll بالـ except، و defineFactory بـ build و create و reset، و fakeClock، و startServer على port صفر، و pyramid بالـ shape، و findFlaky. كل التفاصيل في الكومنت، وشغّل node test.js.
الحل. الـ databases: كل حاجة بتتعمل من admin client بيتقفل على طول، والأسماء بين double quotes، والـ template بيتقفل قبل ما ننسخ منه، والـ drop with force. الـ withRollback: الـ rollback والـ release في finally، فالـ test لو فشل الـ error يطلع والـ client يرجع. الـ factory: الـ SQL من الـ keys، والـ values parameters. والـ startServer على port صفر، والـ OS بيختار. نشغّل الـ suite بتاعة الـ orders.
// testkit.js: أدوات الـ integration tests: database لكل test run من template، و rollback لكل test، و truncate، و factories، و clock مزيّف، و server على port عشوائي
// orders.test.js و test.js بيعملوا import من هنا: dbUrl و prepareTemplate و createTestDatabase و withRollback و truncateAll و defineFactory و fakeClock و startServer و pyramid و findFlaky
import pg from "pg";
const ident = (s) => `"${String(s).replace(/"/g, '""')}"`;
let counter = 0;
export function dbUrl(base, name) {
const u = new URL(base);
u.pathname = `/${name}`;
return u.toString();
}
async function withAdmin(adminUrl, fn) {
const c = new pg.Client({ connectionString: adminUrl });
await c.connect();
try { return await fn(c); } finally { await c.end(); }
}
export async function prepareTemplate({ adminUrl, name, migrate }) {
await withAdmin(adminUrl, async (c) => {
await c.query(`drop database if exists ${ident(name)} with (force)`);
await c.query(`create database ${ident(name)}`);
});
const url = dbUrl(adminUrl, name);
const c = new pg.Client({ connectionString: url });
await c.connect();
try { await migrate(c); } finally { await c.end(); }
return url;
}
export async function createTestDatabase({ adminUrl, template, name = `test_${process.pid}_${++counter}` }) {
await withAdmin(adminUrl, async (c) => {
await c.query(`drop database if exists ${ident(name)} with (force)`);
await c.query(`create database ${ident(name)} template ${ident(template)}`);
});
const url = dbUrl(adminUrl, name);
const pool = new pg.Pool({ connectionString: url, max: 5 });
return {
name,
url,
pool,
async drop() {
await pool.end();
await withAdmin(adminUrl, (c) => c.query(`drop database if exists ${ident(name)} with (force)`));
},
};
}
export async function withRollback(pool, fn) {
const client = await pool.connect();
try {
await client.query("begin");
return await fn(client);
} finally {
try { await client.query("rollback"); } finally { client.release(); }
}
}
export async function truncateAll(pool, { except = [] } = {}) {
const { rows } = await pool.query("select tablename from pg_tables where schemaname = 'public' order by tablename");
const tables = rows.map((r) => r.tablename).filter((t) => !except.includes(t));
if (tables.length) await pool.query(`truncate ${tables.map(ident).join(", ")} restart identity cascade`);
return tables;
}
export function defineFactory({ table, defaults }) {
let seq = 0;
const build = (overrides = {}) => ({ ...defaults(++seq), ...overrides });
return {
build,
async create(db, overrides = {}) {
const row = build(overrides);
const cols = Object.keys(row);
const { rows } = await db.query(
`insert into ${ident(table)} (${cols.map(ident).join(", ")}) values (${cols.map((_, i) => `$${i + 1}`).join(", ")}) returning *`,
cols.map((k) => row[k]),
);
return rows[0];
},
reset() { seq = 0; },
};
}
e
…المخرجات
$ node --test --test-reporter=spec orders.test.js ▶ unit: priceOrder ✔ subtotal والضريبة (1.38189ms) ✔ الـ coupon بيتطبق قبل الضريبة (0.227454ms) ✔ الـ coupon اللي خلص ما بيتطبقش، بالـ clock المزيّف (0.190376ms) ✔ الـ inputs الغلط (0.562525ms) ✔ التقريب بالـ cents (0.181591ms) ✔ unit: priceOrder (43.807549ms) ▶ integration: الـ repository على Postgres حقيقية ✔ create و findById مع الـ items (13.36911ms) ✔ الـ foreign key بيرفض user مش موجود (2.014845ms) ✔ الـ check constraint على الـ qty (2.037481ms) ✔ كل test بيبدأ نضيف: مفيش orders من الـ tests اللي فاتت (1.137754ms) ✔ integration: الـ repository على Postgres حقيقية (84.777827ms) ▶ e2e: HTTP و Postgres مع بعض ✔ order بـ coupon من أوله لآخره (69.791937ms) ✔ الـ error بيعمل rollback، ومفيش order نص كامل (11.368044ms) ✔ e2e: HTTP و Postgres مع بعض (126.414084ms) ℹ tests 11 ℹ suites 3 ℹ pass 11 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ duration_ms 417.075815
بص على الأوقات. الـ unit tests: كل واحد جزء من الـ millisecond. الـ integration: كام millisecond، على Postgres حقيقية، وكل test في transaction بتترجع، وآخر test أكّد إن مفيش order فاضل من اللي قبله. والـ e2e: الأبطأ، server حقيقي و HTTP و commit و truncate. وده الشكل اللي عايزه: كتير تحت، وقليل فوق. ولو الـ pricing فيه عشرين حالة، يبقوا عشرين unit test، مش عشرين e2e. ونشغّل test dot js.
المخرجات
$ node test.js
✅ dbUrl بيغيّر اسم الـ database بس
✅ prepareTemplate بيعمل database جديدة فيها الـ migrations، ولو اتنادت تاني بتبدأ من الأول postgres://app:app@localhost:5432/tpl_check_29151
✅ createTestDatabase: نسخة من الـ template بأسماء مختلفة، وكل نسخة معزولة ["test_29151_1","test_29151_2"]
✅ drop بيمسح الـ database حتى لو فيه connection تاني مفتوح عليها
✅ withRollback: اللي اتكتب جُوّه بيبان جُوّه بس، وبيترجع الـ value {"seen":1,"after5":0}
✅ withRollback: لو الـ test رمى، rollback والـ error يطلع زي ما هو، والـ client يرجع للـ pool {"total":1,"idle":1}
✅ truncateAll: كل الـ tables غير اللي في except، والـ ids ترجع من واحد {"t7":["order_items","orders","users"],"id":1}
✅ factory build: sequence ومن غير database، والـ overrides بتكسب [{"email":"[email protected]"},{"email":"[email protected]"},{"email":"[email protected]"}]
✅ factory create: insert بـ returning والـ row كامل بالـ id {"r":{"id":2,"email":"[email protected]"},"cp":{"code":"C1","percent":25,"expires_at":"2030-01-01T00:00:00.000Z"}}
✅ fakeClock: now بيرجّع Date، و advance و set، والـ coupon بيخلص من غير ما نستنى {"p1":{"subtotal":1000,"discount":100,"tax":126,"total":1026,"couponApplied":true},"p2":{"subtotal":1000,"discount":0,"
…كله نجح. الـ tests اللي كتبناها بتختبر حالات احنا اخترناها. طب الحالات اللي ما فكرناش فيها؟ وطب الـ service التانية اللي بتكلّمنا، إزاي نتأكد إننا ما كسرناهاش؟ الدرس الجاي: الـ contract testing والـ property based testing بـ fast check.
// orders.naive.test.js: الـ tests اللي كانت عندنا
import { test } from "node:test";
import assert from "node:assert/strict";
import pg from "pg";
import { createApp, priceOrder } from "./orders.js";
const pool = new pg.Pool({ connectionString: "postgres://dev:[email protected]/shop" }); // نفس الـ database بتاعة الـ dev
const app = createApp({ pool });
app.listen(3000);
test("create order", async () => {
const res = await fetch("http://localhost:3000/orders", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ userId: 1, items: [{ sku: "A", qty: 1, priceCents: 1000 }] }) });
globalThis.lastOrderId = (await res.json()).id;
assert.equal(res.status, 201);
});
test("get order", async () => {
const res = await fetch(`http://localhost:3000/orders/${globalThis.lastOrderId}`); // لازم يشتغل بعد اللي فوقه
assert.equal((await res.json()).userId, 1);
});
test("coupon expired", async () => {
await new Promise((r) => setTimeout(r, 61000)); // نستنى الـ coupon يخلص
// ...
});
test("orders count", async () => {
const { rows } = await pool.query("select count(*) from orders");
assert.equal(rows[0].count, "1"); // كان شغال امبارح
});
test("priceOrder calls Math.round", () => {
const spy = []; const orig = Math.round; Math.round = (x) => { spy.push(x); return orig(x); };
priceOrder([{ qty: 1, priceCents: 100 }]);
Math.round = orig;
assert.equal(spy.length, 2); // بيختبر الـ implementation مش السلوك
});
// وعشرين test تانيين كلهم عن طريق HTTP، لكل حالة في الـ pricing03 / دروس المسار (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