<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های رضا</title><link>https://virgool.ir/feed/@thedarkorlightthrity</link><description></description><language>fa</language><pubDate>2026-11-11 05:03:32</pubDate><image><url>https://files.virgool.ir/upload/users/4920371/avatar/dHZYTz.png</url><title>رضا</title><link>https://virgool.ir/@thedarkorlightthrity</link></image><item><title>برنامه نویسی راکتیو به زبان ساده</title><link>https://virgool.ir/@thedarkorlightthrity/برنامه-نویسی-راکتیو-به-زبان-ساده-yyo2bmx6r7fr</link><description>برنامه نویسی راکتیو چیست؟برنامهنویسی راکتیو یک پارادایم برنامهنویسی است که بر روی جریانهای داده و انتشار غیرهمزمان تغییرات تمرکز دارد. برخلاف برنامهنویسی امری (دستور دادن مرحلهبهمرحله به کامپیوتر)، برنامهنویسی راکتیو تعریفی است (توصیف کردن اینکه چه تبدیلهایی باید بر روی دادهها اعمال شود). در این مقاله برای فهم مفاهیم برنامه نویسی راکتیو مفهوم Mono و Flux را به شکل ساده در این پروژه گیت هاب پیاده سازی میکنیم. اصول کلیدیبرنامهنویسی راکتیو بر پنج اصل بنیادی استوار است:بدون مسدودیت (Non-blocking): عملیات thread فراخواننده را مسدود نمیکندغیرهمزمان (Asynchronous): نتایج در طول زمان تولید میشوند، نه به طور فوریمبتنی بر Push: دادهها از منبع به سمت مصرفکننده جریان پیدا میکندقابل ترکیب (Composable): عملیاتها میتوانند به هم زنجیر شوندفشار پسین (Backpressure): مصرفکننده نرخ جریان داده را کنترل میکندمفهوم  Mono (مُنو)مُنو بیانگر:0 یا 1 مقدار که بهطور غیرهمزمان تولید خواهد شدتکمیل موفق بدون مقدارخطایی که ممکن است رخ دهدمیتوانید آن را بهعنوان نسخهای پیشرفتهتر از Futureیا Promise تصور کنید.پیاده سازی ساده از مُنو با زبان کاتلین اینجا قابل دسترس است.جریان فراخوانی به این شکل است:1. subscribe(subscriber) فراخوانی میشود2. Publisher، onSubscribe(subscription) را فراخوانی میکند3. Subscriber، subscription.request(n) را فراخوانی میکند4. Publisher مقدار via onNext(value) منتشر میکند5. Publisher با onComplete() یا (exception) پایان میبخشدمفهوم Flux (فلاکس)فلاکس چیست؟فلاکس این مقدارها را نمایندگی میکند:0 تا N مقدار که بهطور غیرهمزمان تولید خواهند شدتکمیل موفق پس از منتشر شدن تمام مقادیرخطایی که ممکن است در طول جریان رخ دهدمیتوانید آن را بهعنوان یک Iteratorراکتیو، Stream، یا event emitter تصور کنید.یک پیاده سازی ساده از فلاکس به زبان کاتلین اینجا قابل دسترس استجریان فراخونی به این شکل است:1. subscribe(subscriber) فراخوانی میشود2. Publisher، onSubscribe(subscription) را فراخوانی میکند3. Subscriber، subscription.request(n) برای درخواست تا n مقدار فراخوانی میکند4. Publisher مقادیر را via onNext(value) منتشر میکند (Backpressure را رعایت میکند)5. Subscriber میتواند در هر زمان مقادیر بیشتری درخواست کند6. Publisher با onComplete() یا (exception) پایان میبخشدمفاهیم کلیدی1. جریانهای سرد (Lazy Evaluation)هم Mono و هم Flux بهطور پیشفرض سرد هستند:تا زمان اشتراک هیچ اتفاقی رخ نمیدهدهر اشتراک یک اجرای تازه را تحریک میکندقبل از اشتراک هیچ مقداری منتشر نمیشود2. فشار پسین (Backpressure)Backpressure توانایی مصرفکننده برای کنترل نرخ انتشار است. مصرفکننده N مقدار را از طریق subscription.request(n) درخواست میکند.3. عملگرها (Operators)عملگرها به شما امکان میدهند جریانها را تبدیل، فیلتر و ترکیب کنید:map() - تبدیل هر مقدارfilter() - انتخاب مقادیر منطبقflatMap() - زنجیر کردن عملیاتهاtake() - محدود کردن تعداد مقادیر4. مدیریت خطاخطاها بخشی از قرارداد راکتیو هستند. خطاها از طریق عملگرها به اشتراککنندگان جریان پیدا میکند.5. اشتراک کلید استتا زمان اشتراک هیچ اتفاقی رخ نمیدهد. اعمال عملگرها هیچ کاری را اجرا نمیکندقرار داد ریکتیوهر جریان راکتیو این قرارداد را دنبال میکند:onSubscribe(subscription) →  (onNext(value) *)       → ((exception) | onComplete())یعنی:onSubscribe ابتدا فراخوانی میشود (ضروری است)onNext 0 یا چند بار فراخوانی میشودسپس یا  یا onComplete فراخوانی میشود (هرگز هردو)هیچ فراخوانی بعد از رویداد پایانی (خطا یا تکمیل) اتفاق نمیافتدبرنامهنویسی راکتیو با درک این مفاهیم شامل Mono و Flux، به شما امکان میدهد کد کارآمدتر و مقیاسپذیرتری بنویسید که بتواند با حجمهای بزرگ داده و درخواستهای متعدد به طریقی بهینه کار کند.مثالبرای ایجاد جریان داده به کمک مُنو میتوان مثالهای اینجا را دنبال کرد.برای ایجاد جریان داده به کمک فلاکس میتوان مثالهای اینجا را دنبال کرد.</description><category>رضا</category><author>رضا</author><pubDate>Sat, 29 Aug 2026 17:38:11 +0330</pubDate></item><item><title>پترن Transactional Outbox</title><link>https://virgool.ir/@thedarkorlightthrity/پترن-transactional-outbox-ppfnmzjcrozv</link><description>مقدمهدر سیستم های توضیع شده وقتی دو برنامه (اپلیشن/سرویس) نیاز به سازگاری (consistency) ۱۰۰٪ی دارند. می بایست بتوانند با قابلیت اطمینان بالا ارسال اطلاعات را انجام دهند. در این مقاله یک مثال ساده را به کار خواهیم گرفت تا این پترن را با هم بهتر درک کنیم. در این مقاله فرض شده دو سرویس داریم یکی مسئول مدیریت کاربران و دیگری مسئول ارسال نوتیفیکشن. وقتی در وضعیت کاربری از حالت Inactive به Active تغییر کند سرویس نوتیفیکشن فرخوانده خواهد شد. کدهای مربوط به این پروژه در گیت هاب قابل دسترس است.مروری بر پترن (الگو) Transactional Outboxپترن Transactional Outbox روشی مطمئن برای انتشار رویدادهای دامنه است؛ روشی که از گم‌شدن رویدادها پس از ثبت موفق تراکنش پایگاه داده جلوگیری می‌کند.در یک روند معمول، ممکن است اطلاعات کاربر با موفقیت ذخیره شود، اما ارسال اعلان یا انتشار رویداد خارجی شکست بخورد. در نتیجه، پایگاه داده و سرویس‌های جانبی از یکدیگر جدا می‌شوند. الگوی Outbox این مشکل را با ذخیره‌کردن رویداد در همان تراکنش تغییر اصلی حل می‌کند. سپس یک پردازشگر پس‌زمینه، رویدادهای ذخیره‌شده را به‌صورت غیرهمزمان پردازش می‌کند.معماری پروژهاین پروژه از معماری پیازی (Onion Architecture) پیروی می‌کند. مدل دامنه در مرکز قرار دارد، سرویس‌های application موارد کاربرد را هماهنگ می‌کنند و زیرساخت‌هایی مانند پایگاه داده، HTTP و jobهای زمان‌بندی‌شده در لایه بیرونی قرار می‌گیرند. وابستگی‌ها همیشه به سمت مرکز هستند؛ بنابراین دامنه اصلی به فریم‌ورک‌ها و جزئیات فنی وابسته نیست.دامنه کاربر (user domain)دامنه کاربر شامل aggregate مربوط به User و رویدادهایی مانند موارد زیر است:UserCreatedEventUserUpdatedEventUserStatusChangedEventهر کاربر دارای فیلدهایی مانند نام کاربری، نام، نام خانوادگی و وضعیت ACTIVE یا INACTIVE است. هنگام ایجاد یا به‌روزرسانی کاربر، aggregate به‌جای اجرای مستقیم side effectها، رویداد دامنه را ثبت می‌کند.دامنه نوتیفیکیشن (notification domain)دامنه نوتیفیکیشن مسئول واکنش به تغییرات مهم کسب‌وکار است. در این مثال، هنگامی که وضعیت کاربر از INACTIVE به ACTIVE تغییر کند، یک اعلان فعال‌سازی ثبت می‌شود.این دامنه از دامنه کاربر جداست و در جریان تراکنش به‌روزرسانی کاربر مستقیماً فراخوانی نمی‌شود؛ بلکه بعداً توسط پردازشگر Outbox اجرا می‌شود.روند پردازشایجاد یا به‌روزرسانی کاربر در یک تراکنش انجام می‌شود.aggregate رویداد دامنه تولید می‌کند.سرویس application، اطلاعات کاربر و رویداد Outbox را در همان تراکنش ذخیره می‌کند.هر دو مورد با هم commit می‌شوند.UserOutboxEventPublisher رویدادهای پردازش‌نشده را پیدا می‌کند.اگر رویداد از نوع UserStatusChangedEvent باشد، تغییر وضعیت بررسی می‌شود.در صورت تغییر از INACTIVE به ACTIVE، سرویس اعلان فراخوانی شده و اعلان در repository ثبت می‌شود.به این ترتیب، تراکنش اصلی اتمیک باقی می‌ماند و side effectها در مرحله‌ای قابل‌اعتماد و غیرهمزمان اجرا می‌شوند.</description><category>رضا</category><author>رضا</author><pubDate>Sat, 22 Aug 2026 16:02:53 +0330</pubDate></item><item><title>بهبود سرعت نرم افزار: کوئری سریعتر با فیلتر به موقع</title><link>https://virgool.ir/@thedarkorlightthrity/بهبود-سرعت-نرم-افزار-کوئری-سریعتر-با-فیلتر-به-موقع-qith93o3g6ss</link><description>مقدمهوقتی درباره‌ی «بهبود سرعت نرم‌افزار» حرف می‌زنیم، خیلی وقت‌ها همه چیز را به سرعت CPU، بهبود UI یا بهینه‌سازی کدهای بیرونی محدود می‌کنیم. اما در دنیای واقعی—خصوصاً وقتی پای پایگاه داده وسط است—بزرگ‌ترین تفاوت‌ها اغلب از تصمیم‌های کوچک در سطح کوئری و شیوه‌ی پردازش داده‌ها می‌آید. اگر به این بخش توجه نکنید، نرم‌افزار می‌تواند از نظر ظاهری درست کار کند، ولی در پس‌زمینه زمان زیادی صرف انجام کارهای بی‌فایده مانند درخواست‌ها کند شوند. در این مقاله یک روش برای بهبود سرعت کوئری را باهم بررسی خواهیم کرد و نتایج بهبود را بررسی خواهیم کرد. تمامی مراحل به صورت کد رد گیت هاب قابل دسترس خواهد بود.آزمایشفرض کنید سیستم شما دو موجودیت اصلی دارد:User: شامل شناسه، نام، وضعیت (status) و یک نقش (role)Role: شامل شناسه، نام و وضعیت (status)و حالا یک واقعیت رایج در بسیاری از سیستم‌های سازمانی وجود دارد:اکثر کاربران inactive هستند و فقط تعداد کمی active محسوب می‌شوند.در تست ما این داده‌ها (تقریباً) چنین توزیعی دارند:100,000 کاربر500 نقش2,000 کاربر فعالباقی کاربران غیرفعالاین یعنی اگر کوئری شما قبل از فیلتر کردن وضعیت، مجبور شود تعداد زیادی سطر را در JOIN درگیر کند، احتمالاً بخش بزرگی از کار واقعاً «لازم نیست». شما دارید روی داده‌هایی حساب می‌کنید که قرار است در نهایت کنار گذاشته شوند—ولی تا قبل از اعمال فیلتر، همان‌ها نقش بازی می‌کنند.روش اول: اول join بزنیمدر روش اول (که برای خیلی‌ها شهودی و طبیعی است):ابتدا User را JOIN کنید با Roleبعد در شرط WHERE، کاربران فعال را فیلتر کنیدایده‌ی ذهنی این است که:«اول ساختار داده را با JOIN کامل می‌کنم، بعد نتیجه را پالایش می‌کنم.»اما مشکل اینجاست که در بسیاری از موتورهای پایگاه داده، اگر فیلتر شما انتخاب‌پذیری بالایی ندارد (یا اگر سیستم مجبور شود قبل از اعمال شرط status حجم زیادی را بسازد/تطبیق دهد)، ممکن است این کوئری مجبور شود مقدار زیادی از ترکیب‌های JOIN را بررسی کند و تازه بعد بفهمد که بیشترشان در نهایت به خاطر inactive بودن حذف می‌شوند.به زبان خودمانی: شبیه این است که شما قبل از اینکه بفهمید «کدام مشتری واقعاً فعال است»، لیست کامل مشتری‌ها را با اطلاعات مربوط به نقش‌شان ترکیب کنید، بعد تازه غربال کنید و بگویید: «نه… خیلی‌ها اصلاً نباید اینجا باشند.»روش دوم: اول فیلتر کنیمروش دوم دقیقاً برعکس است:ابتدا کاربران فعال را فیلتر کنیدبعد باقی‌مانده‌ها را با شرایط مربوط به نقش (role condition) محدود کنیداین کار باعث می‌شود حجم کاری خیلی زود کم شود. وقتی اکثر کاربران غیرفعال‌اند، فیلتر کردن status در ابتدای مسیر یعنی:تعداد سطرهای ورودی به JOIN کوچک می‌شودتعداد ردیف‌های میانی کاهش پیدا می‌کندموتور پایگاه داده مجبور نیست میلیون‌ها (یا ده‌ها هزار) تطبیق JOIN بی‌فایده انجام دهدنتیجه‌ی عملی معمولاً این است: کوئری دوم سریع‌تر می‌شود چون «working set» یعنی مجموعه‌ی داده‌هایی که واقعاً وارد محاسبه می‌شوند زود محدود می‌شود.چقدر سریعتر؟اگر نگاهی به میزان بهبودی که ایجاد شده بیندازیم خواهیم دید در لپتاپ من بهبودی چند برابری حاصل شد! که این عدد ممکن است در لپتاپ شما متفاوت باشد:joinThenFilterElapsedMs=659.063375msfilterThenJoinElapsedMs=101.911583mstimeDifferenceMs=557.151792msشما میتوانید این ازمایش را با اجرا کردن این تست تکرار کنید.چرا این اتفاق می افتد؟حتی اگر شما به عنوان توسعه‌دهنده فکر کنید «ترتیب منطقی» کوئری مهم است، موتور دیتابیس معمولاً برای اجرای واقعی، مسیر خودش را انتخاب می‌کند:این مسیر شامل مراحل میانی مثل ساختن نتایج JOIN، اعمال فیلترها، و انتخاب روش‌های بهینه (مانند استفاده از ایندکس‌ها یا ترتیب عملگرها) است.در سناریوی ما، چون داده‌ها بسیار نامتقارن (skewed) هستند (یعنی اکثریت inactive)، اعمال فیلتر status از نظر هزینه‌ای خیلی «پربازده» است. بنابراین اجرای صحیح‌تر معمولاً می‌تواند زودتر جلوی انباشت کار بی‌فایده را بگیرد.تجربه‌ی این Benchmark دقیقاً یک پیام کلاسیک را با عدد و حس واقعی تبدیل می‌کند:اگر یک predicate مثل status = ACTIVE انتخاب‌پذیر و بسیار محدودکننده باشد، فیلتر کردن زودتر معمولاً بهتر است.اگر JOIN شما می‌تواند روی جدول بزرگی انجام شود که قبل از JOIN شدن باید محدود شود، معمولاً JOIN کردن قبل از فیلتر هزینه‌ی زیادی اضافه می‌کند.به‌خصوص وقتی داده‌ها بزرگ‌اند، skew شدیدتر است، و تعداد غیرفعال‌ها غالب است، این تفاوت بیشتر خودش را نشان می‌دهد.نتیجه گیریدر این مقاله دیدیم که عملکرد نرم‌افزار فقط بهینه‌سازی‌های ظاهری نیست؛ بلکه در لایه‌ی پایگاه داده هم می‌تواند با تصمیم‌های ریز، تفاوت‌های بزرگی بسازد. سپس با یک سناریوی Big Table Join، نشان دادیم که وقتی بیشتر سطرها inactive هستند، اجرای فیلتر قبل از JOIN (filter-first) معمولاً «مجموعه‌ی کاری» را کوچک‌تر می‌کند و زمان اجرای کوئری را کاهش می‌دهد، در حالی که JOIN-first ممکن است قبل از حذف شدن، تعداد زیادی ترکیب بی‌فایده بسازد.</description><category>رضا</category><author>رضا</author><pubDate>Sun, 16 Aug 2026 16:48:07 +0330</pubDate></item></channel></rss>