<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های Farhad Adeli</title><link>https://virgool.ir/feed/@adeli.farhad</link><description>مهندس نرم‌افزار ارشد با ۱۶+ سال تجربه در فرانت‌اند، بک‌اند و سیستم‌های بلادرنگ؛ علاقه‌مند به معماری نرم‌افزار، WebRTC و یادگیری سریع با کمک AI.</description><language>fa</language><pubDate>2026-11-10 18:22:14</pubDate><image><url>https://files.virgool.ir/upload/users/1312608/avatar/MuQjYp.jpg</url><title>Farhad Adeli</title><link>https://virgool.ir/@adeli.farhad</link></image><item><title>چطور دو اپلیکیشن بسیار مشابه را یکی کنیم، بدون اینکه یک کابوس نگهداری بسازیم؟</title><link>https://virgool.ir/@adeli.farhad/چطور-دو-اپلیکیشن-بسیار-مشابه-را-یکی-کنیم-بدون-اینکه-یک-کابوس-نگهداری-بسازیم-qbqmmppzqzzy</link><description>بسیاری از تیم‌ها دیر یا زود به این وضعیت می‌رسند:یک پروژه داریم که به‌خوبی کار می‌کند. برای راه‌اندازی محصول دوم، همان پروژه را کپی می‌کنیم، چند بخش را تغییر می‌دهیم و آن را به‌عنوان یک محصول مستقل منتشر می‌کنیم.در ابتدا این تصمیم سریع و منطقی به نظر می‌رسد.اما با گذشت زمان، مشکلات شروع می‌شوند:هر قابلیت جدید باید دو بار پیاده‌سازی شود.هر باگ باید در دو پروژه برطرف شود.تغییرات دیتابیس باید در هر دو پروژه هماهنگ شوند.کدهای دو پروژه به‌تدریج از هم فاصله می‌گیرند.هزینه تست، توسعه و نگهداری بیشتر می‌شود.ما نیز با شرایط مشابهی روبه‌رو بودیم:دو محصول بسیار مشابهفرانت‌اند با Next.js، React و TypeScriptبک‌اند با NestJS و TypeScriptقابلیت‌های مشترک مانند احراز هویت، محصولات، کاربران و بازارهاتفاوت‌هایی در برخی فرایندهای کسب‌وکارپیاده‌سازی دوباره هر قابلیت جدید در هر دو پروژههدف مشخص بود:تفاوت‌های واقعی محصولات را حفظ کنیم، کدهای تکراری را حذف کنیم و توسعه قابلیت‌های بعدی را ساده‌تر کنیم.راه‌حل وسوسه‌انگیز اما خطرناکاولین ایده معمولاً این است:دو پروژه را مستقیماً با هم ادغام کنیم و به یک اپلیکیشن واحد برسیم.این راه در ظاهر ساده است، اما ریسک زیادی دارد.دو پروژه‌ای که از یک کد پایه شروع شده‌اند، ممکن است به‌مرور زمان تغییرات متفاوتی پیدا کرده باشند:باگ‌فیکس‌های متفاوتتغییرات متفاوت دیتابیسقوانین اعتبارسنجی متفاوترفتارهای متفاوت در رابط کاربرینیازهای متفاوت در استقرارIntegrationهای اختصاصیقوانین کسب‌وکاری که در ظاهر مشخص نیستنداگر یکی از پروژه‌ها را مبنا قرار دهیم و پروژه دیگر را روی آن ادغام کنیم، احتمال زیادی وجود دارد که بخشی از رفتارهای معتبر یا قابلیت‌های مهم از بین بروند.بنابراین بهتر است کار را با یکی‌کردن اپلیکیشن‌ها شروع نکنیم.بهتر است ابتدا زیرساخت و بخش‌های مشترک را یکی کنیم.معماری پیشنهادیاولین قدم مناسب، انتقال هر دو پروژه به یک Monorepo است، بدون اینکه استقلال آن‌ها در Build و Deploy از بین برود.ساختار پیشنهادی می‌تواند شبیه زیر باشد:platform/&#xA;├── apps/&#xA;│   ├── project-a-web/&#xA;│   ├── project-b-web/&#xA;│   ├── project-a-api/&#xA;│   └── project-b-api/&#xA;│&#xA;└── packages/&#xA;    ├── domain/&#xA;    ├── frontend/&#xA;    ├── backend/&#xA;    ├── contracts/&#xA;    └── config/پوشه apps شامل اپلیکیشن‌های اجرایی و قابل استقرار است.پوشه packages شامل کدهایی است که میان پروژه‌ها به اشتراک گذاشته می‌شوند.با این ساختار، می‌توانیم بیشتر کد را مشترک کنیم، بدون اینکه هر دو محصول را مجبور کنیم در یک Runtime یا یک Pipeline استقرار قرار بگیرند.برای پیاده‌سازی این معماری می‌توان از ابزارهای زیر استفاده کرد:pnpm workspaceNxTypeScript Project Referencesتنظیمات مشترک Lint، Test و BuildNx در این معماری انتخاب مناسبی است، چون وابستگی میان پروژه‌ها را درک می‌کند و می‌تواند فقط بخش‌هایی را Build یا Test کند که تحت تأثیر یک تغییر قرار گرفته‌اند.چه چیزهایی باید مشترک شوند؟همه چیز نباید Shared باشد.قبل از استخراج کدها، بهتر است هر بخش را در یکی از چهار گروه زیر قرار دهیم.۱. کدهای کاملاً یکساننمونه‌ها:ابزارهای احراز هویتDTOهای مشترکTypeهای APISchemaهای اعتبارسنجیکامپوننت‌های پایه UILoggingزیرساخت‌های مشترکاین بخش‌ها معمولاً بدون تغییر خاصی قابل انتقال به Packageهای مشترک هستند.۲. کدهای مشترک با تفاوت در تنظیماتگاهی رفتار اصلی یکسان است، اما تنظیمات محصولات فرق می‌کند.برای مثال:عنوان یک صفحه متفاوت است.صدور فاکتور در یکی از محصولات فعال است.یک ماژول فقط در یکی از پروژه‌ها نمایش داده می‌شود.منوی هر محصول متفاوت است.برندینگ یا متن‌ها با هم فرق دارند.این نوع تفاوت‌ها بهتر است با Configuration مدیریت شوند.export interface ProductConfig {&#xA;  name: string;&#xA;  features: {&#xA;    invoices: boolean;&#xA;    preOrders: boolean;&#xA;    payments: boolean;&#xA;  };&#xA;}هر پروژه می‌تواند تنظیمات خودش را ارائه دهد:export const projectAConfig: ProductConfig = {&#xA;  name: &#39;Project A&#39;,&#xA;  features: {&#xA;    invoices: true,&#xA;    preOrders: false,&#xA;    payments: true,&#xA;  },&#xA;};export const projectBConfig: ProductConfig = {&#xA;  name: &#39;Project B&#39;,&#xA;  features: {&#xA;    invoices: false,&#xA;    preOrders: true,&#xA;    payments: false,&#xA;  },&#xA;};۳. مفاهیم مشترک با رفتار متفاوتگاهی مفهوم در هر دو محصول وجود دارد، اما رفتار آن متفاوت است.برای مثال، هر دو محصول ممکن است فرایندی به نام سفارش داشته باشند، اما:در محصول اول سفارش واقعی ثبت می‌شود.موجودی رزرو می‌شود.فاکتور تولید می‌شود.در محصول دوم فقط پیش‌سفارش ثبت می‌شود.موجودی رزرو نمی‌شود.فاکتور صادر نمی‌شود.در اینجا Configuration به‌تنهایی کافی نیست.برای چنین تفاوت‌هایی بهتر است از Strategy یا Policy استفاده کنیم.export interface OrderWorkflow {&#xA;  submit(order: Order): Promise&lt;Order&gt;;&#xA;  approve(order: Order): Promise&lt;Order&gt;;&#xA;  cancel(order: Order): Promise&lt;Order&gt;;&#xA;}هر محصول Implementation مخصوص خودش را دارد:export class ProjectAOrderWorkflow implements OrderWorkflow {&#xA;  async submit(order: Order) {&#xA;    // Reserve inventory and create invoice&#xA;    return order;&#xA;  }&#xA;}export class ProjectBOrderWorkflow implements OrderWorkflow {&#xA;  async submit(order: Order) {&#xA;    // Save as preorder without reserving inventory&#xA;    return order;&#xA;  }&#xA;}این ساختار بسیار بهتر از پراکنده‌کردن شرط‌های متعدد در کل کد است:if (projectType === &#39;a&#39;) {&#xA;  // ...&#xA;} else {&#xA;  // ...&#xA;}استفاده محدود از شرط مشکلی ندارد، اما وقتی منطق کسب‌وکار در ده‌ها نقطه به نوع پروژه وابسته باشد، معماری به‌مرور غیرقابل نگهداری می‌شود.۴. کدهای کاملاً اختصاصیبرخی قابلیت‌ها واقعاً فقط متعلق به یکی از محصولات هستند.این بخش‌ها باید داخل همان App باقی بمانند.هدف این نیست که تمام کدها را به زور Shared کنیم.هدف این است که فقط بخش‌هایی را مشترک کنیم که واقعاً رفتار و مسئولیت یکسانی دارند.معماری مشترک در فرانت‌اندیکی از اشتباهات رایج این است که فقط کامپوننت‌های ساده مانند Button، Input و Modal را مشترک کنیم.این کار مفید است، اما مشکل اصلی را حل نمی‌کند.اگر هر Feature همچنان در هر دو پروژه جداگانه نوشته شود، بخش عمده Duplication باقی می‌ماند.بهتر است Featureهای کامل را Shared کنیم.برای مثال:packages/frontend/products/&#xA;├── components/&#xA;├── hooks/&#xA;├── api/&#xA;├── schemas/&#xA;├── state/&#xA;└── index.tsاین Package می‌تواند شامل موارد زیر باشد:ProductListProductFormProductFiltersuseProductsAPIهای محصولSchemaهای اعتبارسنجیPermissionهای مربوط به محصولسپس هر دو اپلیکیشن Next.js از همین Feature استفاده می‌کنند:export default function ProductsPage() {&#xA;  return &lt;ProductList config={productConfig} /&gt;;&#xA;}در این معماری، هر App بیشتر مسئول موارد زیر خواهد بود:RoutingBrandingConfigurationصفحه‌های اختصاصیترکیب Featureهای مشترکمعماری مشترک در بک‌انددر بک‌اند نیز می‌توان ماژول‌های مشترک NestJS را در Packageهای مستقل قرار داد.packages/backend/&#xA;├── auth/&#xA;├── users/&#xA;├── products/&#xA;├── markets/&#xA;├── database/&#xA;└── observability/سپس هر پروژه فقط ماژول‌های موردنیاز خود را Compose می‌کند.@Module({&#xA;  imports: [&#xA;    AuthModule,&#xA;    ProductsModule,&#xA;    MarketsModule,&#xA;    OrdersModule.register({&#xA;      workflow: ProjectAOrderWorkflow,&#xA;    }),&#xA;  ],&#xA;})&#xA;export class ProjectAAppModule {}پروژه دوم نیز Workflow مخصوص خودش را ارائه می‌دهد:@Module({&#xA;  imports: [&#xA;    AuthModule,&#xA;    ProductsModule,&#xA;    MarketsModule,&#xA;    OrdersModule.register({&#xA;      workflow: ProjectBOrderWorkflow,&#xA;    }),&#xA;  ],&#xA;})&#xA;export class ProjectBAppModule {}در نتیجه، پیاده‌سازی مشترک Backend فقط یک‌بار نوشته می‌شود، اما تفاوت‌های رفتاری هر محصول همچنان مستقل باقی می‌مانند.قراردادهای مشترک بین فرانت‌اند و بک‌اندیکی دیگر از منابع Duplication، تعریف جداگانه Typeها در فرانت‌اند و بک‌اند است.برای مثال، فرانت‌اند یک نسخه از CreateOrderRequest دارد و بک‌اند نسخه دیگری از آن را تعریف می‌کند.با گذشت زمان، این دو Type ممکن است با هم ناسازگار شوند.برای جلوگیری از این مشکل، می‌توان یک Package مشترک برای Contractها ایجاد کرد:packages/contracts/&#xA;├── orders/&#xA;├── products/&#xA;├── users/&#xA;└── common/این Package می‌تواند شامل موارد زیر باشد:DTOهاSchemaهای APIEnumهاResponse TypeهاEvent Payloadهامثال:import { z } from &#39;zod&#39;;&#xA;&#xA;export const CreateOrderSchema = z.object({&#xA;  customerId: z.string(),&#xA;  items: z.array(&#xA;    z.object({&#xA;      productId: z.string(),&#xA;      quantity: z.number().positive(),&#xA;    }),&#xA;  ),&#xA;});&#xA;&#xA;export type CreateOrderRequest = z.infer&lt;&#xA;  typeof CreateOrderSchema&#xA;&gt;;اکنون فرانت‌اند و بک‌اند هر دو از یک Contract استفاده می‌کنند.برنامه مهاجرت مرحله‌به‌مرحلهاین مهاجرت باید تدریجی انجام شود.تلاش برای انتقال همه چیز در یک مرحله، ریسک پروژه را بسیار زیاد می‌کند.مرحله ۱: ایجاد Monorepoیک Workspace جدید با pnpm و Nx ایجاد کنید.مرحله ۲: انتقال پروژه‌های فعلی بدون تغییر رفتارapps/&#xA;├── project-a-web/&#xA;├── project-b-web/&#xA;├── project-a-api/&#xA;└── project-b-api/در این مرحله فقط محل پروژه‌ها تغییر می‌کند.هر چهار App باید همچنان مستقل Build، Test و Deploy شوند.مرحله ۳: تحلیل و دسته‌بندی کدهاهر Feature را در یکی از این گروه‌ها قرار دهید:کاملاً مشترکمشترک با تفاوت تنظیماتمشترک با تفاوت رفتاریکاملاً اختصاصیمرحله ۴: استخراج تنظیمات مشترکتنظیمات TypeScript، ESLint، Prettier و Build را به مسیر زیر منتقل کنید:packages/config/مرحله ۵: استخراج Contractهای مشترکDTOها، Schemaها، Enumها و API Typeها را به مسیر زیر منتقل کنید:packages/contracts/مرحله ۶: استخراج کامپوننت‌های پایه UIابتدا از بخش‌های کم‌ریسک شروع کنید:ButtonInputTableDialogFormLayoutمرحله ۷: استخراج Featureهای کامل فرانت‌اندFeatureهایی مانند موارد زیر را یکی‌یکی منتقل کنید:AuthenticationProductsUsersMarketsPermissionsمرحله ۸: استخراج منطق مشترک کسب‌وکارقوانین، محاسبات و تغییر وضعیت‌های مشترک را به مسیر زیر منتقل کنید:packages/domain/مرحله ۹: استخراج ماژول‌های مشترک NestJSاز ماژول‌هایی شروع کنید که در هر دو پروژه تقریباً یکسان هستند.مرحله ۱۰: اضافه‌کردن Product Configتفاوت‌های کوچک را با Configuration و Feature Flag مدیریت کنید.مرحله ۱۱: اضافه‌کردن Strategy و Policyبرای تفاوت‌های مهم رفتاری، Implementationهای مجزا ایجاد کنید.مرحله ۱۲: حذف کدهای تکراریپس از استخراج هر Package مشترک، کد قدیمی را از هر دو پروژه حذف کنید.مرحله ۱۳: اضافه‌کردن تست‌های مشترکبرای Packageهای Shared، Unit Test و Integration Test بنویسید.مرحله ۱۴: حفظ تست‌های End-to-End مستقلهر محصول باید همچنان تست‌های E2E مخصوص فرایندهای حیاتی خودش را داشته باشد.مرحله ۱۵: به‌روزرسانی CI/CDفقط پروژه‌هایی را Build و Test کنید که تحت تأثیر تغییرات هستند:nx affected -t lint test buildمرحله ۱۶: مهاجرت Feature به Featureکل سیستم را در یک Release مهاجرت ندهید.هر بار یک Feature را منتقل، تست و پایدار کنید.مرحله ۱۷: بازبینی معماری نهاییپس از حذف بخش عمده Duplication، بررسی کنید که آیا هنوز به موارد زیر نیاز دارید:دو فرانت‌اند جدادو بک‌اند جدادو Pipeline استقراردو دیتابیس مستقلدر این مرحله، تصمیم‌گیری بسیار ساده‌تر خواهد بود، چون تفاوت‌های واقعی دو محصول به‌صورت شفاف مشخص شده‌اند.چه کارهایی نباید انجام دهیم؟ساختن یک پوشه بزرگ Sharedیک پوشه عمومی مانند shared معمولاً به‌مرور تبدیل به محل انباشته‌شدن انواع کدهای نامرتبط می‌شود.بهتر است Packageها مرز و مسئولیت مشخص داشته باشند:packages/frontend/products&#xA;packages/backend/auth&#xA;packages/domain/ordersاستفاده بیش از حد از شرط‌های مربوط به محصولوجود چنین شرط‌هایی در نقاط متعدد یک علامت هشدار است:if (product === &#39;a&#39;) {&#xA;  // ...&#xA;}اگر این شرط‌ها در همه‌جای پروژه دیده شوند، یعنی تفاوت محصولات به‌درستی در معماری ایزوله نشده است.رفتن زودهنگام به سمت Microserviceوجود دو محصول مشابه به‌معنای نیاز به Microservice نیست.در بسیاری از موارد، Modular Monolith انتخاب مناسب‌تری است.Microservice پیچیدگی‌های زیادی ایجاد می‌کند:پیچیدگی DeploymentDistributed Transactionخطاهای شبکهپیچیدگی Observabilityمشکلات Data Consistencyفقط زمانی یک ماژول را به سرویس مستقل تبدیل کنید که دلیل عملیاتی یا مقیاس‌پذیری مشخصی داشته باشید.Shared کردن همه چیزهر فایل مشابهی الزاماً نباید به Package مشترک منتقل شود.یک بخش زمانی باید Shared شود که:رفتار آن واقعاً یکسان باشد.مسئولیت آن مشخص باشد.مرز پایداری داشته باشد.اشتراک‌گذاری باعث کاهش هزینه نگهداری شود.Coupling غیرضروری ایجاد نکند.آیا در نهایت باید دو پروژه به یک اپلیکیشن تبدیل شوند؟شاید.داشتن یک Frontend و یک Backend زمانی منطقی است که:هر دو محصول هم‌زمان منتشر شوند.زیرساخت آن‌ها یکسان باشد.بیشتر Workflowها مشابه باشند.تفاوت‌ها با Configuration قابل مدیریت باشند.مالکیت هر دو محصول با یک تیم باشد.اما حفظ Appهای جدا زمانی بهتر است که:Deployها باید مستقل باشند.نیازهای امنیتی متفاوت باشند.مشتریان به محیط‌های جدا نیاز داشته باشند.مسیر توسعه محصولات در حال جداشدن باشد.خرابی یک محصول نباید محصول دیگر را تحت تأثیر قرار دهد.Monorepo به‌معنای یک Deployment نیست.می‌توان بخش عمده کد را مشترک کرد و همچنان Docker Image و Pipeline جداگانه داشت.جمع‌بندیاز همان ابتدا تلاش نکنید دو محصول را به یک اپلیکیشن بزرگ تبدیل کنید.ابتدا یک Monorepo بسازید که شامل این بخش‌ها باشد:دو اپلیکیشن فرانت‌انددو اپلیکیشن بک‌اندFeatureهای مشترک Frontendماژول‌های مشترک BackendContractهای مشترکمنطق مشترک Domainتنظیمات مشترکتفاوت‌های کوچک را با Configuration مدیریت کنید.تفاوت‌های رفتاری را با Strategy و Policy جدا کنید.مهاجرت را Feature به Feature انجام دهید.پس از پایدارشدن Shared Core، بررسی کنید که آیا ادغام نهایی Runtime و Deployment واقعاً ارزش انجام‌دادن دارد یا نه.هدف اصلی فقط داشتن یک Repository نیست.هدف اصلی این است که هر قابلیت جدید فقط یک‌بار پیاده‌سازی شود.</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Mon, 13 Jul 2026 07:40:04 +0330</pubDate></item><item><title>کامپوننتهای قابل استفاده مجدد را خیلی زود نسازید</title><link>https://virgool.ir/@adeli.farhad/کامپوننتهای-قابل-استفاده-مجدد-را-خیلی-زود-نسازید-kszo7qpz6hh1</link><description>کامپوننتهای قابل استفاده مجدد یکی از جذابترین ایدهها در توسعه فرانتاند هستند.در نگاه اول، کاملاً منطقی به نظر میرسند.چرا یک UI را دو بار بنویسیم؟چرا یک کامپوننت انعطافپذیر نسازیم و همهجا از آن استفاده نکنیم؟چرا از همان ابتدا یک design system تمیز نسازیم؟ایده خوبی است.اما یک دام مهم دارد.خیلی از تیمهای فرانتاند تلاش میکنند کامپوننتها را بیش از حد زود reusable کنند.وقتی یک کامپوننت قبل از اینکه نیازمندیها واضح شوند reusable میشود، معمولاً از همان duplication سادهای که قرار بود حذف کند پیچیدهتر میشود.Reusability هزینه داردیک کامپوننت reusable بهصورت خودکار بهتر از یک کامپوننت تکراری نیست.کد reusable مسئولیت دارد.باید چند use case مختلف را پشتیبانی کند.باید propهای انعطافپذیر داشته باشد.باید naming واضح داشته باشد.باید مرزهای مشخص داشته باشد.باید مستند شود.باید طوری تغییر کند که همه جاهایی که به آن وابستهاند خراب نشوند.یعنی reusable بودن هزینه دارد.وقتی یک کامپوننت reusable میسازیم، فقط یک کامپوننت نمینویسیم.در واقع داریم یک API میسازیم.مثلاً:&lt;Button&#xA;  variant=&#34;primary&#34;&#xA;  size=&#34;lg&#34;&#xA;  loading&#xA;  disabled&#xA;  iconLeft={&lt;PlusIcon /&gt;}&#xA;  fullWidth&#xA;  ={handleClick}&#xA;&gt;&#xA;  Create&#xA;&lt;/Button&gt;این ممکن است برای یک button واقعی در design system کاملاً مناسب باشد.اما برای یک دکمه مخصوص یک feature، این همه انعطافپذیری ممکن است غیرضروری باشد.گاهی سادهترین نسخه بهتر است:&lt;button ={handleCreate}&gt;&#xA;  Create order&#xA;&lt;/button&gt;مشکل، کامپوننت reusable نیست.مشکل این است که قبل از اینکه الگوی واقعی را بفهمیم abstraction میسازیم.Duplication همیشه دشمن نیستتوسعهدهندهها معمولاً یاد گرفتهاند از duplication دوری کنند.اصل DRY یا “Don’t Repeat Yourself” مفید است.اما گاهی آن را خیلی زود اعمال میکنیم.دو تکه کد ممکن است امروز شبیه هم باشند، اما فردا در مسیرهای متفاوتی تغییر کنند.مثلاً دو کارت را تصور کنید:ProductCard&#xA;CustomerCardدر ابتدا هر دو ممکن است این موارد را داشته باشند:title&#xA;subtitle&#xA;status&#xA;actionsپس یک InfoCard عمومی میسازیم.اما یک هفته بعد:ProductCard به هشدار موجودی نیاز دارد.&#xA;CustomerCard به risk label نیاز دارد.&#xA;ProductCard به قوانین قیمتگذاری نیاز دارد.&#xA;CustomerCard به منطق وضعیت حساب نیاز دارد.حالا کامپوننت generic شروع میکند به بزرگ شدن:&lt;InfoCard&#xA;  title={title}&#xA;  subtitle={subtitle}&#xA;  status={status}&#xA;  showInventoryWarning={true}&#xA;  showRiskLabel={false}&#xA;  pricingMode=&#34;discounted&#34;&#xA;  accountStatus={undefined}&#xA;  actions={actions}&#xA;/&gt;در این نقطه، کامپوننت reusable است، اما تمیز نیست.زیادی میداند.موارد نامرتبط زیادی را پشتیبانی میکند.و هر نیازمندی جدید آن را بدتر میکند.یک مقدار duplication ارزانتر بود.شکل مشابه به معنی abstraction مشترک نیستیکی از مهمترین درسها در معماری فرانتاند این است:چیزهایی که شبیه هم هستند، همیشه یکی نیستند.دو کامپوننت میتوانند UI مشابهی داشته باشند، اما معنی بیزینسی متفاوتی داشته باشند.این تفاوت مهم است.مثلاً:OrderStatusBadge&#xA;PaymentStatusBadge&#xA;DeliveryStatusBadge&#xA;UserStatusBadgeهمه آنها ممکن است شبیه badgeهای کوچک باشند.اما قوانینشان ممکن است کاملاً متفاوت باشد.یکی ممکن است به وضعیت پرداخت وابسته باشد.یکی دیگر به وضعیت ارسال.یکی دیگر به permissionهای کاربر.اگر خیلی زود همه را مجبور کنیم داخل یک StatusBadge عمومی قرار بگیرند، ممکن است کامپوننتی بسازیم که از نظر فنی reusable است، اما از نظر معنایی گیجکننده است.سؤال بهتر این نیست:آیا این کامپوننتها شبیه هم هستند؟سؤال بهتر این است:آیا این کامپوننتها به یک دلیل تغییر میکنند؟اگر جواب نه است، احتمالاً نباید در یک abstraction مشترک قرار بگیرند.Reusability زودهنگام API بد میسازدهر کامپوننت reusable یک API ارائه میدهد.در React، این API معمولاً همان props است.هرچه کامپوننت انعطافپذیرتر شود، API آن بزرگتر میشود.در ابتدا:&lt;Card title=&#34;Orders&#34; /&gt;بعد:&lt;Card&#xA;  title=&#34;Orders&#34;&#xA;  subtitle=&#34;Last 30 days&#34;&#xA;  actions={&lt;ExportButton /&gt;}&#xA;  footer={&lt;Pagination /&gt;}&#xA;  loading={isLoading}&#xA;  error={error}&#xA;  emptyState={&lt;EmptyOrders /&gt;}&#xA;  variant=&#34;dashboard&#34;&#xA;  collapsible&#xA;  defaultCollapsed={false}&#xA;/&gt;بخشی از اینها ممکن است کاملاً معتبر باشد.اما سؤال این است:آیا این کامپوننت هنوز ساده و قابل فهم است؟یک کامپوننت reusable با propهای زیاد، درست استفاده کردن را سخت میکند.توسعهدهندهها شروع میکنند به پرسیدن این سؤالها:کدام propها با هم کار میکنند؟&#xA;کدام propها با هم conflict دارند؟&#xA;اگر loading و error هر دو true باشند چه میشود؟&#xA;آیا footer همراه emptyState قابل استفاده است؟&#xA;آیا variant فقط style را تغییر میدهد یا behavior را هم عوض میکند؟کامپوننت تبدیل به یک framework کوچک میشود.و frameworkهای کوچک نیاز به نگهداری دارند.کامپوننتهای reusable میتوانند business logic را پنهان کنندخطر دیگر این است که منطق مخصوص feature را داخل کامپوننتهای shared قرار دهیم.مثلاً:&lt;DataTable&#xA;  type=&#34;orders&#34;&#xA;  showPaymentStatus&#xA;  showRefundAction&#xA;  allowBulkCancel&#xA;/&gt;این ممکن است reusable به نظر برسد، اما حالا table درباره orders، payments، refunds و permissions میداند.یعنی کامپوننت shared دیگر واقعاً shared نیست.تبدیل شده به جایی مخلوط برای business logic.رویکرد بهتر معمولاً این است:Shared component: UI عمومی جدول&#xA;Feature component: رفتار مخصوص سفارشهامثلاً:&lt;DataTable columns={columns} rows={rows} /&gt;و بعد داخل feature سفارشها:&lt;OrderTable /&gt;کامپوننت shared فقط rendering عمومی را مدیریت میکند.کامپوننت feature معنی بیزینسی را مدیریت میکند.این جداسازی باعث میشود کامپوننت reusable تمیز بماند.Reuse معمولاً باید بعد از تکرار بیایدیک قانون عملی که دوست دارم:اول duplicate کن.&#xA;بعداً abstract کن.نه برای همیشه.نه بیدقت.بلکه آگاهانه.بار اولی که چیزی را میسازید، معمولاً هنوز الگوی واقعی را نمیدانید.بار دوم، شباهتها را میبینید.بار سوم، الگو واضحتر میشود.به همین دلیل بعضی توسعهدهندهها از “rule of three” استفاده میکنند:استفاده اول: مستقیم بساز&#xA;استفاده دوم: duplication را تحمل کن&#xA;استفاده سوم: به abstraction فکر کناین به معنی آن نیست که همیشه باید دقیقاً منتظر سه نمونه بمانید.اما یادآوری خوبی است:قبل از پایدار شدن الگو abstraction نسازید.هزینه abstraction اشتباه معمولاً بیشتر از هزینه duplication موقت است.چه چیزی یک کامپوننت reusable خوب میسازد؟یک کامپوننت reusable خوب معمولاً چند ویژگی دارد.هدف واضحی دارد.API کوچکی دارد.زیادی درباره business logic نمیداند.تست کردنش ساده است.مستند کردنش ساده است.بدون خواندن کل سورس کدش قابل استفاده است.به یک دلیل اصلی تغییر میکند.نمونههای خوب:Button&#xA;Input&#xA;Modal&#xA;Tooltip&#xA;Dropdown&#xA;Tabs&#xA;Avatar&#xA;DatePicker&#xA;Toastاین کامپوننتها معمولاً رفتار عمومی UI را نمایش میدهند.گزینههای خوبی برای shared یا design system هستند.اما کامپوننتهایی مثل اینها متفاوتاند:OrderSummaryCard&#xA;ProductInventoryWarning&#xA;CustomerRiskBadge&#xA;PaymentAttemptTimeline&#xA;AdminPermissionPanelاین اسمها معنی بیزینسی دارند.ممکن است داخل یک feature reusable باشند.اما احتمالاً نباید خیلی زود به فولدر shared عمومی منتقل شوند.فولدر Shared نباید تبدیل به سطل زباله شودخیلی از پروژهها فولدرهایی شبیه این دارند:src/shared&#xA;src/components&#xA;src/commonاین فولدرها میتوانند مفید باشند.اما میتوانند خطرناک هم باشند.وقتی توسعهدهندهها نمیدانند چیزی کجا باید قرار بگیرد، آن را داخل shared میگذارند.به مرور، shared تبدیل میشود به سطل زباله همهچیز:UI عمومی&#xA;UI مخصوص feature&#xA;business logic&#xA;formatterها&#xA;API helperها&#xA;permission logic&#xA;کامپوننتهای قدیمی و استفادهنشدهبعد دیگر کسی نمیداند چه چیزی واقعاً reusable است.قانون بهتر:کد را داخل feature نگه دارید تا وقتی ثابت کند واقعاً باید shared شود.کد shared باید ارتقا داده شود.نه اینکه از اول shared فرض شود.Reusability باید از ownership پیروی کندقبل از انتقال یک کامپوننت به shared، بپرسید:مالک این کامپوننت کیست؟اگر فقط feature سفارشها از آن استفاده میکند، داخل orders نگهش دارید.اگر فقط feature پرداختها از آن استفاده میکند، داخل payments نگهش دارید.اگر چند feature غیرمرتبط از آن استفاده میکنند، شاید جای آن در shared باشد.مثلاً:features/orders/components/OrderStatusBadge&#xA;features/payments/components/PaymentStatusBadge&#xA;shared/components/Badgeدر این ساختار:Badge = building block بصری عمومی&#xA;OrderStatusBadge = معنی مخصوص سفارش&#xA;PaymentStatusBadge = معنی مخصوص پرداختاین ساختار به شما reuse میدهد، بدون اینکه business logic را قاطی کند.چکلیست مفید قبل از reusable کردن یک کامپوننتقبل از ساختن یک کامپوننت reusable، بپرسید:۱. آیا حداقل دو یا سه use case واقعی وجود دارد؟&#xA;۲. آیا این use caseها به یک دلیل تغییر میکنند؟&#xA;۳. آیا معنی بیزینسی آنها یکی است؟&#xA;۴. آیا API میتواند کوچک بماند؟&#xA;۵. آیا این کار complexity را کم میکند یا فقط پنهانش میکند؟&#xA;۶. آیا تغییرات آینده سادهتر میشود یا سختتر؟&#xA;۷. آیا این واقعاً shared است یا فقط از نظر ظاهری شبیه است؟&#xA;۸. آیا business logic بیرون از کامپوننت shared میماند؟&#xA;۹. آیا duplication فعلاً ارزانتر است؟&#xA;۱۰. مالک این کامپوننت کیست؟اگر جوابها واضح نیستند، احتمالاً برای abstraction زود است.قانون سرانگشتی منکامپوننت reusable را فقط به خاطر reuse احتمالی آینده نسازید.کامپوننت reusable را وقتی بسازید که reuse واقعاً ظاهر شده باشد.ساده شروع کنید.کد مخصوص feature را داخل همان feature نگه دارید.اجازه دهید الگو کمی تکرار شود.بعد بخشهای پایدار را استخراج کنید.مقدار کمی duplication همیشه بد نیست.اما یک abstraction بد میتواند هر تغییر آینده را کند کند.کامپوننتهای reusable قدرتمند هستند.اما زمانبندی مهم است.هدف این نیست که از reusability فرار کنیم.هدف این است که از reusability زودهنگام فرار کنیم.معماری خوب فرانتاند به این معنی نیست که همهچیز reusable باشد.یعنی بدانیم چه چیزی باید reusable باشد، چه چیزی باید specific بماند، و چه زمانی الگو به اندازه کافی بالغ شده که استخراج شود.گاهی بهترین کامپوننت، انعطافپذیرترین کامپوننت نیست.بهترین کامپوننت، کامپوننتی است که مسئله فعلی را واضح حل میکند.</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Wed, 08 Jul 2026 15:35:53 +0330</pubDate></item><item><title>اپلیکیشن فرانت‌اند را فقط بر اساس نوع فایل مرتب نکنید</title><link>https://virgool.ir/@adeli.farhad/اپلیکیشن-فرانت-اند-را-فقط-بر-اساس-نوع-فایل-مرتب-نکنید-ni7auhk8vxdy</link><description>یکی از ساده‌ترین راه‌ها برای اینکه یک پروژه فرانت‌اند در ابتدا تمیز به نظر برسد، مرتب کردن فایل‌ها بر اساس نوع آن‌هاست.مثلاً:src/&#xA;  components/&#xA;  hooks/&#xA;  services/&#xA;  utils/&#xA;  pages/&#xA;  types/&#xA;  stores/در نگاه اول منطقی است.کامپوننت‌ها داخل components.هوک‌ها داخل hooks.سرویس‌ها داخل services.تایپ‌ها داخل types.ساده و مرتب.اما وقتی پروژه بزرگ‌تر می‌شود، این ساختار می‌تواند کم‌کم دردسرساز شود.نه به این خاطر که این فولدرها ذاتاً اشتباه هستند.مشکل اینجاست که پروژه را بر اساس دسته‌بندی فنی مرتب می‌کنند، نه بر اساس فیچرهای محصول.و اپلیکیشن‌های واقعی معمولاً با فیچرها رشد می‌کنند.مشکل ساختارهای مبتنی بر نوع فایلفرض کنید روی یک داشبورد فروشگاهی کار می‌کنید.فیچرهایی مثل:احراز هویت&#xA;محصولات&#xA;سفارش‌ها&#xA;مشتریان&#xA;پرداخت‌ها&#xA;تخفیف‌ها&#xA;گزارش‌هااما ساختار پروژه این است:components/&#xA;hooks/&#xA;services/&#xA;utils/&#xA;stores/&#xA;types/حالا هر بار که روی فیچر orders کار می‌کنید، باید بین فایل‌های مختلف جابه‌جا شوید:components/OrderTable.tsx&#xA;components/OrderStatusBadge.tsx&#xA;hooks/useOrders.ts&#xA;services/orderService.ts&#xA;stores/orderStore.ts&#xA;types/order.ts&#xA;utils/formatOrderStatus.tsاز نظر فنی فایل‌ها مرتب هستند.اما خود فیچر در کل پروژه پخش شده است.این باعث می‌شود درک کدبیس سخت‌تر شود، چون ساختار فولدرها به شما نمی‌گوید محصول چه کار می‌کند.فقط می‌گوید چه نوع فایل‌هایی وجود دارد.نوع فایل معماری نیستیک اشتباه رایج این است که فکر کنیم فولدرهایی مثل components، hooks و services خودشان معماری خوب ایجاد می‌کنند.این‌طور نیست.این فولدرها فقط فایل‌ها را دسته‌بندی می‌کنند.معماری درباره مرزها، مالکیت، جهت وابستگی‌ها و جدا کردن تغییرات است.سؤال بهتر این نیست:این فایل کامپوننت است، هوک است، سرویس است یا تایپ؟سؤال بهتر این است:این کد متعلق به کدام فیچر است؟این سؤال ما را به ساختار متفاوتی می‌رساند.ساختار مبتنی بر فیچردر ساختار feature-based، فایل‌های مرتبط نزدیک به هم قرار می‌گیرند.مثلاً:src/&#xA;  features/&#xA;    orders/&#xA;      components/&#xA;        OrderTable.tsx&#xA;        OrderStatusBadge.tsx&#xA;      hooks/&#xA;        useOrders.ts&#xA;      api/&#xA;        orderApi.ts&#xA;      types/&#xA;        order.types.ts&#xA;      utils/&#xA;        formatOrderStatus.ts&#xA;      stores/&#xA;        orderStore.tsحالا وقتی روی orders کار می‌کنید، بیشتر چیزهایی که لازم دارید در یک جا قرار دارد.فیچر خانه مشخصی دارد.این کار باعث می‌شود کدبیس راحت‌تر قابل جستجو، حذف، refactor و واگذاری به یک توسعه‌دهنده باشد.چرا این موضوع در پروژه‌های واقعی مهم است؟در پروژه‌های کوچک، تقریباً هر ساختاری جواب می‌دهد.می‌توانید همه‌چیز را بر اساس نوع فایل مرتب کنید، بر اساس فیچر مرتب کنید یا حتی خیلی چیزها را کنار هم نگه دارید و باز هم مشکلی نداشته باشید.مشکل از جایی شروع می‌شود که پروژه رشد می‌کند.وقتی تعداد توسعه‌دهنده‌ها بیشتر می‌شود، صفحه‌های بیشتری اضافه می‌شود، APIهای بیشتری ساخته می‌شود و قوانین بیزینسی پیچیده‌تر می‌شوند، هزینه مالکیت نامشخص خودش را نشان می‌دهد.کم‌کم این سؤال‌ها پیش می‌آیند:این منطق کجا باید باشد؟&#xA;آیا می‌توانم این فایل را با خیال راحت تغییر دهم؟&#xA;این کامپوننت فقط همین‌جا استفاده شده یا همه‌جا؟&#xA;این هوک مخصوص یک فیچر است یا shared است؟&#xA;آیا می‌توانم این فیچر قدیمی را حذف کنم بدون اینکه جای دیگری خراب شود؟ساختار feature-based کمک می‌کند سریع‌تر به این سؤال‌ها جواب دهیم.کد Shared باید واقعاً Shared باشدیکی از خطرهای ساختارهای مبتنی بر نوع فایل این است که همه‌چیز کم‌کم shared به نظر می‌رسد.وقتی یک کامپوننت داخل src/components قرار می‌گیرد، توسعه‌دهنده‌ها ممکن است فکر کنند یک کامپوننت عمومی و قابل استفاده مجدد است.اما همیشه این‌طور نیست.گاهی آن کامپوننت فقط برای یک فیچر خاص معنی دارد.مثلاً:OrderStatusBadge&#xA;ProductVariantSelector&#xA;CustomerRiskLabel&#xA;PaymentAttemptTimelineاین‌ها شاید شبیه کامپوننت‌های reusable به نظر برسند، اما معمولاً معنی بیزینسی خاصی دارند.قرار دادن آن‌ها داخل فولدر global components می‌تواند باعث ابهام شود.قانون بهتر:کد مخصوص فیچر را داخل همان فیچر نگه دارید.&#xA;فقط وقتی چیزی واقعاً مشترک است، آن را به shared منتقل کنید.کد shared باید آگاهانه shared شود.نه به صورت پیش‌فرض.یک ساختار عملی برای پروژه‌های فرانت‌اندساختاری که برای بسیاری از اپلیکیشن‌های فرانت‌اند دوست دارم، شبیه این است:src/&#xA;  app/&#xA;    providers/&#xA;    routes/&#xA;    layouts/&#xA;&#xA;  features/&#xA;    auth/&#xA;    orders/&#xA;    products/&#xA;    customers/&#xA;&#xA;  shared/&#xA;    components/&#xA;    hooks/&#xA;    utils/&#xA;    types/&#xA;&#xA;  lib/&#xA;    api/&#xA;    config/&#xA;    constants/اسم دقیق فولدرها به اندازه ایده اصلی مهم نیست.یک جداسازی خوب می‌تواند این‌طور باشد:app      → تنظیمات و ترکیب‌بندی اصلی اپلیکیشن&#xA;features → فیچرهای بیزینسی&#xA;shared   → UI و utilityهای واقعاً قابل استفاده مجدد&#xA;lib      → زیرساخت‌ها و helperهای فنیاین مدل ذهنی پروژه را واضح‌تر می‌کند.فولدر app نشان می‌دهد اپلیکیشن چطور compose شده است.فولدر features نشان می‌دهد محصول چه کار می‌کند.فولدر shared چیزهایی را نگه می‌دارد که واقعاً reusable هستند.فولدر lib helperها و زیرساخت‌های فنی را نگه می‌دارد.چه چیزی باید داخل shared باشد؟فولدر shared باید با دقت استفاده شود.گزینه‌های مناسب برای shared:Button&#xA;Input&#xA;Modal&#xA;DatePicker&#xA;formatDate&#xA;helperهای className&#xA;هوک generic مثل useDebounce&#xA;API client عمومی&#xA;validation helperهای عمومیگزینه‌های نامناسب برای shared:OrderStatusBadge&#xA;CustomerPaymentSummary&#xA;ProductInventoryWarning&#xA;AdminUserPermissionPanelاین اسم‌ها معمولاً معنی بیزینسی دارند.یعنی احتمالاً متعلق به یک فیچر هستند.یک تست ساده:اگر این فیچر را حذف کنم، آیا این فایل هم باید حذف شود؟اگر جواب بله است، آن فایل را داخل همان فیچر نگه دارید.Feature-based یعنی هر duplication بد نیستبعضی توسعه‌دهنده‌ها خیلی زود کد را shared می‌کنند، چون از duplication می‌ترسند.اما duplication کوچک گاهی بهتر از abstraction زودهنگام است.اگر امروز دو فیچر کامپوننت‌های مشابه دارند، لزوماً به این معنی نیست که همین الان باید یک abstraction مشترک ساخته شود.شاید این شباهت موقتی باشد.شاید قوانین بیزینسی هفته بعد از هم جدا شوند.شاید مجبور کردن دو فیچر به استفاده از یک کامپوننت مشترک، تغییر هر دو را سخت‌تر کند.قانون خوب:اگر معنی بیزینسی متفاوت است، اول duplication را بپذیرید.&#xA;وقتی الگو ثابت شد، بعداً abstraction بسازید.اشتراک‌گذاری زودهنگام coupling ایجاد می‌کند.و coupling معمولاً از duplication گران‌تر است.جهت وابستگی‌ها مهم استساختار feature-based وقتی بهتر کار می‌کند که جهت وابستگی‌ها واضح باشد.مثلاً:features/orders می‌تواند از shared استفاده کند&#xA;features/products می‌تواند از shared استفاده کند&#xA;shared نباید به features وابسته باشد&#xA;features نباید بی‌دلیل به هم وابسته شونداین نکته مهم است.اگر هر فیچر از هر فیچر دیگری import کند، دوباره کدبیس درهم‌تنیده می‌شود.قانون ساده:کد shared می‌تواند توسط فیچرها استفاده شود.&#xA;فیچرها بهتر است مستقیماً به فیچرهای دیگر وابسته نشوند.اگر دو فیچر باید با هم ارتباط داشته باشند، معمولاً بهتر است از application-level state، routing، event یا یک abstraction مشترک استفاده شود، نه import عمیق از داخل همدیگر.چه زمانی ساختار مبتنی بر نوع فایل هنوز خوب است؟ساختار type-based همیشه بد نیست.برای موارد زیر می‌تواند کاملاً قابل قبول باشد:پروژه‌های کوچک&#xA;prototypeها&#xA;landing pageهای ساده&#xA;admin panelهای کوچک&#xA;پروژه‌های آموزشیهمچنین داخل خود یک فیچر هم می‌تواند خوب کار کند.مثلاً داخل features/orders داشتن فولدرهای components، hooks، api و types کاملاً منطقی است.مشکل استفاده از فولدرهای مبتنی بر نوع فایل نیست.مشکل این است که از آن‌ها به‌عنوان معماری top-level برای یک محصول در حال رشد استفاده کنیم.قانون سرانگشتی مندر سطح top-level، پروژه را بر اساس فیچرهای بیزینسی مرتب کنید.داخل هر فیچر، اگر کمک می‌کند، بر اساس نوع فایل مرتب کنید.مثلاً:features/orders/components&#xA;features/orders/hooks&#xA;features/orders/api&#xA;features/orders/typesاین مدل بهترین بخش هر دو رویکرد را دارد.پروژه بر اساس معنی محصول مرتب می‌شود.و هر فیچر هم ساختار داخلی خودش را دارد.جمع‌بندییک ساختار فولدر خوب باید به توسعه‌دهنده‌ها کمک کند محصول را بفهمند.نه فقط نوع فایل‌ها را.وقتی یک اپلیکیشن فرانت‌اند رشد می‌کند، سؤال مهم این می‌شود:مالک این کد کیست؟معماری feature-based مالکیت را واضح‌تر می‌کند.فایل‌های مرتبط را نزدیک هم نگه می‌دارد.تغییر، حذف و refactor فیچرها را ساده‌تر می‌کند.جلوی shared شدن تصادفی کد را می‌گیرد.به تیم‌ها کمک می‌کند مستقل‌تر کار کنند.و باعث می‌شود کدبیس با رشد محصول قابل فهم‌تر بماند.نوع فایل مفید است.اما مالکیت فیچر معماری است.اگر اپلیکیشن فرانت‌اند شما در حال رشد است و برای هر تغییر باید بین ده فولدر نامرتبط جابه‌جا شوید، شاید وقتش رسیده باشد که فقط بر اساس نوع فایل سازمان‌دهی نکنید.بر اساس فیچر سازمان‌دهی کنید.</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Mon, 06 Jul 2026 08:51:54 +0330</pubDate></item><item><title>بیشتر باگ‌های فرانت‌اند در واقع باگ‌های مالکیت State هستند</title><link>https://virgool.ir/@adeli.farhad/بیشتر-باگ-های-فرانت-اند-در-واقع-باگ-های-مالکیت-state-هستند-p8gywmvgrtlx</link><description>بیشتر باگ‌های فرانت‌اند به خاطر UI بد نیستند.مشکل اصلی معمولاً از مالکیت نامشخص State شروع می‌شود.ممکن است باگ خودش را در ظاهر رابط کاربری نشان دهد، اما ریشه مشکل معمولاً جای دیگری است:مالک این State کیست؟&#xA;چه کسی اجازه دارد آن را تغییر دهد؟&#xA;چه بخش‌هایی به آن وابسته هستند؟&#xA;چه زمانی باید reset شود؟&#xA;آیا باید بعد از تغییر صفحه باقی بماند؟&#xA;آیا باید با سرور sync شود؟وقتی جواب این سؤال‌ها مشخص نباشد، نگهداری یک اپلیکیشن فرانت‌اند به مرور سخت‌تر می‌شود.در ابتدا همه‌چیز ساده به نظر می‌رسد.یک useState اینجا اضافه می‌کنید.یک prop آنجا پاس می‌دهید.بعد Context اضافه می‌کنید چون prop drilling اذیت‌کننده شده.بعدتر شاید Zustand، Redux، React Query یا یک ابزار دیگر برای مدیریت state اضافه شود.و برای مدتی همه‌چیز کار می‌کند.اما وقتی اپلیکیشن بزرگ‌تر می‌شود، باگ‌های عجیب شروع می‌شوند:فیلترها بی‌دلیل reset می‌شوندمودال‌ها از جای اشتباه باز می‌شوندفرم‌ها مقدارهای قدیمی را نگه می‌دارندداده‌های API داخل local state کپی می‌شوندچند کامپوننت یک مقدار مشترک را تغییر می‌دهندیک تغییر کوچک سه صفحه مختلف را خراب می‌کنددر این نقطه، مشکل معمولاً React نیست.مشکل، مالکیت نامشخص state است.مدیریت State فقط انتخاب ابزار نیستوقتی توسعه‌دهنده‌ها درباره مدیریت state صحبت می‌کنند، بحث خیلی سریع به سمت ابزارها می‌رود.آیا باید از Redux استفاده کنیم؟آیا باید Zustand استفاده کنیم؟آیا Context API کافی است؟آیا باید React Query داشته باشیم؟Jotai، Recoil، MobX یا ابزارهای دیگر چطور؟همه این ابزارها مفید هستند.اما هیچ ابزاری به‌تنهایی طراحی بد state را درست نمی‌کند.اگر اپلیکیشن قوانین واضحی برای محل زندگی state نداشته باشد، حتی بهترین کتابخانه مدیریت state هم می‌تواند تبدیل به یک آشفتگی شود.Global store می‌تواند تبدیل به سطل زباله همه‌چیز شود.Context می‌تواند بیش از حد استفاده شود.Local state می‌تواند تکراری شود.Server data می‌تواند داخل client state کپی شود و stale بماند.سؤال اصلی این نیست:از کدام کتابخانه مدیریت state استفاده کنم؟سؤال بهتر این است:مالک این مقدار چه کسی باید باشد؟این سؤال معمولاً ما را به معماری بهتری می‌رساند.سه نوع رایج Stateدر بیشتر اپلیکیشن‌های فرانت‌اند، state معمولاً در سه دسته کلی قرار می‌گیرد:server state&#xA;local UI state&#xA;shared client stateهرکدام مالک متفاوت و lifecycle متفاوتی دارند.اشتباه گرفتن این‌ها با هم یکی از ساده‌ترین راه‌ها برای ساختن باگ‌های فرانت‌اند است.۱. Server StateServer state داده‌ای است که متعلق به بک‌اند است.مثال‌ها:کاربران&#xA;سفارش‌ها&#xA;محصولات&#xA;دسترسی‌ها&#xA;اعلان‌ها&#xA;پیام‌ها&#xA;تاریخچه پرداخت&#xA;اطلاعات پروفایلاین داده معمولاً از API می‌آید.ممکن است cache شود.ممکن است نیاز به refetch داشته باشد.ممکن است بین چند کاربر مشترک باشد.ممکن است خارج از session فعلی مرورگر تغییر کند.ممکن است stale شود.به همین دلیل، server state معمولاً باید متفاوت از state معمولی UI مدیریت شود.مثلاً اگر لیست کاربران را از بک‌اند می‌گیرید، کپی کردن آن داخل useState و sync نگه داشتن دستی آن می‌تواند دردسرساز شود.رویکرد بهتر معمولاً استفاده از ابزارهای server-state است، مثل:TanStack Query / React Query&#xA;SWR&#xA;Apollo Client&#xA;Relayاین ابزارها برای مسائلی مثل موارد زیر ساخته شده‌اند:caching&#xA;refetching&#xA;loading states&#xA;error states&#xA;pagination&#xA;invalidation&#xA;background updates&#xA;stale dataServer state باید به این سؤال جواب دهد:بک‌اند در حال حاضر چه داده‌ای را می‌شناسد؟نباید با state باز یا بسته بودن یک dropdown یکسان دیده شود.۲. Local UI StateLocal UI state متعلق به یک کامپوننت یا یک بخش کوچک از UI است.مثال‌ها:آیا این dropdown باز است؟&#xA;آیا این modal نمایش داده شود؟&#xA;کدام tab انتخاب شده؟&#xA;مقدار فعلی input چیست؟&#xA;آیا tooltip فعال است؟&#xA;آیا accordion باز شده؟این نوع state معمولاً نیازی به global شدن ندارد.نیازی ندارد در کل اپلیکیشن باقی بماند.نیازی ندارد به‌صورت پیش‌فرض داخل Redux یا Zustand ذخیره شود.در بسیاری از موارد، state ساده React کافی است:const [isOpen, setIsOpen] = useState(false);اشتباه زمانی اتفاق می‌افتد که توسعه‌دهنده‌ها local UI state را خیلی زود global می‌کنند.مثلاً state باز یا بسته بودن یک dropdown معمولاً به global store تعلق ندارد.یک draft موقت فرم هم لزوماً نباید در global store باشد، مگر اینکه لازم باشد بعد از navigation باقی بماند یا بین چند بخش مستقل از اپلیکیشن shared شود.Local UI state باید به این سؤال جواب دهد:این بخش از رابط کاربری همین الان به چه چیزی نیاز دارد؟اگر جواب فقط داخل یک کامپوننت اهمیت دارد، آن را local نگه دارید.۳. Shared Client StateShared client state داده‌ای است که متعلق به اپلیکیشن فرانت‌اند است و چند بخش مستقل از UI به آن نیاز دارند.مثال‌ها:session کاربر لاگین‌شده&#xA;theme&#xA;زبان&#xA;وضعیت باز یا بسته بودن sidebar&#xA;فیلترهای global&#xA;سبد خرید&#xA;workspace انتخاب‌شده&#xA;تعداد اعلان‌ها&#xA;state یک wizard چندمرحله‌ایاین نوع state ممکن است به یک shared store نیاز داشته باشد.بسته به اپلیکیشن، این store می‌تواند یکی از این‌ها باشد:Context API&#xA;Zustand&#xA;Redux Toolkit&#xA;Jotai&#xA;MobX&#xA;custom storeاما shared state باید آگاهانه استفاده شود.هر prop chain به Context نیاز ندارد.هر مقدار تکرارشونده‌ای global store نمی‌خواهد.هر مشکل ارتباط بین کامپوننت‌ها نباید تبدیل به global state شود.Shared client state باید به این سؤال جواب دهد:آیا چند بخش غیرمرتبط از اپلیکیشن واقعاً به این مقدار نیاز دارند؟اگر جواب نه است، احتمالاً این state نباید global باشد.اشتباه رایج: کپی کردن Server State داخل Local Stateیکی از رایج‌ترین code smellها در فرانت‌اند، کپی کردن داده API داخل local state بدون دلیل مشخص است.مثلاً:const { data: user } = useQuery({&#xA;  queryKey: [&#34;user&#34;, userId],&#xA;  queryFn: () =&gt; fetchUser(userId)&#xA;});&#xA;&#xA;const [localUser, setLocalUser] = useState(user);این کار معمولاً دو منبع حقیقت ایجاد می‌کند.حالا اپلیکیشن باید به این سؤال‌ها جواب دهد:کدام درست است؟&#xA;داده API؟&#xA;کپی local؟&#xA;وقتی داده سرور تغییر کند چه می‌شود؟&#xA;بعد از mutation چه اتفاقی می‌افتد؟&#xA;بعد از refetch چه می‌شود؟گاهی اوقات داشتن کپی local منطقی است، مخصوصاً برای draftهای قابل ویرایش.مثلاً یک فرم ممکن است با داده سرور پر شود، اما کاربر قبل از ذخیره آن را ویرایش کند.اما در این حالت، state باید به‌عنوان draft دیده شود، نه نسخه دوم server state.مدل ذهنی بهتر:Server state = منبع حقیقت از سمت بک‌اند&#xA;Form state = draft موقت ساخته‌شده از آن منبعاین تفاوت مهم است.اشتباه رایج دیگر: Global State به‌عنوان پیش‌فرضGlobal state در ابتدا راحت به نظر می‌رسد.از هر جایی می‌توانید به آن دسترسی داشته باشید.از هر جایی می‌توانید آن را تغییر دهید.می‌توانید از prop drilling فرار کنید.اما این راحتی هزینه دارد.اگر همه‌چیز بتواند یک state را بخواند و تغییر دهد، فهمیدن سیستم سخت‌تر می‌شود.کم‌کم این سؤال‌ها پیش می‌آید:چه کسی این مقدار را تغییر داد؟&#xA;چرا این کامپوننت دوباره render شد؟&#xA;چرا این صفحه reset شد؟&#xA;چرا این modal باز است؟&#xA;چرا این فیلتر روی صفحه دیگری اثر گذاشت؟Global state نباید حالت پیش‌فرض باشد.باید یک تصمیم آگاهانه باشد.قبل از global کردن state، بپرسید:آیا این مقدار باید توسط بخش‌های غیرمرتبط اپلیکیشن استفاده شود؟&#xA;آیا باید بعد از تغییر route باقی بماند؟&#xA;آیا نشان‌دهنده رفتار سطح اپلیکیشن است؟&#xA;آیا پاس دادن آن واقعاً کد را بدتر می‌کند یا فقط کمی ناخوشایند است؟گاهی global state کاملاً درست است.اما global کردن همه‌چیز coupling پنهان ایجاد می‌کند.Lifecycle State مهم استیک state variable فقط درباره محل زندگی‌اش نیست.درباره مدت زنده بودنش هم هست.بعضی stateها باید هنگام unmount شدن کامپوننت reset شوند.بعضی stateها باید هنگام تغییر route reset شوند.بعضی stateها باید بعد از navigation باقی بمانند.بعضی stateها باید بعد از refresh هم باقی بمانند.بعضی stateها باید داخل URL ذخیره شوند.بعضی stateها باید داخل local storage persist شوند.مثلاً:فیلترهای جستجو ممکن است به URL تعلق داشته باشند.&#xA;state باز بودن modal ممکن است local component state باشد.&#xA;authentication state ممکن است در shared store باشد.&#xA;داده API ممکن است در server-state cache باشد.&#xA;فرم چندمرحله‌ای ممکن است نیاز به persistence داشته باشد.اگر lifecycle واضح نباشد، احتمال باگ بالا می‌رود.برای همین من دوست دارم این سؤال را بپرسم:این state چه زمانی باید بمیرد؟این سؤال به اندازه سؤال زیر مهم است:این state کجا باید زندگی کند؟URL State دست‌کم گرفته می‌شودبعضی stateهای فرانت‌اند باید در URL زندگی کنند.مثال‌ها:عبارت جستجو&#xA;صفحه pagination&#xA;ترتیب مرتب‌سازی&#xA;فیلترهای انتخاب‌شده&#xA;tab فعال در بعضی صفحاتچرا؟چون URL state قابل اشتراک‌گذاری است.بعد از refresh باقی می‌ماند.از browser navigation پشتیبانی می‌کند.صفحه را bookmarkable می‌کند.debug کردن را ساده‌تر می‌کند.مثلاً این معمولاً بهتر است:/products?category=laptops&amp;page=2&amp;sort=priceتا اینکه تمام state فیلترها را داخل یک کامپوننت یا global store پنهان کنیم.همه UI stateها به URL تعلق ندارند، اما برای فیلترهای سطح صفحه و stateهای مرتبط با navigation، URL اغلب مالک مناسبی است.یک چک‌لیست ساده برای تصمیم‌گیریقبل از اضافه کردن یک state variable جدید، من معمولاً این سؤال‌ها را می‌پرسم:۱. آیا این داده از بک‌اند می‌آید؟&#xA;۲. آیا فقط در یک کامپوننت استفاده می‌شود؟&#xA;۳. آیا چند بخش غیرمرتبط از اپلیکیشن به آن نیاز دارند؟&#xA;۴. آیا باید بعد از navigation باقی بماند؟&#xA;۵. آیا باید بعد از refresh باقی بماند؟&#xA;۶. آیا باید از طریق URL قابل اشتراک‌گذاری باشد؟&#xA;۷. آیا یک draft موقت است؟&#xA;۸. چه کسی اجازه دارد آن را تغییر دهد؟&#xA;۹. چه زمانی باید reset شود؟&#xA;۱۰. منبع حقیقت چیست؟این سؤال‌ها ساده هستند، اما جلوی بسیاری از باگ‌ها را می‌گیرند.قانون سرانگشتی مناز server state برای داده‌ای استفاده کنید که متعلق به بک‌اند است.از local state برای رفتار UI داخل یک کامپوننت یا یک بخش کوچک استفاده کنید.از shared client state فقط زمانی استفاده کنید که چند بخش غیرمرتبط از اپلیکیشن واقعاً به یک مقدار مشترک نیاز دارند.از URL state زمانی استفاده کنید که state مربوط به navigation، فیلترها، جستجو یا تنظیمات قابل اشتراک‌گذاری صفحه است.Server state را داخل local state کپی نکنید، مگر اینکه عمداً در حال ساختن یک draft باشید.State را فقط به این دلیل که پاس دادن prop کمی آزاردهنده است global نکنید.و مهم‌تر از همه:قبل از اضافه کردن یک state variable جدید، بپرسید چه کسی باید مسئول این مقدار باشد.همین یک سؤال می‌تواند جلوی بسیاری از باگ‌های آینده را بگیرد.جمع‌بندیتوسعه فرانت‌اند در سطح senior فقط دانستن hookها، کتابخانه‌های state management یا APIهای فریم‌ورک نیست.موضوع اصلی این است که بدانیم state کجا باید زندگی کند.و کجا نباید زندگی کند.یک معماری فرانت‌اند تمیز معمولاً مالکیت مشخصی دارد:بک‌اند مالک server data است&#xA;کامپوننت مالک local UI behavior است&#xA;app store مالک shared client state واقعی است&#xA;URL مالک navigation state قابل اشتراک‌گذاری استوقتی مالکیت واضح باشد، فهمیدن اپلیکیشن ساده‌تر می‌شود.وقتی مالکیت نامشخص باشد، حتی featureهای ساده هم شکننده می‌شوند.بیشتر باگ‌های فرانت‌اند واقعاً باگ UI نیستند.آن‌ها باگ‌های مالکیت هستند.</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Wed, 01 Jul 2026 14:14:19 +0330</pubDate></item><item><title>AI Writes Code. Engineers Build Software.</title><link>https://virgool.ir/@adeli.farhad/ai-writes-code-engineers-build-software-lgp9a6djjpb6</link><description>AI can help us write code faster.A lot faster.It can generate React components, create API endpoints, suggest database schemas, write validation logic, generate unit tests, explain errors, and even refactor messy code.That is powerful.But writing code is not the same as building software.There is a big difference between:Can this code run?and:Should this solution exist in this form?That second question is where engineering judgment matters.AI is very good at syntaxModern AI tools are excellent at producing code.You can ask for a component, a function, a service, a SQL query, a form validator, or a test case, and you will often get something useful in seconds.For many daily development tasks, AI can remove a lot of friction.It can help with:boilerplate&#xA;syntax&#xA;quick examples&#xA;refactoring ideas&#xA;debugging hints&#xA;test generation&#xA;documentation&#xA;code explanation&#xA;library comparisonsThis is why I believe developers should use AI.Ignoring it completely is not a good strategy.But there is a trap.Because AI can generate something that looks correct, it is easy to confuse speed with quality.Fast code is not always good software.Engineering is not only syntaxSoftware engineering is about tradeoffs.It is about context.It is about understanding the product, the team, the user, the deadline, the risks, and the future cost of today’s decisions.AI can generate an API endpoint.But the engineer still needs to ask:Should this endpoint exist?&#xA;Who is allowed to call it?&#xA;What should happen on invalid input?&#xA;What should the response shape be?&#xA;Should this be cached?&#xA;How will we version it later?&#xA;What happens if the database is slow?&#xA;What should be logged?&#xA;What should not be exposed?AI can generate a React component.But the engineer still needs to ask:Should this state be local or global?&#xA;Is this component doing too much?&#xA;Is this reusable or page-specific?&#xA;Will this re-render too often?&#xA;Is this accessible?&#xA;Is this easy for another developer to maintain?AI can generate a database schema.But the engineer still needs to ask:Does this model reflect the real business rules?&#xA;Will this scale?&#xA;What should be indexed?&#xA;What should be normalized?&#xA;What should not be stored?&#xA;How will migrations be handled?&#xA;What happens when requirements change?These questions are not just syntax questions.They are engineering questions.Junior use of AI vs senior use of AIA junior developer may ask AI:Build this feature for me.A more experienced developer asks:What are the edge cases?&#xA;Where can this fail?&#xA;What are the tradeoffs?&#xA;Is there a simpler solution?&#xA;What should be tested?&#xA;What are the security risks?&#xA;How will this behave under load?&#xA;How will another developer understand this later?The difference is not only experience.The difference is the quality of the questions.AI is most useful when the developer already understands the problem well enough to guide it.Without that, AI can produce code that looks polished but hides bad assumptions.The danger of confident wrong codeOne of the biggest risks with AI-generated code is that it often sounds confident.The code may be formatted well.The explanation may sound reasonable.The names may look clean.But the solution may still be wrong for your actual project.It may ignore edge cases.It may introduce security issues.It may use outdated patterns.It may over-engineer a simple feature.It may create abstractions that make the code harder to maintain.It may solve the wrong problem beautifully.That last one is dangerous.A clean implementation of the wrong idea is still the wrong idea.AI increases the value of fundamentalsSome people say AI will make programming fundamentals less important.I think the opposite.AI makes fundamentals more important.When AI can generate code quickly, the valuable skill becomes knowing whether that code is good.To judge that, you need fundamentals:HTTP&#xA;state management&#xA;database design&#xA;security basics&#xA;asynchronous programming&#xA;system design&#xA;testing strategy&#xA;performance&#xA;error handling&#xA;architecture&#xA;maintainabilityWithout fundamentals, you may accept whatever AI gives you.With fundamentals, AI becomes an accelerator.You can use it to move faster while still keeping control over quality.AI speed without judgment creates technical debt fasterBefore AI, writing bad code took time.Now bad code can be generated very quickly.That means AI can help good engineers move faster, but it can also help weak engineering decisions spread faster.The output speed increased.The need for review did not disappear.In fact, review became more important.Every AI-assisted solution still needs to be checked against the actual context of the project:Does this match our architecture?&#xA;Does this fit our coding style?&#xA;Does this introduce hidden coupling?&#xA;Is this secure?&#xA;Is this testable?&#xA;Is this simple enough?&#xA;Is this the right level of abstraction?If we skip those questions, AI does not remove technical debt.It accelerates it.The best use of AI is collaboration, not delegationI do not like using AI as a blind code generator.I prefer using it as a technical assistant.For example:Give me three possible approaches.&#xA;Compare the tradeoffs.&#xA;Show me the simplest version first.&#xA;Find edge cases.&#xA;Review this code.&#xA;Suggest tests.&#xA;Explain the performance risks.&#xA;Make this easier to maintain.This kind of workflow keeps the engineer in control.The developer provides direction.AI provides speed.The developer makes the final decision.That is the right balance.Product thinking still mattersGood software is not only technically correct.It also needs to solve the right problem.AI may generate a beautiful implementation, but it does not always know the business context.It may not know:what the user actually needs&#xA;what the product goal is&#xA;what the deadline is&#xA;what the team can maintain&#xA;what the company can afford&#xA;what should be built now&#xA;what should be delayedThis is where product thinking matters.Sometimes the best engineering decision is not the most advanced solution.Sometimes the best solution is the simplest one that solves the real problem and keeps the product moving.AI can help you build.But you still need to decide what is worth building.My rule of thumbUse AI to move faster.Do not use AI to stop thinking.AI is great for:speed&#xA;syntax&#xA;drafting&#xA;exploration&#xA;boilerplate&#xA;explanation&#xA;comparisonEngineers are still responsible for:architecture&#xA;security&#xA;tradeoffs&#xA;maintainability&#xA;product fit&#xA;testing strategy&#xA;long-term consequencesThe future does not belong to developers who ignore AI.It also does not belong to developers who blindly trust AI.The future belongs to engineers who can combine:AI speed&#xA;+&#xA;engineering fundamentals&#xA;+&#xA;product thinkingAI makes coding faster.Judgment makes software better.</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Mon, 29 Jun 2026 16:42:39 +0330</pubDate></item><item><title>A Good Developer Does Not Marry a Framework</title><link>https://virgool.ir/@adeli.farhad/a-good-developer-does-not-marry-a-framework-yiql1ophzib3</link><description>A good developer does not marry a framework.React, Angular, Vue, Next.js, NestJS, Laravel, .NET — they all matter.They are useful tools. They help us build faster, organize code better, and solve real business problems. But after working with different technologies for some years, you start noticing something important:The syntax changes.The ecosystem changes.The mental models repeat.Most modern software stacks are solving very similar problems:state management&#xA;routing&#xA;rendering&#xA;data fetching&#xA;caching&#xA;validation&#xA;authentication&#xA;authorization&#xA;error handling&#xA;testing&#xA;deployment&#xA;architectureThe names are different.The patterns are often familiar.Frameworks are tools, not identitiesI see many developers define themselves only by one framework:React developer&#xA;Angular developer&#xA;Vue developer&#xA;Node.js developer&#xA;Laravel developer&#xA;.NET developerThere is nothing wrong with specialization. In fact, deep knowledge of a framework is valuable.But I think there is a danger when a developer’s entire identity depends on one tool.Frameworks change.Market demand changes.Best practices change.New libraries appear.Old libraries disappear.A few years ago, a specific framework or library might have looked like the future. Today, it might be considered legacy in some companies.That does not mean learning frameworks is useless.It means we should learn them as tools, not as permanent identities.A stronger identity is:I build reliable software systems.That mindset survives framework changes.The same problems appear in different formsOnce you understand component architecture, moving between React, Vue, and Angular becomes much easier.The syntax is different, but the questions are familiar:How should components communicate?&#xA;Where should state live?&#xA;What belongs in a reusable component?&#xA;What should stay page-specific?&#xA;How do we avoid unnecessary coupling?&#xA;How do we keep the UI predictable?Once you understand API design, moving between Node.js, Laravel, .NET, or NestJS becomes less scary.Again, the syntax changes, but the core questions remain:What should the API expose?&#xA;How do we validate input?&#xA;How do we handle errors?&#xA;How do we separate business logic from transport logic?&#xA;How do we avoid leaking database internals?&#xA;How do we keep the system maintainable?Once you understand clean separation of concerns, the language becomes less important than people think.Not unimportant.Just less scary.Switching frameworks is not starting from zeroWhen a junior developer learns a new framework, it often feels like starting from zero.When a more experienced developer learns a new framework, the process is different.You are not really learning software development again.You are mapping familiar concepts into a new ecosystem.For example:React state        → Angular signals/services/RxJS&#xA;Vue composables    → React hooks&#xA;Express middleware → NestJS guards/interceptors/pipes&#xA;Laravel services   → .NET services or Node.js service classesOf course, every framework has its own philosophy.You still need to learn the details.You still need to respect the ecosystem.You still need to write idiomatic code.But you are not lost.You already understand the problems the framework is trying to solve.That is a huge advantage.Fundamentals compoundFramework knowledge is useful.Fundamentals are reusable.Things like:data structures&#xA;HTTP&#xA;browser behavior&#xA;security basics&#xA;database design&#xA;API contracts&#xA;state management&#xA;asynchronous programming&#xA;performance optimization&#xA;testing strategy&#xA;deployment and monitoring&#xA;software architectureThese skills compound over time.A framework may help you get a job today.Fundamentals help you survive multiple changes in the industry.This is why I believe developers should go deep enough into their current stack to be productive, but not so narrow that they become afraid of everything outside it.AI makes adaptability even more importantAI tools are making it easier to learn new frameworks quickly.But AI does not remove the need for engineering judgment.In fact, it makes judgment more important.AI can help you generate code.AI can explain syntax.AI can compare libraries.AI can help you move faster.But you still need to know whether the generated solution is clean, secure, maintainable, and appropriate for the project.If you understand architecture, AI becomes a powerful accelerator.If you only know syntax, AI can easily lead you into messy code that looks correct at first glance.That is why adaptability is not just about learning faster.It is about having enough fundamentals to evaluate what you are learning.Specialization is still valuableThis does not mean every developer should become a generalist with shallow knowledge.Deep specialization is valuable.A great React engineer is valuable.A great Angular engineer is valuable.A great backend engineer is valuable.A great Laravel, .NET, or Node.js engineer is valuable.The point is not:Do not specialize.The point is:Do not become dependent on one tool for your entire engineering identity.There is a difference between being specialized and being trapped.The best engineers I have worked with usually have both:deep expertise in their current stack&#xA;+&#xA;strong fundamentals that transfer across stacksThat combination is powerful.My rule of thumbLearn frameworks seriously.Use them properly.Respect their patterns.Understand their ecosystems.But do not let your whole career depend on one framework.Frameworks are temporary.Engineering fundamentals compound.A good developer should be able to say:I know this framework well.&#xA;But I am not limited by it.That is the mindset that keeps you useful when the industry changes.And the industry always changes.</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Thu, 25 Jun 2026 15:24:07 +0330</pubDate></item><item><title>Simulcast: Manually Controlling Video Quality Layers in WebRTC</title><link>https://virgool.ir/@adeli.farhad/simulcast-manually-controlling-video-quality-layers-in-webrtc-o4jsdcmadwnm</link><description>Most developers misunderstand “manual layer selection” in WebRTC simulcast.They think the receiver simply says:“Give me 360p, 720p, or 1080p.”But in real production systems, it is usually more nuanced.With simulcast, the sender may produce multiple video layers:low     → 360p&#xA;medium  → 720p&#xA;high    → 1080pThe important question is:Who decides which layer is actually delivered?In a simple peer-to-peer setup, the sender can control which encodings are active.But in an SFU-based architecture, the SFU usually receives multiple layers and forwards the most appropriate one to each participant.For example:active speaker → high qualitynormal participant → medium qualitysmall thumbnail → low qualityweak network → lower layerpinned user → higher layerThis is why simulcast is not just a “video quality feature.”It is a bandwidth management strategy.Manual layer selection can be useful when you want to let users choose quality, debug video behavior, or optimize layouts like gallery view vs pinned speaker view.The key lesson:Simulcast is not only about sending multiple qualities.It is about choosing the right quality at the right time for the right receiver.That is one of the things that separates a basic WebRTC demo from a production-grade video platform.Have you ever worked with simulcast, SFUs, mediasoup, Janus, or Jitsi?#WebRTC #Simulcast #JavaScript #FrontendDevelopment #NodeJS #RealTimeCommunication #SoftwareEngineering</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Tue, 23 Jun 2026 21:10:24 +0330</pubDate></item><item><title>What is Simulcast?</title><link>https://virgool.ir/@adeli.farhad/what-is-simulcast-gjwopcfc03my</link><description>Most developers think WebRTC video quality is only about internet speed.But in real-time video apps, especially group calls, there is another important concept:Simulcast.Instead of sending only one video stream, the sender can send multiple versions of the same video at different qualities.For example:Low quality  →  for weak networks / small thumbnails&#xA;Medium       →  for normal participants&#xA;High quality →  for active speaker / large viewThen the receiver, SFU, or media server can choose the best layer depending on:available bandwidthdevice performancescreen sizeactive speaker layoutnetwork changesnumber of participantsThis is why in many video call apps, one person can appear in high quality when pinned, but lower quality when shown as a small thumbnail.The smart part is not just encoding video.The smart part is sending the right quality to the right user at the right time.Without this, large WebRTC rooms become expensive, unstable, and bandwidth-heavy very quickly.My simple explanation:Simulcast helps WebRTC apps avoid wasting bandwidth by sending multiple video layers and letting the system pick the most useful one.This is one of those concepts that separates a basic video call demo from a production-grade real-time communication system.Have you worked with WebRTC, mediasoup, Janus, Jitsi, or any SFU-based video system before?#WebRTC #JavaScript #FrontendDevelopment #RealTimeCommunication #SoftwareEngineering #VideoStreaming #NodeJS #WebDevelopment</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Tue, 23 Jun 2026 21:10:02 +0330</pubDate></item><item><title>What is one of the easiest ways to create a memory leak in an Angular app?</title><link>https://virgool.ir/@adeli.farhad/what-is-one-of-the-easiest-ways-to-create-a-memory-leak-in-an-angular-app-nj5pkcumloub</link><description>Angular interview question:What is one of the easiest ways to create a memory leak in an Angular app?A very common answer is:“Forgetting to unsubscribe.”That is correct, but the real issue is usually deeper.The problem is not just the missing unsubscribe().The problem is uncontrolled subscriptions.For example:ngOnInit() {&#xA;  this.userService.getUser().subscribe(user =&gt; {&#xA;    this.user = user;&#xA;  });&#xA;}&#xA;This may look harmless.But if the observable does not complete automatically, and the component gets destroyed, the subscription can stay alive.Now imagine this pattern repeated across:dashboardsfilterssearch inputsWebSocket streamsroute-based componentsreal-time notificationsSmall leaks become real production problems.A better approach is to avoid manual subscriptions when possible.Use the async pipe:&lt;div *ngIf=&#34;user$ | async as user&#34;&gt;&#xA;  {{ user.name }}&#xA;&lt;/div&gt;&#xA;Or in newer Angular versions, use takeUntilDestroyed():this.userService.getUser()&#xA;  .pipe(takeUntilDestroyed(this.destroyRef))&#xA;  .subscribe(user =&gt; {&#xA;    this.user = user;&#xA;  });&#xA;My rule of thumb:If the data is only used in the template, prefer async pipe.If you need side effects in TypeScript, make the subscription lifecycle explicit.Senior Angular development is not just knowing RxJS operators.It is knowing who owns the subscription — and when it dies.How do you usually manage subscriptions in Angular?#Angular #RxJS #FrontendDevelopment #TypeScript #WebDevelopment #SoftwareEngineering #InterviewQuestions</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Wed, 17 Jun 2026 01:41:53 +0330</pubDate></item><item><title>Why should your API not return database models directly?</title><link>https://virgool.ir/@adeli.farhad/why-should-your-api-not-return-database-models-directly-kdqrxpjmevjq</link><description>Backend interview question:Why should your API not return database models directly?A common junior answer is:“Because it works and it is faster to develop.”And honestly, for a small prototype, that might be fine.But in real production systems, returning database models directly can create problems very quickly.For example, your User table may have fields like:passwordHash&#xA;resetToken&#xA;internalNotes&#xA;roleId&#xA;createdBy&#xA;deletedAtEven if you “hide” some fields today, the risk is still there.The bigger problem is coupling.When your API response is the same shape as your database model, every database change can accidentally become an API change.That means:Frontend becomes tightly coupled to backend internalsRefactoring the database becomes riskySensitive fields may leak by mistakeAPI versioning becomes harderYour response shape is designed around storage, not user needsA better approach is to use DTOs or response mappers.The database model answers:“How do we store this data?”The API response should answer:“What does the client actually need?”That small separation makes your system much easier to maintain as it grows.Senior backend development is not just about making the endpoint work.It is about protecting future changes.How do you usually structure API responses in your projects?#NodeJS #BackendDevelopment #APIDesign #TypeScript #SoftwareEngineering #FullStackDevelopment</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Sun, 14 Jun 2026 05:47:55 +0330</pubDate></item><item><title>When would you use container queries instead of media queries?</title><link>https://virgool.ir/@adeli.farhad/when-would-you-use-container-queries-instead-of-media-queries-gxv2qwq1ntvj</link><description>Frontend interview question:A lot of developers answer:“Media queries are for responsive design.”True, but incomplete.Media queries respond to the screen size.Container queries respond to the actual size of the component’s parent/container.That difference matters a lot when you build reusable components.For example, imagine a ProductCard component.On one page, it is inside a 3-column grid.On another page, it is inside a narrow sidebar.On another page, it is inside a dashboard widget.The screen size may be the same, but the available space for the component is completely different.That is where container queries are much cleaner.Instead of asking:“Is the viewport smaller than 768px?”You ask:“How much space does this component actually have?”My rule of thumb:Use media queries for page-level layout.Use container queries for reusable components.This avoids a lot of layout bugs when the same component is reused in different places.Senior frontend development is not just about making things responsive.It is about making components survive real-world reuse.How would you explain this in an interview?#FrontendDevelopment #CSS #WebDevelopment #ReactJS #Angular #VueJS #SoftwareEngineering</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Sun, 14 Jun 2026 05:34:36 +0330</pubDate></item><item><title>React performance problems</title><link>https://virgool.ir/@adeli.farhad/react-performance-problems-vpsxdpqnzhmq</link><description>Most React performance problems are not fixed with useMemo.A lot of developers see a component re-rendering and immediately reach for:useMemo()&#xA;useCallback()&#xA;React.memo()But in many cases, that just makes the code more complex without solving the real problem.The better question is not:“How do I stop this component from re-rendering?”The better question is:“Why is this component receiving changes it does not actually need?”In my experience, most React performance issues come from:State living too high in the component treePassing unstable objects/functions as propsContext providers wrapping too much of the appComponents doing too many unrelated jobsRendering large lists without virtualizationuseMemo can help, but it should usually be the second or third solution — not the first one.A simple rule I use:Before optimizing a component, first fix the data flow.Clean architecture beats random memoization almost every time.What is your usual first step when a React component becomes slow?#ReactJS #FrontendDevelopment #JavaScript #WebDevelopment #SoftwareEngineering</description><category>Farhad Adeli</category><author>Farhad Adeli</author><pubDate>Fri, 12 Jun 2026 17:18:39 +0330</pubDate></item></channel></rss>