<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های مهراد صادقی</title><link>https://virgool.ir/feed/@mehradsadeghi</link><description>برنامه‌نویس بَک‌اِند، عاشق موسیقی</description><language>fa</language><pubDate>2026-11-10 19:45:52</pubDate><image><url>https://files.virgool.ir/upload/users/1796/avatar/2xJb0I.jpg</url><title>مهراد صادقی</title><link>https://virgool.ir/@mehradsadeghi</link></image><item><title>تست‌های اتوماتیک در Backend؛ راهنمای جامع و حرفه‌ای تست‌‌نویسی از Unit Test تا Fuzz Testing</title><link>https://virgool.ir/@mehradsadeghi/backend-automated-testing-xea7oonblwmp</link><description>تصویر از Max Duzijاگر مجبور شوید در یکی از سرویس‌های حیاتی خود تغییراتی ایجاد کنید، چقدر مطمئنید که این تغییر بخش دیگری از نرم‌افزار شما را خراب نمی‌کند ؟و سخت‌تر از آن، اگر برای مطمئن شدن مجبور باشید کل سیستم را دستی تست کنید، چه خواهید کرد ؟اینجاست که موضوع Automated Tests (تست‌های اتوماتیک) اهمیت پیدا می‌کنند.تست‌های اتوماتیک فقط ابزاری برای پیدا کردن باگ نیستند، بلکه در یک پروژه حرفه‌ای بخش مهم ایمن‌سازی سیستم هستند. تست‌ها به ما اجازه می‌دهند کد را Refactor کنیم، Dependencyها را تغییر دهیم، دیتابیس را بهینه کنیم یا حتی معماری یک سرویس را تغییر دهیم، بدون اینکه برای هر تغییر مجبور باشیم کل سیستم را از ابتدا به‌صورت دستی بررسی و تست کنیم.اما همه تست‌ها یکسان نیستند!تست‌ها انواع مختلفی مانند Unit Test، Integration Test، Feature Test، Contract Test، Fuzz Test و End-to-End Test دارند که هرکدام مساله متفاوتی را حل می‌کنند. حتی تستی که در یک پروژه بسیار مفید است، ممکن است در پروژه‌ای دیگر فقط هزینه نگهداری ایجاد کند.تست اتوماتیک چیست و چرا در Backend اهمیت دارد ؟به‌ زبان ساده Automated Test برنامه‌ای است که بخشی از نرم‌افزار را اجرا می‌کند، نتیجه واقعی را با نتیجه مورد انتظار مقایسه می‌کند و در صورت وجود اختلاف، Test را Fail می‌کند.برای مثال فرض کنید یک سرویس داریم که قیمت نهایی سفارش را محاسبه می‌کند:subtotal = 1,000,000&#xA;discount = 10%&#xA;shipping = 50,000اگر انتظار داریم قیمت نهایی برابر با ۹۵۰,۰۰۰ باشد، می‌توانیم این رفتار را به یک تست تبدیل کنیم.حالا هر بار که منطق محاسبه قیمت تغییر کند، تست می‌تواند به‌صورت خودکار بررسی کند که رفتار مورد انتظار هنوز برقرار است یا نه.تست‌های اتوماتیک می‌توانند برای Regression، مستندسازی رفتار سیستم و افزایش اطمینان هنگام تغییر کد بسیار مفید باشند. همچنین می‌توان آن‌ها را در فرآیندهای CI/CD اجرا کرد.هدف این تست‌ها این نیست که همه خطوط کد را تست کنیم؛ هدف این است که با هزینه منطقی، ریسک‌های مهم سیستم را پوشش دهیم.انواع تست در توسعه Backendبرای درک بهتر انواع تست، باید Scope آن را مشخص کنیم؛ یعنی تست چه بخش مشخصی از سیستم را درگیر می‌کند. در نتیجه خواهیم داشت:۱. Unit Test۲. Integration Test۳. Feature / Functional Test۴. Contract Test۵. End-to-End Test۶. Characterization Testدر کنار این‌ها، روش‌هایی وجود دارند که بیشتر روی نحوه تولید ورودی یا ارزیابی قدرت تست‌ها تمرکز می‌کنند، مانند:۷. Property-Based Test۸. Fuzz Testing۹. Mutation Testingو در نهایت تست‌هایی که عملکرد و کیفیت سیستم را بررسی می‌کنند، مانند:‍۱۰. Performance / Load / Stress Test۱۱. Smoke Test۱۲. Regression Testاین دسته‌بندی‌ها کاملا مستقل از هم نیستند. برای مثال یک Feature Test می‌تواند همزمان Integration Test هم باشد، یا Fuzzing می‌تواند روی یک Unit Test هم اعمال شود.بنابراین مهم‌تر از اسم تست‌ها، این است که بدانیم تست دقیقا چه ریسکی را پوشش می‌دهد ؟Unit Test چیست ؟Unit Testing کوچک‌ترین و سریع‌ترین سطح تست است. در Unit Test معمولا یک Unit (رفتار) مشخص از کد را جدا می‌کنیم و بررسی می‌کنیم که با ورودی مشخص، رفتار مورد انتظار را دارد یا نه.این Unit می‌تواند یک:FunctionMethodClassیا یک بخش کوچک از Domain Logicباشد. برای مثال:calculateDiscount(price, customerType)را در نظر بگیرید. می‌توانیم تست‌هایی مانند این را داشته باشیم:Customer = Regular&#xA;Price = 1,000,000&#xA;Expected Discount = 0و:Customer = Premium&#xA;Price = 1,000,000&#xA;Expected Discount = 100,000در یونیت تست Dependencyهای خارجی مثل Database، File System یا Network را وارد نمی‌کنیم و آن‌ها را با Test Doubleهایی مثل Mock، Stub یا Fake جایگزین می‌کنیم (تعریف هر کدام در را در ادامه مقاله خواهیم دید). در تعریف Microsoft از Unit Test، آن را تست یک Component یا Method مشخص می‌داند و بخش‌های زیرساختی (Infrastructure) مانند Database، File System و Network را خارج از محدوده معمول Unit Test قرار می‌دهد.مزایای Unit Testبسیار سریعهزینه اجرای پایینمناسب برای CIپیدا کردن دقیق‌تر محل باگمناسب برای Refactoringامکان داشتن تعداد زیادی تستعیب Unit Testممکن است همه اجزای سیستم به‌صورت جداگانه درست باشند، اما در کنار هم درست کار نکنند.برای نمونه:OrderService&#xA;      ↓&#xA;Repository&#xA;      ↓&#xA;PostgreSQLممکن است منطق OrderService کاملا درست باشد، اما کوئری اشتباه باشد. در نتیجه Unit Test به‌تنهایی این مشکل را پیدا نمی‌کند. اینجاست که Integration Test وارد می‌شود.Integration Test چیست ؟Integration Testing بررسی می‌کند که دو یا چند بخش از برنامه شما، یا یک بخش با یک Dependency خارجی، واقعا می‌توانند با هم کار کنند یا نه. مثال‌های رایج در Backend عبارت‌اند از:اپلیکیشن + PostgreSQLاپلیکیشن + Redisاپلیکیشن + Message Brokerاپلیکیشن + File Systemاپلیکیشن + External APIRepository + Databaseفرض کنید Repository شما چنین کاری انجام می‌دهد:CreateOrder(order)در Unit Test ممکن است Database را Mock کنید.اما Integration Test می‌تواند:۱. PostgreSQL را بالا بیاورد۲. Application را به آن متصل کند۳. Order را ذخیره کند۴. دوباره آن را بخواند۵. نتیجه را بررسی کنددر اینجا دیگر فقط منطق کد را تست نمی‌کنیم؛ بلکه نحوه تعامل واقعی اجزا را هم بررسی می‌کنیم. Integration Testها معمولا Scope بزرگ‌تری نسبت به Unit Testها دارند و می‌توانند Infrastructure را نیز درگیر کنند.مزایای Integration Testتست رفتار واقعی‌ترپیدا کردن مشکلات Databaseپیدا کردن مشکلات Serializationبررسی کانفیگ‌هااطمینان بیشتر نسبت به Unit Test به‌تنهاییمعایب Integration Testکندتر است.Setup پیچیده‌تری دارد.(احتمالا) به Infrastructure نیاز دارد.Debug کردن آن سخت‌تر است.Failure آن می‌تواند دلایل زیادی داشته باشد.بنابراین منطقی نیست برای تمام  Business Logic‌ها، Integration Test بنویسیم.Feature Test یا Functional Test چیست ؟در Feature Test یک قابلیت کامل‌تر از سیستم را از دید رفتار مورد انتظار، بررسی می‌کنیم. در Backend این تست می‌تواند از API شروع شود. برای نمونه Feature مربوط به ثبت سفارش:POST /ordersورودی:{&#xA;  &#34;productId&#34;: 123,&#xA;  &#34;quantity&#34;: 2&#xA;}و انتظار داریم در پاسخ:201 Created&#xA;&#xA;{&#xA;  &#34;orderId&#34;: 456,&#xA;  &#34;status&#34;: &#34;created&#34;&#xA;}را دریافت کنیم. در اینجا دیگر مهم نیست داخل OrderService چند Class وجود دارد.ما رفتار Feature را بررسی می‌کنیم: «آیا کاربر می‌تواند یک سفارش معتبر ایجاد کند ؟»از این جهت Feature Test با Unit Test تفاوت دارد.Unit Test می‌پرسد: آیا این Class یا Function درست کار می‌کند ؟Feature Test می‌پرسد: آیا این قابلیت (Feature) مشخص سیستم طبق انتظار کار می‌کند ؟به همین دلیل Feature Test در توسعه API بسیار کاربردی است.Feature Test چه تفاوتی با Integration Test دارد ؟این دو مفهوم ممکن است با هم هم‌پوشانی داشته باشند. برای نمونه یک تست API ممکن است به ترتیب زیر انجام شود:HTTP Request&#xA;   ↓&#xA;Controller&#xA;   ↓&#xA;Service&#xA;   ↓&#xA;Repository&#xA;   ↓&#xA;Databaseدر این حالت هم Feature را تست کرده‌ایم و هم Integration بین اجزای مختلف را.پس این دو نام الزاما دو لایه کاملا جدا نیستند. یک روش کاربردی این است که بپرسیم:این تست دقیقا چه ریسکی را پوشش می‌دهد ؟اگر هدف تست این است که یک Feature از دید API درست کار می‌کند، آن را Feature/Functional Test در نظر بگیرید. اگر هدف بررسی اتصال Repository به PostgreSQL است، آن را Integration Test بدانید.Contract Test چیست ؟فرض کنید سرویس Order به سرویس Payment درخواست می‌فرستد:POST /paymentsو انتظار دارد Response این شکل را داشته باشد:{&#xA;  &#34;paymentId&#34;: &#34;123&#34;,&#xA;  &#34;status&#34;: &#34;paid&#34;&#xA;}حالا اگر تیم Payment بدون اطلاع شما Response را تغییر دهد:{&#xA;  &#34;id&#34;: &#34;123&#34;,&#xA;  &#34;state&#34;: &#34;completed&#34;&#xA;}ممکن است Unit Testهای شما همچنان سبز باشند. اما Integration بین دو سرویس شکسته است.اینجاست که Contract Testing ارزش پیدا می‌کند.Contract Test بررسی می‌کند که دو طرف یک Integration روی یک قرارداد مشخص توافق داشته باشند. برای ارتباط HTTP، این قرارداد می‌تواند Request و Response مورد انتظار را مشخص کند؛ همین مفهوم برای Message-based Integration نیز قابل استفاده است.یکی از رویکردهای شناخته‌شده، Consumer-Driven Contract Testing است. در این مدل Consumer نیازها و تعاملات مورد انتظار خود را در قالب Contract تعریف می‌کند و Provider آن Contract را Verify می‌کند.مزیت Contract TestContract Test می‌تواند بدون اجرای کل سیستم، بخشی از ریسک Integration بین سرویس‌ها را کاهش دهد.این موضوع مخصوصا زمانی مفید است که:تیم‌های مختلف روی سرویس‌های مختلف کار می‌کنند.سرویس‌ها مستقل Deploy می‌شوند.APIها زیاد تغییر می‌کنند.معماری Microservice دارید.باید به این نکته توجه کرد که Contract Test جای انواع دیگر تست‌ها را به‌طور کامل نمی‌گیرد. در عوض، برای یک مشکل مشخص طراحی شده است:آیا دو سرویس هنوز روی یک Interface مورد انتظار توافق دارند ؟ End-to-End Test چیست ؟End-to-End Test یا E2E کل مسیر یک سناریو را از ابتدا تا انتها بررسی می‌کند.برای مثال در یک فروشگاه آنلاین:Login&#xA; ↓&#xA;Add Product&#xA; ↓&#xA;Create Order&#xA; ↓&#xA;Payment&#xA; ↓&#xA;Order Confirmationتست‌های E2E کل این مسیر را اجرا می‌کنند. در توسعه Backend حتی اگر UI نداشته باشیم، می‌توانیم چنین سناریویی را از طریق API اجرا کنیم:POST /login&#xA;POST /cart&#xA;POST /orders&#xA;POST /payments&#xA;GET /orders/{id}و در نهایت بررسی کنیم:Order Status = Paidمزیت E2E Testبیشترین شباهت را به رفتار واقعی سیستم دارد. به همین دلیل برای سناریوهای مهم Business می‌تواند اطمینان بالایی ایجاد کند.چالش اصلی تست‌های E2Eهزینه آن‌ها بالاست.E2E Test معمولا:کندتر است.Failureهای پیچیده‌تری دارد.Debug کردن آن سخت‌تر است.به Environment گسترده‌تری وابسته است.Maintenance بیشتری نیاز دارد.به همین دلیل منطقی نیست تمام حالت‌های سیستم را E2E تست کنیم.Characterization Test چیست ؟اینجا به یکی از مهم‌ترین تکنیک‌ها برای پروژه‌های Legacy می‌رسیم. فرض کنید وارد پروژه‌ای شده‌اید که به مدت ۱۰ سال در Production است، اما وضعیت پروژه به این صورت است:Test Coverage = پایین&#xA;Documentation = ناقص&#xA;Code = پیچیدهمدیر پروژه می‌گوید: فقط یک تغییر کوچک در این بخش بده.مشکل چیست ؟ شما نمی‌دانید کدهای موجود واقعا چه کاری انجام می‌دهند.ممکن است چیزی که از نظر شما باگ به نظر برسد، در واقع رفتاری باشد که سیستم سال‌ها بر اساس آن کار کرده و Clientها به آن وابسته باشند.اینجاست که Characterization Test کاربرد پیدا می‌کند.Characterization Test قرار نیست بگوید:این رفتار درست است.بلکه می‌گوید:این رفتار فعلی سیستم است.این رویکرد برای کار با Legacy Code اهمیت زیادی دارد و در کنار تکنیک‌هایی مثل ایجاد Seam می‌تواند امکان تغییر تدریجی سیستم‌های قدیمی را فراهم کند.منظور از Seam نقطه‌ای در کد است که امکان تغییر یا جایگزین کردن یک Dependency را برای تست یا تغییر رفتار فراهم می‌کند. مثلا اگر یک سرویس مستقیما به دیتابیس وابسته باشد، با قرار دادن یک Interface بین آن‌ها، یک Seam ایجاد می‌کنیم تا در تست بتوانیم دیتابیس واقعی را با یک Mock یا Fake جایگزین کنیم.یک مثال از Characterization Testفرض کنید یک تابع قدیمی داریم:calculateShipping(order)و هیچ تستی برای آن وجود ندارد. ما نمی‌دانیم برای این ورودی:Order = 1,000,000&#xA;City = Tehran&#xA;Weight = 3kg&#xA;Customer = VIPچه نتیجه‌ای تولید می‌شود. به‌جای اینکه حدس بزنیم نتیجه «باید» چه باشد، تابع را اجرا می‌کنیم و خروجی واقعی را مشاهده می‌کنیم. فرض کنید خروجی ۱۲۰,۰۰۰ است. حالا همین رفتار را به تست تبدیل می‌کنیم:calculateShipping(order)&#xA;Expected = 120,000از این لحظه به بعد، اگر Refactoring ما باعث شود نتیجه به ۱۵۰,۰۰۰ تغییر کند، تست Fail می‌شود. این یعنی قبل از اینکه کد را کاملا بفهمیم، رفتار موجود آن را ثبت کرده‌ایم.Characterization Test چه زمانی ارزشمند است ؟اختصاصا در زمانی که:پروژه Legacy است.Test Coverage پایین است.Documentation ناقص است.قرار است Refactoring بزرگی انجام شود.قرار است یک سیستم قدیمی Migration شود.نکته مهم: Characterization Test الزاما نمی‌گوید رفتار فعلی درست است. ممکن است بعدا بفهمیم رفتار ثبت‌شده یک باگ است. در آن مرحله می‌توانیم:Current Behavior&#xA;      ↓&#xA;Characterization Test&#xA;      ↓&#xA;Understanding&#xA;      ↓&#xA;Specification&#xA;      ↓&#xA;     Fix&#xA;      ↓&#xA;Normal Testرا طی کنیم. بنابراین Characterization Test بیشتر شبیه یک Safety Net برای تغییر سیستم ناشناخته است.Property-Based Test چیست ؟تا اینجا بیشتر تست‌ها به این شکل بودند:Input → Expected Outputبرای نمونه:Input: 10&#xA;Expected: 20این‌ تست‌ها Example-Based هستند؛ یعنی خودمان ورودی‌ها را مشخص می‌کنیم. اما گاهی می‌توانیم به‌جای بررسی Exampleهای مشخص، یک Property از سیستم را تعریف کنیم. فرض کنید چنین تابعی داریم:sort(items)به‌جای اینکه فقط بنویسیم:sort([3,1,2]) == [1,2,3]می‌توانیم بگوییم: خروجی باید مرتب باشد و تعداد عناصر آن با ورودی برابر بماند.حالا یک Property-Based Testing Framework می‌تواند تعداد زیادی ورودی تولید کند و Property را روی آن‌ها بررسی کند. کتابخانه‌هایی مانند Hypothesis و fast-check برای چنین سناریوهایی طراحی شده‌اند.برای مثال:[]&#xA;[1]&#xA;[3,1]&#xA;[10,5,8,2]&#xA;[random data...]و برای همه آن‌ها بررسی می‌کنیم:isSorted(result) == true&#xA;length(result) == length(input)چه زمانی Property-Based Testing مفید است ؟وقتی:تعداد حالت‌های ورودی زیاد است.پیدا کردن Edge Caseها دشوار است.یک Invariant یا Property واضح دارید.الگوریتم یا Domain Rule پیچیده است.می‌خواهید Exampleهای بیشتری را با هزینه کمتر بررسی کنید.برای نمونه در سیستم‌های مالی می‌توانیم چنین Propertyهایی داشته باشیم: را حفظ کند Balance مجموع تراکنش‌ها بایدیا در Serialization:decode(encode(x)) == xاین نوع Propertyها می‌توانند طیف بزرگی از Inputها را بررسی کنند.Fuzz Testing چیست ؟تست‌های Fuzz Testing یا Fuzzing تکنیکی در تست‌نویسی هستند که در آن برنامه را با ورودی‌های غیرمنتظره، malformed، غیرعادی یا تولیدشده به‌صورت خودکار اجرا می‌کنیم تا باگ، Crash، Vulnerability یا رفتار غیرمنتظره در سیستم پیدا کنیم. OWASP نیز Fuzzing را به‌عنوان تکنیکی برای پیدا کردن باگ‌ها، آسیب‌پذیری‌ها و رفتارهای غیرمنتظره از طریق ورودی‌های غیرمنتظره یا خراب‌شده تشریح می‌کند.فرض کنید API زیر را داریم:POST /usersو انتظار داریم با این ورودی‌ها درست کار کند:{&#xA;  &#34;name&#34;: &#34;Ali&#34;,&#xA;  &#34;age&#34;: 30,&#xA;  &#34;email&#34;: &#34;ali@example.com&#34;&#xA;}در تست معمولی شاید این موارد را به عنوان ورودی ارسال کنیم:age = 30&#xA;age = 0&#xA;age = -1اما Fuzzing می‌تواند ورودی‌های بسیار متنوع‌تری تولید کند. مانند:age = -999999&#xA;age = 999999999&#xA;name = &#34;&#34;&#xA;name = بسیار طولانی&#xA;email = &#34;&#34;&#xA;email = malformed&#xA;غیرمعمول Unicode &#xA;ناقص JSON &#xA;بسیار بزرگ JSON &#xA;null&#xA;nested objects&#xA;unexpected arraysهدف این نیست که ثابت کنیم خروجی دقیقا چه باید باشد؛ هدف این است که سیستم در برابر ورودی‌های غیرمنتظره به‌صورت امن و قابل پیش‌بینی رفتار کند.تفاوت Fuzz Testing و Property-Based Testing چیست ؟این دو مفهوم به هم نزدیک هستند و در بعضی ابزارها حتی با هم ترکیب می‌شوند، اما بهتر است آن‌ها را یکی ندانیم.در Property-Based Testing سوال اصلی این است:آیا یک Property مشخص برای تعداد زیادی Input برقرار است ؟مثلا sort(list) باید برای هر List معتبر، همیشه یک List مرتب‌شده برگرداند.در Fuzz Testing سوال این است: اگر تعداد زیادی Input غیرمنتظره و یا malformed به سیستم بدهیم، آیا Crash، Security Issue یا رفتار غیرمنتظره‌ای رخ می‌دهد ؟به‌صورت ساده:Property-Based Testing&#xA;        ↓&#xA;Generate Inputs&#xA;        ↓&#xA;Check Propertiesدر مقابل:Fuzz Testing&#xA;        ↓&#xA;Generate Unexpected Inputs&#xA;        ↓&#xA;Look for Failures / Crashes / Vulnerabilitiesالبته مرز این دو کاملا ملموس نیست. بعضی ابزارهای Property-Based Testing از تکنیک‌های Fuzzing استفاده می‌کنند و بعضی Fuzzerها نیز روی Inputهای ساختاریافته و Propertyهای مشخص کار می‌کنند. حتی مستندات Hypothesis نیز به ارتباط نزدیک میان این دو اشاره کرده است.Fuzz Testing برای Backend کجا مفید است ؟Fuzzing در توسعه Backend می‌تواند روی نقاطی مثل این‌ها بسیار خوب باشد:API Input:&#xA;&#xA;JSON&#xA;Query Parameters&#xA;Path Parameters&#xA;HeadersParsers:&#xA;&#xA;JSON Parser&#xA;XML Parser&#xA;CSV Parser&#xA;Custom ProtocolAuthentication و Authorization:&#xA;&#xA;Token یا Header ,Session ورودی‌های غیرمنتظره برایFile Upload:&#xA;&#xA;Empty File&#xA;Huge File&#xA;Malformed File&#xA;Unexpected FormatSearch و Filtering:&#xA;&#xA;Very Long Strings&#xA;Special Characters&#xA;Unicode&#xA;Unexpected CombinationsSerialization / Deserialization:&#xA;&#xA;رفتار نادرست ،Exception ‌هایی که ممکن است باعث Input برای پیدا کردن &#xA;یا مصرف غیر عادی منابع شوندTrade-offهای Fuzz Testingمزایای آن شامل:پیدا کردن Edge Caseهای غیرمنتظرهکشف Crashهاپیدا کردن رفتارهای ناشناختهمناسب برای Inputهای پیچیدهقابلیت اجرای طولانی‌مدت در CI یا محیط‌های مخصوص Fuzzingو معایب آن شامل:طراحی Fuzzer مناسب ممکن است زمان‌بر باشد.همه Inputهای تصادفی ارزش یکسان ندارند.تحلیل Failureها ممکن است سخت باشد.برای هر Domain همیشه نمی‌توان به‌راحتی خروجی درست را مشخص کرد.اجرای گسترده Fuzzing ممکن است منابع زیادی مصرف کند.بنابراین Fuzzing معمولا جایگزین Unit Test یا Integration Test نیست؛ بلکه یک لایه مکمل برای پیدا کردن کلاس دیگری از مشکلات است.Mutation Testing چیست ؟اگر همه Testهای شما سبز (موفق) باشند، از کجا مطمئن شویم که خود Testها واقعا توانایی پیدا کردن باگ را دارند ؟Mutation Testing برای پاسخ به همین سوال است.در Mutation Testing، ابزار عمدا تغییرات کوچکی در کد اصلی برنامه ایجاد می‌کند تا ببیند تست‌ها می‌توانند این تغییرات را شناسایی کنند یا نه.مثلا:if price &gt; 100را به:if price &gt;= 100تغییر می‌دهد. یا:+را با:-جایگزین می‌کند. حالا Test Suite (مجموعه تست‌هایی که نوشته‌ایم) اجرا می‌شود. اگر تست‌ها Fail شوند:Mutation = Killedیعنی تست‌ها توانسته‌اند تغییر را تشخیص دهند.اگر تست‌ها همچنان سبز باشند:Mutation = Survivedاین می‌تواند نشانه یک ضعف در Test Suite باشد.بنابراین Mutation Testing بیشتر از اینکه یک نوع Test برای Production Code (کدهای اصلی برنامه) باشد، روشی برای ارزیابی قدرت Test Suite است.Performance Test، Load Test و Stress Testگاهی سوال ما این است: آیا سیستم با ۱۰ هزار Request در ثانیه همچنان پایدار است ؟اینجا وارد حوزه Performance Testing می‌شویم.تست Loadبررسی می‌کند سیستم در یک Load مشخص چه عملکردی دارد. برای مثال:1,000 concurrent users&#xA;100 requests/sec&#xA;Response Time &lt; 300msهدف Load Test بررسی رفتار سیستم تحت بار مورد انتظار است. Microsoft نیز Load Testing را برای بررسی توانایی سیستم در مدیریت Load مشخص، مانند تعداد کاربران همزمان و پاسخ‌گویی سیستم به آنها، تعریف می‌کند.تست StressLoad را فراتر از ظرفیت مورد انتظار افزایش می‌دهیم تا ببینیم سیستم چگونه رفتار می‌کند:100 req/s&#xA;200 req/s&#xA;500 req/s&#xA;1000 req/s&#xA;...و بررسی می‌کنیم:سیستم چه زمانی Degrade می‌شود ؟آیا Crash می‌کند ؟آیا Recovery دارد ؟آیا Queue رشد می‌کند ؟آیا Error Rate افزایش پیدا می‌کند ؟این تست‌ها برای Backendهای پرترافیک اهمیت زیادی دارند.Smoke Test چیست ؟Smoke Test یک تست سریع و سطح بالا است که بررسی می‌کند سیستم به درستی اجرا می‌شود و قابلیت‌های اصلی آن درست کار می‌کنند.مثلا بعد از Deploy:GET /healthیا:POST /loginرا اجرا کنیم. اگر همین مسیرهای پایه Fail شوند، احتمالا ادامه به اجرای تست‌های پیچیده‌تر، ارزشی ندارد.Smoke Test معمولا کوتاه و سریع است و می‌تواند بعد از Deployment اجرا شود.Regression Test چیست ؟Regression Test یعنی اجرای دوباره تست‌های قبلی بعد از تغییرات، برای اینکه مطمئن شویم قابلیت‌های موجود همچنان درست کار می‌کنند. وقتی باگی پیدا می‌کنیم، می‌توانیم یک تست بنویسیم که همان باگ را بازتولید کند.مثلا باگ:Order with quantity = 0قبلا باعث ثبت سفارش می‌شده است.بعد از Fix:quantity = 0 → 400 Bad Requestیک تست برای این رفتار می‌نویسیم. از این به بعد اگر کسی با تغییر دیگری دوباره همین باگ را ایجاد کند، تست Fail خواهد شد.تفاوت Regression Test با سایر انواع تست‌ها این است که یک نوع تست مستقل بر اساس Scope نیست و تمرکزش بیشتر در هدف و زمان اجرای تست است. مثلا فرض کنید قبلا قابلیت CreateOrder را تست کرده‌ایم و همه‌چیز درست بوده. حالا یک تغییر جدید در سیستم انجام می‌دهیم؛ برای نمونه منطق تخفیف را تغییر می‌دهیم. اگر دوباره تست CreateOrder را اجرا کنیم تا مطمئن شویم این تغییر جدید، رفتار قبلی را خراب نکرده است، این یک Regression Test محسوب می‌شود.نکته مهم این است که یک تست می‌تواند هم‌زمان چند ویژگی داشته باشد. مثلا یک Unit Test می‌تواند Regression Test هم باشد، اگر برای جلوگیری از برگشتن یک باگ قدیمی نگه داشته شده باشد.ساختار Test Pyramidیک روش ساده برای فکر کردن به تست‌های یک پروژه، Test Pyramid است.ایده اصلی این است که تست‌ها را در چند سطح داشته باشیم:Unit Test → تست‌های کوچک و سریع&#xA;Integration / Feature Test → تست چند بخش در کنار هم&#xA;E2E Test → تست کل سیستم از ابتدا تا انتهامعمولا پیشنهاد می‌شود تعداد تست‌های کوچک و سریع، بیشتر باشد و تست‌های بزرگ‌تر، کمتر باشند. به این دلیل که برای نمونه یک Unit Test ممکن است در چند میلی‌ثانیه اجرا شود، اما یک E2E Test باید بخش‌های مختلف سیستم را اجرا کند و معمولا زمان و هزینه بیشتری دارد.اما این به این معنی نیست که مثلا ۷۰٪ تست‌های پروژه حتما باید Unit Test باشند. مقدار مناسب هر نوع تست به خود پروژه و ریسک‌های آن بستگی دارد.پس به جای اینکه بگوییم: «چند درصد تست‌ها باید Unit Test باشند ؟»بهتر است بپرسیم: «برای تست کردن این بخش، ساده‌ترین و سریع‌ترین تستی که می‌تواند از درست بودن آن مطمئن‌مان کند چیست ؟»به طور مثال اگر یک محاسبه ساده داریم، احتمالا یک Unit Test کافی است. اما اگر می‌خواهیم مطمئن شویم API، Database و چند سرویس با هم درست کار می‌کنند، باید سراغ Integration Test یا E2E Test برویم.پس Test Pyramid یک قانون ثابت نیست؛ فقط یک راهنمای ساده برای انتخاب و متعادل کردن تست‌هاست.مقایسه انواع تست‌هااما مهم‌ترین تفاوت، سوال هر تست است:Unit: آیا این منطق درست است ؟Integration: آیا این دو جزء درست با هم کار می‌کنند ؟Feature: آیا این قابلیت درست کار می‌کند ؟Contract: آیا دو سرویس هنوز قرارداد مشترکشان را رعایت می‌کنند ؟E2E: آیا کل سناریوی واقعی از ابتدا تا انتها درست کار می‌کند ؟Characterization: سیستم فعلی واقعا چه رفتاری دارد ؟Property-Based: آیا یک قانون برای طیف بزرگی از ورودی‌ها برقرار است ؟Fuzz Testing: آیا ورودی‌های غیرمنتظره می‌توانند سیستم را دچار رفتار مشکل‌دار کنند ؟Mutation: آیا تست‌های من واقعا می‌توانند تغییرات اشتباه را تشخیص دهند ؟Load Test: سیستم تحت بار چگونه رفتار می‌کند ؟یک سناریوی واقعی؛ از Bug تا Testفرض کنید در یک فروشگاه آنلاین، کاربران گاهی می‌توانند با مقدار منفی سفارش ثبت کنند:{&#xA;  &#34;productId&#34;: 10,&#xA;  &#34;quantity&#34;: -2&#xA;}و سیستم سفارش را ایجاد می‌کند.مرحله اول: باگ را بازتولید کنیمابتدا یک Test می‌نویسیم که رفتار مورد انتظار را مشخص کند:quantity = -2&#xA;&#xA;Expected: 400 Bad RequestTest ابتدا Fail می‌شود. این خوب است، چون نشان می‌دهد توانسته‌ایم باگ را با یک تست قابل تکرار ثبت کنیم.مرحله دوم: محل باگ را پیدا کنیمچنین مسیری را بررسی می‌کنیم:API&#xA; ↓&#xA;Controller&#xA; ↓&#xA;OrderService&#xA; ↓&#xA;Validatorمشخص می‌شود Validator مقدار quantity را بررسی نمی‌کند.مرحله سوم: Unit Test اضافه کنیمیک تست کوچک برای این Rule می‌نویسیم:quantity = -2&#xA;Expected = Validation Errorحالا این تست Failure را دقیق‌تر در سطح Business Rule ثبت می‌کند.مرحله چهارم: Feature Testبعد یک Feature Test می‌نویسیم:POST /orders&#xA;quantity = -2&#xA;&#xA;Expected: 400این تست مطمئن می‌شود که Rule در مسیر واقعی API هم به‌درستی اعمال شده است.مرحله پنجم: FixValidator را اصلاح می‌کنیم:quantity &gt; 0مرحله ششم: اجرای Test Suiteحالا:Unit Tests ✓&#xA;Feature Tests ✓&#xA;Integration ✓اگر همه سبز باشند، اطمینان بیشتری داریم که هم Rule و هم مسیر API درست شده‌اند.مرحله هفتم: جلوگیری از Regressionاز این به بعد هر تغییر در Order Validation اگر دوباره اجازه quantity = -2 بدهد، تست Fail خواهد شد. این دقیقا جایی است که Automated Testing ارزش واقعی خود را نشان می‌دهد:تست فقط باگ فعلی را پیدا نکرد؛ هزینه ایجاد دوباره همان باگ را هم کاهش داد.اگر همین API را Fuzz کنیم چه می‌شود ؟حالا یک قدم جلوتر برویم. ما باگ مربوط به quantity = -2 را با یک Test مشخص پیدا کردیم.اما آیا فقط همین حالت مشکل‌دار است ؟ممکن است این موارد هم رفتارهای جالبی ایجاد کنند:quantity = -999999999&#xA;quantity = 0&#xA;quantity = 2147483647&#xA;quantity = null&#xA;quantity = &#34;2&#34;&#xA;quantity = []&#xA;quantity = {}در Fuzz Test لازم نیست همه حالت‌های ورودی را خودمان تعریف کنیم؛ ابزارها می‌توانند ورودی‌های مختلف و غیرمنتظره را تولید کنند تا بررسی کنیم که سیستم در همه آن‌ها رفتار درستی داشته باشد.برای نمونه:For every generated request:&#xA;&#xA;System must not crash&#xA;AND&#xA;invalid quantity must not create an orderحالا اگر Fuzzer ورودی خاصی پیدا کند که باعث Exception یا رفتار غیرمنتظره شود، همان Input می‌تواند به یک Test ثابت و قابل تکرار تبدیل شود.این یک Workflow بسیار کاربردی است:Fuzzing&#xA;   ↓&#xA;Unexpected Input&#xA;   ↓&#xA;Bug Found&#xA;   ↓&#xA;Minimize / Reproduce&#xA;   ↓&#xA;Regression Test&#xA;   ↓&#xA;Permanent Safety Netدر Property-Based Testing، وقتی یک ورودی باعث شکست تست می‌شود، فریم‌ورک‌های Property-Based Testing می‌توانند آن ورودی را آنقدر ساده‌ کنند تا کوچک‌ترین نمونه‌ای که باعث خطا می‌شود پیدا شود. به این کار Shrinking می‌گویند.چگونه Test بنویسیم که بعدا از آن متنفر نشویم ؟نوشتن Test آسان است. نوشتن Test قابل نگهداری سخت‌تر است.چند اصل مهم:۱. رفتار را تست کنید، نه Implementation رابه جای اینکه تست به این وابسته باشد که:OrderService&#xA; → Method A&#xA; → Method B&#xA; → Method Cبهتر است تا حد امکان رفتار قابل مشاهده را بررسی کند. چرا که اگر Implementation را Refactor کنیم، نباید تست‌ها بی‌دلیل دچار مشکل شوند. Microsoft نیز در توصیه‌های مربوط به Unit Testing روی خوانایی و مقاومت تست‌ها در برابر تغییرات غیرضروری تاکید می‌کند.۲. Test باید خوانا باشداگر برای فهمیدن یک Test مجبور شویم Implementation مربوط به آن را بخوانیم، احتمالا Test بیش از حد پیچیده است. این نام:CreateOrder_WithInvalidQuantity_ReturnsBadRequestمعمولا از نامی مثل:ShouldCallValidatorThenRepositoryAnd...معنادارتر است. نام Test باید سناریو را توضیح دهد.۳. Edge Case‌ها را جدی بگیریدتستی که فقط Happy Path را بررسی کند، کافی نیست. برای نمونه:quantity = 1را تست کرده‌ایم. اما چه اتفاقی برای این‌ها می‌افتد ؟quantity = 0&#xA;quantity = -1&#xA;quantity = MAX_INT&#xA;quantity = nullEdge Caseها همان جایی هستند که باگ‌ها پنهان می‌شوند.آیا Code Coverage 100٪ یعنی سیستم کامل تست شده است ؟این یکی از رایج‌ترین سوءبرداشت‌ها در Automated Testing است. فرض کنید:Code Coverage = 100%آیا یعنی سیستم بدون باگ است ؟خیر. Coverage فقط نشان می‌دهد چه مقدار از کدهایمان هنگام اجرای Testها اجرا شده‌اند؛ نه اینکه آیا رفتارهای مهم واقعا درست تست باشند. برای مثال ممکن است تست ما فقط این کار را انجام دهد:function calculatePrice() {&#xA;    ...&#xA;}و صرفا Function را اجرا کند، بدون اینکه نتیجه مهم آن را Assert کنیم. در این مثال Coverage بالا است، اما اطمینان از کارکرد مورد نظر پایین خواهد بود. بنابراین بهتر است Coverage را به‌عنوان یک Signal ببینیم، نه هدف نهایی.سوال مهمتر این است که آیا اصلی‌ترین Business Ruleها و Failure Modeهای سیستم تست شده‌اند یا نه.Test Double چیست ؟Test Double یعنی استفاده از یک جایگزین موقت برای Dependency واقعی، که در زمان تست از آن استفاده می‌کنیم.انواع رایج آن:۱. Stub: برای برگرداندن یک داده مشخص:getUser() → returns User(id=10)Stub در واقع داده‌‌‌ایست که یک تست بدون آن نمی‌تواند درست اجرا شود.۲. Mock: برای بررسی Invocation‌ها. برای نمونه:PaymentService.charge()باید دقیقا یک بار فراخوانی شود، در غیر این صورت تست Fail می‌شود.۳. Fake: یک پیاده‌سازی ساده‌تر اما واقعی‌تر از Dependency است. مثلا:InMemoryUserRepositoryبه‌جای Database واقعی.۴. Spy: اطلاعاتی از رفتار واقعی Dependency در زمان اجرای تست ثبت می‌کند. برای مثال در زمان تست بگذار EmailService کار خودش را انجام دهد؛ بعد بررسی می‌کنیم که sendEmail چند بار و با چه پارامترهایی صدا زده شده است.نکته مهم این است که این اصطلاحات در Frameworkها و منابع مختلف همیشه دقیقا یک معنی ندارند؛ بنابراین مهم‌تر از نام، این است که بدانیم Test Double چه نقشی در تست‌ها دارد.چه تستی را کجا بنویسیم ؟برای تصمیم‌گیری سریع می‌توان از این قانون استفاده کرد:اگر Business Logic ساده‌ای دارید:Unit Test:&#xA;&#xA;Discount Calculation&#xA;Tax Calculation&#xA;Permission Rules&#xA;Validation Rules&#xA;Pricingاگر با Database یا Infrastructure تعامل دارید:Integration Test:&#xA;&#xA;Repository&#xA;Database&#xA;Redis&#xA;Message Broker&#xA;File Systemاگر یک قابلیت API را می‌خواهید بررسی کنید:Feature / Functional Test:&#xA;&#xA;POST /orders&#xA;POST /login&#xA;GET /profileاگر دو Microservice با هم ارتباط دارند:Contract Test:&#xA;&#xA;Order Service ↔ Payment Serviceاگر کل Business Flow مهم است:E2E Test:&#xA;&#xA;Login → Order → Payment → Confirmationاگر با Legacy Code بدون Test روبه‌رو هستید:Characterization Test:&#xA;&#xA;Current Behavior → Capture → Refactor Safelyاگر Input Space بسیار بزرگ است:Property-Based Test:&#xA;&#xA;Generate Many Inputs&#xA;        ↓&#xA;Check Invariantاگر ورودی‌های غیرمنتظره یا malformed مهم هستند:Fuzz Testing:&#xA;&#xA;Generate Unexpected Inputs&#xA;        ↓&#xA;Execute System&#xA;        ↓&#xA;Find Crash / Failure / Unexpected Behaviorاگر می‌خواهید ظرفیت سیستم را بدانید:Load / Performance Test:&#xA;&#xA;100 → 500 → 1000 req/sیک Test Suite حرفه‌ای چه شکلی است ؟یک Backend حرفه‌ای احتمالا چیزی شبیه این دارد:tests/&#xA;│&#xA;├── unit/&#xA;│   ├── pricing/&#xA;│   ├── validation/&#xA;│   └── authorization/&#xA;│&#xA;├── integration/&#xA;│   ├── database/&#xA;│   ├── redis/&#xA;│   └── messaging/&#xA;│&#xA;├── feature/&#xA;│   ├── orders/&#xA;│   ├── users/&#xA;│   └── payments/&#xA;│&#xA;├── contract/&#xA;│   └── payment-service/&#xA;│&#xA;├── e2e/&#xA;│   └── checkout/&#xA;│&#xA;├── characterization/&#xA;│   └── legacy-order/&#xA;│&#xA;├── property/&#xA;│   └── pricing/&#xA;│&#xA;└── fuzz/&#xA;    ├── api/&#xA;    └── parsers/این ساختار فقط یک مثال است و لازم نیست دقیقا به همین شکل پیاده‌سازی شود. مهم این است که تیم بداند هر Test برای چه ریسکی وجود دارد.مهم‌تر از تعداد Testها، Feedback Loop استیکی از اشتباهات رایج این است که موفقیت Test Suite را با تعداد تست‌ها بسنجیم. برای مثال  ۵,۰۰۰ تست عدد بزرگی به نظر می‌رسد. اما اگر اجرای آن ۴۵ دقیقه طول بکشد، توسعه‌دهنده احتمالا دیگر آن را دائما اجرا نمی‌کند. در مقابل ۱,۵۰۰ تست که در ۳۰ ثانیه اجرا می‌شود، ممکن است ارزش بسیار بیشتری در چرخه توسعه داشته باشند. به همین دلیل یکی از مهم‌ترین ویژگی‌های Test Suite حرفه‌ای Fast Feedback است.هرچه بتوانیم Test مناسب را در پایین‌ترین Scope ممکن بنویسیم، معمولا Feedback سریع‌تر و Failure قابل‌تشخیص‌تر خواهد بود.الگوی Arrange/Act/Assertیک ساختار بسیار رایج برای نوشتن Test، الگوی Arrange/Act/Assert است. این ساختار در تست‌های کوچک و حتی تست‌های سطح بالاتر نیز به خوانایی کمک می‌کند. یعنی:Arrange: آماده‌سازی داده‌ها و DependencyهاAct: اجرای کدی که می‌خواهیم تست کنیمAssert: بررسی نتیجهبرای مثال:Arrange:&#xA;&#xA;price = 1,000,000&#xA;customer = PremiumAct:&#xA;&#xA;result = calculateDiscount(price, customer)Assert:&#xA;&#xA;result == 100,000تست را بر اساس ریسک انتخاب کنید، نه عادتمهم‌ترین چیزی که از این مقاله باید به خاطر بسپاریم این نیست که Unit Test خوب است یا E2E بد است. این نگاه بیش از حد ساده است. سوال درست این نیست که کدام نوع تست بهترین است، بلکه این است که برای یک ریسک مشخص، ارزان‌ترین تستی که اطمینان کافی ایجاد می‌کند کدام است.جمع‌بندیAutomated Testing فقط نوشتن چند Unit Test برای افزایش Code Coverage نیست. یک Test Suite حرفه‌ای باید مجموعه‌ای متعادل از تست‌ها باشد که هرکدام ریسک متفاوتی را پوشش می‌دهند.Unit Test برای منطق‌های کوچک و سریع، Integration Test برای ارتباط بین Unit‌ها، Feature Test برای قابلیت‌های سیستم، Contract Test برای مرز Microserviceها، E2E برای سناریوهای مهم واقعی و Characterization Test برای شناخت و ایمن‌سازی Legacy Code کاربرد دارند.در کنار این‌ها، Property-Based Testing و Fuzz Testing می‌توانند روش‌های متفاوتی برای پیدا کردن Edge Caseها و رفتارهای غیرمنتظره ارایه کنند و Mutation Testing به ما کمک می‌کند بفهمیم Test Suite خودمان چقدر قدرت تشخیص تغییرات اشتباه را دارد.تست خوب، تستی نیست که بیشترین حجم از کدها را اجرا می‌کند؛ بلکه تستی است که با کمترین هزینه، بیشترین ریسک مهم را پوشش دهد.اگر این نگاه را وارد طراحی Test Suite کنیم، تست‌ها از یک هزینه جانبی به بخشی از معماری و فرآیند توسعه تبدیل می‌شوند.سوال پایانیاگر فردا قرار باشد یک سرویس Legacy را که تقریبا هیچ Testی ندارد را Refactor کنید، اولین تستی که می‌نویسید چیست ؟ تجربه شما در پروژه‌های واقعی چطور است ؟ منابع معتبر برای مطالعه بیشترMartin Fowler — Test PyramidMartin Fowler — The Practical Test PyramidMartin Fowler — On the Diverse And Fantastical Shapes of TestingMicrosoft Learn — Testing in .NETMicrosoft Learn — Unit Testing Best PracticesMicrosoft Learn — Integration Tests in ASP.NET CorePact — Contract TestingOWASP — FuzzingHypothesis — Property-Based Testingfast-check — Property-Based TestingMartin Fowler — Legacy Seam</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Sat, 26 Sep 2026 16:10:59 +0330</pubDate></item><item><title>الگوی Outbox Pattern چیست ؟ راهنمای کامل پیاده‌سازی و کاربرد آن در سیستم‌های توزیع‌شده</title><link>https://virgool.ir/@mehradsadeghi/outbox-pattern-jkr1j7c84gy9</link><description>تصویر از Abhinav Thakurاگر دیتابیس با موفقیت عملیاتش را Commit کند و Message Broker در همان لحظه از دسترس خارج شود، آیا سیستم شما همچنان می‌تواند عملیات را موفقیت‌آمیز بداند ؟فرض کنید کاربر در یک فروشگاه اینترنتی سفارشی ثبت می‌کند و سفارش با موفقیت در دیتابیس ذخیره می‌شود:Order #123 → Createdاما چند میلی‌ثانیه بعد سرویس شما می‌خواهد رویداد OrderCreated را به Kafka یا RabbitMQ ارسال کند و ناگهان:شبکه قطع می‌شود.Broker در دسترس نیست.اپلیکیشن کرش می‌کند.Timeout رخ می‌دهد.نتیجه چیست ؟دیتابیس می‌گوید:سفارش ایجاد شده است.اما سایر سرویس‌ها می‌گویند:ما هیچ خبری از این سفارش نداریم!این دقیقا یکی از معروف‌ترین مشکلات سیستم‌های توزیع‌شده و معماری Microservices است؛ مشکلی که به آن Dual Write Problem می‌گوییم.اینجاست که Outbox Pattern وارد می‌شود.ایده‌ی اصلی Outbox در ظاهر ساده است:به‌جای اینکه هم‌زمان در دیتابیس و Message Broker مقداری وارد کنیم، تغییر وضعیت Business Data و پیام مربوط به آن را در یک Transaction ذخیره می‌کنیم و ارسال پیام را به مرحله‌ای جداگانه می‌سپاریم.این تغییر کوچک در طراحی، می‌تواند تفاوت بزرگی در Reliability سیستم ایجاد کند.Outbox Pattern چیست ؟Outbox Pattern یا به شکل دقیق‌تر Transactional Outbox Pattern یک الگوی طراحی برای ارسال قابل‌اعتماد پیام (Message) و رویداد (Event) در سیستم‌های توزیع‌شده است.در این الگو به‌جای اینکه اپلیکیشن مستقیما بعد از تغییر دیتابیس یک Event را به Message Broker ارسال کند، ابتدا Event را در یک Outbox Table در همان دیتابیس ذخیره می‌کند.مهم‌ترین نکته این است که تغییر Business Data و ذخیره Event در Outbox باید در یک Database Transaction انجام شوند. بعد یک Process جداگانه رویدادهای موجود در Outbox را می‌خواند و به Message Broker ارسال می‌کند.به شکل ساده: Application&#xA;     │&#xA;     │ Transaction&#xA;     ▼&#xA;┌───────────────────────┐&#xA;│       Database        │&#xA;│                       │&#xA;│  Orders               │&#xA;│  Outbox Events        │&#xA;└───────────┬───────────┘&#xA;            │&#xA;            │ Async&#xA;            ▼&#xA;     Outbox Publisher&#xA;            │&#xA;            ▼&#xA;     Message Broker&#xA;            │&#xA;       ┌────┴────┐&#xA;       ▼         ▼&#xA;   Service A  Service Bچرا به Outbox Pattern نیاز داریم ؟برای درک Outbox ابتدا باید مشکل اصلی را بشناسیم. فرض کنید یک Order Service داریم که باید دو کار انجام دهد:۱. سفارش را در دیتابیس ذخیره کند.۲. رویداد OrderCreated را به Kafka ارسال کند.ممکن است کد ما چیزی شبیه این باشد:BEGIN TRANSACTION&#xA;INSERT INTO orders (...)&#xA;COMMITpublish(OrderCreated)در نگاه اول منطقی است. اما بین COMMIT و publish یک شکاف خطرناک وجود دارد. اگر دیتابیس Commit شود ولی اپلیکیشن قبل از publish کرش کند، چه اتفاقی می‌افتد؟Database&#xA;   │&#xA;   └── Order Created ✅&#xA;&#xA;Message Broker&#xA;   │&#xA;   └── OrderCreated ❌حالا سیستم وارد وضعیتی شده که State داخلی تغییر کرده و Event مربوط به آن هرگز منتشر نشده است. AWS نیز Transactional Outbox را برای حل مساله‌ی Dual Write معرفی می‌کند؛ یعنی حالتی که یک عملیات باید هم دیتابیس و هم سیستم پیام‌رسان را تغییر دهد، ولی شکست یکی از آن‌ها می‌تواند باعث ناسازگاری شود.Dual Write Problem چیست ؟Dual Write یعنی یک عملیات منطقی باید در دو سیستم مستقل Write شود. برای نمونه:Database&#xA;    +&#xA;  Kafkaیا:Database&#xA;    +&#xA;RabbitMQیا حتی:Database&#xA;    +&#xA;External APIمشکل اینجاست که معمولا بین این دو سیستم یک Transaction مشترک نداریم. برای نمونه:1. UPDATE database ✅&#xA;2. SEND message ❌یا برعکس:1. SEND message ✅&#xA;2. UPDATE database ❌در حالت اول Event گم شده است. در حالت دوم یک Event منتشر شده که Business State مربوط به آن اصلا وجود ندارد.چرا یک Transaction معمولی کافی نیست ؟ممکن است بگویید:خب، ارسال Message را داخل Transaction قرار می‌دهیم!مشکل اینجاست که دیتابیس و Message Broker دو Resource مستقل هستند و برای Atomic بودن عملیات‌مان باید نوعی Distributed Transaction داشته باشیم. یکی از راهکارهای کلاسیک برای این کار Two-Phase Commit یا 2PC است.در روش 2PC، یک هماهنگ‌کننده (coordinator) از همه سیستم‌ها می‌پرسد «آماده‌ای که تغییرات را ثبت کنی ؟» و فقط وقتی همه موافق باشند، به همه دستور ثبت نهایی تغییرات را می‌دهد؛ اگر یکی مشکل داشته باشد، کل عملیات متوقف می‌شود.این روش از نظر تئوری جذاب است، اما در معماری‌های مدرن مشکلاتی مانند Complexity، Latency، Coupling و Failure Handling ایجاد می‌کند. علاوه بر این، همه‌ی دیتابیس‌ها و Message Brokerها الزاما از چنین تراکنشی پشتیبانی نمی‌کنند.Outbox یک سوال متفاوت می‌پرسد:آیا واقعا لازم است دیتابیس و Broker را در یک Transaction قرار دهیم ؟پاسخ Outbox این است:نه. کافی است عملیات حیاتی را در یک Transaction داخلی و قابل اتکا ثبت کنیم و ارسال را به بعد موکول کنیم.Outbox Pattern چگونه مشکل Dual Write را حل می‌کند ؟به‌جای این:Database → Commit&#xA;Broker   → Publishاز این روش استفاده می‌کنیم:Database&#xA; ├── Business Data&#xA; └── Outbox Eventبرای نمونه:BEGIN;&#xA;INSERT INTO orders (id, customer_id, status) VALUES (123, 456, &#39;CREATED&#39;);&#xA;INSERT INTO outbox (id, aggregate_type, aggregate_id, event_type, payload) VALUES (&#39;event-789&#39;, &#39;Order&#39;, 123, &#39;OrderCreated&#39;, &#39;{...}&#39;);&#xA;COMMIT;اگر Transaction موفق شود:Order ✅&#xA;Outbox Event ✅و اگر شکست بخورد:Order ❌&#xA;Outbox Event ❌یعنی دیتابیس تضمین می‌کند که این دو تغییر با هم Commit یا Rollback شوند. این همان نقطه‌ی کلیدی Outbox Pattern است.Outbox Table چیست ؟Outbox معمولا یک جدول (Table) معمولی است. برای مثال:CREATE TABLE outbox (&#xA;    id              UUID PRIMARY KEY,&#xA;    aggregate_type  VARCHAR(100),&#xA;    aggregate_id    VARCHAR(100),&#xA;    event_type      VARCHAR(200),&#xA;    payload         JSONB,&#xA;    created_at      TIMESTAMP,&#xA;    processed_at    TIMESTAMP NULL&#xA;);البته Schema واقعی بسته به نیاز سیستم می‌تواند بسیار متفاوت باشد. فیلدهای مهم معمولا شامل این موارد هستند:1. idشناسه‌ی یکتا برای Event. وجود این ID برای Deduplication و Idempotency بسیار مهم است.2. aggregate_idبرای نمونه:order_id = 123این فیلد برای حفظ Ordering در بسیاری از معماری‌ها اهمیت دارد.3. event_typeبرای نمونه:OrderCreated&#xA;OrderPaid&#xA;OrderCancelled4. payloadاطلاعات Event مثل:{&#xA;  &#34;orderId&#34;: 123,&#xA;  &#34;customerId&#34;: 456,&#xA;  &#34;totalAmount&#34;: 250&#xA;}5. created_atزمان ایجاد Event.6. processed_atدر روش‌های Polling می‌توان از این فیلد برای مشخص کردن Eventهای پردازش‌شده استفاده کرد.چرخه کامل Outbox Pattern چگونه کار می‌کند ؟بیایید کل جریان را ببینیم:Client&#xA;   │&#xA;   ▼&#xA;Order Service&#xA;   │&#xA;   │ BEGIN TRANSACTION&#xA;   ├───────────────────────┐&#xA;   │                       │&#xA;   ▼                       ▼&#xA;Orders Table          Outbox Table&#xA;   │                       │&#xA;   └──────────┬────────────┘&#xA;              │&#xA;           COMMIT&#xA;              │&#xA;              ▼&#xA;       Outbox Publisher&#xA;              │&#xA;              ▼&#xA;        Message Broker&#xA;              │&#xA;       ┌──────┴──────┐&#xA;       ▼             ▼&#xA; Inventory        Notification&#xA; Service            Serviceقدم به قدم:۱. Client درخواست ایجاد Order را ارسال می‌کند.۲. Order Service یک Transaction باز می‌کند.۳. Order در دیتابیس ذخیره می‌شود.۴. Event مربوط به Order در Outbox ذخیره می‌شود.۵. Transaction Commit می‌شود.۶. یک Publisher رویداد را از Outbox می‌خواند.۷. Event به Message Broker ارسال می‌شود.۸. Consumerهای مختلف Event را می‌خوانند.۹. Outbox Event بعد از Publish موفق، Cleanup یا Mark می‌شود.نکته‌ی مهم اینجاست که اپلیکیشن برای موفقیتِ درخواستِ اصلی لازم نیست منتظر Broker بماند. به همین دلیل ارسال Event می‌تواند Asynchronous باشد.یک سناریوی واقعی: ثبت سفارش در فروشگاه اینترنتیفرض کنید کاربر سفارش شماره 1001 را ثبت می‌کند.مرحله ۱: ایجاد سفارشPOST /ordersاپلیکیشن وارد Transaction می‌شود.مرحله ۲: ذخیره Orderorders&#xA;&#xA;id: 1001&#xA;status: CREATEDمرحله ۳: ذخیره Eventدر همان Transaction:outbox&#xA;&#xA;id: event-abc&#xA;event_type: OrderCreated&#xA;aggregate_id: 1001مرحله ۴: Commitاگر Commit موفق باشد:Order → persisted&#xA;Event → persistedحالا حتی اگر اپلیکیشن بلافاصله Crash کند، Event از بین نرفته است.مرحله ۵: Publisher رویداد را ارسال می‌کند:Kafka → OrderCreatedمرحله ۶: ConsumerهاInventory Service:&#xA;&#xA;OrderCreated&#xA;      ↓&#xA;Reserve StockNotification Service:&#xA;&#xA;OrderCreated&#xA;      ↓&#xA;Send Confirmation EmailAnalytics Service:&#xA;&#xA;OrderCreated&#xA;      ↓&#xA;Record Conversionاین همان جایی است که Outbox به Event-Driven Architecture کمک می‌کند.آیا Outbox جلوی Duplicate Message را می‌گیرد ؟خیر. این یکی از مهم‌ترین نکات Outbox Pattern است. Outbox معمولا تضمین می‌کند که Event گم نشود، اما الزاما تضمین نمی‌کند که Event دقیقا یک بار منتشر شود.فرض کنید:۱. Publisher رویداد را می‌خواند۲. Event را به Kafka می‌فرستد۳. Kafka پیام را دریافت می‌کند۴. Publisher قبل از ثبت وضعیت processed کرش می‌کندبعد از Restart:Publisher&#xA;   ↓&#xA;&#34;این ایونت هنوز پردازش نشده&#34;&#xA;   ↓&#xA;Publish Againبنابراین OrderCreated ممکن است دو بار به Consumer برسد. این رفتار کاملا طبیعی است و به مدل At-Least-Once Delivery مربوط است. AWS نیز برای Transactional Outbox صراحتا به احتمال Duplicate Message اشاره می‌کند و Idempotent بودن Consumer را توصیه می‌کند.Idempotency؛ نیمه‌ی دیگر داستاناگر از Outbox استفاده می‌کنید، باید از خودتان بپرسید اگر یک Event دوبار به Consumer رسید، آیا آن Consumer به شکلی خراب عمل می‌کند ؟برای نمونه:PaymentCompletedاگر Consumer دوبار آن را پردازش کند و هر بار:$100 → Charge Customerآن‌وقت مشتری ممکن است دوبار شارژ شود. Consumer باید بتواند Duplicate بودن را تشخیص دهد.یکی از روش‌های رایج:processed_events&#xA;&#xA;event_id&#xA;--------&#xA;event-123&#xA;event-456قبل از Processing:Does event-123 exist ?اگر وجود داشت:Ignoreاگر وجود نداشت:Process&#xA;Insert event_idبهتر است این عملیات نیز در یک Transaction انجام شود. به این ترتیب Outbox و Idempotent Consumer مکمل یکدیگر می‌شوند.آیا Outbox یعنی Exactly-Once ؟اینجا باید خیلی مراقب اصطلاحات باشیم. گاهی گفته می‌شود:Outbox باعث Exactly-Once Delivery می‌شود.این عبارت بیش از حد ساده‌سازی شده است. Outbox به‌تنهایی چنین تضمینی نمی‌دهد.معمولا چنین ساختاری داریم:Producer&#xA;   ↓&#xA;Outbox&#xA;   ↓&#xA;At-Least-Once Publish&#xA;   ↓&#xA;Idempotent Consumerو این ترکیب می‌تواند از نظر اثر نهایی روی Business State رفتار بسیار قابل‌اعتمادتری ایجاد کند. اما اگر Consumer مثلا یک External API را صدا بزند، تضمین Exactly-Once در کل زنجیره بسیار پیچیده‌تر می‌شود.نکته مهم:Exactly-Once یک ویژگی ساده‌ی یک Component نیست؛ یک خاصیت End-to-End است.Outbox Event چگونه به Broker ارسال می‌شود ؟برای این‌ کار معمولا دو رویکرد اصلی وجود دارد:۱. Polling۲. Change Data Capture یا CDCروش اول: Polling Publisherدر این روش یک Worker مرتب از جدول Outbox کوئری می‌گیرد. برای نمونه:SELECT * FROM outbox WHERE processed_at IS NULL ORDER BY created_at LIMIT 100;سپس:Read&#xA; ↓&#xA;Publish&#xA; ↓&#xA;Mark as processedمزیت Polling در سادگی است. نیازی نیست سیستم شما با Transaction Log دیتابیس کار کند. تقریبا با هر دیتابیسی که بتواند کوئری بدهد، می‌توان چنین معماری‌ای ساخت.عیب Polling برای نمونه اگر هر ۵ ثانیه کوئری بزنیم، ممکن است یک Event درست بعد از کوئری ما ایجاد شود و مجبور باشیم تا کوئری بعدی، یعنی حدود ۵ ثانیه، منتظر بمانیم.اگر Polling را خیلی سریع کنیم:100ms&#xA;50ms&#xA;10msفشار بیشتری روی دیتابیس ایجاد می‌شود. بنابراین یک Trade-off داریم:Polling Frequency&#xA;       ↕&#xA;     Latency&#xA;       ↕&#xA;Database Loadروش دوم: CDC یا همان Change Data Captureدر CDC به‌جای اینکه مرتب به یک جدول کوئری بزنیم، تغییرات دیتابیس را از Transaction Log دنبال می‌کنیم.برای نمونه:Application&#xA;    ↓&#xA;Database&#xA;    ↓&#xA;Transaction Log&#xA;    ↓&#xA;CDC Connector&#xA;    ↓&#xA;Message Brokerدر دیتابیس‌های مختلف ممکن است لاگ‌هایی مانند موارد زیر وجود داشته باشد:WAL&#xA;Binlog&#xA;Redo Logابزارهایی مانند Debezium می‌توانند تغییرات Outbox Table را از Transaction Log دریافت کرده و به Event تبدیل کنند. مستندات رسمی Debezium حتی Outbox Event Router مشخصی برای این سناریو ارایه کرده است.مزایای CDCLatency کمترکاهش نیاز به Polling مداوممناسب برای Throughput بالاهماهنگی بهتر با تغییرات Transaction Logمعایب CDCمعماری پیچیده‌ترنیاز به زیرساخت و Monitoring بیشتروابستگی به قابلیت‌های دیتابیسOperational Complexity بالاتربنابراین CDC لزوما بهتر از Polling نیست؛ فقط در بعضی مقیاس‌ها و بر اساس محدودیت‌های Latency، مناسب‌تر است.Polling یا CDC ؟ کدام را انتخاب کنیم ؟اگر سیستم شما تازه در حال شکل‌گیری است، Polling می‌تواند انتخاب ساده‌ای باشد. اگر حجم Eventها زیاد است و Latency پایین اهمیت زیادی دارد، CDC می‌تواند گزینه‌ی مناسب‌تری باشد.آیا Outbox ترتیب Eventها را حفظ می‌کند ؟به‌صورت خودکار و در همه‌ی معماری‌ها نه. فرض کنید:OrderCreated&#xA;OrderPaid&#xA;OrderShippedConsumer باید همین ترتیب را ببیند. اگر:OrderShipped&#xA;OrderCreated&#xA;OrderPaidدریافت شود، ممکن است Business Logic دچار مشکل شود. بنابراین باید Ordering را آگاهانه طراحی کنیم. برای مثال:aggregate_id = order-123و سپس Eventهای مربوط به یک Aggregate را به یک Partition در Broker هدایت کنیم.در کافکا Partition Key می‌تواند در چنین طراحی‌ای کمک کند که Eventهای یک Aggregate به ترتیب در یک Partition قرار بگیرند.Outbox Pattern و Message Ordering دو مساله‌ی مرتبط اما متفاوت هستند.Outbox Table به‌مرور چه اتفاقی برایش می‌افتد ؟فرض کنید سیستم شما در هر ثانیه ۱,۰۰۰ Event تولید کند. در یک روز تقریبا ۸۶,۴۰۰,۰۰۰ Event خواهید داشت.اگر Eventها را بعد از Publish حذف نکنید، جدول Outbox خیلی سریع رشد می‌کند. در نتیجه:افزایش حجم دیتابیسIndex‌های بزرگ‌ترBackupهای سنگین‌ترکوئری‌های کندترهزینه Storage بیشتررا در بر دارد. بنابراین Outbox به یک Lifecycle Strategy نیاز دارد.برای نمونه:Pending&#xA;   ↓&#xA;Published&#xA;   ↓&#xA;Retention Period&#xA;   ↓&#xA;Delete / Archiveسه استراتژی رایج۱. Deleteبعد از Publish موفق Event حذف شود. ساده و کم‌هزینه است، اما Replay تاریخی را سخت‌تر می‌کند.۲. ArchiveEventها بعد از مدتی به Storage دیگری منتقل شوند. برای Audit و Debugging مناسب‌تر است.۳. Partitioningجدول بر اساس زمان Partition شود. برای نمونه:outbox_2026_09_18&#xA;outbox_2026_09_19&#xA;outbox_2026_09_20و Partitionهای قدیمی حذف یا Archive شوند.Outbox Pattern چه مزایایی دارد ؟۱. جلوگیری از Lost Eventمهم‌ترین مزیت Outbox این است که اگر Business Transaction کامیت شود، Event نیز همراه آن ذخیره می‌شود. در نتیجه Crash کردن Publisher بلافاصله بعد از Commit، باعث Lost Event نمی‌شود.۲. عدم نیاز به 2PCدیتابیس و Broker مجبور نیستند یک Distributed Transaction مشترک داشته باشند.۳. جداسازی Business Operation از Message PublishingBusiness Request لازم نیست منتظر Broker بماند.۴. مناسب برای Event-Driven ArchitectureOutbox برای ارتباط بین Microserviceها بسیار کاربردی است.۵. Retry ساده‌تراگر Broker موقتا در دسترس نباشد:Outbox&#xA;  ↓&#xA;Retry&#xA;  ↓&#xA;Retry&#xA;  ↓&#xA;SuccessEvent همچنان در دیتابیس باقی می‌ماند.معایب Outbox Pattern چیست ؟۱. Complexity بیشتربه جای دیتابیس تنها، چنین چیزی داریم:Database&#xA;+&#xA;Outbox&#xA;+&#xA;Publisher&#xA;+&#xA;Broker&#xA;+&#xA;Retry&#xA;+&#xA;Monitoring&#xA;+&#xA;Cleanup۲. Duplicate EventPublisher ممکن است Event را بیش از یک بار ارسال کند. پس Consumer باید Idempotent باشد.۳. Eventual ConsistencyEvent بلافاصله بعد از Commit به همه سرویس‌ها نمی‌رسد. برای مدت کوتاهی ممکن است:Order Service → CREATED&#xA;Inventory Service → هنوز چیزی نمی‌دانداین تاخیر باید در طراحی Business قابل قبول باشد.۴. رشد Outboxاگر Lifecycle مناسبی نداشته باشید، Outbox تبدیل به یک جدول بسیار بزرگ می‌شود.۵. Failureهای جدیدحالا باید مشکلاتی همچون:Publisher Crash&#xA;Broker Down&#xA;Database Down&#xA;Poison Message&#xA;Retry Storm&#xA;Duplicate Event&#xA;Outbox Backlogرا نیز مدیریت کنید. بنابراین Outbox مشکل Reliability را حذف نمی‌کند؛ نوع Failureهایی که باید مدیریت کنید را تغییر می‌دهد.Outbox Pattern در برابر 2PCOutbox به‌جای تلاش برای ساختن یک Transaction سراسری، Transaction را در محدوده‌ی یک دیتابیس قرار می‌دهد.Outbox در برابر ارسال مستقیم Eventروش ساده:UPDATE DB&#xA;   ↓&#xA;PUBLISH EVENTمزایا:سادهکم‌هزینهLatency پایینمعایب:Dual Writeاحتمال Lost EventFailure Windowروش Outbox:UPDATE DB&#xA;   +&#xA;INSERT OUTBOX&#xA;   ↓&#xA;COMMIT&#xA;   ↓&#xA;PUBLISH ASYNCمزایا:Reliability بالاترRetry قابلیتعدم Lost Event در Failureهای معمول بین Commit و Publishمعایب/هزینه‌ها:افزایش ComplexityEventual ConsistencyDuplicate HandlingOperational Overheadبنابراین اگر Event واقعا حیاتی نیست، Outbox همیشه بهترین انتخاب نیست.برای نمونه اگر Event‌ها فقط برای تحیلی‌های آماری غیرحیاتی استفاده می‌شوند و از دست رفتن درصد بسیار کوچکی از آن‌ها قابل قبول است، ممکن است حفظ سادگی معماری ارزش بیشتری داشته باشد.آیا Outbox فقط برای Microservices است ؟خیر.یکی از سوءبرداشت‌های رایج این است که Outbox فقط مخصوص مایکروسرویس‌ها است. در یک Monolith نیز ممکن است نیاز داشته باشید بعد از تغییر دیتابیس، یک عملیات خارجی انجام دهید. برای نمونه:Order Created&#xA;    ↓&#xA;Call a Webhookیا:User Registered&#xA;    ↓&#xA;Send Emailاگر Email Provider موقتا در دسترس نباشد، شاید نخواهید Registration را Fail کنید. می‌توانید:User&#xA;+&#xA;Email Eventرا در یک Transaction ذخیره کنید و Email را بعدا ارسال کنید.در نتیجه Outbox بیشتر از آنکه یک الگوی Microservices باشد، یک راهکار برای Asynchronous Messaging قابل اطمینان است.Outbox Pattern چه زمانی ارزش استفاده دارد ؟معمولا وقتی Outbox ارزش زیادی پیدا می‌کند که:Event از نظر Business مهم باشد.Lost Event قابل قبول نباشد.دیتابیس و Broker دو سیستم مستقل باشند.سیستم Event-Driven باشد.بین Serviceها ارتباط Asynchronous داشته باشید.Retry و Reliability اهمیت داشته باشد.Consistency فوری بین سرویس‌ها ضروری نباشد.چه زمانی Outbox لازم نیست ؟اگر Event شما کاملا غیرحیاتی است، اضافه‌کردن Outbox ممکن است پیچیدگی غیرضروری ایجاد کند. برای نمونه:User clicked buttonاین Event صرفا برای تحلیل‌های (Analytics) تقریبی استفاده می‌شود. اگر از دست رفتن بعضی Eventها قابل قبول باشد، شاید Direct Publish یا حتی یک سیستم Analytics مستقل، انتخاب ساده‌تری باشد.نکات فنی مهم در پیاده‌سازی Outbox۱. Event ID یکتا داشته باشید. هر Event باید یک ID پایدار داشته باشد:event_id = UUIDاین ID برای Deduplication بسیار مهم است.۲. Consumerها را Idempotent طراحی کنیدفرض کنید:OrderPaidدو بار دریافت شود. نتیجه نباید چنین چیزی باشد:Payment = Payment × 2Consumer باید بتواند Duplicate بودن را تشخیص دهد.۳. Outbox را Index کنید. اگر از روش Polling استفاده می‌کنید، کوئری‌هایی مانند:WHERE processed_at IS NULL ORDER BY created_atباید با توجه به حجم و الگوی دسترسی مناسب Index شوند.۴. Batch Processing را در نظر بگیریدبه‌جای:1 Event → 1 Queryممکن است:100 Events → 1 Batchبهتر باشد. اما Batch بزرگ نیز می‌تواند Lock، Memory یا Retry Cost را افزایش دهد.۵. Retry با Backoffاگر Broker در دسترس نیست، نباید در هر میلی ثانیه درخواست جدید بفرستید. برای نمونه:1s&#xA;2s&#xA;4s&#xA;8s&#xA;16s&#xA;...به همراه محدودیت و سیاست مناسب Retry.۶. Dead Letter را فراموش نکنیدممکن است رویدادی داشته باشید که همیشه Fail شود. اگر بدون محدودیت Retry کنید:Event&#xA; ↓&#xA;Fail&#xA; ↓&#xA;Retry&#xA; ↓&#xA;Fail&#xA; ↓&#xA;Retry&#xA; ↓&#xA;...یک Retry Storm ایجاد می‌شود. بنابراین باید برای Eventهای مشکل‌دار Strategy مشخصی داشته باشید.یک نکته مهم درباره Monitoringیکی از Metricهای مهم در Outbox، تعداد Eventهای در انتظار انتشار است. برای نمونه:Outbox Pending Events = 12اما اگر:Outbox Pending Events = 2,000,000باشد، یعنی Publisher احتمالا عقب افتاده.Metricهای مفید می‌توانند شامل این موارد باشند:Outbox Backlog&#xA;Publish Latency&#xA;Publish Failure Rate&#xA;Retry Count&#xA;Oldest Unprocessed Event Age&#xA;Consumer Lag&#xA;Dead Letter Countمخصوصا:Oldest Unprocessed Event Ageمی‌تواند Metric بسیار ارزشمندی باشد. برای نمونه اگر قدیمی‌ترین Event پردازش‌نشده:Age = 45 minutesباشد، حتی اگر تعداد Eventهای Pending کم باشد، احتمالا یک مشکل عملیاتی وجود دارد.Outbox Pattern و Eventual ConsistencyOutbox شما را مجبور می‌کند یک واقعیت مهم در سیستم‌های توزیع‌شده را بپذیرید:همه‌چیز قرار نیست بلافاصله Consistent باشد.ممکن است:T0:&#xA;Order Created&#xA;&#xA;T0 + 20ms:&#xA;Outbox Published&#xA;&#xA;T0 + 50ms:&#xA;Inventory Updated&#xA;&#xA;T0 + 100ms:&#xA;Notification Sentپس در فاصله‌ای کوتاه:Order Service → CREATED&#xA;Inventory Service → قبلی State هنوزاین الزاما Bug نیست. اگر نیازمندی Business شما اجازه‌ی چنین تاخیری را می‌دهد، Eventual Consistency می‌تواند یک Trade-off کاملا منطقی باشد.در معماری‌های Event-Driven، این موضوع یکی از پایه‌های مهم طراحی است. Microsoft نیز در راهنمای معماری خود تاکید می‌کند که در چنین معماری‌هایی باید Duplication، Ordering و Idempotency را آگاهانه طراحی کرد.جمع‌بندیOutbox Pattern راهکاری برای حل یکی از مشکلات کلاسیک سیستم‌های توزیع‌شده یعنی Dual Write Problem است. به‌جای اینکه اپلیکیشن هم‌زمان:Database&#xA;+&#xA;Message Brokerرا تغییر دهد، تغییر Business State و Event را در یک Transaction محلی ذخیره می‌کند:Database&#xA;├── Business Data&#xA;└── Outbox Eventو بعد، یک Process مستقل Event را به Broker منتقل می‌کند.مزیت اصلی این رویکرد این است که اگر Transaction اصلی Commit شود، Event مربوط به آن نیز ذخیره شده است و می‌توان ارسال آن را Retry کرد.اما Outbox پایان ماجرا نیست. شما همچنان باید با مواردی مثل:پیام‌های DuplicateIdempotencyOrderingEventual ConsistencyRetryOutbox GrowthMonitoringCleanupکنار بیایید.یک سوال برای بحثاگر Lost Event برای سیستم شما غیرقابل‌قبول باشد، اما چند ثانیه Eventual Consistency قابل‌تحمل باشد، آیا حاضر هستید پیچیدگی مربوط به Outbox، Retry و Idempotency را به معماری اضافه کنید ؟ یا در چه نقطه‌ای ترجیح می‌دهید سادگی معماری را به Reliability بالاتر ترجیح دهید ؟منابعبرای مطالعه‌ی عمیق‌تر، این منابع پیشنهاد می‌شوند:Transactional Outbox در Microservices.io: توضیح رسمی Chris Richardson درباره‌ی مساله، اجزای Pattern، مزایا، محدودیت‌ها و Message Relay.AWS Prescriptive Guidance — Transactional Outbox: بررسی Dual Write، Outbox Table، CDC، Duplicate Message و Idempotency.Debezium Documentation — Outbox Event Router: مستندات رسمی برای پیاده‌سازی Outbox با CDC و Transaction Log.Microsoft Azure Architecture Center: نمونه‌ی پیاده‌سازی Transactional Outbox و استفاده از Change Feed و Message Bus.مقاله‌ی The Outbox Pattern: A Love Letter to Eventual Consistency برای نگاه مفهومی و بررسی Polling، CDC، Idempotency، Ordering و مقایسه با Saga و Event Sourcing.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Mon, 21 Sep 2026 17:20:27 +0330</pubDate></item><item><title>الگوی Circuit Breaker چیست ؟ چگونه سیستم‌های توزیع‌شده را در برابر خرابی سرویس‌ها مقاوم کنیم ؟</title><link>https://virgool.ir/@mehradsadeghi/circuit-breaker-pattern-yhwlpkxu5fqn</link><description>Circuit Breaker Patternفرض کنید سرویس پرداخت شما از کار افتاده است. در حالت عادی هر درخواست خرید باید چیزی شبیه این مسیر را طی کند:User&#xA;  ↓&#xA;Order Service&#xA;  ↓&#xA;Payment Service&#xA;  ↓&#xA;Bankحالا Payment Service از کار افتاده (یا کند شده) و هر درخواست به‌جای ۲۰۰ میلی‌ثانیه، ۱۰ ثانیه طول می‌کشد.اگر سیستم شما در این شرایط همچنان به ارسال درخواست ادامه دهد، چه اتفاقی می‌افتد ؟صف درخواست‌ها بزرگ‌تر می‌شود، Threadها و Connectionها اشغال می‌شوند، Timeoutها بیشتر می‌شوند و سرویس‌های دیگری که ظاهرا سالم هستند هم شروع به خطا می‌کنند. یعنی ممکن است یک سرویس خراب، کل سیستم را از کار بیندازد.اینجاست که Circuit Breaker وارد بازی می‌شود. Circuit Breaker به سیستم می‌گوید:اگر یک سرویس خارجی به‌اندازه‌ی کافی خراب شده، فعلا دیگر به آن درخواست نفرست. سریع Fail کن و اجازه بده سیستم نفس بکشد.الگوی Circuit Breaker چیست ؟الگوی Circuit Breaker طرحی برای افزایش Resiliency و Reliability در سیستم‌های توزیع‌شده است. ایده‌ی آن از همان فیوز برق (Circuit Breaker) گرفته شده است. وقتی جریان برق بیش از حد شود، فیوز مدار را قطع می‌کند تا آسیب بیشتری ایجاد نشود.در نرم‌افزار نیز وقتی یک سرویس خارجی دچار خطاهای مکرر می‌شود، Circuit Breaker جریان درخواست‌ها به آن سرویس را متوقف می‌کند.هدف این نیست که سرویس خراب را تعمیر کند. هدف این است که:از ارسال درخواست‌های بی‌فایده جلوگیری کند.منابع سرویس خودمان را حفظ کند.فشار روی سرویس خارجی خراب را کاهش دهد.جلوی Cascading Failure را بگیرد.امکان Recovery سرویس خراب را فراهم کند.در صورت امکان سیستم را با یک رفتار جایگزین همچنان قابل استفاده نگه دارد.به زبان ساده Circuit Breaker خرابی را حل نمی‌کند؛ جلوی گسترش خرابی را می‌گیرد.چرا در سیستم‌های توزیع‌شده به Circuit Breaker نیاز داریم ؟در یک برنامه‌ی ساده‌ی Monolith، ممکن است یک تابع را صدا بزنیم:calculatePrice()اگر تابع شکست بخورد، معمولا کنترل شرایط ساده‌تر است. اما در یک سیستم توزیع‌شده، هر فراخوانی از شبکه عبور می‌کند:Service A&#xA;    ↓&#xA;Service B&#xA;    ↓&#xA;Service C&#xA;    ↓&#xA;External APIو برخلاف یک تابع معمولی، یک Remote Call می‌تواند:Timeout شود.Connection را از دست بدهد.بسیار کند شود.خطای 5xx بدهد.Rate Limit شود.کاملا از دسترس خارج باشد.مشکل مهم‌تر این است که شکست یک سرویس خارجی می‌تواند به سرویس‌های دیگر منتقل شود.برای نمونه:Payment Service&#xA;      ↓&#xA;   مشکل دارد&#xA;      ↓&#xA;منتظر می‌ماند Order Service&#xA;      ↓&#xA;اشغال می‌شوند Threadها&#xA;      ↓&#xA;درخواست‌ها صف می‌کشند&#xA;      ↓&#xA;افزایش پیدا می‌کند Latency&#xA;      ↓&#xA;بالا می‌رود Error Rate&#xA;      ↓&#xA;کل سیستم تحت فشار قرار می‌گیرد این همان چیزی است که به آن Cascading Failure می‌گوییم.Circuit Breaker یکی از ابزارهایی است که برای جلوگیری از چنین زنجیره‌ای استفاده می‌شود.حالا Reliability و High Availability چه ارتباطی با Circuit Breaker دارند ؟دو مفهوم مهم در طراحی سیستم‌های توزیع‌شده:Reliability: یعنی سیستم بتواند در برابر خطاها رفتار قابل‌اعتماد داشته باشد و پس از Failure بتواند به وضعیت سالم برگردد.High Availability: یعنی سیستم تا حد امکان در دسترس کاربران باقی بماند، حتی زمانی که بخشی از زیرساخت یا سرویس‌ها دچار مشکل شده‌اند.Circuit Breaker مستقیما باعث نمی‌شود سرویس خارجی همیشه Available باشد.اما کمک می‌کند:Dependency Failure&#xA;       ↓&#xA;Circuit Breaker&#xA;       ↓&#xA;Failure Isolation&#xA;       ↓&#xA;Graceful Degradation&#xA;       ↓&#xA;System remains responsiveدر واقع Circuit Breaker بخشی از استراتژی Failure Isolation است. به‌جای اینکه خرابی یک سرویس را به کل سیستم منتقل کنیم، سعی می‌کنیم Blast Radius آن را محدود کنیم.الگوی Circuit Breaker چگونه کار می‌کند ؟این الگو معمولا به‌صورت یک State Machine پیاده‌سازی می‌شود و سه وضعیت اصلی دارد:Closed وضعیتOpen وضعیتHalf-Open وضعیتوضعیت Closed، همه‌چیز عادی استدر حالت Closed مدار در وضعیت عادی قرار دارد. درخواست‌ها اجازه دارند به سرویس‌های خارجی ‌ارسال شوند:Client&#xA;  ↓&#xA;Circuit Breaker&#xA;  ↓&#xA;Payment ServiceCircuit Breaker در همین زمان وضعیت درخواست‌ها را بررسی می‌کند.برای نمونه:100 requests&#xA;95 success&#xA;5 failureاگر Failureها از آستانه‌ی تعیین‌شده عبور کنند Circuit به وضعیت OPEN می‌رود.نکته مهمآستانه‌ی Failure الزاما به معنی «۵ درخواست پشت سر هم شکست خورد» نیست.می‌تواند بر اساس موارد مختلف تعریف شود:تعداد خطاهادرصد خطاهاتعداد Timeoutهاتعداد Failure در یک بازه زمانیترکیبی از چند Metricبرای نمونه: 50 failures در ۱۰۰ درخواست اخیر، یا Failure Rate &gt; 30% در ۳۰ ثانیه اخیر.انتخاب آستانه (Threshold) باید بر اساس رفتار واقعی سرویس خارجی و SLOهای سیستم انجام شود.وضعیت Open، دیگر به سرویس خراب درخواست ندهوقتی Circuit به وضعیت Open می‌رود، رفتار سیستم کاملا تغییر می‌کند.قبل:Request&#xA;  ↓&#xA;Circuit Breaker&#xA;  ↓&#xA;Payment Serviceبعد:Request&#xA;  ↓&#xA;Circuit Breaker&#xA;  ↓&#xA;FAIL FASTیعنی درخواست اصلا به Payment Service ارسال نمی‌شود. این قسمت مهم‌ترین ایده‌ی Circuit Breaker است. اگر می‌دانیم یک سرویس احتمالا خراب است، چرا دوباره آن را صدا بزنیم ؟در حالت Open درخواست بدون منتظر ماندن برای Network Timeout یا پاسخ سرویس، سریع Fail می‌شود. این همان مفهوم Fail Fast است. فرض کنید Payment Service در دسترس نیست و Timeout آن 10 ثانیه است. اگر ۱۰۰۰ درخواست هم‌زمان داشته باشیم و همه منتظر Timeout بمانند:1000 requests × 10 secondsبخش زیادی از منابع سیستم درگیر انتظار می‌شوند. اما با Circuit Breaker:Request&#xA;   ↓&#xA;Circuit = OPEN&#xA;   ↓&#xA;Immediate Failureجلوی ارسال درخواست در مدت بسیار کوتاه گرفته می‌شود.در نتیجه:Threadها کمتر درگیر می‌شوند.Connectionها آزاد می‌مانند.CPU و Memory کمتر مصرف می‌شود.Latency قابل‌کنترل‌تر می‌شود.فشار روی سرویس خارجی افزایش پیدا نمی‌کند.در واقع گاهی سریع Fail کردن بهتر از منتظر ماندن برای یک Failure دیرهنگام است.اما Open قرار نیست برای همیشه باز بماند. اگر Circuit برای همیشه Open بماند، سیستم هیچ‌وقت متوجه نمی‌شود که سرویس خارجی دوباره سالم شده است. پس بعد از یک مدت مشخص Circuit وارد حالت HALF-OPEN می‌شود. این مدت را معمولا با مفهومی مثل Timeout / Recovery Timeout / Cooldown Period مشخص می‌کنیم.برای نمونه:Circuit Open&#xA;     ↓&#xA;Wait 30 seconds&#xA;     ↓&#xA;Half-Openوضعیت Half-Open، آیا سرویس دوباره زنده شده است ؟Half-Open یکی از ظریف‌ترین بخش‌های Circuit Breaker است. فرض کنید Payment Service دوباره سالم شده است. آیا باید ناگهان تمام درخواست‌های منتظر را دوباره به آن ارسال کنیم؟نه. ممکن است سرویس تازه Recovery شده باشد و هنوز ظرفیت کامل را نداشته باشد. پس Circuit Breaker تعداد محدودی درخواست آزمایشی را به آن سرویس ارسال می‌کند:Half-Open&#xA;    ↓&#xA;5 test requests&#xA;    ↓&#xA;Success ?اگر این درخواست‌ها موفق باشند:HALF-OPEN&#xA;     ↓&#xA;Successful requests&#xA;     ↓&#xA;CLOSEDو سیستم دوباره به حالت عادی برمی‌گردد. اما اگر درخواست آزمایشی دوباره شکست بخورد:HALF-OPEN&#xA;     ↓&#xA;Failure&#xA;     ↓&#xA;OPENو Timeout دوباره شروع می‌شود.چرا Half-Open فقط چند درخواست را ارسال می‌کند ؟چون Recovery یک سرویس لزوما به معنی Recovery کامل نیست. برای نمونه تصور کنید Payment Service بعد از یک قطعی دوباره بالا آمده، اما فقط توان پاسخ‌گویی به ۲۰ درخواست در ثانیه را دارد. اگر ناگهان ۱۰,۰۰۰ درخواست به آن بفرستیم:Recovery&#xA;   ↓&#xA;Traffic Spike&#xA;   ↓&#xA;Overload&#xA;   ↓&#xA;Failure Againیعنی خودمان دوباره سرویس را خراب کرده‌ایم.Half-Open شبیه یک تست کنترل‌شده است. یعنی به‌جای:0 request&#xA;      ↓&#xA;10000 requestsداریم:0 request&#xA;      ↓&#xA;چند درخواست آزمایشی&#xA;      ↓&#xA;بررسی نتیجه&#xA;      ↓&#xA;افزایش تدریجی ترافیکتعداد درخواست‌های آزمایشی باید بر اساس نوع سیستم انتخاب شود و لزوما یک عدد ثابت برای همه‌ی معماری‌ها وجود ندارد.یک سناریوی واقعی: خرابی سرویس پرداختفرض کنید یک فروشگاه اینترنتی داریم:User&#xA; ↓&#xA;Order Service&#xA; ↓&#xA;Payment Service&#xA; ↓&#xA;Bank Gatewayدر حالت عادی:Order Service&#xA;      ↓&#xA;Circuit Breaker&#xA;      ↓&#xA;Payment Service&#xA;  ↓&#xA;Bankهمه‌چیز خوب است.مرحله اول: خرابی شروع می‌شود. Bank Gateway دچار مشکل می‌شود، در نتیجه Payment Service شروع به Timeout دادن می‌کند.Payment Request&#xA;      ↓&#xA;TimeoutCircuit Breaker این Failureها را ثبت می‌کند.مرحله دوم: آستانه‌ی Failure رد می‌شود. برای نمونه:100 requests&#xA;40 failuresو Policy ما می‌گوید:Failure Rate &gt; 30% → OPENپس Circuit باز می‌شود.مرحله سوم: درخواست‌ها Fail Fast می‌شوند. حالا درخواست جدید:User&#xA; ↓&#xA;Order Service&#xA; ↓&#xA;Circuit = OPEN&#xA; ↓&#xA;Immediate Failureیعنی دیگر Payment Service را صدا نمی‌زنیم.مرحله چهارم: سیستم وارد حالت Degraded می‌شود. حالا باید تصمیم بگیریم با کاربر چه کنیم. برای نمونه:پرداخت در حال حاضر در دسترس نیست. لطفاً چند دقیقه دیگر دوباره تلاش کنید.یا ممکن است بتوانیم:سفارش را موقتا Pending کنیم.عملیات را وارد Queue کنیم.از یک Payment Provider جایگزین استفاده کنیم.اطلاعات غیرحیاتی را از Cache برگردانیم.اینجاست که مفهوم Fallback اهمیت پیدا می‌کند.Fallback چیست ؟Fallback یعنی «اگر مسیر اصلی شکست خورد، آیا مسیر جایگزین معناداری داریم ؟»برای نمونه:Primary Payment Provider&#xA;          ↓&#xA;        FAILED&#xA;          ↓&#xA;Secondary Payment Providerاما Fallback همیشه وجود ندارد. برای نمونه برای پرداخت نمی‌توانیم صرفا یک پاسخ جعلی موفقیت‌آمیز برگردانیم. بنابراین Fallback باید با Business Logic سازگار باشد.یک نکته بسیار مهم: Circuit Breaker ≠ Retryاین دو الگو گاهی با هم اشتباه گرفته می‌شوند.Retry می‌گوید:این درخواست شکست خورده؛ شاید اگر دوباره تلاش کنم موفق شود.Circuit Breaker می‌گوید:این سرویس خارجی چند بار پشت سر هم شکست خورده؛ فعلا دیگر به آن درخواست نده.Retry برای Transient Failureها بسیار مفید است.برای نمونه:Network glitch&#xA;Temporary timeout&#xA;Temporary throttlingاما اگر سرویس خارجی واقعا Down باشد، Retry بیش از حد می‌تواند مشکل را شدیدتر کند.چگونه Retry باعث Cascading Failure می‌شود ؟فرض کنید ۱۰۰۰ درخواست داریم. اگر هر درخواست شکست بخورد، ۳ بار Retry می‌کنیم. یعنی:1000 original requests + 3000 retries = 4000 requestsیعنی دقیقا در زمانی که سرویس Down یا Overloaded است، بار بیشتری به آن وارد می‌کنیم.حالا فرض کنید سرویس A به B و B به C وابسته است:A → B → CC خراب می‌شود. B درخواست‌ها را Retry می‌کند. A هم درخواست‌های خود را Retry می‌کند.در نتیجه:C Failure&#xA;   ↓&#xA;B Retries&#xA;   ↓&#xA;A Retries&#xA;   ↓&#xA;More traffic&#xA;   ↓&#xA;C becomes even more overloaded&#xA;   ↓&#xA;More failures&#xA;   ↓&#xA;Cascading Failureاین همان چیزی است که به آن Retry Storm گفته می‌شود.پس Retry را حذف کنیم ؟نه. مساله Retry نیست؛ Retry بدون کنترل مشکل‌ساز است. یک معماری مقاوم معمولا از ترکیبی از این مفاهیم استفاده می‌کند: برای نمونه:Request&#xA;   ↓&#xA;Timeout&#xA;   ↓&#xA;Retry&#xA;   ↓&#xA;Exponential Backoff + Jitter&#xA;   ↓&#xA;Circuit Breakerنکته مهم این است که Retry باید فقط برای خطاهایی انجام شود که احتمال موفقیت مجدد دارند. برای مثال retry کردن خطای 503 Service Unavailable ممکن است منطقی باشد، اما retry کردن 401 Unauthorized معمولا چیزی را حل نمی‌کند.چرا Exponential Backoff و Jitter مهم‌اند ؟فرض کنید ۱۰,۰۰۰ درخواست هم‌زمان شکست خورده‌اند. اگر همه دقیقا یک ثانیه بعد دوباره تلاش کنند:1 sec&#xA;  ↓&#xA;10,000 requestsدوباره یک Spike ایجاد می‌شود.اما در Exponential Backoff، فاصله‌ی Retry افزایش پیدا می‌کند:1s&#xA;2s&#xA;4s&#xA;8s&#xA;...اما حتی این هم می‌تواند مشکل داشته باشد، یعنی اگر همه دقیقا در همان زمان Retry کنند، دوباره Spike داریم.اینجاست که Jitter وارد می‌شود. یعنی یک مقدار تصادفی به زمان انتظار اضافه کنیم:1.2s&#xA;1.8s&#xA;2.4s&#xA;3.1s&#xA;...در نتیجه درخواست‌ها در طول زمان پخش می‌شوند.Idempotency؛ نکته‌ای که هنگام Retry نباید فراموش کنیمفرض کنید این درخواست را داریم:POST /paymentsدرخواست به Payment Service ارسال شده است و پرداخت واقعا انجام شده، اما پاسخ به دلیل مشکل Network به Client نرسیده است. Client چه می‌بیند؟Timeoutحالا اگر بدون طراحی مناسب Retry کنیم:POST /paymentsممکن است پرداخت دوم هم انجام شود.یعنی:Request #1 → Payment successful&#xA;&#xA;Response lost&#xA;   ↓&#xA;Retry&#xA;   ↓&#xA;Request #2 → Payment successful againاینجاست که Idempotency اهمیت پیدا می‌کند. با استفاده از یک Idempotency Key (برای نمونه payment_id = ABC123) می‌توانیم مشخص کنیم اگر همان عملیات دوباره ارسال شد، سرویس تشخیص دهد که این عملیات قبلا پردازش شده است.در نتیجه:Retry&#xA;  ↓&#xA;Same Idempotency Key&#xA;  ↓&#xA;Same logical operation&#xA;  ↓&#xA;No duplicate side effectبنابراین هرجا Retry داریم، باید بپرسیم آیا این Operation واقعا قابل Retry است ؟مهم‌ترین پارامترهای Circuit Breakerپیاده‌سازی Circuit Breaker فقط قرار دادن یک شرط ساده در کد نیست. چند پارامتر مهم باید مشخص شوند.۱. آستانه Failure: چه مقدار Failure باعث Open شدن Circuit شود ؟برای نمونه:Failure Rate &gt; 50%Threshold خیلی پایین ممکن است باعث شود Circuit با خطاهای موقتی بیش از حد باز شود.Threshold خیلی بالا هم ممکن است زمانی Circuit را باز کند که سیستم شما قبلا آسیب دیده است.۲. Timeout: بعد از Open شدن، چه مدت صبر کنیم ؟برای نمونه:30 secondsTimeout خیلی کوتاه:Open&#xA;  ↓&#xA;Half-Open&#xA;  ↓&#xA;Failure&#xA;  ↓&#xA;Open&#xA;  ↓&#xA;Half-Open&#xA;...Timeout خیلی طولانی نیز ممکن است باعث شود سیستم مدت زیادی حتی پس از Recovery سرویس، درخواست‌ها را به آن ارسال نکند.۳. تعداد درخواست‌های Half-Open: وقتی Circuit وارد Half-Open شد، چند درخواست اجازه‌ی عبور داشته باشند ؟برای نمونه:1 requestیا:5 requestsیا درصد محدودی از Traffic. هرچه تعداد بیشتر باشد، Recovery سریع‌تر ارزیابی می‌شود و فشار بیشتری به سرویس خارجی وارد می‌شود.۴. چه Failureهایی Circuit را باز کنند ؟ همه‌ی خطاها نباید یکسان باشند. برای نمونه:Timeout → مهم&#xA;503 → مهم&#xA;Connection Error → مهم&#xA;401 → احتمالا نه&#xA;400 → احتمالا نهاگر برای هر خطایی Circuit را باز کنیم، ممکن است کل سرویس خارجی را خارج از دسترس فرض کنیم.Circuit Breaker خودش هم هزینه داردهیچ الگویی رایگان نیست. Circuit Breaker هم پیچیدگی‌هایی ایجاد می‌کند.از جمله مزایای این الگو می‌توان به موارد زیر اشاره کرد:جلوگیری از Cascading Failureکاهش فشار روی سرویس خارجی خرابFail Fastکاهش مصرف منابعکمک به Graceful Degradationافزایش Resilienceامکان Recovery کنترل‌شدهاز جمله معایب این الگو می‌توان به موارد زیر اشاره کرد:افزایش Complexityنیاز به Monitoringنیاز به تنظیم Thresholdهااحتمال False Positiveاحتمال False Negativeنیاز به مدیریت Stateنیاز به طراحی Fallbackدشوارتر شدن Debuggingبرای نمونه اگر آستانه بیش از حد حساس باشد:Temporary Failure&#xA;      ↓&#xA;Circuit Opens&#xA;      ↓&#xA;Requests rejectedدر حالی که سرویس مقصد واقعا Down نبوده است. از طرف دیگر اگر آستانه خیلی بزرگ باشد:Dependency is failing&#xA;      ↓&#xA;Circuit still Closed&#xA;      ↓&#xA;More failures&#xA;      ↓&#xA;More resource consumptionپس تنظیم Circuit Breaker یک Trade-off است، نه یک عدد جادویی.Circuit Breaker را کجا قرار دهیم ؟معمولا Circuit Breaker نزدیک به جایی قرار می‌گیرد که Remote Call انجام می‌شود:Order Service&#xA;     ↓&#xA;Circuit Breaker&#xA;     ↓&#xA;Payment Serviceاما در معماری‌های بزرگ‌تر می‌توان قابلیت Circuit Breaking را در لایه‌های دیگری نیز پیاده کرد؛ برای نمونه در:Application سطحAPI GatewayService MeshSidecarمزیت Service Mesh این است که می‌تواند برخی قابلیت‌های تاب‌آوری (Resilience) را بدون قرار دادن منطق مشابه در تک‌تک سرویس‌ها مدیریت کند. اما این هم Trade-off دارد. هرچه تاب‌آوری را بیشتر به زیرساخت منتقل کنیم، Application Code ساده‌تر می‌شود؛ ولی در مقابل Debugging و درک رفتار واقعی سیستم ممکن است پیچیده‌تر شود.آیا همیشه باید Circuit Breaker داشته باشیم ؟خیر. Circuit Breaker برای هر نوع سرویس خارجی‌ای لازم نیست. اگر یک Operation بسیار سریع است:function calculateTax()استفاده از Circuit Breaker احتمالا فقط پیچیدگی را اضافه می‌کند.اما وقتی با:Remote APIدیتابیسExternal Providerمایکروسرویس‌هاThird-party سرویس‌هایسروکار داریم، ارزش بررسی و استفاده از آن بیشتر می‌شود.Circuit Breaker به‌تنهایی کافی نیستیکی از اشتباهات رایج این است که تصور کنیم Circuit Breaker = Resiliency است، در حالی که Circuit Breaker فقط یکی از ابزارهاست. یک سیستم مقاوم ممکن است ترکیبی از این الگوها باشد:              ┌─────────────┐&#xA;     │   Timeout   │&#xA;     └──────┬──────┘&#xA;            ↓&#xA;     ┌─────────────┐&#xA;     │    Retry    │&#xA;     └──────┬──────┘&#xA;            ↓&#xA;┌───────────────────────┐&#xA;│ Exponential Backoff   │&#xA;│       + Jitter        │&#xA;└───────────┬───────────┘&#xA;            ↓&#xA;    ┌─────────────┐&#xA;    │   Circuit   │&#xA;    │   Breaker   │&#xA;    └──────┬──────┘&#xA;           ↓&#xA;    ┌─────────────┐&#xA;    │  Fallback   │&#xA;    └─────────────┘در کنار این‌ها، مفاهیمی مانند:BulkheadRate LimitingQueue(Caching) کشHealth ChecksObservabilityIdempotencyنیز می‌توانند بخشی از طراحی Resiliency باشند.از کجا بفهمیم Circuit Breaker درست کار می‌کند ؟داشتن Circuit Breaker بدون Observability تقریبا مثل داشتن چراغ هشدار بدون نگاه کردن به داشبورد است.ما در حداقلی‌ترین حالت باید بتوانیم مواردی مانند این‌ها را مشاهده کنیم:Circuit State وضعیتFailure RateSuccess RateTimeout RateOpen CountHalf-Open AttemptsRecovery TimeFallback RateRequest Latencyهمچنین تغییر وضعیت Circuit می‌تواند یک Event مهم برای Monitoring و Alerting باشد.برای نمونه:Circuit: CLOSED → OPENباید بتواند در Monitoring سیستم دیده شود، چون ممکن است نشان‌دهنده‌ی یک حادثه جدی در سرویس‌های خارجی باشد.یک نکته مهم: هدف Circuit Breaker «کم کردن Error» نیست. ممکن است بعد از فعال کردن Circuit Breaker، تعداد Errorهایی که کاربر دریافت می‌کند حتی بیشتر شود. اما این الزاما بد نیست. فرض کنید بدون Circuit Breaker:1000 requests&#xA;    ↓&#xA;1000 timeout&#xA;    ↓&#xA;10 seconds each&#xA;    ↓&#xA;Thread exhaustion&#xA;    ↓&#xA;Entire service becomes unhealthyبا Circuit Breaker:1000 requests&#xA;    ↓&#xA;Circuit OPEN&#xA;    ↓&#xA;1000 fast failures&#xA;    ↓&#xA;Dependency protected&#xA;    ↓&#xA;Main service remains responsiveدر حالت دوم هنوز Failure داریم، اما کنترل شده است. هدف Circuit Breaker این نیست که Failure را ناپدید کند، هدفش این است که Failure را به یک Failure قابل‌کنترل تبدیل کند.این تفاوت، یکی از مهم‌ترین اصول طراحی سیستم‌های توزیع‌شده است.جمع‌بندیCircuit Breaker یک الگوی Resiliency برای زمانی است که سیستم ما به یک سرویس خارجی یا Remote Service وابسته است و احتمال Failure یا Timeout وجود دارد.این الگو با سه وضعیت اصلی کار می‌کند:CLOSED&#xA;  ↓&#xA;درخواست‌ها عبور می‌کنندOPEN&#xA;  ↓&#xA;می‌شوند Fail Fast درخواست‌هاHALF-OPEN&#xA;  ↓&#xA;Recovery چند درخواست آزمایشی برای بررسیاگر سرویس خارجی سالم باشد:Closedاگر Failureها از آستانه عبور کنند:Closed → Openپس از گذشت Timeout:Open → Half-Openو اگر Recovery موفق باشد:Half-Open → Closedدر غیر این صورت:Half-Open → Openاما مهم‌تر از حفظ کردن این Stateها، درک فلسفه‌ی پشت آن‌هاستوقتی یک سرویس خارجی خراب است، ادامه دادن به درخواست‌های بیشتر همیشه نشانه‌ی Resiliency نیست؛ گاهی بهترین کاری که سیستم می‌تواند انجام دهد این است که از ایجاد درخواست‌های جدید جلوگیری کند.Circuit Breaker در کنار Timeout، Retry محدود، Exponential Backoff، Jitter، Fallback و Idempotency می‌تواند جلوی تبدیل شدن یک Failure کوچک به یک قطعی بزرگ را بگیرد.حالا یک سوال چالشیفرض کنید Payment Service شما Down شده و ۱۰,۰۰۰ درخواست پرداخت در هر دقیقه دریافت می‌کنید.کدام رفتار را ترجیح می‌دهید؟۱. هر درخواست 3 بار Retry شود۲. بعد از عبور Failure Rate از Threshold، Circuit باز شود و درخواست‌ها Fail Fast شوند۳. Retry + Exponential Backoff + Circuit Breakerطراحی همین تصمیم‌های کوچک است که تفاوت بین یک سرویس معمولی و یک سیستم واقعا Resilient را مشخص می‌کند.منابع برای مطالعه بیشتر:Microsoft Azure — Circuit Breaker PatternMicrosoft Azure — Transient Fault HandlingMicrosoft Azure — Retry Storm AntipatternAWS — Using the Circuit Breaker Pattern with Step Functions and DynamoDBAWS Builders’ Library — Making retries safe with idempotent APIsMartin Fowler — Circuit BreakerCircuit Breaker Design PatternResilient Distributed SystemsTop Strategies to Improve Reliability in Distributed SystemsHow failure cascades in Distributed SystemsRecovery in Distributed SystemsMonolithic ArchitectureFinite-State MachineService-Level ObjectiveRetries Strategies in Distributed SystemsFailure Detection and Recovery in Distributed SystemsGraceful Degradation in Distributed SystemsRecovering from Transient ErrorsRetry Storm AntipatternUnderstanding Retries, Exponential Backoffs, and Circuit Breakers in Distributed SystemsExponential Backoff And JitterWhat Are False Positives and Negatives in Software Testing ?What is a Service Mesh ?Sidecar Design Pattern for MicroservicesDistributed Systems and Microservices: Guide to Streamline Software ArchitecturesBulkhead PatternRate Limiting in Distributed SystemDistributed Task Queue - Distributed SystemsHealth Checks and Graceful Degradation in Distributed SystemsObservability in Distributed SystemsIdempotency in Distributed Systems: When and Why It MattersDistributed Computing</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Wed, 16 Sep 2026 06:57:07 +0330</pubDate></item><item><title>مبحث Isolation Level در اِنجین InnoDB در MySQL؛ وقتی دو تراکنش هم‌زمان سراغ یک داده می‌روند چه اتفاقی می‌افتد ؟</title><link>https://virgool.ir/@mehradsadeghi/mysql-innodb-isolation-levels-kmo08gethsr6</link><description>تصویر از Arya Pathakدو تراکنش، یک داده، یک سوال: چه کسی برنده می‌شود ؟ وقتی چند کاربر هم‌زمان سراغ یک رکورد می‌روند، MySQL چطور تصمیم می‌گیرد چه چیزی را ببینند و چه چیزی را قفل کنند ؟ پاسخ این سوال در Transaction Isolation و به‌خصوص رفتار InnoDB نهفته است.فرض کنید در یک فروشگاه اینترنتی فقط یک عدد از یک محصول باقی مانده است. در همان لحظه دو کاربر روی دکمه‌ی «خرید» کلیک می‌کنند. هر دو درخواست تقریبا هم‌زمان می‌رسند و هر دو این سوال را از دیتابیس می‌پرسند:SELECT stock FROM products WHERE id = 10;اگر هر دو جواب بگیرند:stock = 1حالا چه اتفاقی می‌افتد ؟ آیا هر دو می‌توانند محصول را بخرند ؟اگر پاسخ «بله» باشد، چه کسی مسئول اشتباه است ؟ کد اپلیکیشن ؟ دیتابیس ؟ یا نحوه‌ی مدیریت Transactionها ؟اینجاست که مفهومی به نام Transaction Isolation Level وارد بازی می‌شود.نکته‌ی جالب این است که انتخاب Isolation Level فقط یک انتخاب بین «سریع» و «کند» نیست؛ بلکه مشخص می‌کند وقتی چند تراکنش هم‌زمان با یک داده کار می‌کنند، هر تراکنش دقیقا چه چیزی را ببیند و چه چیزی را نبیند.اصلا چرا به Transaction نیاز داریم ؟قبل از اینکه سراغ Isolation Level برویم، باید یک قدم عقب‌تر برگردیم. چرا باید کوئری‌های دیتابیس را داخل Transaction قرار دهیم ؟فرض کنید هنگام خرید محصول، قرار است این چند کار همزمان انجام شوند:۱. موجودی محصول بررسی شود.۲. موجودی یک عدد کاهش پیدا کند.۳. سفارش ایجاد شود.۴. مبلغ سفارش ثبت شود.۵. وضعیت سفارش تغییر کند.اگر مرحله‌ی دوم موفق شود ولی مرحله‌ی سوم شکست بخورد، چه اتفاقی می‌افتد ؟ممکن است موجودی محصول کم شده باشد، اما هیچ سفارشی برای مشتری ایجاد نشده باشد. یعنی دیتابیس وارد یک وضعیت ناقص شده است.Transaction برای حل چنین مشکلاتی به وجود آمده است. به‌صورت ساده Transaction یعنی:این مجموعه عملیات را به‌عنوان یک واحد منطقی در نظر بگیر؛ یا همه‌ی آن‌ها موفق شوند، یا تغییرات را برگردان.برای نمونه:START TRANSACTION;&#xA;UPDATE products SET stock = stock - 1 WHERE id = 10;&#xA;INSERT INTO orders (product_id, user_id) VALUES (10, 1001);&#xA;COMMIT;اگر مشکلی پیش بیاید:ROLLBACK;در نتیجه Transaction یکی از پایه‌های اصلی مفهوم ACID است.حرف I در ACID همان Isolation است. Isolation مشخص می‌کند تراکنش‌های هم‌زمان تا چه اندازه از تغییرات یکدیگر آگاه باشند.طبق مستندات MySQL ،InnoDB هر چهار Isolation Level استاندارد SQL را پشتیبانی می‌کند و مقدار پیش‌فرض آن REPEATABLE READ است. اما این چهار سطح دقیقاً چه تفاوتی با هم دارند ؟Isolation Level چیست ؟فرض کنید دو MySQL Transaction داریم:Transaction A&#xA;Transaction Bهر دو تقریباً هم‌زمان در حال خواندن و تغییر داده‌های دیتابیس هستند.Isolation Level تعیین می‌کند Transaction A هنگام کار کردن، چه مقدار از تغییرات Transaction B را ببیند.در MySQL/InnoDB چهار سطح اصلی داریم:اما جدول بالا فقط شروع ماجراست چون تفاوت واقعی زمانی مشخص می‌شود که دو MySQL Transaction را هم‌زمان اجرا کنیم.سطح READ UNCOMMITTEDبیایید از ضعیف‌ترین سطح شروع کنیم. در READ UNCOMMITTED یک Transaction می‌تواند تغییراتی را ببیند که Transaction دیگری هنوز COMMIT نکرده است. یعنی «حتی اگر تغییر هنوز نهایی نشده، ممکن است آن را ببینی.»این همان چیزی است که به آن Dirty Read می‌گوییم. برای نمونه فرض کنید موجودی محصول برابر است با:stock = 10Transaction A شروع می‌شود:START TRANSACTION;&#xA;UPDATE products SET stock = 0 WHERE id = 10;اما هنوز:COMMIT;انجام نشده است. حالا Transaction B اجرا می‌شود:SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;&#xA;START TRANSACTION;&#xA;SELECT stock FROM products WHERE id = 10;Transaction B مقدار 0 را می‌بیند در حالی که Transaction A هنوز تغییر خود را Commit نکرده است.حالا اگر Transaction A تصمیم بگیرد رول‌بک کند (ROLLBACK) موجودی دوباره برابر با ۱۰ می‌شود.پس Transaction B چیزی را دیده که در نهایت اصلا در دیتابیس باقی نمانده است، که به‌ آن Dirty Read می‌گوییم.مستندات InnoDB هم READ UNCOMMITTED را سطحی معرفی می‌کند که در آن Readها می‌توانند از نسخه‌ی قبلی/ناسازگار داده استفاده کنند و ممکن است Dirty Read رخ دهد.مزیت این سطح چیست ؟مزیت اصلی این سطح، سرعت و کاهش محدودیت‌های Isolation است. برای نمونه اگر کوئری ما فقط گزارشی تقریبی از داده‌ها می‌خواهد و Consistency کامل اهمیت ندارد، این سطح عملکرد بهتری ارائه می‌دهد.مشکل کجاست ؟ممکن است داده‌ای را بخوانید که هرگز Commit نشود. برای عملیات حساس مثل: پرداخت، سفارش، موجودی، حساب بانکی، رزرو صندلی و… این رفتار معمولا قابل قبول نیست. در نتیجه READ UNCOMMITTED را می‌توان این‌طور به خاطر سپرد:سریع‌تر و آزادتر، اما بدون تضمین اینکه چیزی که می‌بینی واقعا نهایی شده باشد.سطح READ COMMITTEDحالا یک قدم جلوتر بریم. در READ COMMITTED دیگر Dirty Read نداریم. یعنی Transaction فقط داده‌هایی را می‌بیند که Transactionهای دیگر Commit کرده‌اند.برای نمونه:Transaction A:&#xA;&#xA;UPDATE stock = 0&#xA;    |&#xA;    | هنوز کامیت نشده&#xA;    ↓&#xA;Transaction B:&#xA;&#xA;SELECT stockTransaction B مقدار تغییر داده ‌نشده و فقط Commit ‌شده را می‌بیند، نه مقدار موقتی Transaction A را.اما یک نکته‌ی بسیار مهم:در READ COMMITTED، هر Consistent Read می‌تواند Snapshot جدیدی داشته باشد. یعنی اگر یک Transaction یک Query را چند بار اجرا کند، ممکن است هر بار نتیجه‌ی متفاوتی بگیرد؛ چون ممکن است Transaction دیگری بین این اجراها، داده را تغییر داده و آن را Commit کرده باشد.منظور از Consistent Read، خواندن داده بر اساس یک Snapshot مشخص از دیتابیس است؛ بنابراین تغییرات Commit‌ شده‌ی بعدیِ تراکنش‌های دیگر لزوما در همان Transaction دیده نمی‌شوند.برای نمونه فرض کنید:stock = 10&#xA;&#xA;Transaction A:&#xA;&#xA;SET TRANSACTION ISOLATION LEVEL READ COMMITTED;&#xA;START TRANSACTION;&#xA;SELECT stock FROM products WHERE id = 10;نتیجه:10حالا Transaction B:START TRANSACTION;&#xA;UPDATE products SET stock = 5 WHERE id = 10;&#xA;COMMIT;Transaction A دوباره کوئری می‌زند:SELECT stock FROM products WHERE id = 10;این Transaction A مقدار 5 را می‌بیند.پس یک Transaction، در دو نقطه‌ی زمانی مختلف، برای یک کوئری مشابه نتایج متفاوتی گرفته است. این همان چیزی‌ست که به آن Non-Repeatable Read می‌گوییم.چرا READ COMMITTED می‌تواند انتخاب خوبی باشد ؟در سیستم‌هایی که داده‌ها دائما تغییر می‌کنند، دیدن Snapshot تازه‌تر می‌تواند مفیدتر باشد. برای نمونه سیستم‌های گزارش‌گیری، سیستم‌های پرترافیک، برخی سرویس‌های OLTP، برنامه‌هایی که نمی‌خواهند یک Transaction مدت زیادی به یک Snapshot قدیمی متکی باشد.اما باید توجه کنیم که در این سطح، یک Transaction ممکن است در هر Query داده‌های جدیدتری ببیند؛ بنابراین لزوما در تمام مدت اجرای خود یک تصویر ثابت از دیتابیس ندارد.از طرف دیگر در این سطح هنگام اجرای UPDATE ،DELETE یا READ همراه با قفل، InnoDB فقط رکوردهایی را که با شرط جست‌وجو مطابقت دارند قفل می‌کند و از قفل‌کردن فاصله‌های خالی بین آن‌ها (Gap Lock) صرف‌نظر می‌کند. بنابراین، Transaction دیگری می‌تواند در همان محدوده رکورد جدیدی درج کند. در نتیجه اگر Transaction اول همان محدوده را دوباره بررسی کند، ممکن است رکورد تازه‌درج‌شده را نیز ببیند و نتیجه‌ی کوئری نسبت به اجرای قبلی تغییر کند.در نتیجه READ COMMITTED را این‌طور به خاطر بسپارید:Dirty Read نداریم، اما دیتایی که در طول Transaction می‌بینیم می‌تواند تازه‌تر شود.سطح REPEATABLE READ: انتخاب پیش‌فرض InnoDBREPEATABLE READ سطح پیش‌فرض Isolation در InnoDB است. اسم آن تقریبا همه‌چیز را توضیح می‌دهد: اگر در یک Transaction داده‌ای را بخوانی، Readهای بعدی همان Transaction می‌توانند همان Snapshot را ببینند. فرض کنید:stock = 10&#xA;&#xA;Transaction A:&#xA;&#xA;SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;&#xA;START TRANSACTION;&#xA;SELECT stock FROM products WHERE id = 10;نتیجه: 10حالا Transaction B مقدار را تغییر می‌دهد:START TRANSACTION;&#xA;UPDATE products SET stock = 5 WHERE id = 10;&#xA;COMMIT;Transaction A دوباره:SELECT stock FROM products WHERE id = 10;در یک Consistent Read معمولی، Transaction A همچنان Snapshot خودش را می‌بیند، یعنی 10، نه 5.طبق مستندات MySQL، در REPEATABLE READ، Consistent Readهای داخل یک Transaction از Snapshot ایجادشده توسط اولین Read استفاده می‌کنند.اما اینجا یک نکته‌ی بسیار مهم وجود دارد!تا اینجا تصور بر این است که: «در REPEATABLE READ هر کوئری داخل Transaction همیشه همان داده را می‌خواند»، اما این تعریف دقیق نیست، چون بین SELECT معمولی و SELECT ... FOR UPDATE تفاوت مهمی وجود دارد.SELECT معمولی یک Consistent Read است، اما SELECT ... FOR UPDATE یک Locking Read است.SELECT معمولی داده را از Snapshot تراکنش می‌خواند؛ یعنی ممکن است نسخه‌ای قدیمی‌تر از داده را ببینید.اما SELECT ... FOR UPDATE برای تغییر داده استفاده می‌شود و وضعیت فعلی رکورد را می‌خواند و هم‌زمان آن را قفل می‌کند تا تراکنش دیگری نتواند قبل از پایان تراکنش شما، آن را تغییر دهد.این تفاوت برای طراحی سیستم‌های واقعی فوق‌العاده مهم است.سناریوی معروف Oversellingحالا برگردیم به همان فروشگاه اینترنتی. فرض کنید فقط یک محصول باقی مانده:product_id = 10&#xA;stock = 1دو مشتری تقریباً هم‌زمان خرید می‌کنند.اگر کد ما چیزی شبیه این باشد:START TRANSACTION;&#xA;SELECT stock FROM products WHERE id = 10;&#xA;-- Check if stock is greater than 0 in application code&#xA;UPDATE products SET stock = stock - 1 WHERE id = 10;&#xA;INSERT INTO orders (...);&#xA;COMMIT;ممکن است دو Transaction هم‌زمان مقدار 1 را ببینند و هر دو فکر کنند محصول موجود است، و بعد هر دو بخواهند موجودی را کاهش دهند.اینجاست که فقط انتخاب Isolation Level کافی نیست. ما باید عملی که قرار است روی داده انجام دهیم را نیز به شکل صحیح طراحی کنیم.راه‌ حل: استفاده از SELECT ... FOR UPDATE. در چنین سناریویی یکی از الگوهای رایج استفاده از SELECT ... FOR UPDATE است.برای نمونه:START TRANSACTION;&#xA;SELECT stock FROM products WHERE id = 10 FOR UPDATE;این کوئری یک Locking Read است.InnoDB رکوردهای پیدا‌شده را قفل می‌کند و Transaction دیگری که بخواهد همان رکورد را برای تغییر یا Lock مشابه در اختیار بگیرد، باید منتظر بماند تا Transaction اول COMMIT یا ROLLBACK کند.حالا فرض کنیم Transaction A زودتر به محصول برسد.Transaction A:&#xA;&#xA;SELECT ... FOR UPDATE&#xA;&#xA;-&gt; Product #10 locked and stock is 1&#xA;&#xA;UPDATE stock = 0&#xA;INSERT order&#xA;COMMIT;&#xA;&#xA;-&gt; Unlockدر همین زمان Transaction B وقتی بخواهد:SELECT stock FROM products WHERE id = 10 FOR UPDATE;را اجرا کند، باید منتظر آزاد شدن Lock بماند.بعد از Commit شدن Transaction B، Transaction A می‌تواند وضعیت جدید را بررسی کند. اگر موجودی صفر باشد، دیگر نباید سفارش دوم ایجاد شود.یک پیاده‌سازی ساده برای خرید:برای نمونه:START TRANSACTION;&#xA;SELECT stock FROM products WHERE id = 10 FOR UPDATE;در Application:if stock &gt; 0:&#xA;    continue purchase&#xA;else:&#xA;    reject purchaseسپس:UPDATE products SET stock = stock - 1 WHERE id = 10;&#xA;INSERT INTO orders (product_id, user_id) VALUES (10, 1001);&#xA;COMMIT;نکته‌ی مهم این است که SELECT ... FOR UPDATE باید داخل همان Transaction باشد. Lockهای ایجادشده توسط FOR UPDATE با COMMIT یا ROLLBACK آزاد می‌شوند.آیا REPEATABLE READ جلوی همه‌ی Race Conditionها را می‌گیرد ؟خیر. Isolation Level به‌تنهایی جایگزین طراحی صحیح Transaction نیست. ممکن است Isolation Level مناسبی انتخاب کرده باشید، اما اگر عملیات Read → Check → Update شما بدون Lock مناسب انجام شود و همچنان با Race Condition مواجه شوید.به همین دلیل باید بین این دو مفهوم تفاوت بگذاریم:Isolation: مشخص می‌کند Transaction چه چیزی را از تغییرات سایر Transactionها ببیند.Locking: مشخص می‌کند چه کسی حق دارد هم‌زمان داده‌ی خاصی را تغییر دهد یا Lock کند.در سناریوهایی مانند موجودی کالا، رزرو ظرفیت یا تخصیص منابع، این تفاوت حیاتی است.سطح SERIALIZABLEحالا می‌رسیم به قوی‌ترین سطح Isolation. اسم SERIALIZABLE یک ایده‌ی مهم را منتقل می‌کند:رفتار Transactionهای هم‌زمان تا حد ممکن شبیه این است که یکی‌یکی اجرا شده‌اند.در InnoDB، سطح SERIALIZABLE نسبت به REPEATABLE READ سخت‌گیرانه‌تر است. در این حالت حتی کوئری‌های READ معمولی هم ممکن است داده‌ها را قفل کنند؛ بنابراین تراکنش‌های هم‌زمان بیشتر منتظر یکدیگر می‌مانند و سرعت و هم‌زمانی سیستم کاهش پیدا می‌کند.در نتیجه:Consistency ↑&#xA;Concurrency ↓یعنی هرچه Isolation را قوی‌تر کنیم، معمولا فضای کمتری برای اجرای آزادانه‌ی Transactionهای هم‌زمان باقی می‌ماند.چرا همه‌چیز را SERIALIZABLE نکنیم ؟اگر SERIALIZABLE بیشترین سطح Isolation را فراهم می‌کند، چرا همه جا از آن استفاده نکنیم ؟چون سیستم واقعی فقط به Consistency نیاز ندارد. ما Performance و Concurrency هم می‌خواهیم.تصور کنید هزاران کاربر هم‌زمان در حال خرید هستند. اگر تعداد زیادی Transaction مجبور شوند منتظر Lockهای یکدیگر بمانند:Transaction A&#xA;&#xA;      ↓&#xA;&#xA;    LOCK&#xA;&#xA;      ↓&#xA;&#xA;Transaction B → WAIT&#xA;&#xA;      ↓&#xA;&#xA;Transaction C → WAIT&#xA;&#xA;      ↓&#xA;&#xA;Transaction D → WAITدر نتیجه Throughput سیستم کاهش پیدا می‌کند. بنابراین هدف این نیست که همیشه بیشترین Isolation را انتخاب کنیم. هدف این است که کمترین Isolation Levelی را انتخاب کنیم که نیاز Consistency و Business Logic ما را به‌درستی برآورده کند.پس Trade-off واقعی چیست ؟حالا چهار سطح را کنار هم ببینیم:البته این جدول را نباید به شکل یک قانون مطلق در نظر گرفت. Performance واقعی به موارد بسیار بیشتری وابسته است: نوع Query، Indexها، تعداد Transactionهای هم‌زمان، مدت زمان Transaction، تعداد Rowهای Lock شده، الگوی دسترسی به داده، Hot Rowها، طراحی Application، حجم داده و ...یک نکته‌ی مهم درباره Deadlockوقتی درباره Lock صحبت می‌کنیم، یک سوال طبیعی به وجود می‌آید: «آیا Lock یعنی احتمال Deadlock ؟»بله، ممکن است. فرض کنید:Transaction A:&#xA;&#xA;Lock Row 1&#xA;↓&#xA;Wait for Row 2و هم‌زمان:Transaction B:&#xA;&#xA;Lock Row 2&#xA;↓&#xA;Wait for Row 1حالا:A → منتظر B&#xA;B → منتظر Aهیچ‌کدام نمی‌توانند جلو بروند. این یک Deadlock است. Deadlock لزوما به معنی بد بودن Isolation Level نیست. MySQL نیز تاکید می‌کند که Deadlock می‌تواند زمانی اتفاق بیفتد که Transactionها Rowها یا Rangeهای مختلف را در ترتیب متفاوت Lock کنند. همچنین مستندات تصریح می‌کنند که احتمال Deadlock مستقیما با Isolation Level تعیین نمی‌شود؛ Deadlock بیشتر به نحوه‌ی انجام Writeها و Lockها مربوط است.چگونه احتمال Deadlock را کاهش دهیم ؟۱. Transaction را کوتاه نگه دارید. این:BEGIN&#xA;↓&#xA;SELECT&#xA;↓&#xA;UPDATE&#xA;↓&#xA;COMMITمعمولا بهتر از Transactionی است که:BEGIN&#xA;↓&#xA;SELECT&#xA;↓&#xA;API Call&#xA;↓&#xA;Heavy Computations&#xA;↓&#xA;Network Request&#xA;↓&#xA;UPDATE&#xA;↓&#xA;COMMITهرچه Transaction بیشتر باز بماند، Lockها نیز ممکن است مدت بیشتری درگیر باشند.۲. ترتیب Lock گرفتن را ثابت نگه داریداگر Transactionهای مختلف قرار است دو Row را Lock کنند، بهتر است همه‌ی آن‌ها همان ترتیب را رعایت کنند. برای نمونه همیشه:Row A&#xA;↓&#xA;Row Bنه اینکه یک Transaction:A → Bو دیگری:B → Aاین موضوع یکی از توصیه‌های رسمی MySQL برای کاهش Deadlock است.۳. Index مناسب داشته باشیدکوئری‌هایی مانند:SELECT * FROM products WHERE id = 10 FOR UPDATE;وقتی روی ستون جست‌وجو Index مناسب داشته باشند، کنترل Lockها بسیار قابل‌پیش‌بینی‌تر می‌شود. MySQL نیز ایجاد Index روی ستون‌هایی را که در SELECT ... FOR UPDATE و UPDATE ... WHERE استفاده می‌کنند را از راهکارهای کاهش Deadlock می‌داند.۴. Deadlock را در Application مدیریت کنیدیک تصور اشتباه این است که «اگر Deadlock رخ داد، یعنی سیستم خراب است»نه لزوما. در سیستم‌های همزمان (Concurrent)، Deadlock ممکن است رخ دهد و InnoDB می‌تواند یکی از Transactionها را به‌عنوان Victim انتخاب و Rollback کند. Application باید در چنین شرایطی بتواند Transaction را در صورت مناسب بودن سناریو دوباره اجرا کند.یک اشتباه رایج، Lock کردن همه‌چیز است. گاهی بعد از آشنایی با FOR UPDATE، وسوسه می‌شویم:SELECT * FROM products FOR UPDATE;اما این ایده‌ی خوبی نیست. اگر فقط قرار است یک محصول را تغییر دهیم، چرا باید تعداد زیادی Row را Lock کنیم ؟بهتر است کوئری تا حد ممکن دقیق باشد:SELECT stock FROM products WHERE id = 10 FOR UPDATE;و مهم‌تر از آن، ستون id باید Index مناسب داشته باشد.قاعده‌ی ساده:فقط چیزی را Lock کن که واقعا لازم داری و Transaction را سریع تمام کن.آیا همیشه باید REPEATABLE READ + FOR UPDATE استفاده کنیم ؟خیر. اما برای سناریویی که در آن یک Row را می‌خوانیم، بر اساس مقدار آن تصمیم می‌گیریم، سپس همان Row یا داده‌ی وابسته را تغییر می‌دهیم، ترکیب:REPEATABLE READ&#xA;+&#xA;SELECT ... FOR UPDATEاغلب یک الگوی بسیار مناسب است. برای مثال Overselling:START TRANSACTION;&#xA;SELECT stock FROM products WHERE id = 10 FOR UPDATE;&#xA;--- Check Stock in the Application Code&#xA;UPDATE products SET stock = stock - 1 WHERE id = 10;&#xA;INSERT INTO orders (...);&#xA;COMMIT;FOR UPDATE باعث می‌شود Row موردنظر برای تغییرات هم‌زمان Lock شود و Transaction دیگر نتواند بدون انتظار همان Row را برای تغییر، در اختیار بگیرد.البته این کد یک نکته‌ی دیگر هم دارد: حتماً باید نتیجه‌ی stock بررسی شود. یعنی اگر:stock = 0نباید سفارش ایجاد شود.یک راه دیگر: شرط را داخل UPDATE بیاوریم. گاهی می‌توانیم منطق را حتی Atomicتر طراحی کنیم.برای نمونه:UPDATE products SET stock = stock - 1 WHERE id = 10 AND stock &gt; 0;سپس بررسی کنیم که چند Row تغییر کرده است. اگر:Rows affected = 1یعنی موجودی با موفقیت کاهش یافته است.اگر:Rows affected = 0یعنی محصول موجود نبوده یا شرایط Update برقرار نبوده است.الگوی:SELECT&#xA;↓&#xA;CHECK&#xA;↓&#xA;UPDATEدر بسیاری از سناریوها مناسب است، اما اینکه کدام الگو بهتر است به Business Logic واقعی شما بستگی دارد.Isolation Level را چطور تنظیم کنیم ؟برای نمونه می‌توان برای Transaction یا Session سطح Isolation را تنظیم کرد. یعنی:SET TRANSACTION ISOLATION LEVEL READ COMMITTED;یا:SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;و برای SERIALIZABLE:SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;MySQL امکان تنظیم Isolation Level را برای Session و همچنین به‌صورت پیش‌فرض برای Connectionهای بعدی فراهم می‌کند. مقدار پیش‌فرض InnoDB نیز REPEATABLE READ است.در یک Application واقعی بهتر است تغییر Isolation Level کاملا آگاهانه انجام شود؛ نه صرفا برای اینکه یک کوئری کند است، سطح Isolation را پایین بیاوریم.یک راهنمای عملی ساده:اگر Consistency خیلی مهم نیست، ممکن است READ UNCOMMITTED مناسب باشد، اما برای عملیات حساس معمولا انتخاب مناسبی نیست.اگر می‌خواهید Readها داده‌ی Commit‌شده‌ی جدید را ببینند READ COMMITTED می‌تواند گزینه‌ی مناسبی باشد.اگر با InnoDB کار می‌کنید و نیاز به Snapshot پایدار در Transaction دارید REPEATABLE READ انتخاب پیش‌فرض و قدرتمند InnoDB است.اگر نیاز دارید رفتار Transactionها تا حد زیادی سریال شود SERIALIZABLE را بررسی کنید؛ اما هزینه‌ی آن روی Database Concurrency را فراموش نکنید.جمع‌بندیIsolation Level در واقع یک سوال ساده اما بسیار مهم است:وقتی چند MySQL Transaction هم‌زمان با یک داده کار می‌کنند، هر Transaction دقیقا چه چیزی را باید ببیند و برای چه چیزی باید منتظر بماند ؟چهار سطح اصلی InnoDB را می‌توان این‌طور خلاصه کرد:READ UNCOMMITTED: بیشترین آزادی، کمترین IsolationREAD COMMITTED: فقط داده‌های Commit شدهREPEATABLE READ: اسنپ‌شات پایدار برای Consistent ReadSERIALIZABLE: بیشترین محدودیت و کمترین Database Concurrencyو این نکته مهم‌ که:انتخاب Isolation Level به‌تنهایی مشکل Concurrency را حل نمی‌کند.اگر قرار است داده‌ای را بخوانید و بر اساس آن تصمیم بگیرید که همان داده را تغییر دهید، باید به Locking نیز فکر کنید.همینطور Deadlock را نمی‌توان صرفا با انتخاب یک Isolation Level «خاموش» کرد. باید Transactionها را درست طراحی کنیم، ترتیب Lock گرفتن را هماهنگ نگه داریم و در Application امکان Retry کردن Transaction شکست‌خورده را در نظر بگیریم.یک سوال برای بحثحالا اگر در یک فروشگاه اینترنتی با این سناریو مواجه شوید:۱۰۰۰ کاربر &#xA;↓&#xA;هم‌زمان&#xA;↓&#xA;برای خرید آخرین 10 عدد از یک محصولبه نظرتان کدام طراحی بهتر است ؟A) REPEATABLE READ + SELECT ... FOR UPDATEB) READ COMMITTED + UPDATE ... WHERE stock &gt; 0C) SERIALIZABLED) Other Combinationاگر در پروژه‌ای با Overselling، Deadlock یا مشکلات Transaction روبه‌رو شده‌اید، خوشحال می‌شوم تجربه‌تان را در کامنت‌ها با من به اشتراک بگذارید.منابع برای مطالعه بیشتربرای مطالعه‌ی عمیق‌تر، مستندات رسمی MySQL بهترین نقطه‌ی شروع هستند:مستندات MySQL درباره Transaction Isolation Levels در InnoDBمستندات MySQL درباره SELECT ... FOR UPDATE و Locking Readsمستندات MySQL درباره Deadlock در InnoDBمستندات MySQL درباره راهکارهای کاهش و مدیریت Deadlockمستندات MySQL درباره Locking و Transaction Model در InnoDB</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Sat, 12 Sep 2026 15:06:49 +0330</pubDate></item><item><title>چطور در آروان‌کلاد با ساختمان‌داده‌ و الگوریتم‌های بهینه مشکل حجم زیاد اطلاعات را حل کردیم</title><link>https://tech.arvancloud.ir/metric-exporter-kcbica7jtnni</link><description>لاگ‌ها در ۸ بهمن‌ماه ۱۴۰۳، آروان‌کلاد اختلالی را در محصول متریک‌اکسپورتر تجربه کرد که بر اکثر مشتریان تأثیر گذاشت. اشکال از این قرار بود که کاربران امکان دریافت متریک‌های دامنه‌ی خود را نداشتند و با  HTTP Timeout مواجه میشدند.در این پست توضیح میدهیم چه اتفاقی افتاد و برای جلوگیری از این واقعه چه اقداماتی انجام دادیم. همینطور امیدوارم اطلاعات ارایه‌ شده در این پست برای سایر مهندسین (حتی اگر از این سرویس استفاده نمیکنند) مفید واقع شود.پیش‌زمینهشبکه آروان‌کلاد یک سیستم توزیع شده در سطح جهانی است و خدمات مختلفی را ارایه میدهد. هر قسمت از هر بخش از این سیستم Event Log هایی تولید میکند که جزییات اتفاقاتی که در هر بخش از سیستم رخ داده را شامل میشود، برای نمونه لاگی که به ازای هر ریکوئست ایجاد میشود.سی‌دی‌ان آروان‌کلاد روزانه نزدیک به یک میلیون لاگ ایجاد، ارسال و پردازش و متریک‌های دامنه را تولید میکند، که به همین دلیل چالش‌های متنوعی نیز در این بین گریبانگیر توسعه‌دهندگان خواهد بود.معماریسیستم لاگ CDN (متریک‌ اکسپورتر) متشکل از یک پایپ‌لاین با ۴ استیج بوده که هر جز نقش ویژه‌ای دارند. استیج اول (A) لاگ‌های تولید‌شده توسط پروکسی CDN را دریافت میکند و روی کافکا (به عنوان مسیج بروکر) میریزد. استیج دوم (B) لاگ‌ها را از کافکا خوانده، بر اساس الگو‌هایی که به آن داده شده تغییراتی روی آنها بوجود می‌آورد تا سایر بخش‌ها بتوانند از لاگ‌ها بهره‌برداری کنند. استیج سوم که به آن LogForwarder هم میگوییم وظیفه‌ی اساسی ارسال لاگ را برعهده دارد و چهارمین استیج با نام Metric Exporter لاگ‌هایی که استیج سوم آماده‌سازی کرده (در کافکا) را برداشته، تجمیع و پروسس‌های مورد نظر را روی آن انجام داده و کاربر در نهایت با صدا زدن اندپویت HTTP متریک‌هایش را دریافت میکند.شایان ذکر است که کامپوننت Metric Exporter هیچ External Storage ی ندارد و اطلاعات را در مموری نگه میدارد تا بیشترین پرفورمنس برای آپدیت، حذف، اضافه و تحویل متریک‌ها به مشتریان وجود داشته باشد. همینطور تنها کانسیومر Stage B لاگ‌فرورادر نیست و کانسیومرهای دیگری بر حسب نیاز مسیج‌های این تاپیک را میخوانند. به همین ترتیب LogForwarder علاوه بر ارسال لاگ به مقاصد مختلف مانند SysLog Server، انواع S3 ها و ... ، یکی از مقاصدش کافکا و تاپیک مورد نظر Metric Exporter است.معماری متریک‌اکسپورتر آروان‌کلادچه اتفاقی افتاددر تاریخ ۷ بهمن نسخه جدیدی منتشر کردیم که امکان اسنپ‌شات گرفتن از آخرین وضعیت متریک‌ها را ایجاد کند. در تاریخ ۸ بهمن متوجه شدیم تعداد زیادی از درخواست‌های کاربران Timeout شده و سیستم دچار اختلال گردیده است. اختلال در واقع افزایش مموری (RAM) تا حدی بود که اپ توان پردازش را از دست میداد. طبیعتا اولین حدسمان این بود که مشکل از آخرین فیچری است که به برنامه اضافه کردیم، اما بعد از کالبدشکافی‌های مفصل در محیط استیجینگ متوجه شدیم قابلیت Persistency که اضافه شده بود در واقع کمک میکرد تا مشکل اصلی زودتر از موعد خودش را نشان دهد.پر شدن مموری و ری‌استار‌ت‌های خودکار اپ پر شدن مموری به صورت غیرقابل پیش‌بینی تا هر اندازه‌ای که به آن ظرفیت بدهیم ریشه‌یابیبه کمک محیط استیجینگ و شبیه‌سازی حالت رخ داده، مشکل اصلی را در استفاده نادرست از ساختمان‌داده‌ و الگوریتم‌های غیر بهینه برای نگه‌داری مقادیر مختلف، تشخیص دادیم. ساختار به این شکل بود که متریک‌ها تا بینهایت (تعداد بسیار بسیار زیاد) میتوانستند تولید شوند و این امر خارج از کنترل ما بود. همین موضوع باعث میشد رفتار سیستم پیش‌بینی‌پذیر نباشد و اختلالات مشابه رخ بدهد.چه کردیممهترین قدم تغییر ساختمان‌های داده و الگوریتم‌های مربوط به آنها به انواع بهینه‌تر است به طوری که رفتار برنامه‌ را پیش‌بینی‌پذیر کنیم. برای نمونه استفاده از الگوریتم SpaceSaving که یک روش کارآمد برای تخمین فرکانس پرتکرارترین اقلام در یک جریان داده (Stream) است. این الگوریتم از یک تعداد محدود شمارنده (Counters) استفاده می‌کند و مقدار تقریبی تکرار اقلام را نگه می‌دارد. نمونه‌ کدی که برای پیاده‌سازی الگوریتم SpaceSaving نوشتیم:type Entry struct {&#xA;   Value string // The value of the element&#xA;   Count int    // The count of the element&#xA;   index int    // The index of the element in the heap&#xA;}&#xA;&#xA;// PriorityQueue implements heap.Interface and holds Entries&#xA;type PriorityQueue []*Entry&#xA;&#xA;func (pq PriorityQueue) Len() int { return len(pq) }&#xA;&#xA;func (pq PriorityQueue) Less(i, j int) bool {&#xA;   // we want Pop to give us the lowest count, so less means more here.&#xA;   return pq[i].Count &lt; pq[j].Count&#xA;}&#xA;&#xA;func (pq PriorityQueue) Swap(i, j int) {&#xA;   pq[i], pq[j] = pq[j], pq[i]&#xA;   pq[i].index = i&#xA;   pq[j].index = j&#xA;}&#xA;&#xA;func (pq *PriorityQueue) Push(x interface{}) {&#xA;   n := len(*pq)&#xA;   entry := x.(*Entry)&#xA;   entry.index = n&#xA;   *pq = append(*pq, entry)&#xA;}&#xA;&#xA;func (pq *PriorityQueue) Pop() interface{} {&#xA;   old := *pq&#xA;   n := len(old)&#xA;   entry := old[n-1]&#xA;   entry.index = -1 // for safety&#xA;   *pq = old[0 : n-1]&#xA;   return entry&#xA;}&#xA;&#xA;// SpaceSaving tracks top-k elements&#xA;type SpaceSaving struct {&#xA;   k        int&#xA;   elements map[string]*Entry&#xA;   pq       PriorityQueue&#xA;}&#xA;&#xA;func newSpaceSaving(k int) *SpaceSaving {&#xA;   return &amp;SpaceSaving{&#xA;      k: k,&#xA;      elements: make(map[string]*Entry),&#xA;      pq: make(PriorityQueue, 0, k),&#xA;   }&#xA;}&#xA;&#xA;func (ss *SpaceSaving) Add(value string) {&#xA;   if entry, exists := ss.elements[value]; exists {&#xA;      entry.Count++&#xA;      heap.Fix(&amp;ss.pq, entry.index)&#xA;   } else {&#xA;      if ss.pq.Len() &lt; ss.k {&#xA;         entry := &amp;Entry{&#xA;            Value: value,&#xA;            Count: 1,&#xA;         }&#xA;         heap.Push(&amp;ss.pq, entry)&#xA;         ss.elements[value] = entry&#xA;      } else {&#xA;         min := heap.Pop(&amp;ss.pq).(*Entry)&#xA;         delete(ss.elements, min.Value)&#xA;         min.Value = value&#xA;         min.Count++&#xA;         heap.Push(&amp;ss.pq, min)&#xA;         ss.elements[value] = min&#xA;      }&#xA;   }&#xA;}&#xA;&#xA;func (ss *SpaceSaving) TopK() []*Entry {&#xA;   entries := make([]*Entry, len(ss.pq))&#xA;   copy(entries, ss.pq)&#xA;&#xA;   // sort the slice based on count&#xA;   sort.Slice(entries, func(i, j int) bool {&#xA;      return entries[i].Count &gt; entries[j].Count&#xA;   })&#xA;&#xA;   return entries&#xA;}از سایر اقدامات میتوان به بهبود سیستم مانیتورینگ اشاره کرد. همینطور بهبود اساسی محیط استیجینگ و تست‌های با لود بالا در این محیط برای اطمینان از عملکرد صحیح برنامه.کاهش قابل‌توجه مصرف مموری و ثابت شدن نرخ تغییرات آنروبه‌جلودر صورتی که SpaceSaving به طور کامل جواب کار مارا ندهد روش‌های دیگری هم برای بهینه سازی داریم از جمله Fixed-size Time Buckets و Circular Buffer. همینطور افزایش متریک‌های داخلی اپ به منظور مانیتور بهتر آن و الرت‌های دقیق‌ برای همین موضوع باید در دستور کار قرار بگیرد.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Mon, 17 Mar 2025 19:50:15 +0330</pubDate></item><item><title>اصول CSP از Tony Hoare - الهام‌بخش طراحی Concurrency در زبان برنامه‌نویسی Go</title><link>https://virgool.ir/codenevis/tony-hoare-csp-votfcg3risji</link><description>تصویر Tony Hoare اصول CSPاندیشمند برجسته علوم کامپیوتر Tony Hoare، در سال ۱۹۷۸ مفهوم Communicating Sequential Processes (CSP) را معرفی کرد؛ مدلی که تأثیر عمیقی بر طراحی سیستم‌های هم‌زمان (Concurrent Systems) داشت. این مدل سه اصل کلیدی دارد:اجرای ترتیبی پردازش‌هاهر پردازش به گونه‌ای طراحی میشود که به صورت ترتیبی (Sequential) و مستقل اجرا شود. این اصل سادگی در طراحی و پیش‌بینی‌پذیری رفتار هر فرآیند (پردازش) را تضمین میکند.استفاده از پردازش‌گرها (Processes) برای جابجایی داده‌ها، به‌جای حافظه مشترک (Shared Memory)به جای به‌اشتراک‌گذاری مستقیم حافظه که منجر به پیچیدگی در هم‌زمانی (Concurrency) میشود، پروسس‌ها از طریق کانال‌های ارتباطی (Channels) اطلاعات را ارسال و دریافت میکنند. این روش، ایمنی و قابل اطمینان بودن سیستم را افزایش میدهد.مقیاس‌پذیری با افزودن پروسس‌های مشابهبه جای طراحی پیچیده برای مدیریت مقیاس‌پذیری، سیستم میتواند با افزودن فرآیندهای (Processes) مشابه (مانند یک الگوی تکرارشونده) توسعه و مقیاس پیدا کند. این اصل، بهره‌وری و انعطاف سیستم را بهبود میبخشد.پیاده‌سازی اصول CSP در زبان برنامه‌نویسی Goزبان برنامه‌نویسی Go که توسط گوگل در سال ۲۰۰۹ توسعه یافت، در پیاده‌سازی مدل Concurrency اش مستقیماً از اصول CSP الهام گرفته است. سه ویژگی برجسته در Go نشان‌دهنده استفاده از این اصول هستند:گوروتین‌ها (Goroutines): گوروتین‌ها واحدهای سَبُکی برای اجرای ترتیبی (Sequential) کد هستند که اجرای مستقل هر پروسس را بر اساس اصل اول تضمین می‌کنند.کانال‌ها (Channels): Go به جای به‌اشتراک‌گذاری مستقیم حافظه، از کانال‌ها برای ارسال و دریافت داده میان گوروتین‌ها استفاده میکند که اصل دوم را به خوبی اجرا میکند.سادگی مقیاس‌پذیری: طراحی گوروتین‌ها و کانال‌ها به گونه‌ای است که میتوان با افزودن گوروتین‌های بیشتر و بدون پیچیدگی اضافی، سیستم را مقیاس‌پذیر کرد؛ چیزی که اصل سوم CSP را تحقق میبخشد.زبان Go با بهره‌گیری از این اصول، زبان ساده، سریع و کارآمدی برای توسعه سیستم‌های Concurrent ارائه میدهد. به همین دلیل این زبان به انتخاب اول بسیاری از مهندسان نرم‌افزار برای ساخت برنامه‌های توزیع‌شده و مقیاس‌پذیر تبدیل شده است.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Fri, 17 Jan 2025 10:58:19 +0330</pubDate></item><item><title>آیا نوشتن تست، فرآیند توسعه نرم‌افزارتان را واقعا کندتر می‌کند ؟</title><link>https://tech.arvancloud.ir/faster-development-with-tests-tmzjcz7yx1to</link><description>در حوزه نرم‌افزار تفکری قالب شده مبنی بر اینکه نوشتن تست (Automated Test) وقت‌گیر بوده و ننوشتن تست باعث سریعتر پیش رفتن کارها می‌شود. در این نوشته سعی دارم از تجارب شخصی‌ام در این رابطه نوشته و توضیح دهم چرا چنین تفکری در اکثر موارد اشتباه است.چرا فکر می‌کنیم نوشتن تست، سرعت توسعه را کند می‌کند ؟همیشه وقتی صحبت از تست‌نویسی می‌شود، شاید اولین تصویری که در ذهن مخاطب نقش می‌بندد نوشتن صدها خط کدِ تست در قالب ده‌ها Test Case است، آن‌هم برای یک اندپوینت ساده و ناچیز HTTP. حالا تصور کنید یک پروژه با بیش از صد اندپوینت دارید، پس حتما صدها تست‌کِیس و هزاران تست خواهید داشت که نوشتن آنها روزها و هفته‌ها زمان می‌برد.شما به مدیرتان می‌گویید برای بالا بردن کیفیت پروژه باید تست بنویسیم، او به چشمان شما زُل می‌زند و با چهره‌ای نسبتا عصبانی و لحنی تَحَکُم آمیز به شما می‌گوید:فقط کارو بزن بره، نمی‌خواد تست بنویسی، باید به دِدلاین‌ها برسیم.خب به نظر، حق با مدیر شماست. اما اگر برای نوشتن تست، مجبور نباشیم روزها و هفته وقت بگذاریم چطور ؟نوشتن فقط یک تست چقدر زمان می‌برد ؟تصور کنید یک اندپوینت HTTP را پیاده‌سازی کرده‌اید که متد آن POST بوده و Payload ی را در Body نیز باید قرار دهید، همینطور در Header آن نیز مقداری را برای این اندپوینت باید ارسال کنید. برای تست آن اگر قصد بر نوشتن Automated Test نداشته باشید، احتمالا از یک کلاینت HTTP مانند Postman استفاده می‌کنید.پست‌مَن را باز می‌کنید،‌ یک ریکوئست جدید در آن ایجاد می‌کنید، متد را روی POST قرار می‌دهید، آدرس (مسیر) اندپوینت را وارد می‌کنید، وارد تب Body شده و مقادیر آن را وارد می‌کنید، وارد تب Header شده و مقادیر آن را نیز وارد می‌کنید، و در نهایت کلید Send را زده تا ریسپانس را ببینید و فرضا از 200 بودن کد HTTP مطمئن شوید.شاید تمام این فرآیند حدودا یک دقیقه از شما زمان‌ ببرد.حالا می‌خواهیم دقیقا همین پروسه را در قالب یک Automated Test بنویسیم. یک فایل Test Case جدید در پروژه ایجاد میکنیم (احتمالا فایل Example را Copy Paste می‌کنیم)، فانکشن زیر را در آن می‌نویسیم:public function test_making_an_api_request()&#xA;{&#xA;    $this-&gt;post(&#39;/api/something&#39;, [&#39;name&#39; =&gt; &#39;mehrad&#39;], [&#39;Authorization&#39; =&gt; &#39;Bearer some_token&#39;])-&gt;assertStatus(200);&#xA;}و در نهایت در ترمینال کامند مربوط به اجرای تست‌ها را ران می‌کنیم.به نظر شما این‌کار چقدر زمان می‌برد ؟ بعید است بیشتر از یک دقیقه زمان ببرد.اگر بخواهیم موارد دیگری مانند اطلاعات درون دیتابیس را نیز چک کنیم، همچنان از نظر زمان تفاوت خاصی نخواهد داشت. فرض کنیم بعد از دیدن نتیجه درخواست در پست‌من، کلاینت دیتابیس خود را باز می‌کنید و از صحت اطلاعات اطمینان حاصل می‌کنید. در زمان نوشتن تست هم چنین کاری را با اضافه کردن این Assertion می‌توانید انجام دهید:$this-&gt;assertDatabaseHas(&#39;users&#39;, [&#xA;    &#39;name&#39; =&gt; &#39;mehrad&#39;&#xA;]);که از نظر زمانِ انجام، تفاوت خاصی باهم ندارند.نوشتن یک تست چه فایده‌ای دارد ؟به این سناریو توجه کنید:تصور کنید ۵۰ اندپوینت در پروژه خود دارید. اگر برای آنها تست ننویسید قاعدتا ۵۰ ریکوئست در پست‌من به ازای هرکدام از این اندپوینت‌ها باید داشته باشید. مدیر شما به سراغتان آمده و از شما می‌خواهد لاجیک (منطق) یکی از اندپوینت‌های موجود را تغییر دهید و شما همین کار را می‌کنید. به عنوان یک برنامه‌نویس مسئولیت‌پذیر حدس می‌زنید که شاید تغییری که در کد داده‌اید Side Effect هایی داشته باشد و به همین دلیل تمام ۵۰ اندپوینتی که در پست‌من دارید را یکبار تست می‌کنید تا مطمئن شوید همه‌چیز سر جای درستش است. نیم ساعت بعد یک باگ کریتیکال به شما ریپورت می‌شود و باید سریع تغییراتی جدید در کد ایجاد کنید. از آنجایی که برنامه‌نویس متعهدی هستید دوباره همه اندپوینت‌ها را تست می‌کنید. روز بعد تسک جدیدی به شما اختصاص داده می‌شود و باید یک اندپوینت جدید به پروژه اضافه کنید، بعد از انجام اینکار دوباره تمام اندپوینت‌ها را اجرا می‌کنید و عملا هر نوع توسعه‌ای که در پروژه می‌دهید، باید یکبار تک تک اندپوینت‌ها را برای اطمینان به صورت Manual (دستی) تست کنید. حالا تصور کنید از همان ابتدای پروسه توسعه، به ازای هر اندپوینتی که نوشته بودید، همان یک تستِ اصطلاحا Happy Path (تستهایی که فقط حالت و شرایط صحیح را تست می‌کنند) را می‌نوشتید. به ازای هر تغییری که دوباره در برنامه ایجاد می‌کردید، به ازای هر اندپوینت جدیدی که اضافه می‌کردید و ...، فقط کافی بود با یک کامند در ترمینال یکدور تست‌ها را ران کنید تا از صحت بقیه بخش‌های پروژه اطمینان حاصل کنید.همانطور که می‌بینید نوشتن تست سرعت کار ما را نتنها کاهش نمی‌دهد،‌ بلکه افزایش می‌دهد. وقتی از تست نویسی صحبت می‌کنیم، منظور این نیست که از همان ابتدا صدها تست‌کیس بنویسیم. باید شرایط پروژه، نوع پروژه و پارامترهای دیگری را در نظر گرفت.اگر دِدلاین های کوتاه مدت دارید، به تست‌های Happy Path اکتفا کنید. نوشتن آنها شما را در ادامه توسعه نجات خواهند داد. اگر ددلاین‌های آزادتری دارید تست‌های Corner Case (تست‌هایی که حالت‌های خاصِ برنامه را تست می‌کنند) را نیز بنویسید.  سناریوهای پیچیده‌ترسناریو‌ی اولتصور کنید چیزی که باید تست کنید، صرفا یک اندپوینت نیست، و باید یک فلویی از اندپوینت‌ها را مورد تست قرار دهید. مثلا در اندپوینت اول کاربر باید ثبت‌نام شود و یک کد تایید که برای مدت ۳۰ ثانیه معتبر است برایش ساخته شود، در اندپوینت بعدی باید کد تایید را وارد کند تا آدرس ایمیلش تایید شود، و در اندپوینت بعد باید اطلاعات تکمیلی‌اش را کامل کند تا از این پس بتواند از پنل و داشبورد سایت ما استفاده کند.تستِ چنین فلویی با پست‌من سخت و خسته‌کننده خواهد شد، زیرا هم در حین توسعه همین اندپوینت‌ها، و هم در ادامه کار پروژه که تغییری در بقیه بخش‌های پروژه ایجاد می‌کنید، باید بارها و بارها این فلو را به ترتیب و به صورت دستی در پست‌من اجرا کنید. در مقابل با داشتن Automated Test برای چنین فلویی، همیشه باخیال راحت‌تر و سرعت بیشتری می‌توانید بخش‌های جدید را توسعه دهید و یا در کدهای فعلی تغییر ایجاد کنید.سناریوی دومتصور کنید در پروژه‌‌ای مشغول به کار هستید که معماری پیچیده‌تری دارد. مثلا از سه دیتابیس همزمان استفاده می‌کند و اندپوینت‌هایی که در پروژه وجود دارند، بعضا روی دو یا هر سه دیتابیس تغییراتی ایجاد می‌کنند.تست کردن اندپوینت‌های چنین پروژه‌ای به صورت Manual به شدت طاقت‌فرسا و زمان‌بر خواهد بود. همینطور احتمال خطای انسانی در این شرایط بیشتر وجود دارد. در مقابل با داشتن Automated Test سرعت تست و توسعه پروژه به شدت افزایش می‌یابد و احتمال خطا نیز کاهش خواهد یافت.سناریوی سوماگر تجربه کار تیمی، به شکلی که چند نفر دقیقا روی یک سورس‌کد کار کنند را داشته باشید، احتمالا با این چالش مواجه شده‌اید که تغییرات شما کار همکارتان، یا تغییرات آن‌ها کار شما را خراب کرده باشد. با بیشتر شدن تیم‌های نرم‌افزاری چنین دغدغه‌هایی هر روز بیشتر خواهد شد و راه توسعه صحیح در این شرایط، داشتن Automated Test است، تا هر تغییری که در کد می‌دهیم، به واسطه تست‌ها مطمئن باشیم که کار سایر اعضای تیم دچار مشکل نمی‌شود و از صرفِ ساعت‌ها وقت برای دیباگِ پروژه جلوگیری کنیم.سناریوی چهارمتصور کنید به تازگی وارد پروژه‌ای شده‌اید که سورسی لِگِسی (Legacy) داشته و دارای میلیون‌ها خط کد بوده که طی چند سال برنامه‌نویس‌های مختلف روی آن کار کرده‌اند. در چنین شرایطی اگر Automated Test در پروژه وجود نداشته باشد، هر تغییری که به عنوان عضو جدید در پروژه می‌دهید، به احتمال خیلی خیلی زیاد Side Effect هایی را در سایر بخش‌های پروژه ایجاد می‌کند که ساعت‌ها و حتی روز‌ها برای دیباگ از شما زمان خواهد برد.سناریوی پنجمتصور کنید خودتان به صورت انفرادی پروژه‌ای را از صفر شروع کرده‌اید و نسبت به تمام اجزای پروژه تسلط دارید. بعد از مدتی توسعه (فرضا بعد از ۱۰ ماه) ناگزیر یکسری از ریز جزییات پروژه از حافظه شما خارج شده و در زمان توسعه یا تغییر در بخش‌های مختلف، می‌بینید که باگ‌هایی ایجاد می‌کنید (که شما را درگیر ساعت‌ها دیباگینگ می‌کند). در این شرایط هم داشتن Automated Test ها به شکل قابل توجهی به شما کمک می‌کنند تا سرعت و دقت کارتان افزایش بیابد.سناریوی ششمترکیب دو یا سه مورد از سناریوهای بالا.توصیه‌ای برای پروژه‌هایی که هیچ تستی ندارندوقتی پروژه‌ای مدت‌ها توسعه داده شده و هیچ تستی ندارد، انگیزه توسعه‌دهندگان آن برای پرداخت این بدهی فنی هر روز کمتر خواهد شد. شاید دلیل این موضوع حجم زیاد این بدهی فنی باشد.در این شرایط می‌توانید کار تست نویسی را فقط برای توسعه‌های جدید انجام دهید. همانطور که بالاتر نیز اشاره شد، تست‌های Happy Path برای فیچر‌های جدیدی که توسعه می‌دهید بنویسید. در همین حین با برنامه‌ریزی تست‌های Happy Path برای سایر بخش‌های پروژه نیز بنویسید تا به مرور زمان و بعد از چند ماه تمام پروژه تست‌های Happy Path داشته باشد. بعد از آن با همین روش می‌توانید تست‌های Corner Case را نیز اضافه کنید.جمع‌بندیدر این نوشته توضیح داده شد که چرا نوشتن تست بر عکس تصور قالب، باعث افزایش سرعت توسعه ما خواهد شد. شاید در بعضی شرایط (برای نمونه پروژه‌هایی که یک برنامه‌نویس به صورت مقطعی انجام داده و بعد از تحویل کار سرنوشت پروژه دقیقا مشخص نخواهد بود و مثال‌هایی از این دست) نوشتن Automated Test صرفه‌ی اقتصادی نداشته باشد. اما به طور کلی اگر به اصول و ابزارهای اینکار تسلط داشته باشید، سرعت توسعه شما افزایش یافته و آینده پروژه مطمئن‌تر خواهد بود.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Tue, 04 Jan 2022 22:19:35 +0330</pubDate></item><item><title>چند خطی درباره‌ی آنچه از تاریخ شی‌گرایی نمی‌دانید</title><link>https://virgool.ir/@mehradsadeghi/alan-kay-oop-f5nalpergoq3</link><description>image source: https://www.colorado.edu/atlas/alan-kay-2019در دهه ۱۹۶۰ میلادی دکتر Alan Kay واژه‌ی مشهور Object Oriented Programming را وارد دنیای نرم‌افزار کرد. گرچه پیش از آن مفهوم Object وجود داشت، اما اَلِن کِی کسی بود که ایده برنامه‌نویسی شکل شی‌گرا را ارایه داد.در منابع مختلف آموزشی چهار اصل Encapsulation ، Inheritance ، Polymorphism  و Abstraction به عنوان اصول و پایه‌های شی‌گرایی معرفی می‌شوند، درحالیکه این موارد صرفا روش‌‌هایی برای پیاده‌سازی تعریفی‌ست که اَلِن کِی از OOP داشت.در مورد ایده اَلِن کِی ابهامات مختلفی وجود داشت و بر همین اساس زبان‌های برنامه‌نویسی به اشکال گوناگون ایده‌ی OOP را پیاده سازی کردند. در کنفرانسی در سال ۱۹۹۷ اَلِن کِی چنین می‌گوید:I made up the term ‘object-oriented’, and I can tell you I didn’t have C++ in mind.در سال ۲۰۰۳ ایمیل‌هایی بین Stefan Ram و اَلِن کِی رد و بدل شد. اَلِن کِی در قسمتی از این ایمیل‌ها می‌گوید:I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is messaging.یکی از سوالاتی که استفان از اَلِن پرسید چنین بود که:What does &#34;object-oriented [programming]&#34; mean to you?و در پاسخ اَلِن کِی چنین می‌گوید:OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I&#39;m not aware of them.او با توجه به سابقه تحصیلی ریاضیات و زیست‌شناسی‌ای که داشت، هر آبجکت را به یک سلول زیستی (یک سرور) در یک شبکه تشبیه کرد که تنها راه ارتباط آنها ارسال پیام (messaging) است. همچنین ما نمی‌دانیم درون یک سرور چه می‌گذرد (local retention and protection and hiding of state-process)، و عناصر آن تا جای امکان، از وضعیت بعدی خودشان بی‌خبر هستند (extreme late-binding).ایده OOP از پروژه ARPANET (که پایه‌های اینترنت امروزی است) در ذهن اَلِن کِی شکل گرفته بود. در همین راستا زبان برنامه‌نویسی Smalltalk بوسیله او و تیمش ساخته شد که دقیقا ایده وی از شی‌گرایی را پیاده‌سازی می‌کرد.منابع:https://amirrezaghaderi.com/fa/blog/on-oophttp://www.purl.org/stefan_ram/pub/doc_kay_oop_enhttps://www.youtube.com/watch?v=oKg1hTOQXoYhttps://ovid.github.io/articles/alan-kay-and-oo-programming.htmlhttps://ovid.github.io/articles/alan-kay-and-missing-messages-a-follow-up.htmlhttp://lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.htmlhttp://worrydream.com/EarlyHistoryOfSmalltalk</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Fri, 26 Mar 2021 21:28:31 +0430</pubDate></item><item><title>آمار بازدید پست‌های من در سال ۹۹</title><link>https://virgool.ir/@mehradsadeghi/آمار-بازدید-پست-های-من-در-سال-۹۹-fjhigmjy0c6d</link><description>در طول تاریخ از اعداد استفاده کردیم تا اغلب داد و ستد کنیم و آن‌چیزی که شمردنی است را بشماریم. برای هر عدد واحد درست کردیم تا عددهای زندگی قاطی نشوند و از اعداد، شفاف‌تر استفاده کنیم؛ مثلا وقتی می‌گوییم ده هزار تومان به پول اشاره داریم و وقتی می‌گوییم ده هزار بلیط به بلیط!روز به روز که در زندگی جلو‌تر رفتیم عددها فرقی نکردند ولی این واحدها بودند که زیاد شدند. واحد کریپتو، واحد اصله درخت، واحد فاصله و …«واحد» یک توافق عمومی است برای شمردن؛ تا همانطور که گفتم شمردن‌ها قاطی نشود. مشاهده افراد دارای ثروت (اجتماعی یا مالی) به من ثابت کرده اینکه چه چیزی را بشماریم از اینکه چطور بشماریم مهم‌تر است. هرکس با واحد خاصی مسائل زندگی را می‌شمارد. اینطور به نظرم آمده که مشخص کردن واحد یعنی مشخص کردن اینکه من در زندگی برای چه چیزهایی ارزش قائلم و می‌خواهم چه چیزهایی را در زندگی بشمارم. https://cdn.virgool.io/annual-report/1399/enw2yny3nrur-UFjDU.mp4 اعدادی که بدون واحد ثبت کردمبه ویدیویی که ویرگول برایم ساخته که نگاه می‌کنم میبینم که در سال ۹۹، من در مجموع ۱,۰۲۰ کلمه در ویرگول نوشتم و منتشر کردم و مخاطبین، پست‌های من را ۳۶ مرتبه پسندیدند و  ۱۳ بار هم نظر خود را روی پست‌های من به اشتراک گذاشتند. در سال ۹۹، ۲۸ نفر در ویرگول من را دنبال کردند تا پست‌های بعدیم را بخوانند. این اعداد نشان میدهند من کاری کرده‌ام. هرکدام به واحدی وصل هستند. از خودم می‌پرسم من کدام واحد را شمارش کرده‌ام؟ کدامیک از واحدهای بالا از همه برای من مهم‌تر است؟ ادامه ویدیو را می‌بینم.آمار از اثر بیرونی می‌گویندطبق آمار پست‌های من ۲,۵۸۸ بار خوانده شدند و ۲۹۴,۵۰۸ ثانیه صرف مطالعه آنها شده است، که با توجه به جمعیتی که در ایران به اینترنت دسترسی دارند، ویرگول به من می‌گوید که توانستم  ۰/۰۰۴۰۳۷۶۷۵ ثانیه، سرانه مطالعه دیجیتال کشور را بالا ببرم.از طرف دیگر ویرگول به من می‌گوید که اگر قرار بود پست‌هایم را چاپ و به دست تک تک خوانندگان برسانم باید ۸,۶۱۰ کاغذ مصرف می‌کردم.آن عددهای کوچک ابتدای ویدیو حالا تبدیل شده‌اند به عددهای بزرگ به اینکه من جلوی مصرف این تعداد کاغذ را گرفتم یا به اینکه من  ۰/۰۰۴۰۳۷۶۷۵ ثانیه، سرانه مطالعه دیجیتال کشور را جابه جا کرده‌ام. واحد این عددها برای من ملموس‌تر است.واحد نوشتن چیست؟همه عددهای بالا و همینطور اثر بیرونی که روی خوانندگان و همینطور در مقیاس بزرگتر طبیعت و جامعه اطرافم گذاشتم اعدادی هستند که من دوستشان دارم و به آنها افتخار می‌کنم. اگر چنین ویدیویی دست شما نیز رسید به شما بابت تک تک اعداد تبریک می‌گویم.اثر هر نوشته تا حدودی معلوم است، اگر بنویسید جلوی قطع درخت را می‌گیرید، به سرانه مطالعه کشور اضافه می‌کنید و خوانندگانی جذب می‌کنید که شما را از طریق نوشته‌هایتان می‌شناسند و …به نظرم می‌رسد که نوشته‌های من و شما واحد ندارند ولی اثر بیرونی دارند.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Mon, 22 Mar 2021 12:57:13 +0430</pubDate></item><item><title>چطور در لاراول به شکلی درست کوئری‌ خود را بر اساس URL Query String فیلتر کنیم</title><link>https://tech.arvancloud.ir/laravel-filter-querystring-v61hgupba5y0</link><description>نمونه ای از کد غیر اصولیاحتمالا با شرایطی مواجه شده‌اید که می‌خواستید بر اساس پارامترهایی که در query string آدرس وجود دارد، کوئری‌های خود را فیلتر کنید و بعد از انجام اینکار و توسعه بیشتر پروژه، با کدی شبیه به کد تصویر بالا مواجه شدید.این روش خروجی لازم را به شما می‌دهد ولی روش اصولی یا اصطلاحا یک good practice نیست.چرا روش بالا اصولی نیست ؟زمانی که تعداد پارامترهای ‌query string افزایش پیدا می‌کند، تعداد این if ها در کد زیاد شده که این به خودی خود بعد از ماه‌ها توسعه پروژه، شما را با یک کد بزرگ که به سختی قابل نگهداری است روبرو می‌کند.اشکال دیگری که در این کد وجود دارد این هست که اصل دوم از اصول SOLID را نقض می‌کند.بطور خلاصه طبق اصل دوم سالید باید برنامه را طوری طراحی کنیم که برای اضافه کردن امکانی جدید، مجبور به دستکاری کدهای قبلی نباشیم.در واقع اصل دوم سالید تلاش می‌کند ما را از دستکاری کدهایی که به درستی کار می‌کنند منع کند تا اشکالی در برنامه فعلی ایجاد نکنیم.در ادامه توضیح اِشکال مربوط به تصویر ابتدای متن، هر بار که نیاز به فیلتر نمودن پارامتر جدیدی در query string باشد، مجبور هستیم در کدهای قبلی دست برده و تغییراتی در آن ایجاد کنیم، که احتمال تولید باگ نیز به همین ترتیب بالا می‌رود.چه روشی بهتر است ؟برای حل اشکالات بالا، باید روشی طراحی کرد که به ما امکان جداسازی منطق هر بخش از فیلترهای query string را بدهد، به شکلی که اگر نیاز به فیلتر جدیدی داشتیم، در قالب یک متد جدید (جدای از کدهای قبلی) بتوان این فیلتر را اضافه کرد و در نهایت تاثیرش را خروجی کوئری دیتابیس ببینیم.به همین منظور پکیجی برای فریم‌ورک لاراول نوشته‌ام که علاوه بر ایجاد ساختار مورد نظر برای افزودن فیلترها، تعدادی فیلتر پر کاربرد نیز در آن تعبیه شده که در ادامه به توضیح آنها می‌پردازم. پکیج Laravel Filter Query String۱. ابتدا با دستور زیر پکیج را در پروژه خود نصب کنید:$ composer require mehradsadeghi/laravel-filter-querystring ۲. سپس ترِیتِ FilterQueryString را در مدل مورد نظر خود use کنید. همینطور پراپرتی‌ای به نام filters را به مدل خود اضافه کنید که حاوی آرایه‌ایست از فیلرتهای مورد نظر شما.برای نمونه در مدل User چنین کدی خواهیم داشت:use Mehradsadeghi\FilterQueryString\FilterQueryString;&#xA;&#xA;class User extends Model&#xA;{&#xA;    use FilterQueryString;&#xA;    protected $filters = [];&#xA;    ...&#xA;}۳. برای اِعمال فیلترها در کوئری خود، باید متد filter را در eloquent query استفاده کنید. برای نمونه:User::select(&#39;name&#39;)-&gt;filter()-&gt;get();فیلترهای موجودSortComparisonsInLikeWhere clauseبه منظور توضیح هر فیلتر، تصور کنید جدول users ی با این اطلاعات داریم:و کوئری ما در هر مثال نیز به این ترتیب است:User::filter()-&gt;get();فیلتر Sortاین فیلتر در واقع معادل order by در sql است که به شکل منعطفی در پکیج FilterQueryString می‌توان از آن استفاده کرد.قاعده استفاده:?sort=field&#xA;?sort=field,sort_type&#xA;?sort[0]=field1&amp;sort[1]=field2&#xA;?sort[0]=field1&amp;sort[1]=field2,sort_type&#xA;?sort[0]=field1,sort_type&amp;sort[1]=field2,sort_typeدر فایل User.php:protected $filters = [&#39;sort&#39;];مثال:https://example.com?sort=created_atخروجی:توجه داشته باشید که وقتی نوع sort را تعیین نمی‌کنید، این مقدار به طور پیش‌فرض asc خواهد بود.مثالی دیگر:https://example.com?sort[0]=age,desc&amp;sort[1]=created_at,descخروجی:فیلترهای Comparisonsاین فیلترها که جنبه مقایسه‌ای دارند شامل ۶ مورد می‌باشند:greatergreater_or_equallessless_or_equalbetweennot_betweenقاعده استفاده:?greater=field,value&#xA;?greater_or_equal=field,value&#xA;?less=field,value&#xA;?less_or_equal=field,value&#xA;?between=field,value1,value2&#xA;?not_between=field,value1,value2در فایل User.php:protected $filters = [&#xA;    &#39;greater&#39;,&#xA;    &#39;greater_or_equal&#39;,&#xA;    &#39;less&#39;,&#xA;    &#39;less_or_equal&#39;,&#xA;    &#39;between&#39;,&#xA;    &#39;not_between&#39;&#xA;];مثالی از فیلتر greater:https://example.com?greater=age,20خروجی:مثالی از فیلتر not_between:https://example.com?not_between=age,21,30خروجی:فیلتر Inاین فیلتر معادل where in در sql است.قاعده استفاده:?in=field,value1,value2,...در فایل User.php:protected $filters = [&#39;in&#39;];مثال:https://example.com?in=name,mehrad,rezaخروجی:فیلتر Likeاین فیلتر معادل like &#39;%value%&#39;در sql است.قاعده استفاده:?like=field,value&#xA;?like[0]=field1,value1&amp;like[1]=field2,value2در فایل User.php:protected $filters = [&#39;like&#39;];مثال:https://example.com?like=name,mehخروجی:مثال:https://example.com?like[0]=name,meh&amp;like[1]=username,darخروجی:فیلتر پیش‌فرض Where Clauseبطور کلی زمانی که در query string یکی از فیلترهای معرفی شده در بالا موجود نباشد، پارامتر و مقدار آن در where کوئری قرار می‌گیرد. این فیلتر مناسب زمانی‌ست که می‌خواهید یکی از ستون‌های جدول خود را مستقیما فیلتر کنید.قاعده استفاده:?field=value&#xA;?field1=value&amp;field2=value&#xA;?field1[0]=value1&amp;field1[1]=value2&#xA;?field1[0]=value1&amp;field1[1]=value2&amp;field2[0]=value1&amp;field2[1]=value2تصور کنید می‌خواهیم ستون‌های name ، username و age را فیلتر کنیم.در فایل User.php:protected $filters = [&#39;name&#39;, &#39;username&#39;, &#39;age&#39;];مثال:https://example.com?name=mehradخروجی:مثال:https://example.com?age=22&amp;username=dariush123خروجی:مثال:https://example.com?name[0]=mehrad&amp;name[1]=dariushخروجی:مثال:https://example.com?name[0]=mehrad&amp;name[1]=dariush&amp;username[0]=mehrad123&amp;username[1]=reza1234خروجی:فیلترهای سفارشیاز طریق فیلترهای سفارشی شما این امکان را دارید تا فیلترهای خود را در قالب متدهایی در مدل خود تعریف کنید. این امکان به رعایت اصل دوم سالید کمک می‌کند، به این صورت که به ازای هر فیلتر جدید، متدی جدید می‌نویسیم و نیازی به ایجاد تغییر در کدهای قبلی نیست.برای نمونه فرض کنید فیلتری با نام all_except نیاز داریم که تمام کاربران به بجز کاربری که نام آن وارد می‌شود را بر‌می‌گرداند:در فایل User.php:protected $filters = [&#39;all_except&#39;];&#xA;&#xA;public function all_except($query, $value) {&#xA;    $query-&gt;where(&#39;name&#39;, &#39;!=&#39;, $value);&#xA;}برای آزمایش فیلتر اضافه شده:https://example.com?all_except=mehradتوجه کنید که متدهایی که به عنوان فیلتر تعریف می‌کنید بالاترین اولویت را دارند و حتی این امکان وجود دارد که فیلترهای موجود را override کنید.برای نمونه می‌خواهیم فیلتر in را به گونه‌ای تغییر دهیم که فقط و فقط سه مقدار را بپذیرد:در فایل User.php:protected $filters = [&#39;in&#39;];&#xA;&#xA;public function in($query, $value) {&#xA;    $exploded = explode(&#39;,&#39;, $value);&#xA;    if(count($exploded) != 4) {&#xA;        // throwing an exception or whatever you like to do&#xA;    } &#xA;    $field = array_shift($exploded);&#xA;    $query-&gt;whereIn($field, $exploded);&#xA;}نمونه‌ای دیگر از کاربردهای خوب فیلترهای سفارشی زمانی‌ست که می‌خواهید نام ستون‌های جدول را مستقیما به کاربر (در query string) نمایش ندهید. برای نمونه فرض کنید می‌خواهیم بجای username از by استفاده کنیم:در فایل User.php:protected $filters = [&#39;by&#39;];&#xA;&#xA;public function by($query, $value) {&#xA;    $query-&gt;where(&#39;username&#39;, $value);&#xA;}سپس:https://example.com?by=dariush123خروجی:به عنوان توصیه برای جلوگیری از شلوغ شدن مدل از متدهای فیلترینگ، می‌توانید trait ی ساخته و فیلترهای آن مدل را در آن قرار دهید.همینطور می‌توانید در گیت‌هاب پکیج Laravel Filter Query String را مشاهده کنید.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Fri, 31 Jul 2020 20:57:34 +0430</pubDate></item><item><title>درآمدی بر Serialize در PHP</title><link>https://virgool.ir/@mehradsadeghi/php-serialize-casrzxjvc6id</link><description>تصویر از Blake Connally در Unsplashنزدیکترین معادل فارسی برای واژه Serialize مرتب‌سازی یا پشت سر هم قرار دادن است. در برنامه‌نویسی وقتی صحبت از Serialize می‌کنیم، در واقع منظور ایجاد شکلی قابل ذخیره از یک مقدار است. مقادیری مانند string ها، int ها و سایر دیتا تایپ های ساده به راحتی قابل ذخیره سازی روی مموری، فایل یا جابجایی در سطح شبکه هستند، اما زمانی که به ذخیره یا انتقال یک آبجکت نیاز داریم چطور می‌توانیم اینکار را انجام دهیم ؟ اینجاست که Serialize به ما کمک می‌کند مقادیر را بدون تغییر در نوع و ساختار، ذخیره یا جابجا کنیم.سریالایز به عمل قرار دادن یک از سری بیت ها در کنار هم، بر اساس قاعده‌ای مشخص  گفته می‌شود.  منظور از قاعده‌ای مشخص‌ در واقع فرمتی است که در سریالایز از آن استفاده می‌شود. JSON، YAML، XML و دیگر فرمت ها می‌توانند به عنوان فرمتی برای سریالایز مورد استفاده قرار گیرند.در PHP فانکشنی به نام `serialize` برای همین منظور وجود دارد :&lt;?php&#xA;$serialized = serialize([1, 2, 3, 4, 5]);&#xA;echo $serialized;خروجی کد بالا به این صورت خواهد بود :a:5:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;}نمونه‌ای از سریالایز یک آبجکت :&lt;?php&#xA;&#xA;class SomeClass {&#xA;    &#xA;    private $somePrivateProperty;&#xA;    public $somePublicProperty;&#xA;&#xA;    public function somePublicMethod() {&#xA;        // ...&#xA;    }&#xA;&#xA;    private function somePrivateMethod() {&#xA;        // ...&#xA;    }&#xA;}&#xA;&#xA;$serialized = serialize(new SomeClass);&#xA;echo $serialized;خروجی کد بالا به این صورت خواهد بود :O:9:&#34;SomeClass&#34;:2:{s:30:&#34;SomeClasssomePrivateProperty&#34;N;s:18:&#34;somePublicProperty&#34;N;}همانطور که می‌بینید فرمت سریالایز در PHP به این صورت است. برای کسب اطلاعات بیشتر در مورد این فرمت، از این پاسخ در استک‌اُوِر‌فلو استفاده نمایید.نحوه Un-Serialize کردن مقدار Serialize شدههمانطور که برای سریالایز، فانکشن `serialize` را داریم، برای بازگرداندن آن فانکشنی به نام `unserialize` وجود دارد.در مثال پیشین :print_r(unserialize($serialized));خروجی کد بالا به این صورت خواهد بود :SomeClass Object&#xA;(&#xA;    [somePrivateProperty:SomeClass:private] =&gt; &#xA;    [somePublicProperty] =&gt; &#xA;)همانطور که واضح است کلاس ما دوباره بازگشته و قابل استفاده است.اگر از علاقه‌مندان به فریم‌ورک محبوب لاراول باشید، احتمالا با قابلیت Job/Queue در این فریم‌ورک کار کرده‌اید. در واقع اتفاقی که در Job های لاراول می‌افتد این است که تمام کلاس Job ی که نوشته‌اید سریالایز می‌شود و تحت عنوان یک Job در Queue قرار می‌گیرد. و در هنگام اجرای آن Job مقدار سریالایز شده از دیتابیس خوانده شده، Un-Serialize شده و سپس متد `handle` آن اجرا می‌شود.منابع :https://www.php.net/manual/en/function.serialize.phphttps://en.wikipedia.org/wiki/Serialization#Serialization_formatshttps://stackoverflow.com/questions/8641889/how-to-use-php-serialize-and-unserialize</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Tue, 21 Jan 2020 00:59:14 +0330</pubDate></item><item><title>چطور PHP کار Autoloading را انجام می‌دهد و نقش Composer در آن چیست ؟</title><link>https://virgool.ir/@mehradsadeghi/چطور-php-کار-autoloading-را-انجام-میدهد-و-نقش-composer-در-آن-چیست-gt5j52r4mahc</link><description> تصویر از Elly Johnsonاگر با زبان PHP برنامه‌نویسی شی‌گرا انجام داده و از آبجکت‌ها استفاده کرده باشید، حتما با موضوع لود کردن فایل‌ها (کلاس‌ها) ی مختلف روبرو شده‌اید. به طور عادی کافیست همه کلاس‌های مورد نظرمان را در ابتدای اسکریپتمان لود کنیم. مشکل این راه این است که در طول برنامه احتمال دارد بنا بر شرایطی از بعضی از کلاس‌هایی که لود کرده‌ایم استفاده نکنیم و یا بخواهیم تحت شرایطی خاص کلاسی را لود کنیم. در این صورت به قابلیتی به نام Autoloading نیاز داریم.فرض کنید دایرکتوری‌ای به نام test داریم و در آن فایلی با نام index.php و دایرکتوری دیگری با نام src داریم که شامل کلاس‌های ما شده و در آن دایرکتوری دو کلاس A و B داریم.ساختار فایل‌هابرای استفاده از کلاس‌های  A و B در فایل index.php صرفا کافیست آنها را require کنیم :&lt;?php&#xA;require_once &#34;A.php&#34;&#xA;require_once &#34;B.php&#34;&#xA;$a = new A();&#xA;print_r(get_included_files());خروجی این کد به این صورت خواهد بود :Array &#xA;(&#xA;    [0] =&gt; /home/index.php&#xA;    [1] =&gt; /home/A.php&#xA;    [2] =&gt; /home/B.php&#xA;)همانطور که می‌بینید کلاس B هم لود شده است درحالیکه از آن استفاده نکرده‌ایم. راه حل بهتر استفاده از Autoloading است.&lt;?php&#xA;function autoloader($class) {&#xA;    require_once(&#39;src/&#39;.$class.&#39;.php&#39;);&#xA;}&#xA;spl_autoload_register(&#39;autoloader&#39;);&#xA;&#xA;$a = new A();&#xA;&#xA;print_r(get_included_files());در این حالت Autoloader پی‌اچ‌پی به صورت خودکار هر زمانی که در اسکریپتمان از کلاس A استفاده کردیم، بر اساس منطقی که در فانکشن `autoloader` نوشته‌ایم، این کلاس را لود و مورد استفاده قرار می‌دهد.خروجی کد بالا به این صورت خواهد بود :Array&#xA;(&#xA;    [0] =&gt; /home/index.php&#xA;    [1] =&gt; /home/A.php&#xA;)نکته‌ای که در اینجا وجود دارد این است که نام کلاس‌ها و نام فایل‌ها باید یکسان باشند و در هر فایل فقط یک کلاس وجود داشته باشد.اما Composer چیست و چطور Autoloading را انجام می‌دهد ؟یکی از کارهای اصلی Composer مدیریت پکیج‌ ها در PHP بوده که احتمالا در پروژه های مختلف با آن کار کرده‌اید. اما کار مهم دیگری که Composer انجام می‌دهد مربوط به Autoload کلاس های مورد استفاده ما در برنامه است.در مسیر اصلی (دایرکتوری test) فایلی با نام composer.json بسازید و آن را به این صورت کامل کنید :{&#xA;    &#34;autoload&#34;: {&#xA;         &#34;classmap&#34;: [&#xA;            &#34;src/&#34;&#xA;        ]&#xA;    }&#xA;}سپس در ترمینال (خط فرمان) خود دستور `composer update` را وارد کرده تا دایرکتوری vendor ساخته شود.حال فایل index.php را به این صورت تغییر می‌دهیم :&lt;?php&#xA;require_once __DIR__.&#34;/vendor/autoload.php&#34;&#xA;&#xA;$a = new A();&#xA;$b = new B();&#xA;&#xA;print_r(get_included_files());اگر به خروجی این کد نگاه کنید می‌بینید که فایل های A و B به صورت خودکار توسط Composer در اسکریپتمان لود شده‌اند. در واقع Composer از همان فانکشن `spl_autoload_register` استفاده می‌کند، اما بسیار مفصل‌تر. برای مشاهده دقیق تر جزئیات در دایرکتوری `vendor/composer` کلاس‌ های autoload.php ، autoload_real.php و ClassLoader.php را مشاهده نمایید.در اینجا ما از حالت `classmap` در Composer استفاده کردیم (به فایل composer.json توجه کنید). این حالت ساده ترین بوده و جزئیات آن را در کلاس `autoload_classmap.php`  می‌توانید ببینید. این فایل یک آرایه که شامل نام کلاس ها و مسیرشان است را باز می‌گرداند و هر بار که کلاس جدید به اسکریپتمان اضافه می‌کنیم باید یکبار دستور `composer update` را وارد کنیم تا این آرایه بروز شود.اما روش های دیگری از ‌Autoloading در Composer وجود دارد که فریم ورک معروف Laravel نیز از آن استفاده می‌کند و آن PSR-4 است.{&#xA;    &#34;autoload&#34;: {&#xA;        &#34;psr-4&#34;: {&#xA;            &#34;Src\\&#34;: &#34;src/&#34;&#xA;        }&#xA;    }&#xA;}این روش امکان استفاده از از کلید namespace در PHP را به ما می‌دهد که کار لود کلاس ها را بسیار ساده کرده و هر بار با اضافه کردن کلاسی جدید نیازی به وارد کردن دستور `composer update` نداریم. جزئیات این روش در فایل `autoload_psr4.php` است.برای نمونه :&lt;?php&#xA;&#xA;namespace App;&#xA;&#xA;use Illuminate\Contracts\Auth\MustVerifyEmail;&#xA;use Illuminate\Foundation\Auth\User;&#xA;use Illuminate\Notifications\Notifiable;&#xA;&#xA;class User extends Authenticatable {&#xA;...در واقع مسیری که جلوی namespace می‌نویسیم به شکل path پشت نام کلاس مورد نظر قرار گرفته و از یکتا بودن آن اطمینان حاصل می‌شود و به این ترتیب Composer به صورت داینامیک در زمان ران تایم کلاس مورد نظر را لود می‌کند (به همین دلیل هر بار با اضافه کردن کلاسی جدید مانند حالت classmap نیازی به وارد کردن دستور `composer update` نداریم).همینطور دستور use به ما کمک می‌کند تا در برنامه مجبور نباشیم مسیر و نام کلاسمان را تکرار کنیم و در زمانی که قصد استفاده از از دو کلاس همنام با مسیرهای متفاوت را داریم، با استفاده از use می‌توانیم نام مستعاری (alias) به کلاس‌مان بدهیم و از آن نام در برنامه استفاده کنیم.منابع :http://php.net/manual/en/language.oop5.autoload.phphttp://php.net/manual/en/function.spl-autoload-register.phphttp://php.net/manual/en/function.get-included-files.phphttps://getcomposer.org/doc/04-schema.md#autoloadhttps://www.php-fig.org/psr/psr-4https://dev.to/blocksbylukas/the-magic-behind-autoloading-php-files-using-composer-1e19https://medium.com/tech-tajawal/php-composer-the-autoloader-d676a2f103aa</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Sun, 19 Jan 2020 13:07:58 +0330</pubDate></item><item><title>اصطلاحات stubs و drivers در تست نرم‌افزار چه مفهومی دارند</title><link>https://virgool.ir/@mehradsadeghi/stubs-and-drivers-osgtef5goiin</link><description>عکس از Irvan Smithدر توسعه نرم‌افزار، تست از اهمیت بالایی برخوردار می‌باشد. تصور کنید درتست‌هایی که می‌نویسید، تست شما وابسته به یکی از ماژول‌ها، فانکشن‌ها و یا بخشی از برنامه باشد، که آن بخش هنوز توسعه داده نشده، و به هر طریق، آن بخش در لحظه فعلی برای شما قابل استفاده نیست.در این شرایط از چه امکانی در تست‌نویسی می‌توان استفاده کرد ؟اِستاب‌ها (Stubs)اِستاب‌ها در واقع شبیه‌سازی‌ای از قطعه کد، فانکشن، ماژول و یا هر بخشی از برنامه شما هستند که تستی که می‌نویسید به آن وابسته است، و این ماژول واقعا برنامه‌نویسی نشده و کار شما را متوقف کرده‌ است.برای نمونه تصور کنید دو ماژول در برنامه شما وجود دارد. ماژول اول Login و ماژول دوم Dashboard نام دارد و منطق برنامه به این صورت است که پس از لاگین شدن کاربر، باید به صفحه Dashboard هدایت شود.حال فرض کنید توسعه‌ی ماژول Login تمام شده و برای تست شدن به وجود ماژول Dashboard نیاز دارد؛ اما ماژول Dashboard هنوز توسعه داده نشده و آماده استفاده نمی‌باشد.در این شرایط برنامه‌نویس با نوشتن قطعه کدی، وجود صفحه Dashboard را شبیه‌سازی می‌کند تا بتواند ماژول Login را تست کند. &lt;?php&#xA;&#xA;class DashboardTest {&#xA;&#xA;    public function show() {&#xA;        return &#39;dashboard is ready&#39;;&#xA;    }&#xA;}&#xA;&#xA;class UserTest {&#xA;&#xA;    public function testIfLoginWorksFine() {&#xA;&#xA;        $userData = [&#xA;         &#39;username&#39; =&gt; &#39;mehrad&#39;,&#xA;          &#39;password&#39; =&gt; &#39;secret&#39;&#xA;        ];&#xA;&#xA;        $response = $this-&gt;post(&#39;login&#39;, $userData);&#xA;&#xA;        $response-&gt;assertSee(&#39;dashboard is ready&#39;);&#xA;    }&#xA;} در این سناریو، Dashboard نوشته شده در واقع یک Stub است.اِستاب‌ها در واقع در تست‌های به اصطلاح بالا به پایین استفاده می‌شوند.تست‌های بالا به پایین به تست‌هایی گفته می‌شود که در آن‌ها ماژول‌های سطح پایین‌تر که هنوز توسعه داده نشده‌اند را شبیه‌سازی می‌کنیم، در حالیکه توسعه‌ی ماژول‌های سطح بالا تمام شده است.  درایوِرها (Drivers) درایوِرها شباهت زیادی به استاب‌ها دارند؛ به این معنی که درایورها نیز در واقع شبیه‌سازی‌ای از قطعه کد، فانکشن، ماژول و یا هر بخشی از برنامه شما هستند که تستی که می‌نویسید به آن وابسته است، و این ماژول واقعا برنامه‌نویسی نشده و کار شما را متوقف کرده‌ است.اما تفاوت آن با استاب در این است که در تست‌های به اصطلاح پایین به بالا مورد استفاده قرار می‌گیرند.تست‌های پایین به بالا به تست‌هایی گفته می‌شود که در آن‌ها ماژول‌های سطح بالاتر که هنوز توسعه داده نشده‌اند را شبیه سازی می‌کنیم، در حالیکه توسعه‌ی ماژول‌های سطح پایین‌ تمام شده است. </description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Sun, 26 May 2019 02:30:30 +0430</pubDate></item><item><title>سند RFC چیست و چگونه اینترنت را تحت تاثیر قرار می‌دهد</title><link>https://virgool.ir/@mehradsadeghi/what-is-rfc-afzircwhknyk</link><description>عکس از Annie Sprattسند RFC (بطور کامل  Request For Comments) یک پیش‌نویس رسمی از طرف سازمان IETF یا همان (Internet Engineering Task Force) می‌باشد که در آن مشخصات یک تکنولوژی بطور خاص توضیح داده شده است.زمانی که یک RFC به تصویب می‌رسد، به یک استاندارد رسمی برای آن تکنولوژی تبدیل می‌شود.سندهای RFC در ابتدا برای پروتکل APPANET (که امروزه به آن اینترنت می‌گوییم) استفاده شدند و از آن پس در تمام تکنولوژی‌های زیرساختی اینترنت استفاده می‌شوند.برای نمونه پروتکل SSL که پیش از این توضیحاتی در مورد آن داده بودم، تحت یک RFC به تایید رسید و منتشر شد.یک استاندارد در اینترنت زمانی به تصویب می‌رسد که یک RFC در کمیته‌های مربوطه مورد بحث و بازبینی قرار گیرد تا زمانیکه آخرین نسخه از آن تایید شود. در این هنگام دیگر هیچ نظر (Comment) و تغییری مورد پذیرش نیست.عملا اگر نیاز به برزورسانی و تغییری در یک پروتکل باشد، باید RFC های جدیدتری به تصویب برسد. برای نمونه پروتکل FTP در RFC 114 در آوریل 1971 منتشر شد. سپس در RFC 765 در سال 1980 و در نهایت در RFC 959 دستخوش تغییراتی شد.این مطلب ترجمه‌ای از وب‌سایت techopedia بود و برای کسب اطلاعات بیشتر در مورد RFC ها می‌توانید از ویکی‌پدیا استفاده نمایید.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Sun, 26 May 2019 02:15:52 +0430</pubDate></item><item><title>چند خطی در مورد پروتکل‌های HTTPS - SSL - TLS</title><link>https://virgool.ir/@mehradsadeghi/چند-خطی-در-مورد-پروتکلهای-https-ssl-tls-ehbamqnsuiae</link><description>عکس از Chris Panasاگر از دوستداران توسعه وب هستید حتما اصطلاحاتی مانند پروتکل HTTPS، پروتکل SSL و TLS به گوشتان خورده. در این نوشته کوتاه سعی دارم تفاوت این پروتکل‌ها را به صورت مختصر شرح دهم.پروتکل HTTPSپروتکل HTTPS تا حد زیادی همان پروتکل HTTP است، با این تفاوت که اطلاعات در آن به صورت امن جابجا می‌شود.پروتکل HTTPS برای امن سازی اطلاعات از پروتکلی دیگر به نام SSL (که در نسخه جدید، آن را با نام TLS می‌شناسیم) استفاده می‌کند.در‌واقع HTTPS از یک پروتکل دیگر درون خود برای رمزگذاری اطلاعات استفاده می‌کند.پروتکل‌های SSL/TLSشرکت Netscape (که در مقاله چند خطی در مورد اِکما اسکریپت به یکی از دستاوردهای آن اشاره کردم) در سال 1995 پروتکل SSL را (Secure Sockets Layer) تولید کرد. این پروتکل از نسخه 2 ارائه شد و به دلیل مشکلات امنیتی‌ای که داشت به سرعت نسخه سوم آن در همان سال منتشر شد. سپس در سال 1999 که رقابت بین مرورگر مایکروسافت و سایر مرورگرها بالا گرفته بود، الزام برای بدست گرفتن استاندارد سازی این پروتکل (و استفاده همه شرکت‌ها از آن) احساس می‌شد.به همین دلیل شرکت Netscape در اواخر سال 1999 کنترل کامل این پروتکل را در اختیار IETF (یک سازمان استاندارد سازی داوطلبانه در وب) قرار داد.در همان سال با توسعه SSL سازمان IETF نام این پروتکل را به TLS یا همان Transport Layer Security نسخه 1 تغییر داد. در سال 2006 نسخه 1.1 ، در سال 2008 نسخه 1.2 و در سال 2018 نسخه 1.3 از این پروتکل منتشر شده است.بطور خلاصه SSL پروتکل رمزگذاری بر روی اطلاعات بود که توسط شرکت Netscape توسعه داده شد و پس از آنکه این شرکت استاندارسازی این پروتکل را به IETF سپرد نام آن به TLS تغییر پیدا کرد. همینطور پروتکل HTTPS در حال حاضر از TLS برای امن سازی اطلاعات درون خود استفاده میکند.این نوشته خلاصه ای از مطالب وب‌سایت‌های howhttps.works و wikipedia بوده که در صورت علاقه‌مندی بیشتر می‌توانید از طریق همین لینک‌ها به مطالعه ادامه دهید.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Sun, 26 May 2019 02:07:06 +0430</pubDate></item><item><title>مقدمه ای بر Bytecode و Opcode در زبان Solidity</title><link>https://virgool.ir/@mehradsadeghi/introduction-to-solidity-bytecode-and-opcode-ongcahunsh52</link><description>Photo by Patrick Lindenberg هرچقدر که در Smart Contract ها عمیق‌تر می‌شویم بیشتر با اصطلاحاتی چون PUSH1 ، SSTORE ، MSTORE و ... مواجه می‌شویم.این‌ها دقیقا چه هستند و آیا ما باید به آنها اهمیت دهیم ؟برای درک درست این اصطلاحات باید در Ethereum Virtual‌ Machine یا همان EVM عمیق‌تر شویم.زبان Solidity از نوع زبان‌های High Level Language است. ما آن را می‌فهمیم ولی ماشین آن را نمی‌فهمد. زمانی که ما یکی از کلاینت‌های اِتِریوم مانند geth را نصب می‌کنیم، EVM خودکار با آن نصب می‌شود. در واقع EVM یک سیستم عامل سبک می‌باشد که اختصاصا برای اجرای Smart Contract ها ساخته شده است.وقتی ما کدهای Solidity را کامپایل می‌کنیم، کدها به Bytecode تبدیل می‌شوند و این همان چیزیست که ماشین می‌فهمد.این یک نمونه Smart Contract بسیار ساده است :pragma solidity ^0.4.11;contract MyContract {&#xA;    uint i = (10 + 2) * 2;&#xA;}اگر این Smart Contract را در Remix اجرا نمایید، با کلیک بر روی دکمه details اطلاعات زیر را می‌بینیم :Solidity Bytecode and Opcodesاین بخش کامپایل شده‌ی کدهای Solidity است : 60606040525b600080fd00a165627a7a7230582012c9bd00152fa1c480f6827f81515bb19c3e63bf7ed9ffbb5fda0265983ac7980029این چیزی نیست جز یک عدد بر مبنای شانزده (هگزا)، که از کدهایی که در Contract نوشتیم بدست آمده است و به آن Bytecode گفته می‌شود.در همین پنجره (details) در بخش Web3 Deploy چنین چیزی می‌بینیم :...&#xA;   {&#xA;     from: web3.eth.accounts[0], &#xA;     data: &#39;0x606060405260186000553415601357600080fd5b5b60368060216000396000f30060606040525b600080fd00a165627a7a7230582012c9bd00152fa1c480f6827f81515bb19c3e63bf7ed9ffbb5fda0265983ac7980029&#39;, &#xA;     gas: &#39;4300000&#39;&#xA;   }, function (e, contract){&#xA;    console.log(e, contract);&#xA;    if (typeof contract.address !== &#39;undefined&#39;) {&#xA;         console.log(&#39;Contract mined! address: &#39; + contract.address + &#39; transactionHash: &#39; + contract.transactionHash);&#xA;    }&#xA; })در نهایت چیزی که از Contract ما بر روی شبکه اِتِریوم قرار می‌گیرد همین عدد هگزا ای است که در فیلد data می‌بینید و برای این عمل به پرداخت  4,300,000 gas نیاز دارد.در واقع EVM با همه چیز به عنوان یک مقدار هگزا برخورد می‌کند. اگر تابحال برایتان سوال پیش آمده که چرا در ابتدای آدرس اَکانت های اِتِریوم یا هَشِ تراکنش‌ها &#34;0x&#34; را می‌بینیم، دلیل آن همین است.در واقع &#34;0x&#34; به این معنی است که مقداری که در جلوی آن قرار می‌گیرد، یک عدد بر مبنای شانزده است.وجود &#34;0x&#34; الزامی نیست، چراکه همانطور که گفته شد EVM با همه مقادیر به صورت هگزا برخورد می کند.همینطور عباراتی به شکل زیر را می‌بینیم :PUSH1 0x60 PUSH1 0x40 MSTORE PUSH1 0x18 PUSH1 0x0 SSTORE CALLVALUE ISZERO PUSH1 0x13 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST JUMPDEST PUSH1 0x36 DUP1 PUSH1 0x21 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x60 PUSH1 0x40 MSTORE JUMPDEST PUSH1 0x0 DUP1 REVERT STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 SLT 0xc9 0xbd STOP ISZERO 0x2f LOG1 0xc4 DUP1 0xf6 DUP3 PUSH32 0x81515BB19C3E63BF7ED9FFBB5FDA0265983AC798002900000000000000000000به این بخش Operation Code یا همان Opcode گفته می‌شود.این بخش برای انسان قابل خواندن است ولی درک آن بسیار مشکل می باشد. تمام این Operation ها در واقع معادلی در مبنای شانزده دارند. بطور مثال معادل MSTORE مقدار 0x52 می باشد و یا SSTORE معادل 0x55 می‌باشد. در ریپازیتوری گیت‌هاب اِتِریوم می‌توانید لیست کامل Opcode ها را مشاهده نمایید.ماشین EVM از نوع Stack Machine می‌باشد. برای درک ساده‌ی اِستَک‌ها تصور کنید قطعات نان را در فِر قرار می‌دهید. طبیعتا قطعه نان آخری که در فِر قرار داده‌اید را، در هنگام خارج کردن نان‌ها، اول از همه بیرون خواهید آورد. در علوم کامپیوتر به این فرآیند Last In First Out یا همان LIFO گفته می‌شود.در محاسبات ریاضیاتی، ما چنین عمل می‌کنیم :۱۰ + ۲ * ۲&#xA;ابتدا ۲ را در ۲ ضرب نموده و سپس با ۱۰ جمع می‌کنیمدر Stack Machine اصول LIFO به این شکل عمل می‌کنند :Stack Machine : 2 2 * 10  + به این معنی که : ابتدا ۲ را در اِستَک قرار بده، سپس عدد ۲ بعدی را قرار بده و بعد از آن عملیات ضرب را انجام بده. حاصل آن ۲ می‌شود که در راس اِستَک قرار می‌گیرد. سپس عدد ۱۰ را در اِستَک قرار بده (بعد از ۴) و این دو عدد را با هم جمع کن. مقدار نهایی استک ۱۴ خواهد بود. عمل وارد کردن داده در استک PUSH و عمل خارج کردن داده POP نام دارد. در Opcode ـی که بالا تر دیدم تعداد زیادی PUSH1 وجود دارد که به معنی قرار دادن ۱ بایت از داده ای مشخص در اِستَک می باشد. (در ادامه این مورد با مثال‌های متعدد بیشتر توضیح داده می‌شود)در نتیجه، این عبارت :PUSH1 0x60به معنی قرار دادن ۱ بایت با مقدار &#34;0x60&#34; در اِستَک می‌باشد. تصادفا مقدار هگزایِ عملیات PUSH برابر با &#34;0x60&#34; است که اگر عبارت غیر الزامی &#34;0x&#34; را حذف کنیم، Bytecode بدست آمده برابر با &#34;6060&#34; می‌شود.یک قدم پیش تر برویم :PUSH1 0x60 PUSH1 0x40 MSTOREبا نگاهی به لیست Opcode ها، می‌بینیم که MSTORE (هگزا : 0x52) دو ورودی می‌گیرد و خروجی‌ای ندارد. Opcode بالا بدین معنی است :PUSH1 0x60 : مقدار 0x60 را وارد اِستَک کن2. PUSH1 0x40 :مقدار 0x40 را وارد اِستَک کن3. MSTORE 0x52 :به مقدار 0x60 از فضای حافظه را اختصاص بده و به نقطه 0x40 برو(در ادامه توضیح ساده تری از این Opcode می‌بینید)نتیجه نهایی Bytecode اینچنین خواهد بود :Result : 6060604052در حقیقت ما همیشه مقدار &#34;6060604052&#34; را در ابتدای بایت کدهای Solidity می‌بینیم و دلیل این موضوع این است که Smart Contract ها با این Opcode راه اندازی (Bootstrap) می‌شوند.در Opcode بالا، مقدار 0x60 در مبنای ده برابر ۹۶ و مقدار 0x40 در مبنای ده برابر ۶۴ است. در نتیجه Opcode بالا به این معنی است که :۹۶ بایت از حافظه را تخصیص بده و مکان پوینتر را به ابتدای ۶۴ اُمین بایت تغییر بده.در EVM داده‌ها در سه بخش می‌توانند ذخیره شوند : اِستَک : که نمونه آن PUSH می‌باشد و بالا تر مثال آن را دیدیمحافظه تصادفی (RAM) : که نمونه آن MSTORE در Opcode ها می‌باشدحافظه دیسک : که نمونه Opcode آن SSTORE استدر اِتِریوم برای ذخیره داده در حافظه دیسک باید gas پرداخت شود و به همین دلیل ذخیره داده در دیسک گران‌ترین و ذخیره داده در اِستَک ارزان‌ترین است.زبان Assemblyنوشتن کامل Smart Contract با Opcode ها امکان پذیر است و برای این منظور زبان اسمبلی Solidity ساخته شده است. طبیعتا نوشتن در این حالت بسیار سخت تر است، ولی برای نوشتن Contract هایی که در آن‌ها باید در مصرف gas صرفه جویی شود و یا زمانی که کاری می‌خواهید انجام دهید که اجرای آن در Solidity امکان پذیر نیست، استفاده از زبان اسمبلی Solidity کارا خواهد بود.در انتهابرای شروع به نوشتن Smart Contract ها نیازی به دانستن Opcode ها نداریم. اما دانستن آنها علاوه بر اینکه دانش ما را در این زمینه بالا می‌برد، در هنگام Error Handling (که به خوبی در EVM انجام نمی‌شود) بسیار مفید می باشد. نویسنده : Bernard Peh</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Fri, 26 Oct 2018 23:19:06 +0330</pubDate></item><item><title>شیوه‌هایی بهتر در PHP</title><link>https://virgool.ir/Software/php-best-practices-rddp8o7gr9wr</link><description>شیوه‌هایی بهتر در PHP در هر زبان برنامه‌نویسی‌ای مفهومی به نام Best Practice یا همان روش ایده‌آل وجود دارد، که معمولا این روش از بین چند روشی که برای انجام یک کار میتوان به‌کار بست انتخاب می‌شود.اینکه چرا باید از Best Practice ها استفاده نمود، دلایل مختلفی از جمله مسائل امنیتی، عملکردی (Performance) و ... می‌تواند داشته باشد.۱. ذخیره کلمات عبوراز جمله روش‌هایی که برای ذخیره کلمات عبور استفاده می‌شود، استفاده از الگوریتم‌های MD5 و SHA1 است که این روش‌ها امنیت کافی برای ذخیره کلمات عبور را ندارند.برای ذخیره کلمات عبور از فانکشن password_hash استفاده کنید، چراکه از جدیدترین و بهترین مدتهای هَش سازی استفاده می‌کند.&lt;?php&#xA;    // Hash the password.  $hashedPassword will be a 60-character string.&#xA;    $hashedPassword = password_hash(&#39;mypassword&#39;, PASSWORD_DEFAULT);&#xA; &#xA;    password_verify(&#39;the wrong password&#39;, $hashedPassword); // false&#xA;    password_verify(&#39;mypassword&#39;, $hashedPassword); // true&#xA;?&gt;۲. اتصال به دیتابیسبرای اتصال به دیتابیس از درایور PDO استفاده کنید. این درایور به دیتابیس‌های مختلفی می‌تواند متصل شود و به صورت OOP قابل استفاده است. در PDO می‌توانید از فانکشن‌های Prepend برای جلوگیری از حملات SQL Injection استفاده نمایید.&lt;?php&#xA;try {&#xA;    $link = new \PDO(&#39;mysql:host=your-hostname;dbname=your-db;charset=utf8mb4&#39;,&#xA;    &#39;your-username&#39;,&#xA;    &#39;your-password&#39;,&#xA;        array(&#xA;            \PDO::ATTR_ERRMODE =&gt; \PDO::ERRMODE_EXCEPTION,&#xA;            \PDO::ATTR_PERSISTENT =&gt; false&#xA;        )&#xA;    );&#xA;&#xA;    $handle = $link-&gt;prepare(&#39;select Username from Users where UserId = ? or Username = ? limit ?&#39;);&#xA;    &#xA;    $handle-&gt;bindValue(1, 100, PDO::PARAM_INT);&#xA;    $handle-&gt;bindValue(2, &#39;Bilbo Baggins&#39;);&#xA;    $handle-&gt;bindValue(3, 5, PDO::PARAM_INT);&#xA;    $handle-&gt;execute();&#xA;    &#xA;    $result = $handle-&gt;fetchAll(\PDO::FETCH_OBJ);&#xA;    foreach($result as $row) {&#xA;        print($row-&gt;Username);&#xA;    }&#xA;} catch(\PDOException $ex) {&#xA;    print($ex-&gt;getMessage());&#xA;}&#xA;?&gt;۳. تگ شروع و پایان PHPاز تگ‌های &lt;? ?&gt; و یا &lt;? =?&gt; استفاده نکنید و بجای آن از شکل کامل &lt;? php?&gt; استفاده کنید. با اینکه شکل کوتاه راحت‌تر به نظر می‌رسد ولی بطور پیش‌فرض در تنظیمات PHP غیرفعال است و برای استفاده از آنها پارامتر short_open_tag باید فعال شود. همینطور ممکن است در سروری که از آن استفاده می‌کنید امکان فعال‌سازی این پارامتر وجود نداشته باشد.حالت =?&gt; بدون نیاز به فعال بودن  short_open_tag کار می‌کند و برای استفاده از آن مشکلی وجود ندارد.۴. استفاده از autoloadروش قدیمی autoload کردن کلاس‌هایی که لود نشده‌اند استفاده از فانکشن function __autoload() {}می‌باشد. مشکل در استفاده از این فانکشن آنجاست که اگر کتابخانه‌ای را وارد پروژه کنید که از این فانکشن استفاده می‌‌کند؛ در این صورت با تداخل (conflict) مواجه خواهید شد.روش مناسب و صحیح در autoload کردن، تعیین نامی منحصربه‌فرد برای فانکشنی که کار autoload را در کد شما انجام می‌دهد و سپس ثبت آن درspl_autoload_register()است. &lt;?php&#xA;// First, define your auto-load function.&#xA;function MyAutoload($className) {&#xA;    include_once($className . &#39;.php&#39;);&#xA;}&#xA;&#xA;// Next, register it with PHP.&#xA;spl_autoload_register(&#39;MyAutoload&#39;);&#xA;&#xA;// Try it out!&#xA;// Since we haven&#39;t included a file defining the MyClass object, our auto-loader will kick in and include MyClass.php.&#xA;&#xA;// For this example, assume the MyClass class is defined in the MyClass.php file.&#xA;$var = new MyClass();&#xA;?&gt;۵. سینگِل‌کوتِیشِن در مقابل دابِل‌کوتِیشِندر PHP سینگِل‌کوتِیشِن‌ها ( &#39; ) تجزیه (parse) نمی‌شوند، به این معنی که هر کاراکتری که بین آنها قرار دهید مستقیما چاپ می‌شود. در مقابل دابِل‌کوتِیشِن‌ها ( &#34; ) تجزیه می‌شوند و این امکان را دارند تا متغیر بین آنها قرار گیرد. همینطور کاراکترهایی مانند n\ یا t\ در دابِل‌کوتِیشِن‌ها قابلیت اجرایی دارند.دابِل‌کوتِیشِن‌ها در لحظه‌ی اجرای برنامه ارزیابی (evaluate) می‌شوند، در حالیکه سینگِل‌کوتِیشِن‌ها اینچنین نیستند و به همین دلیل سینگِل‌کوتِیشِن‌ها از نظر عملکرد بهتر هستند. معمولا این تفاوت در عملکرد، در نرم‌افزارهای بسیار بزرگ قابل لمس است و در اکثر پروژه‌ها تفاوتی بین آنها احساس نمی‌شود.۶. استفاده از const در مقابل فانکشن defineتفاوت‌های const و define :۱. ()define در زمان run time تعریف می‌شود درحالیکه const در زمان compile time. به همین دلیل const با کمی performance بهتر همراه است (که این موضوع در نرم افزارهای معمول به چشم نمی‌خورد).۲. از ()define برای تعریف ثوابت در کلاس‌ها نمی‌توان استفاده کرد، همینطور ()define ثابت را در بلاکِ GLOBAL قرار می‌دهد.۳. امکان استفاده از عملگرهای بیتی در ()define وجود دارد.۴. از ()define در بلاک‌هایی مانند if می‌توان استفاده نمود درحالیکه از const نمی‌توان. &lt;?php &#xA; // Let&#39;s see how the two methods treat namespaces&#xA; namespace MiddleEarth\Creatures\Dwarves; &#xA; const GIMLI_ID = 1; &#xA; define(&#39;MiddleEarth\Creatures\Elves\LEGOLAS_ID&#39;, 2); &#xA;&#xA;echo(\MiddleEarth\Creatures\Dwarves\GIMLI_ID);  // 1&#xA;echo(\MiddleEarth\Creatures\Elves\LEGOLAS_ID);  // 2; note that we used define(), but the namespace is still recognized &#xA; &#xA;// Now let&#39;s declare some bit-shifted constants representing ways to enter Mordor. &#xA;define(&#39;TRANSPORT_METHOD_SNEAKING&#39;, 1 &lt;&lt; 0); // OK! &#xA;const TRANSPORT_METHOD_WALKING = 1 &lt;&lt; 1; // Compile error! const can&#39;t use expressions as values &#xA;&#xA;// Next, conditional constants. define(&#39;HOBBITS_FRODO_ID&#39;, 1); &#xA;if($isGoingToMordor) { &#xA;    define(&#39;TRANSPORT_METHOD&#39;, TRANSPORT_METHOD_SNEAKING); // OK!    &#xA;    const PARTY_LEADER_ID = HOBBITS_FRODO_ID // Compile error: const can&#39;t be used in an if block &#xA;} &#xA;    &#xA;// Finally, class constants &#xA;class OneRing { &#xA;    const MELTING_POINT_CELSIUS = 1000000; // OK!&#xA;    define(&#39;MELTING_POINT_ELVISH_DEGREES&#39;, 200); // Compile error: can&#39;t use define() within a class &#xA;} &#xA;?&gt; عملا استفاده از ()define انعطاف بیشتری در برنامه‌نویسی به شما می‌دهد مگر اینکه بخواهید در کلاس‌های خود از ثابت استفاده کنید که در این صورت باید از const استفاده کنید. همینطور const خوانایی بیشتری به کدهای شما می‌دهد.۷. عبارات با قاعده در PHPدر PHP دو روش برای کار با عبارات با قاعده (Regex) وجود دارد. PERL که فانکشن‌های آن با *_preg شروع می‌شوند و POSIX که فانکشن‌های آن با *_ereg شروع می‌شوند.POSIX از نسخه 5.3 PHP منسوخ شد و به همین دلیل باید از خانواده *_preg استفاده نمایید.۸. ارسال ایمیل در PHPفانکشن ()mail از فانکشن‌های موجود در هسته‌ی PHP است که استفاده از آن مشکلات امنیتی دارد. به همین دلیل برای ارسال ایمیل باید از کتابخانه PHPMailer استفاده نمود. این کتابخانه‌ی متن‌باز بسیار محبوب بوده و در استفاده از آن نیازی به نگرانی در مورد مسائل امنیتی نیست.&lt;?php&#xA;// Include the PHPMailer library&#xA;require_once(&#39;phpmailer-5.2.7/PHPMailerAutoload.php&#39;);&#xA;&#xA;// Passing &#39;true&#39; enables exceptions.  This is optional and defaults to false.&#xA;$mailer = new PHPMailer(true);&#xA;&#xA;// Send a mail from Bilbo Baggins to Gandalf the Grey&#xA;// Set up to, from, and the message body.  The body doesn&#39;t have to be HTML; check the PHPMailer documentation for details.&#xA;&#xA;$mailer-&gt;Sender = &#39;bbaggins@example.com&#39;;&#xA;$mailer-&gt;AddReplyTo(&#39;bbaggins@example.com&#39;, &#39;Bilbo Baggins&#39;);&#xA;$mailer-&gt;SetFrom(&#39;bbaggins@example.com&#39;, &#39;Bilbo Baggins&#39;);&#xA;$mailer-&gt;AddAddress(&#39;gandalf@example.com&#39;);&#xA;$mailer-&gt;Subject = &#39;The finest weed in the South Farthing&#39;;&#xA;$mailer-&gt;MsgHTML(&#39;&lt;p&gt;You really must try it, Gandalf!&lt;/p&gt;&lt;p&gt;-Bilbo&lt;/p&gt;&#39;);&#xA;&#xA;// Set up our connection information.&#xA;$mailer-&gt;IsSMTP();&#xA;$mailer-&gt;SMTPAuth = true;&#xA;$mailer-&gt;SMTPSecure = &#39;ssl&#39;;&#xA;$mailer-&gt;Port = 465;&#xA;$mailer-&gt;Host = &#39;my smtp host&#39;;&#xA;$mailer-&gt;Username = &#39;my smtp username&#39;;&#xA;$mailer-&gt;Password = &#39;my smtp password&#39;;&#xA;&#xA;// All done!&#xA;$mailer-&gt;Send();&#xA;?&gt;۹. اعتبارسنجی آدرس ایمیلاغلب برنامه‌نویسان PHP از Regex هایی پیچیده برای بررسی اعتبار فرمت ایمیل‌ها استفاده می‌کنند. اما برای این اعتبارسنجی راه بسیار راحت‌تری وجود دارد.&lt;?php&#xA;filter_var(&#39;sgamgee@example.com&#39;, FILTER_VALIDATE_EMAIL); // Returns &#34;sgamgee@example.com&#34;. This is a valid email address.&#xA;filter_var(&#39;sauron@mordor&#39;, FILTER_VALIDATE_EMAIL); // Returns boolean false! This is *not* a valid email address.&#xA;?&gt;منبع :  phpbestpractices</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Wed, 01 Aug 2018 23:52:25 +0430</pubDate></item><item><title>چند خطی در مورد اِکما اسکریپت</title><link>https://virgool.ir/@mehradsadeghi/ecmascirpt-frxu8dqkgnxe</link><description>سلام به همه دوستاندوست دارم توضیحات ساده و کوتاهی در رابطه با اِکما اسکریپت بدم، چون بارها دیدم و شنیدم که دوستان نمی‌دونن اکما اسکریپت چی هست، به چه معناست و از کجا اومده.درآمدی بر JavaScriptدر سال ۱۹۹۵ میلادی بود که JavaScript معرفی شد تا صفحات وبِ پویاتری برای کاربران ایجاد کند. در اون زمان JavaScript در مرورگری به نام Navigator Browser از شرکت Netscape کار می‌کرد. بزرگترین مزیت JavaScript این بود که کاربرها رو قادر می‌ساخت بدون ریلود شدن صفحه وب برای هر عملی، بتونن با وب‌سایت‌ها تعامل داشته باشن. جاوا و جاوا اسکریپت عملا هیچ ارتباطی باهم ندارند.جاوا اِسکریپت در ابتدا با نام LiveScript معرفی شد و محبوبیت چندانی نداشت. در همین حال (در دهه نود میلادی)‌ زبان جاوا از محبوبیت فراوانی برخوردار بود. به همین دلیل بود که با مقاصد تجاری و رشدِ توجه به این زبان، نام LiveScript به JavaScript تغییر پیدا کرد.در این زمان استاندارد سازی، الگوهای اجرایی، و تمام قواعدی که زبان JavaScript را برای اجرا در مرورگرها آماده می‌ساخت توسط شرکت Netscape تعیین می‌شد؛ و با پیشرفت وب، تعداد مرورگرهای بیشتری از JavaScript پشتیبانی می‌کردند.بالاخره زمانی رسید که پروژه Navigator Browser در شرکت Netscape متوقف شد و هر یک از شرکت‌هایی که در مرورگرهایشان از JavaScript پشتیبانی می‌کردند دچار سردرگمی برای استاندارهایی که باید در JavaScript رعایت کنند شده بودند.اِکما (ECMA) :سازمان اِکما ( European Computer Manufacturers Association ) از زمان متوقف شدن پروژه Navigator Browser عهده‌دار استاندارد سازی JavaScript شد، که این استاندارد EcmaScript نام دارد.از این پس همه نام JavaScript و EcmaScript رو دقیقا یکسان به حساب میارن، درحالی‌که در واقع اِکما سازمان معرفی استاندارهای JavaScript هست و استاندارد ورژن‌های مختلف این زبان رو ارائه می‌کنه.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Fri, 12 Jan 2018 17:25:17 +0330</pubDate></item><item><title>موی‌هایم، بلندپروازی را به من یادآور می‌شوند</title><link>https://virgool.ir/@mehradsadeghi/pursue-your-dreams-cnsu1kja0lcv</link><description>منمدتی‌ست موی‌هایم را کوتاه نمی‌کنم. این موضوع اطرافیانم را به واکنش‌های متفاوتی وا داشته است. بعضی موافق، بعضی مخالف؛ بعضی دوستش دارند و بعضی حتی نمی‌توانند به من نگاه کنند.اما واقعا چه اهمیتی دارد که دیگران چه نظری دارند. مردمِ من عادت به اظهار نظر در هر موردی کرده‌اند، و در این مورد حریم خصوصی و غیر خصوصی نمی‌شناسند.موی‌هایم هر روز بلندتر می‌شوند و دیدنشان به من انگیزه‌ای دوچندان می‌دهد. هر روز با دیدنشان این جمله در ذهنم طنین‌انداز می‌گردد که تو آزاد هستی، آزادی تا هرکجایی که می‌خواهی قد بکشی و پرواز کنی. همچون موی‌هایت، که اینطور آزادانه می‌رویند، بی‌هیچ واهمه‌ای.ترس چیزی‌ست که مردمِ من به خورد مغزم دادند؛ روراست باشیم، به خورد همه‌مان داده‌اند.از کودکی به ما یاد دادند که ریسک نکنیم، بلند پرواز نباشیم، تحصیلات عالیه تنها راه موفقیت است، به هنر اهمیت ندهید، کار کارمندی برای خود دست‌وپا کنید، از خط عابر عبور نکنیم و در صفوف نایستید تا شهروندی زرنگ‌تر به نظر آیید، حقوق یکدیگر را به طرق مختلف زیر پا بگذارید چون شما ارجح‌تر هستید، به عقاید یکدیگر احترام نگذارید چراکه فقط این ما هستیم که بر حق سخن می‌گوییم و ...مرا نجات دهیدبیشتر ابعاد شخصیتی یک کودک، در حدود هشت سال ابتدایی زندگی‌اش شکل می‌گیرد. بد نیست به هشت سال ابتدایی زندگی خود بیاندیشید (گرچه مقداری از آن را بخاطر نخواهید آورد)......واقعا چطور می‌توانیم از کودکی که به او، رعایت حقوق هم‌نوعش را نیاموخته‌ایم، توقع داشته باشیم روزی وی به فرزندش رعایت حقوق دیگران را بیاموزد!واقعا چطور می‌توانیم از کودکی که در قالب فکری‌اش، زندگی‌ای بدون ریسک، ثابت‌ و کارمندانه را نهادینه کرده‌ایم، توقع داشته باشیم کارآفرین آینده کشورش باشد!واقعا مگر می‌شود جامعه‌ای پویا داشته باشیم، تا زمانی‌که اذهان را می‌میرانیم... ؟!روزی فریاد خواهم زداما در بین تمام این هیاهو، روزنه‌های امیدی وجود دارد. راهی برای خلاصی از تمام این ناخوش‌آیندی‌ها.خبر خوش آن است که این شما هستید که باید سرنوشت خود را بسازید.هرکَس صاحب قُله‌ایستقُله زندگی هرکَسی با دیگری متفاوت است، شما وظیفه فتح قُله زندگی خود را دارید.موفق‌ترین انسان‌ها در این کُره خاکی، کسانی‌اند که از کودکی روحیه‌ی تلاشگری داشته‌اند. یاد گرفته‌اند که می‌توانند. به داستان‌سرایی درباره کلیشه‌ها نمی‌پردازم؛ چراکه همه میدانیم استیو جابز، مارک زاکِربِرگ، ایلان ماسک، جِف بِزوس و بسیاری دیگر چطور با خودباوری جهان اطرافشان را تحت تاثیر قرار دادند.موفق بودن به این معنی نیست که پول‌دارترین باشید، موفقیت یعنی آنچه شما را برمی‌انگیزاند را کشف کنید و برایش تمام لحظاتتان را زندگی کنید. زیرا فقط در این صورت است که در پایان، به گذشته خود افتخار می‌کنید و حسرت هیچ لحظه‌ی از دست رفته‌ای را نخواهید خورد.زندگی را دوست داشته باشید، اگر شغل، درس و تحصیل و بطور کلی هر فعالیتی دارید که انجامش، پیش از خواب، انگیزه زودتر بیدار شدن را به شما نمی‌دهد، بدانید که در مسیر اشتباهی قرار دارید.شما آن‌چیزی را بدست می‌آورید که برای آن تلاش می‌کنیدهمه ما مجبوریم در مقاطعی از زندگی، چه برای معیشت و یا به هر دلیل دیگری فعالیتی متناقض با علایقمان انجام دهیم، اما حواسمان باشد که این به عادتی که بلای جان رویاهایمان است بدل نشود.بله، تمام آنچه خواندید با هر بار مشاهده‌ی موی‌هایم به من یادآوری می‌شوند. موی‌هایم به من یادآوری می‌کنند که اگر در زندگی رها نباشم و تحت تاثیر پیرامون (مانند نظرات دیگران) زندگی کنم، به انسانی لبریز از استعداد و هیجان در کالبد یک کارمند ساکن بدل‌ خواهم شد.و در آخر فراموش نکنیم که برای داشتن چیزهایی که تابه‌حال نداشته‎‌ایم، باید کارهایی بکنیم که تابه‌حال نکرده‌ایم.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Fri, 05 Jan 2018 18:11:37 +0330</pubDate></item><item><title>یک لقمه Sass دور هم</title><link>https://virgool.ir/JavaScript8/scss-basics-zmwdxicslzpr</link><description>سلام به همه دوستاندر این مقاله دوست دارم در مورد Sass توضیحاتی بدم تا با قابلیت‌های بسیار خوب و حرفه‌ای‌ش آشنا بشید.اگر در برنامه‌نویسیِ فرانت‌اِند تازه کار هستید و یا حتی اگر حرفه‌ای، احتمالا واژه‌ی Sass به گوشتون خورده.در توسعه‌ی فرانت‌اِند بعد از اینکه مدیریت CSS ها در پروژه‌های بزرگ سخت شده بود ، و کار کدنویسی رو تا حدی طاقت‌فرسا می‌کرد، تکنولوژی‌هایی مثل Sass و LESS بوجود اومدن تا کار استایل‌دهی رو بسیار کاراتر، لذت‎بخش‌تر و ساده‌تر کنند.در این بین شاید کلمه SCSS هم به گوشتون خورده باشه، که به اعتقاد اکثریت هیچ تفاوتی با Sass نداره و یکی هستند. درواقع در ابتدا Sass معرفی شد، اما Syntax (سینتَکس) اون به راحتیه CSS نبود و تا حدی پیچیدگی‌هایی داشت. پسوند فایل (به طور مثال) sample در Sass به این صورت نوشته می‌شه :sample.sassبرای اینکه سینتَکس Sass ساده‌تر و بیشتر به CSS شبیه بشه SCSS معرفی شد؛ که مخفف Sassy CSS یا Standard CSS هست؛ که در اون فایل فرضی sample به این صورت ساخته می‌شه :sample.scssپیش‌پردازشگری، راه چاره‌ی توسعه‌ای بهتر :راه حل Sass پیش‌پردازشگری‌ (Pre Processing) هست. یعنی تمام کدی‌هایی که در Sass می‌نویسیم در نهایت پردازش شده و به CSS تبدیل می‌شوند. چراکه در نهایت مرورگر‌ها برای استایل‌دهی CSS رو می‌فهمند؛ نه Sass.اما بریم سراغ معرفی تعدادی از ویژگی‌های مفید در SCSS (برای ساده‌تر شدن مطلب از بیان کدهای Sass صرف نظر می‌کنم و مستقیما SCSS رو توضیح می‌دم)۱. متغیر، گمشده‌ای در CSS :یکی از مزیت‌های بزرگ SCSS نسبت به CSS امکان تعریف متغیرهاست. متغیرها در SCSS به این صورت قابل تعریف هستند :$primary-color: #333;CSS :body {&#xA;    font: 100% Helvetica, sans-serif;&#xA;    color: #333;&#xA;}SCSS :$font-stack : Helvetica, sans-serif;&#xA;$primary-color: #333;&#xA;body {&#xA;    font: 100% $font-stack;&#xA;    color: $primary-color;&#xA; }در اینجا شاید براتون این سوال پیش بیاد که در کد SCSS تعداد خطوط کد ما بیشتر از تعداد خطوط کدی هست که در CSS داریم، پس چه دلیلی برای استفاده از SCSS وجود داره ؟!درسته هست که در اینجا تعداد خطوط کدهای SCSS بیشتر هستند. اما در یک پروژه واقعی، کار به همینجا ختم نمی‌شه.مثلا تصور کنید یک قالب که دارای سه صفحه هست رو باید کدنویسی کنید، که در این قالب، دو رنگ، یکی Primary Color  و دیگری Secondary Color هستند و در اکثر اِلِمان‌های صفحه از این دو رنگ استفاده شده. حالا به هر دلیلی تصمیم بر تغییر یکی از این دو رنگ می‌گیرید. اگر با CSS خام کار استایل‌دهی را انجام داده باشید باید در تک تکِ اِلِمان‌ها رنگ را تغییر دهید که به احتمال بسیار زیاد اینکار با باگ‌هایی مواجه خواهد شد. اما اگر بر اساس SCSS استایل‌دهی را انجام داده باشید فقط کافیست رنگ متغیرتان را تغییر دهید تا این تغییر در همه‌ی اِلِمان‌ها اِعمال شود. ۲. زیبایی کدها با قابلیت Nesting :  آنچنان این ویژگی زیبا و لذت‌بخش هست که ترجیح میدم قبل از هر توضیحی ابتدا به کدهای زیر توجه کنـیــد :CSS :nav ul {&#xA;    margin: 0;&#xA;    padding: 0;&#xA;    list-style: none;&#xA;}&#xA;nav li {&#xA;    display: inline-block;&#xA;}&#xA;nav a {&#xA;    display: block;&#xA;    padding: 6px 12px;&#xA;    text-decoration: none;&#xA;}SCSS :nav {&#xA;    ul {&#xA;        margin: 0;&#xA;        padding: 0;&#xA;        list-style: none;&#xA;    }&#xA;    li { display: inline-block; }&#xA;    a {&#xA;        display: block;&#xA;        padding: 6px 12px;&#xA;        text-decoration: none;&#xA;    }&#xA;}قابلیت تودرتویی (Nesting) در کم‌تر نوشتن کدها به شما کمک می‌کند. فرزند هر اِلِمان در توی اِلِمان پدر نوشته می‌شود، که خوانایی بالایی برای برنامه‌نویس ایجاد کرده و در یک کلام واقعا زیباست. ۳. کدنویسی هوشمندانه‌تر با استفاده از عملگرها :به کدهای زیر توجه کنید :CSS :.container {&#xA;    width: 100%;&#xA;}&#xA;article[role=&#34;main&#34;] {&#xA;    float: left;&#xA;    width: 62.5%;&#xA;}&#xA;aside[role=&#34;complementary&#34;] {&#xA;    float: right;&#xA;    width: 31.25%;&#xA;}SCSS :.container { width: 100%; }&#xA;article[role=&#34;main&#34;] {&#xA;    float: left;&#xA;    width: 600px / 960px * 100%;&#xA;}&#xA;aside[role=&#34;complementary&#34;] {&#xA;    float: right;&#xA;    width: 300px / 960px * 100%;&#xA;}در SCSS همانند اکثر زبان‌های برنامه‌نویسی می‌توان از عملگرهایی مثل جمع ، تفریق ، ضرب و تقسیم استفاده نمود. ۴. استایل‌دهی حرفه‌ای تر با Mixins :قابلیت جذاب و البته کاربردی دیگر در SCSS که شباهت زیادی به فانکشن‌ها دارند، Mixins ها هستند.CSS :.box {&#xA;    -webkit-border-radius: 10px;&#xA;    -moz-border-radius: 10px;&#xA;    -ms-border-radius: 10px;&#xA;    border-radius: 10px;&#xA;}SCSS :@mixin border-radius($radius) {&#xA;    -webkit-border-radius: $radius;&#xA;    -moz-border-radius: $radius;&#xA;    -ms-border-radius: $radius;&#xA;    border-radius: $radius;&#xA;}&#xA;.box { @include border-radius(10px); }همونطور که می‌بینید یکبار میکسینی رو تعریف می‌کنیم و می‌تونیم بارها از اون استفاده کنیم. این ویژگی SCSS هم در کمتر شدن حجم کدهایی که باید بنویسیم بسیار موثر هست. ۵. فانکشن‌ها :بله. ویژگی جذاب دیگر در SCSS فانکشن‌ها هستند.CSS :.container {&#xA;  width 100px;&#xA;}SCSS :@function add-padding($base-value, $coef) {&#xA;    @return $base-value * $coef&#xA;}&#xA;.container {&#xA;   width: add-padding(10px, 2)&#xA;}    همونطور که می‌بینید بین فانکشها و میکسینز شباهت زیادی وجود داره. تفاوت اصلی اونها در این هست که فانشکن‌ها مقداری رو به عنوان خروجی بازمی‌گردونن ولی میکسینز صرفا کدی رو در محلی که include می‌شه، برای ما جایگزین می‌کنه.فکر می‌کنم تا حد مناسبی آشنایی مقدماتی با SCSS پیدا کرده باشید. قابلیت‌های بسیار دیگر از جمله Extend ها، حلقه‌ها، عبارات شرطی و... در SCSS وجود دارند که خارج از بحث معرفی (که در این مقاله به اون پرداختم) وجود داره و شما با مراجعه به وب‌سایت رسمی scss-lang می‌تونید با اونها آشنا بشید.</description><category>مهراد صادقی</category><author>مهراد صادقی</author><pubDate>Fri, 29 Dec 2017 23:30:59 +0330</pubDate></item></channel></rss>