<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های امیرمسعود</title>
        <link>https://virgool.io/feed/@m_77965416</link>
        <description>مهندس نرم افزار که علاقه داره هرچیزی رو که یاد میگیره با بقیه به اشتراک بزاره</description>
        <language>fa</language>
        <pubDate>2026-06-10 14:07:36</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/1661577/avatar/BZ4j0m.jpg?height=120&amp;width=120</url>
            <title>امیرمسعود</title>
            <link>https://virgool.io/@m_77965416</link>
        </image>

                    <item>
                <title>کاربرد های Rule Engine در توسعه نرم افزار</title>
                <link>https://virgool.io/@m_77965416/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-rules-engin-design-patterns-isfctfhpnn6j</link>
                <description>مقدمهچرا باید از Rule Engine Design Pattern استفاده کنیم؟ وقتی در منطق برنامه خود از شرط‌های تو در تو استفاده می‌کنیم، پیچیدگی برنامه افزایش می‌یابد. در اینجا، این الگوی طراحی به کمک ما می‌آید. با مطالعه این الگو، متوجه می‌شوید که جمله‌ای شبیه به زیر در مورد آن بیان می‌شود:&quot;یک الگوی خوب برای از بین بردن پیچیدگی‌ها در یک منطق پر از شرط و جایگزینی آن با کدی که قابل توسعه و ماژولار باشد.&quot;این Rules Engine Design Pattern چیست ؟ یک Rule Engine در عمل مجموعه‌ای از قواعد یا همان Rule‌ها را درون خود نگه می‌دارد و این Rule‌ها را بر روی یک context پیاده‌سازی می‌کند تا یک نتیجه به دست آید. پس، Engine یک بخش از این الگو و مجموعه Rule‌ها بخش دیگر این الگو هستند. این نکته را در نظر بگیرید که این دو بخش از هم جدا هستند. در عمل، Engine این شرط‌ها یا همان Rule‌ها را بر روی یک context پیاده‌سازی کرده و نتیجه را به ما برمی‌گرداند.پس Rule ها مشخص است به یک شرط اشاره میکنن که باعث میشن یک Result برگردانده شود و خب این شرط ها در کنار همدیگه قرار میگیرند تا توسط engine استفاده شود در ضمن این نکته رو هم به یاد داشته باشید که ممکنه به صورت ترکیبی و یا براساس یک Order خاص اینا اجرا بشن یا حتی فیلتر بشن و ممکن براساس دلایلی اصلا کنار گذاشته بشوند. خب اما این بیاد بررسی کنیم تا ببینیم این الگو تو کدوم دسته بندی از این design patterns ها قرار میگیره اما قلبش بیاد ببینیم چند مدل  design patterns داریم Design Pattern ها به 3 مدل دسته بندی میشن که به صورت خلاصه میشه:الگوهای creational الگوهای creational مسئله ی ایجاد شی رو بررسی می کنن. اینکه در هر شرایطی بهتره ایجاد شی رو به چه صورت انجام بدیم.الگوهای Structuralالگوهای Structural یا ساختاری روی مسئله ی ترکیب آبجکت ها و کلاس ها برای شکل دادن ساختار های بزرگ تر تمرکز دارن، به نحوی که قابلیت انعطاف و کارایی برنامه در حد امکان در بالاترین حد باشه.الگوهای Behavioral الگوهای طراحی Behavioral یا رفتاری روی بحث تقسیم وظایف بین کلاس های مختلف تمرکز دارن.بنابراین، الگوی Rules Engine به دسته Behavioral تعلق دارد چون شرط‌های پیچیده ما را بین کلاس‌های مختلف تقسیم می‌کند و رفتارهای متفاوت را چک می‌کند.کاربردهای الگواین الگو کجا به درد می‌خورد؟ به عنوان مثال، فرض کنید یک بانک می‌خواهد به یک فرد وام بدهد. بانک پارامترهای مختلفی را در نظر می‌گیرد: آیا فرد شغل دارد؟ میزان درآمد چقدر است؟ شغل فرد وابسته به چه ارگانی است؟ پس از بررسی و اعمال شرط‌ها، در نهایت مشخص می‌شود که آیا فرد واجد شرایط وام هست یا نه.مشکلاتی که الگو برطرف می‌کندیکی از مشکلاتی که این الگو برطرف می‌کند، مشکل &quot;اصل باز/بسته&quot; (Open/Closed Principle) است. به جای اینکه برای اضافه کردن یک Rule جدید کدهای قدیمی را تغییر دهید، می‌توانید یک Rule جدید را اضافه کنید.اما بدنه این pattern چیزی شبیه به تصویر زیر استکه یک سری شرط داریم که این شرط همون Rule های ما هستش که یک interface را پیاده سازی میکند و یک Engine داریم که لیستی از این Rule ها را به ان پاس میدهیم  که باعث اجرا شدن انها میشود  اما هر Rule باید اولا یک خروجی ساده باشه و یک Rule یک کار بزرگ پیچیده رو انجام نده. و خب اما وقتش رسیده بریم سراغ کد!!!به عنوان مثال، کدی را بررسی می‌کنیم که مقدار کالری مورد نیاز بدن یک شخص را بسته به یک سری اطلاعات محاسبه می‌کند.کد کثیفpublic class BadCalculateCode
{

 public int CalculateCaloriesPercentage(BmrModel bmrModel)
    {
 int currentCalories = 0 ;

 if (bmrModel.Age &gt;= 1 &amp;&amp; bmrModel.Age &lt;= 9)
        {
 currentCalories += 3;
        }else if (bmrModel.Age &gt;= 10 &amp;&amp; bmrModel.Age &lt;= 20)
        {
 currentCalories += 4;
        }

 if (bmrModel.Gender == Gender.Male)
        {
 currentCalories += 1;
        }
 else
 {
 currentCalories += 2;
        }

 if (bmrModel.Weight &lt; 60)
        {
 currentCalories += 7;
        }
 else
 {
 currentCalories += 8;
        }

 return currentCalories;
    }
}این کد با شرط‌های تو در تو پر شده است. حالا بهبودش می‌دهیم.کد تمیزاول، یک مدل تعریف می‌کنیم که همان context ماست:public class BmrModel 
{
 public Gender Gender { get; set; }
 public int Age { get; set; }
 public int Height { get; set; }
 public int Weight { get; set; }
}سپس، یک interface تعریف می‌کنیم که همه Rule‌ها باید از آن ارث‌بری کنند:که این interface یک متد داره به اسم CalculateCaloriesPercentage که تو توضیحات بالا گفتیم که این rule ها بر روی یک context پیاده سازی میشوند که context ما اینجا BmrModel هستش و یک currentCalories که شرط های مختلف داریم که سبب تغییر currentCalories پس باید به هر rule این مقدار رو پاس بدیم تا بتونیم در پایان از اون استفاده کنیم.public interface ICaloriesRule
{
 int CalculateCaloriesPercentage(BmrModel bmrModel ,int currentCalories); 
}حال، شرط‌های خود را به صورت کلاس‌های جداگانه می‌نویسیم:شرط سنpublic class AgeRule : ICaloriesRule
{
 public int CalculateCaloriesPercentage(BmrModel bmrModel, int currentCalories)
    {
 return bmrModel.Age switch
 {
 &gt;= 1 and &lt;= 9 =&gt; 3,
 &gt;= 10 and &lt;= 20 =&gt; 4,
 &gt;= 21 and &lt;= 30 =&gt; 5,
 &gt;= 31 and &lt;= 40 =&gt; 6,
 &gt;= 41  =&gt; 6,
 _ =&gt; 0
 };
    }
}شرط جنسیتpublic class GenderRule : ICaloriesRule
{
 public int CalculateCaloriesPercentage(BmrModel bmrModel, int currentCalories)
    {
 return bmrModel.Gender == Gender.Male ? 1 : 2;
    }
}شرط قدpublic class HeightRule : ICaloriesRule
{
 public int CalculateCaloriesPercentage(BmrModel bmrModel, int currentCalories)
    {
 return bmrModel.Height switch
 {
 &lt; 150 =&gt; 9,
 &gt;= 150 =&gt; 10
 };
    }
}شرط وزنpublic class WeightRule : ICaloriesRule
{
 public int CalculateCaloriesPercentage(BmrModel bmrModel, int currentCalories)
    {
 return bmrModel.Weight switch
 {
 &lt;= 60 =&gt; 7,
 &gt; 60 =&gt; 8
 };
    }
}حال، Engine خود را می‌سازیم:public class BmrRuleEngin
{
 List&lt;ICaloriesRule&gt; rules = new();

 public BmrRuleEngin(IEnumerable&lt;ICaloriesRule&gt; _rules)
    {
 rules.AddRange(_rules);
    }

 public int CalculateCaloriesPercentage(BmrModel bmrModel)
    {
 var currentCalories = 0;
 foreach (var rule in rules)
        {
    currentCalories = rule.CalculateCaloriesPercentage(bmrModel, currentCalories);
        }
 return currentCalories;
    }
}این کلاس لیستی از Rule‌ها دارد و متدی که context (BmrModel) را می‌گیرد و مقادیر را محاسبه می‌کند.سپس، کلاسی برای استفاده از این Engine می‌نویسیم:public class BmrRuleCalculator
{
 public int CalculateTaxPercentage(BmrModel bmrModel)
   {
 var ruleType = typeof(ICaloriesRule);
 var rules = GetType().Assembly.GetTypes()
         .Where(a =&gt; ruleType.IsAssignableFrom(a) &amp;&amp; !a.IsInterface)
         .Select(q =&gt; Activator.CreateInstance(q) as ICaloriesRule).ToList();

 var engin = new BmrRuleEngin(rules);
 
 return engin.CalculateCaloriesPercentage(bmrModel);

   }

}این کلاس با استفاده از Reflection، همه کلاس‌هایی که  ICaloriesRule پیاده سازی کرده‌اند را پیدا کرده و آن‌ها را به Engine ما می‌دهد.برای تست، یک متد تست می‌نویسیم:public class CaloriesTest
{
 private BmrRuleCalculator _ruleCalculator = new();
    [Fact]
 public void Person_Calories_Calculate()
    {
 //Arrange
 var bmrModel = new BmrModel
 {
 Age = 20,
 Gender = Gender.Male,
 Height = 180,
 Weight = 70
 };
 //Act
 var result = _ruleCalculator.CalculateCaloriesPercentage(bmrModel);
 //Assert
 Assert.Equal(8,result);
    }
}نتیجه‌گیریالگوی Rules Engine مزایای متعددی دارد، از جمله امکان کار گروهی و افزایش قابلیت نگهداری کد. این الگو به ما اجازه می‌دهد که به سادگی Rule‌های جدیدی اضافه کنیم بدون اینکه نیاز به تغییر در کدهای قدیمی داشته باشیم. با استفاده از این الگو، می‌توانیم پیچیدگی‌های مربوط به شرط‌های تو در تو را کاهش دهیم و کدی تمیز و ماژولار بنویسیم.منابع و مراجعبرای مطالعه بیشتر در مورد Rules Engine Design Patterns، می‌توانید به منابع زیر مراجعه کنید:Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John VlissidesEnterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions by Gregor Hohpe and Bobby Woolfhttps://medium.com/امیدوارم این مقاله برای شما مفید واقع شده باشد.</description>
                <category>امیرمسعود</category>
                <author>امیرمسعود</author>
                <pubDate>Tue, 30 Jan 2024 22:41:15 +0330</pubDate>
            </item>
                    <item>
                <title>fire and forget database command with entity framework</title>
                <link>https://virgool.io/@m_77965416/fire-and-forget-database-command-with-entity-framwork-hsjtp6bpzpfc</link>
                <description>در کل برای شما پیش اومده که وقتی میخواین یه کامند دیتابیسی رو اجرا کنید منتظر بموندید که اجرا شدن اون کامند تموم بشه. برای مثال فرض کنید شما قصدا دارید که یک اطلاعات یک کاربری رو در یک جدول دیتایبس ذخیره کنید و بعد از اون یک لاگ هم به عنوان تاریخچه از این عملیات در یک جدوله دیگه ذخیره کنید و خب احتمالا روال شما برای انجام اینکار شبیه کد زیر باشهpublic class UserService
{
 private readonly IUserRepository _userRepository;
 private readonly IUserHistoryRepository _historyRepository;
 private readonly IMapper _mapper;

 public UserService(IUserRepository userRepository,IMapper mapper
 ,IUserHistoryRepository historyRepository)
    {
 _userRepository = userRepository;
 _mapper = mapper;
 _historyRepository = historyRepository;
    }

 public async Task AddUser(AddUserDto userDto)
    {
 var user = _mapper.Map&lt;DomainClass.User.User&gt;(userDto);

 await _userRepository.Insert(user);

 await _historyRepository.AddUserHistory(user);
    }
}که یک متدی هستش که یک اطلاعات کاربر رو دریافت میکنه و بعد از ذخیره کردن در جدول اصلی اون اطلاعات رو هم در یک جدول دیگه به عنوان تاریخچه ذخیره میکنه(احتمالا شما روی دیتای اصلی یک عملیات منطقی پیاده میکنید و بعد اونو به عنوان تاریخچه ذخیره میکنید اما خب اینجا به اون اشاره نشده ) کد بالاهم هیچ مشکلی نداره و کامندها خیلی قشنگ در داخل دیتابیس اجرا میشه و هم اطلاعات کاربر در جدول خودش ذخیره میشه هم به عنوان تاریخچه در جدول دیگه .اما خب همیشه قرار نیست سناریو های ما اینقدر ساده باشه یعنی شما فرض کنید که عملیات لاگ زدن ما  بنابر منطقی که ما میخوایم پیاده کنیم عملیات زمان بری باشه اون موقعه چی؟ ینی اگه عملیات لاگ ما 5 ثانیه طول بکشه ما باید 5 ثانیه منتظر بمونیم تا تموم بشه و بعدش بریم بقیه کارمون رو انجام بدیم خب صد در صد نه پس بریم ببینیم چیکار باید بکنیم که مشکل Performance برای ما پیش نیادراه حل!میتونیم ما اون بخش از عملیاتی که مربوط به لاگ زدن هستش رو بیایم و در background ایجاد کنیم ( خواهشا خیلی سخت فکر نکنید در اینجا که بیایم صف درست کنیم یا از channel ها استفاده کنیم)  باید کار زیر رو انجام بدیم public class UserService
{
 private readonly IUserRepository _userRepository;
 private readonly IUserHistoryRepository _historyRepository;
 private readonly IMapper _mapper;

 public UserService(IUserRepository userRepository,IMapper mapper
 ,IUserHistoryRepository historyRepository)
    {
 _userRepository = userRepository;
 _mapper = mapper;
 _historyRepository = historyRepository;
 
    }

 public async Task AddUser(AddUserDto userDto)
    {
 var user = _mapper.Map&lt;DomainClass.User.User&gt;(userDto);

 await _userRepository.Insert(user);
 await _userRepository.SaveChangesAsync();

 
 await Task.Run(async () =&gt;
 {
 await _historyRepository.AddUserHistory(user);
 await _historyRepository.SaveChangesAsync();
        });
 
    }
}اما خب این تیکه کد بعد اجرا شدن error میده و اونم این هستش که میگه DbContext  رو نمیشه تو چندتا ترد استفاده کرد یا به اصطلاح thread safe  نیستش پس اینجا ما برای رفع این مشکل باید بیایم و یک dbcontext جدید بسازیم اونم به این صورت public class UserService
{
 private readonly IUserRepository _userRepository;
 private readonly IUserHistoryRepository _historyRepository;
 private readonly IMapper _mapper;
 private readonly IServiceProvider _serviceProvider;

 public UserService(IUserRepository userRepository,IMapper mapper
 ,IUserHistoryRepository historyRepository,IServiceProvider serviceProvider)
    {
 _userRepository = userRepository;
 _mapper = mapper;
 _historyRepository = historyRepository;
 _serviceProvider = serviceProvider;
 
    }

 public async Task AddUser(AddUserDto userDto)
    {
 var user = _mapper.Map&lt;DomainClass.User.User&gt;(userDto);

 await _userRepository.Insert(user);
 await _userRepository.SaveChangesAsync();

 
 await Task.Run(async () =&gt;
 {
 var ctx = _serviceProvider.CreateScope().ServiceProvider.GetService&lt;IUserHistoryRepository&gt;();
 await ctx.AddUserHistory(user);
 await ctx.SaveChangesAsync();
        });
 
    }
}برای اینکه ما بهخوایم یک نمونه جدید از ریپازیتوری خودمون   بسازیم باید اول IServiceProvider رو در داخل کلاس خودمون inject کنیمprivate readonly IServiceProvider _serviceProvider;  
 
public UserService(IServiceProvider serviceProvider)   
  {    
_serviceProvider = serviceProvider;      
 }و خب با انجام این کار ما عملکرد برنامه خودمون رو زیاد کردیم برنامه ما user رو در جدول مورد نظرمون insert میکنه و بدون اینکه منتظر بمونه لاگ user در جدول مورد نظر insert بشه .بهترش کن!و خب ما همیشه سعی میکنیم از IServiceScopeFactory که یک dependency injector container تو کدمون کمتر استفاده کنیم و خب استفاده از اون یک انتی پترن هستش اما خب ما در این مورد مجبور هستیم اما میتونیم یک سازوکاری برای این قضیه تهیه کنیم که بتونیم اونو محدود و از دید پنهان کنیم public sealed class FireForget{    private readonly IServiceScopeFactory _serviceScopeFactory;    public FireForget(IServiceScopeFactory serviceScopeFactory)    {        _serviceScopeFactory = serviceScopeFactory;    }        public void Execute&lt;IRepository&gt;(Func&lt;TService, Task&gt; func)    {        Task.Run(async () =&gt;        {            try            {                // initialize the DbContext (or other service) from the service scope                var context = _serviceScopeFactory.CreateScope().ServiceProvider.GetService&lt;IRepository&gt;();                              // executing the function                await func(context);            }            catch (Exception e)            {                Console.WriteLine(e);            }        });    }}یک کلاس ساختیم به اسم FireForget که یک متد داره به اسم Execute که Generic هستش که یک type میگیره و یه نمونه سازی از اون میکنه و اون رو روی یک thread دیگه از thread pool اجرا میکنه و یک lambda function هم داره فقط یادمون باشه که باید fireforget رو به عنوان Singleton تو برنامه خودمون register کنیمbuilder.Services.AddSingleton&lt;FireForget&gt;();بعد از اینکه کار خودمون رو کردیم حالا باید بیایم و کدمون رو refactor کنیمpublic class UserService
{
 private readonly IUserRepository _userRepository;
 private readonly IUserHistoryRepository _historyRepository;
 private readonly IMapper _mapper;
 private readonly FireForget _fireForget;

 public UserService(IUserRepository userRepository,IMapper mapper
 ,IUserHistoryRepository historyRepository,FireForget _fireForget)
    {
 _userRepository = userRepository;
 _mapper = mapper;
 _historyRepository = historyRepository;
 
    }

 public async Task AddUser(AddUserDto userDto)
    {
 var user = _mapper.Map&lt;DomainClass.User.User&gt;(userDto);

 await _userRepository.Insert(user);
 await _userRepository.SaveChangesAsync();
 
 
 _fireForget.Execute&lt;IUserHistoryRepository&gt;( async ctx =&gt;
 {
 await ctx.AddUserHistory(user);
 await ctx.SaveChangesAsync();
      });
 
    }
}و بدیه صورت میتونید لاگ بزنید و عملکرد سیستمون هم خوب باشه.سخن پایانیاین اولین مقاله من در ویرگول هستش قصد دارم چیزایی که یاد میگیرم رو بیشتر با شما به اشتراک بزارماینم ایمیل من هستش خوشحال میشم اگه گفته ای داشته باشید میتونید با این ادرس با من درارتباط باشید.masoudpaidar76@gamil.com</description>
                <category>امیرمسعود</category>
                <author>امیرمسعود</author>
                <pubDate>Sun, 31 Dec 2023 21:32:50 +0330</pubDate>
            </item>
            </channel>
</rss>