توی این پست میخوایم راجب Decorator حرف بزنیم
پس سوال اصلی اینه Decorator چیه؟
دکوریتور ذاتا یک تابع سازندس که خروجیش یک فانکشنه ، حالا چیکار میکنه ؟ دکوریتور میاد یکسری قابلیت و رفتار رو به یک متد ، پراپرتی یا کلس اضافه میکنه بدون اینکه ماهیت و منطق اصلیشو تغییر بده ! این خیلی کمک میکنه به تمیزی و خوانا بودن کدت ، کار اصلی دکوریتور تزریق متادیتاها هست( در پست های بعدی راجبش تخصصی تر صحبت میکنیم)
مثال عملی :
تو این مثال از یک دکوریتور از پیش تعریف شده در NestJs استفاده کردیم ، وقتی این دکوریتور روی این کلس اعمال میشه ، با استفاده از تزریق متادیتا به سیستم DI میفهمونه که این کلس تزریق شدنی(Injectable) هست و باید یک نمونه از این کلس رو بسازه تا برای تزریق اماده باشه !
حالا بیاین یک دکوریتور کاستوم که خودمون نوشتیم رو روی یک کلس اعمال کنیم که درک عمیق تری از دکوریتور داشته باشیم
این دکوریتور میاد این کلس رو قفل میکنه و نمیزاره پراپرتی جدیدی بهش اضافه کنی ، از اونجا که دکوریتور نویسی یک مقدار پیچیدس در پست های بعدی راجبش تخصصی تر صحبت میکنیم.
#ts
پس سوال اصلی اینه Decorator چیه؟
دکوریتور ذاتا یک تابع سازندس که خروجیش یک فانکشنه ، حالا چیکار میکنه ؟ دکوریتور میاد یکسری قابلیت و رفتار رو به یک متد ، پراپرتی یا کلس اضافه میکنه بدون اینکه ماهیت و منطق اصلیشو تغییر بده ! این خیلی کمک میکنه به تمیزی و خوانا بودن کدت ، کار اصلی دکوریتور تزریق متادیتاها هست( در پست های بعدی راجبش تخصصی تر صحبت میکنیم)
مثال عملی :
@Injectable ()
export class UserService {}
تو این مثال از یک دکوریتور از پیش تعریف شده در NestJs استفاده کردیم ، وقتی این دکوریتور روی این کلس اعمال میشه ، با استفاده از تزریق متادیتا به سیستم DI میفهمونه که این کلس تزریق شدنی(Injectable) هست و باید یک نمونه از این کلس رو بسازه تا برای تزریق اماده باشه !
حالا بیاین یک دکوریتور کاستوم که خودمون نوشتیم رو روی یک کلس اعمال کنیم که درک عمیق تری از دکوریتور داشته باشیم
function sealed(target: Function) {
Object.seal(target);
Object.seal(target.prototype);
}
@sealed
class Person {}این دکوریتور میاد این کلس رو قفل میکنه و نمیزاره پراپرتی جدیدی بهش اضافه کنی ، از اونجا که دکوریتور نویسی یک مقدار پیچیدس در پست های بعدی راجبش تخصصی تر صحبت میکنیم.
#ts
تو این پست میخوایم راجب متادیتا صحبت کنیم
- یکی از ویژگیای خاص تایپ اسکریپت توانایی استفاده از متادیتاس ، اما این متادیتا چیه ؟ متادیتا یک سری اطلاعات اضافین که میتونن به class ، property , method تزریق بشن.
این اطلاعات عموما به صورت key-value ذخیره میشن و با استفاده از decorator ها و کتابخونه هایی مثل reflect-metadata قابل set شدن و get شدن هستن
- کاربردای اصلیش میشه به موردای زیر اشاره کرد :
• تزریق وابستگی( DI) : تو فریمورکایی مثل NestJs از طریق متادیتا ما متوجه میشیم چه چیزی باید قابل تزریق باشه و ...
• اعتبارسنجی (validation) : ما با استفاده از متادیتا میتونیم یک سری اطلاعات رو به پراپرتی ها اضافه کنیم ( مثلا بگیم فیلد x حتما required هست ) که مثالش رو باهم در محیط برنامه نویسی میبینیم
اینجا مشخص کردیم که پراپرتی name الزامیه و باید وارد بشه بدون اینکه تو منطق class اختلال یا تغییری ایجاد کنیم
ختم کلام : متادیتا یک لایه اضافی اطلاعاتی رو برنامته که موقع اجرا میتونی بهش دسترسی داشته باشی و با توجه بهش میتونی رفتار و اساس برنامتو تغییر بدی بدون ضربه زدن به منطق اصلیه برنامه !
- یکی از ویژگیای خاص تایپ اسکریپت توانایی استفاده از متادیتاس ، اما این متادیتا چیه ؟ متادیتا یک سری اطلاعات اضافین که میتونن به class ، property , method تزریق بشن.
این اطلاعات عموما به صورت key-value ذخیره میشن و با استفاده از decorator ها و کتابخونه هایی مثل reflect-metadata قابل set شدن و get شدن هستن
- کاربردای اصلیش میشه به موردای زیر اشاره کرد :
• تزریق وابستگی( DI) : تو فریمورکایی مثل NestJs از طریق متادیتا ما متوجه میشیم چه چیزی باید قابل تزریق باشه و ...
• اعتبارسنجی (validation) : ما با استفاده از متادیتا میتونیم یک سری اطلاعات رو به پراپرتی ها اضافه کنیم ( مثلا بگیم فیلد x حتما required هست ) که مثالش رو باهم در محیط برنامه نویسی میبینیم
function Required(target: any, propertyKey: string) {
Reflect.defineMetadata('required', true, target, propertyKey);
}
class User {
@Required
name: string;
}
const isRequired = Reflect.getMetadata('required', User.prototype, 'name');
console.log(isRequired); // trueاینجا مشخص کردیم که پراپرتی name الزامیه و باید وارد بشه بدون اینکه تو منطق class اختلال یا تغییری ایجاد کنیم
ختم کلام : متادیتا یک لایه اضافی اطلاعاتی رو برنامته که موقع اجرا میتونی بهش دسترسی داشته باشی و با توجه بهش میتونی رفتار و اساس برنامتو تغییر بدی بدون ضربه زدن به منطق اصلیه برنامه !
تو این پست میخوایم راجب decleration file حرف بزنیم
- این فایل ها مثل شناسنامه میمونن ! تمامی اطلاعات و تایپ های لازم برای یک فایل جاوا اسکریپتی رو داخل خودش داره. مثل نوع متغیرا ، خروجیای فانکشنا ، تایپ های متدا و ..
حالا سوال اصلی اینه ، چرا اصلا باید همچین فایلی داشته باشیم ؟
این عمل باعث میشه بتونی از کتابخونه های جاوا اسکریپتی بتونی تو فایلای تایپ اسکریپت استفاده کنی ، حتی با اینکه فایل یا پکیج جاوا اسکریپتیه میتونی type checking داشته باشی !
مثال زیرو ببینین
الان ما دوتا فانکشن داریم ، ولی ما نه تایپ ورودیو میدونیم نه خروجیو ! اینجاس ک میایم براش decleration file تعریف میکنیم و این مشکلو برطرف میکنیم
توی decleration file اومدیم گفتیم ما در فایل app.js دوتا فانکشن داریم به اسم add و subtract ، حالا این دو فانکشن ورودی هاشون از نوع نامبر و خروجی هاشونم از نوع نامبره! اینشکلی وقتی بخوایم از app.js توی فایلای تایپ اسکریپتی استفاده کنیم همچنان type checking رو داریم با اینکه فایل جاوا اسکریپتیه.
- این فایل ها مثل شناسنامه میمونن ! تمامی اطلاعات و تایپ های لازم برای یک فایل جاوا اسکریپتی رو داخل خودش داره. مثل نوع متغیرا ، خروجیای فانکشنا ، تایپ های متدا و ..
حالا سوال اصلی اینه ، چرا اصلا باید همچین فایلی داشته باشیم ؟
این عمل باعث میشه بتونی از کتابخونه های جاوا اسکریپتی بتونی تو فایلای تایپ اسکریپت استفاده کنی ، حتی با اینکه فایل یا پکیج جاوا اسکریپتیه میتونی type checking داشته باشی !
مثال زیرو ببینین
// app.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
الان ما دوتا فانکشن داریم ، ولی ما نه تایپ ورودیو میدونیم نه خروجیو ! اینجاس ک میایم براش decleration file تعریف میکنیم و این مشکلو برطرف میکنیم
// app.d.ts
export function add(a: number, b: number): number;
export function subtract(a: number, b: number): number;
توی decleration file اومدیم گفتیم ما در فایل app.js دوتا فانکشن داریم به اسم add و subtract ، حالا این دو فانکشن ورودی هاشون از نوع نامبر و خروجی هاشونم از نوع نامبره! اینشکلی وقتی بخوایم از app.js توی فایلای تایپ اسکریپتی استفاده کنیم همچنان type checking رو داریم با اینکه فایل جاوا اسکریپتیه.
🔥1
تو این پست میخوایم راجب encapsulation صحبت کنیم. چیزی که فکرمیکنین بلدین ولی نیستین
-خب اول از همه باید بدونیم که encapsulation یک مفهوم و یک طرز فکر برای حل یک مسئلس. نه صرفا ی چارچوب برای کد نوشتن. حالا بریم ببینیم چه مشکلی رو ازمون حل میکنه
- ما یک مشکل بزرگی ک داریم ، اینه ک محصولاتی ک داریم وقتی به دست مشتری میرسن ممکنه با تغییراتی در چندین جناح اپ ، عملکردشو مختل کنن ، یعنی مشتری از جزئیات اپ ما خبر داره و به درست یا غلط میره اونارو تغییر میده ! خب این سناریو اصلا نباید اتفاق بیوفته. تصور کن شما ی بانک رو میخوای از صفر طراحی کنی ، ایا این توی تصورت هست ک موجودی حسابی کاربر رو خودش تعیین کنه ؟ نه. پس ما نباید بزاریم به صورت مستقیم کاربر به موجودی حساب خودش دسترسی داشته باشه.
-خب الان ما مشکلو پیدا کردیم. ما مشکلمون اینه که گاهی اوقات جزئیاتیو در اختیار کاربر میزاریم که ممکنه باعث خرابیه سیستم بشن. حالا چجوری بفهمیم چه چیزی این ویژگی رو داره و چجوری ازش جلوگیری کنیم ؟ اینجاست که encapsulation به کمکمون میاد.
-مفهوم encapsulation به زبون ساده میگه شما تا حد ممکن باید جزئیات رو از دید کلاینت مخفی کنی . حالا این مخفی کردنه صرفا این نیست که شما لاجیک رو داخلش پرایوت نگه دازی. مثل اون بانکس ک اجازه دسترسی به داخل یک سیستم رو ندی. و این اصل به ما کمک میکنه که شما اپلیکیشن ایمن تر و قابل اعتماد تری داشته باشی.
- مثال خیلی سادش ماشینه ! مشکل ماشین های ما اینه که اگر ما فرض کنید کرم داشته باشیم و بخوایم تست کنیم و از دنده ۵ یهو بکشیم ۱ ماشین رو باید ساعت ۹ بزاریم دم در(ماشینای دنده ای) ، خب اینجا ما باید جزئی به اسم دنده رو encapsulate کنیم که از این اتفاق جلوگیری کنیم !.
- حالا چرا گفتم که فکرمیکنین ازش خبر دارین ؟ چون اکثر برنامه نویسا وقتی اسم encapsulation میاد ذهنشون میره سمت چیزی به اسم access modifier ها ، که توی زبانی مثل typenoscript به (public , private , protected) میشناسمشون ، و هدف این پست این بود که شما متوجه بشی صرفا پرایوت کردن ی متد یا یک پراپرتی اسمش encapsualtion نیست ، و این یک طرز فکر برای حل یک مسئله بزرگتره ! اینکه توی شئ گرایی ما با access modifier ها encapsulate میکنیم یک سری چیز هارو ، دلیل نمیشه بدون اون ها ما نتونیم encapsulation داشته باشیم.
-خب اول از همه باید بدونیم که encapsulation یک مفهوم و یک طرز فکر برای حل یک مسئلس. نه صرفا ی چارچوب برای کد نوشتن. حالا بریم ببینیم چه مشکلی رو ازمون حل میکنه
- ما یک مشکل بزرگی ک داریم ، اینه ک محصولاتی ک داریم وقتی به دست مشتری میرسن ممکنه با تغییراتی در چندین جناح اپ ، عملکردشو مختل کنن ، یعنی مشتری از جزئیات اپ ما خبر داره و به درست یا غلط میره اونارو تغییر میده ! خب این سناریو اصلا نباید اتفاق بیوفته. تصور کن شما ی بانک رو میخوای از صفر طراحی کنی ، ایا این توی تصورت هست ک موجودی حسابی کاربر رو خودش تعیین کنه ؟ نه. پس ما نباید بزاریم به صورت مستقیم کاربر به موجودی حساب خودش دسترسی داشته باشه.
-خب الان ما مشکلو پیدا کردیم. ما مشکلمون اینه که گاهی اوقات جزئیاتیو در اختیار کاربر میزاریم که ممکنه باعث خرابیه سیستم بشن. حالا چجوری بفهمیم چه چیزی این ویژگی رو داره و چجوری ازش جلوگیری کنیم ؟ اینجاست که encapsulation به کمکمون میاد.
-مفهوم encapsulation به زبون ساده میگه شما تا حد ممکن باید جزئیات رو از دید کلاینت مخفی کنی . حالا این مخفی کردنه صرفا این نیست که شما لاجیک رو داخلش پرایوت نگه دازی. مثل اون بانکس ک اجازه دسترسی به داخل یک سیستم رو ندی. و این اصل به ما کمک میکنه که شما اپلیکیشن ایمن تر و قابل اعتماد تری داشته باشی.
- مثال خیلی سادش ماشینه ! مشکل ماشین های ما اینه که اگر ما فرض کنید کرم داشته باشیم و بخوایم تست کنیم و از دنده ۵ یهو بکشیم ۱ ماشین رو باید ساعت ۹ بزاریم دم در(ماشینای دنده ای) ، خب اینجا ما باید جزئی به اسم دنده رو encapsulate کنیم که از این اتفاق جلوگیری کنیم !.
- حالا چرا گفتم که فکرمیکنین ازش خبر دارین ؟ چون اکثر برنامه نویسا وقتی اسم encapsulation میاد ذهنشون میره سمت چیزی به اسم access modifier ها ، که توی زبانی مثل typenoscript به (public , private , protected) میشناسمشون ، و هدف این پست این بود که شما متوجه بشی صرفا پرایوت کردن ی متد یا یک پراپرتی اسمش encapsualtion نیست ، و این یک طرز فکر برای حل یک مسئله بزرگتره ! اینکه توی شئ گرایی ما با access modifier ها encapsulate میکنیم یک سری چیز هارو ، دلیل نمیشه بدون اون ها ما نتونیم encapsulation داشته باشیم.
🔥1
تو این پست میخوایم راجب polymorphism صحبت کنیم . چیزی که شاید ازش خبر داشته باشین ولی در عمل ازش اصلا استفاده نمیکنین !
- ابن مفهوم مانند encapsulation یک طرز فکر برای حل مسئلس ، و صرفا برای یک پاردایم خاص و چارچوب خاصی تعریف نشده ، حالا بریم ببینیم چه مشکلی داریم و چجوری حلش میکنه.
- ما یک مشکلی ک داریم وقتی سرویس ها فانکشن ها و متد هایی مینویسیم ، خیلی اوقات انعطاف لازم رو ندارن ، فرض کنید ما یک تابعی داریم به اسم add ، ما نیاز داریم بعضی جاها علاوه بر اینکه دوتا عدد رو جمع کنه ، بیاد دوتا string رو هم جمع کنه ، این مسئلرو چجوری حل میکنین ؟ میاین دوتا فانکشن برای اینکار تعریف میکنین ؟ خب این وقتی exeption ها زیاد شدن مدیریتش سخت میشه. اینجاست که polymorphism به دادمون میرسه.
- خب الان راه حل چیه ؟ باید چیکارکنیم ؟ خب راه حلی که به ذهنتون رسیده اینه که بیایم با توجه به تایپای ورودی خروجیای متفاوت بدیم( اگر هر دو string بودن پس خروجی string باشه و اگر هر دو number بودن خروجی number میشه ) . و همینجا شما داری از polymorphism استفاده میکنی بدون اینکه خبر داشته باشی😂.
- پس polymorphism میگه متد ، فانکشن و هر چیزی که لاجیک داخلشه رو باید انعطاف پذیر توسعه بدی که تو شرایطای مختلف کارکنن . مثل همون داستان فانکشن add . پس الان تا حدودی فهمیدیم که polymorphism چه کمکی بهمون میکنه و چطور باید ازش استفاده کرد .
- تو شرایط مختلف میتونیم polymorphism رو داشته باشیم. صرفا محدود به پاردایم ها نیست ، الان برای همین تابع add ما میتونیم با generic type ها مسئلرو حل کنیم
- وقتی ما oop base کد میزنیم یک قابلیت مهمی که بهمون میده و میتونیم از یک روش بهش برسیم همین polymorphism هست ، در oop ما با sub-typing به polymorphism میرسیم. و در بقیه سناریو ها میتونیم از روشای مختلف بهش برسیم
- پس الان فکرکنم متوجه شدین که این مفهوم به ظاهر سخت ، مخصوص oop نیست و ما بدون oop هم میتونیم polymorphism رو داخل کدمون داشته باشیم ، پس اگه ما از class استفاده نکنیم و نتونیم با sub-typing به polymorphic بودن برسیم در تمام اعضای برناممون ، دلیل بر این نیست که polymorphism مختص به oop باشه.
- ابن مفهوم مانند encapsulation یک طرز فکر برای حل مسئلس ، و صرفا برای یک پاردایم خاص و چارچوب خاصی تعریف نشده ، حالا بریم ببینیم چه مشکلی داریم و چجوری حلش میکنه.
- ما یک مشکلی ک داریم وقتی سرویس ها فانکشن ها و متد هایی مینویسیم ، خیلی اوقات انعطاف لازم رو ندارن ، فرض کنید ما یک تابعی داریم به اسم add ، ما نیاز داریم بعضی جاها علاوه بر اینکه دوتا عدد رو جمع کنه ، بیاد دوتا string رو هم جمع کنه ، این مسئلرو چجوری حل میکنین ؟ میاین دوتا فانکشن برای اینکار تعریف میکنین ؟ خب این وقتی exeption ها زیاد شدن مدیریتش سخت میشه. اینجاست که polymorphism به دادمون میرسه.
- خب الان راه حل چیه ؟ باید چیکارکنیم ؟ خب راه حلی که به ذهنتون رسیده اینه که بیایم با توجه به تایپای ورودی خروجیای متفاوت بدیم( اگر هر دو string بودن پس خروجی string باشه و اگر هر دو number بودن خروجی number میشه ) . و همینجا شما داری از polymorphism استفاده میکنی بدون اینکه خبر داشته باشی😂.
- پس polymorphism میگه متد ، فانکشن و هر چیزی که لاجیک داخلشه رو باید انعطاف پذیر توسعه بدی که تو شرایطای مختلف کارکنن . مثل همون داستان فانکشن add . پس الان تا حدودی فهمیدیم که polymorphism چه کمکی بهمون میکنه و چطور باید ازش استفاده کرد .
- تو شرایط مختلف میتونیم polymorphism رو داشته باشیم. صرفا محدود به پاردایم ها نیست ، الان برای همین تابع add ما میتونیم با generic type ها مسئلرو حل کنیم
add<T>( first : T , second : T) : T { }- وقتی ما oop base کد میزنیم یک قابلیت مهمی که بهمون میده و میتونیم از یک روش بهش برسیم همین polymorphism هست ، در oop ما با sub-typing به polymorphism میرسیم. و در بقیه سناریو ها میتونیم از روشای مختلف بهش برسیم
- پس الان فکرکنم متوجه شدین که این مفهوم به ظاهر سخت ، مخصوص oop نیست و ما بدون oop هم میتونیم polymorphism رو داخل کدمون داشته باشیم ، پس اگه ما از class استفاده نکنیم و نتونیم با sub-typing به polymorphic بودن برسیم در تمام اعضای برناممون ، دلیل بر این نیست که polymorphism مختص به oop باشه.
🔥1
تو این پست میخوایم تابو شکنی کنیم و راجب موضوعی حرف بزنیم که خیلیا نسبت بهش گارد میگیرن ، چرا inheritance یک best practice در ۹۹ درصد مواقع نیست ؟
- خب اول بیایم ببینیم inheritance چیه ؟ به زبون ساده یعنی شما بیای یک class واحد والد داشته باشی ( به عنوان مثال shape ) و بیای بگی وقتی میخوام یک مستطیل رو توی برنامم داشته باشم ، پس باید خصوصیات یک shape رو داشته باشه ، پس میام از class shape اون ویژگیارو به ارث میبرم.
- حالا چه مشکلیو از ما حل میکنه ؟ ارث بری برای کمتر شدن دوباره کاری و انعطاف پذیری زیاد استفاده میشه ، فرض کنید داخل shape ما یک چیزی داریم به اسم ضلع(Side) ، ما یک بار میایم تو کلاس پدر(کلاسی که ازش ازث بری میشه) تعریفش میکنیم و به کلاس های فرزند(کلاسایی که از کلاس shape ارث میبرن) میگیم ویژگیی به اسم side داخل شما وجود داره ، و خیلی قابلیت خوبیه
- این مفهوم ظاهرش گول زنندس ! ببینین وقتی ما inheritance رو داریم داخل برناممون ، اولین چیزی که فزاینده میره بالا dependency بین class های ماست ، با کوچیک ترین تغییر ممکنه child class(کلاس هایی که به ارث میبرن) رو عملکردشون رو مختل کنه یا به اصطلاح باعث break شدنشون بشه ، پس میبینیم که ارث بری اونقدر هم چیزه جالبی نیست !
- ارث بری با اصل جایگزینی لیسکوف در تناقضه ! این اصل میگه که " کلاس فرزند باید بتونه بدون مشکل جایگزین کلاس پدر بشه" ، ولی در ۹۹.۹۹ مواقع کلاس فرزند رفتار متفاوتی با کلاس پدر داره. و این عمل باعث شکستن و نقض این اصل مهم میشه.
- ما وقتی از ارث بری استفاده میکنیم ، تغییر یک کلس میتونه به ده ها کلس دیگه اسیب بزنه ، و مارو تو طراحی محدود میکنه ، و اگه بخوایم ویژگیای جدید به کلس های دیگمون استفاده کنیم باید از interface ها و چندین مرحله ارث بری استفاده کنیم که کد مارو بشدت پیچیده میکنه.
- حالا این همه عیب و ایراداتشو گفتیم ، اگه اینقدر بد بود چرا اصلا وجود داره ؟ شئ گرایی فقط در یک سناریو جواب میده ، وقتی کلاس فرزند دقیقا جز به جز شبیه به کلاس پدر هست ولی در یک پوزیشن خاص ! فرض کنید یک db class دارین که میتونه دیتارو به صورت encrypt شده و row بهتون پس بده ، لاجیک اصلی جفت دیتابیس row و encrypt یکیه ولی در یک فرایند باهم تفاوت دارن ، پس میتونیم بگیم اینجا inheritance یک best practice حساب میشه و ازش استفاده کنیم .
ختم کلام : ندونسته به حساب اینکه بقیه استفاده میکنن از چیزی استفاده نکنین
- خب اول بیایم ببینیم inheritance چیه ؟ به زبون ساده یعنی شما بیای یک class واحد والد داشته باشی ( به عنوان مثال shape ) و بیای بگی وقتی میخوام یک مستطیل رو توی برنامم داشته باشم ، پس باید خصوصیات یک shape رو داشته باشه ، پس میام از class shape اون ویژگیارو به ارث میبرم.
- حالا چه مشکلیو از ما حل میکنه ؟ ارث بری برای کمتر شدن دوباره کاری و انعطاف پذیری زیاد استفاده میشه ، فرض کنید داخل shape ما یک چیزی داریم به اسم ضلع(Side) ، ما یک بار میایم تو کلاس پدر(کلاسی که ازش ازث بری میشه) تعریفش میکنیم و به کلاس های فرزند(کلاسایی که از کلاس shape ارث میبرن) میگیم ویژگیی به اسم side داخل شما وجود داره ، و خیلی قابلیت خوبیه
- این مفهوم ظاهرش گول زنندس ! ببینین وقتی ما inheritance رو داریم داخل برناممون ، اولین چیزی که فزاینده میره بالا dependency بین class های ماست ، با کوچیک ترین تغییر ممکنه child class(کلاس هایی که به ارث میبرن) رو عملکردشون رو مختل کنه یا به اصطلاح باعث break شدنشون بشه ، پس میبینیم که ارث بری اونقدر هم چیزه جالبی نیست !
- ارث بری با اصل جایگزینی لیسکوف در تناقضه ! این اصل میگه که " کلاس فرزند باید بتونه بدون مشکل جایگزین کلاس پدر بشه" ، ولی در ۹۹.۹۹ مواقع کلاس فرزند رفتار متفاوتی با کلاس پدر داره. و این عمل باعث شکستن و نقض این اصل مهم میشه.
- ما وقتی از ارث بری استفاده میکنیم ، تغییر یک کلس میتونه به ده ها کلس دیگه اسیب بزنه ، و مارو تو طراحی محدود میکنه ، و اگه بخوایم ویژگیای جدید به کلس های دیگمون استفاده کنیم باید از interface ها و چندین مرحله ارث بری استفاده کنیم که کد مارو بشدت پیچیده میکنه.
- حالا این همه عیب و ایراداتشو گفتیم ، اگه اینقدر بد بود چرا اصلا وجود داره ؟ شئ گرایی فقط در یک سناریو جواب میده ، وقتی کلاس فرزند دقیقا جز به جز شبیه به کلاس پدر هست ولی در یک پوزیشن خاص ! فرض کنید یک db class دارین که میتونه دیتارو به صورت encrypt شده و row بهتون پس بده ، لاجیک اصلی جفت دیتابیس row و encrypt یکیه ولی در یک فرایند باهم تفاوت دارن ، پس میتونیم بگیم اینجا inheritance یک best practice حساب میشه و ازش استفاده کنیم .
ختم کلام : ندونسته به حساب اینکه بقیه استفاده میکنن از چیزی استفاده نکنین
خب امروز میخوایم راجب یک دیزاین پترن صحبت کنیم که به تازگی باهاش مواجه شدم و فهمیدم نیاز دارمش به اسم repository pattern
- خب قبل توضیح دادنش طبق معمول باید سناریومون رو مطرح کنیم و بگیم اصلا چرا راجب این ؟ ما کلی دیزاین پترن داریم چرا راجب repository pattern صحبت کنیم ؟
- خب من داشتم یک orm کاستوم برای خودم میساختم برای یک هدف خاصی، توی requirment های پروژه دیدم که من مجبورم توی service layerd بیام به اصافه business logic بیام لاجیک های مربوط به database رو پیاده کنم ، اینکار نقض اصل SPR هست.
- خب راه حل چیه ؟ میایم یک لایه تعریف میکنیم وطیفش این باشه که مسائل مربوط به دیتابیس رو از business logic جدا کنه. یعنی ما میایم به جای اینکه 5 خط کوئری بنویسم و درگیر این بشم که چطور این Query رو بنویسم. پس من میام یک لایه طراحی میکنم که وطیفش پنهان کردن و ساده تر کردن این فراینذ باشه. یعنی بجای اون پنج خط query بیایم بنویسیم repo.create . خب حالا برنامه نویسی که توسعه دهنده ی اون برنامس ، بجای اینکه درگیر کوئری های پیچیده بشه میاد و رو لاجیک اصلی تمرکز میکنه
- علاوه بر این ، شما با اینکار وابستگی به انواع دیتابیس هارو کاهش دادی ! ، شما کافیه با کمترین تغییر برنامت رو روی دیتابیس جدید سوار کنی جای اینکه بری تک تک کوئریارو تست کنی ببینی درست کارمیکنه یا نه ، این یکی از بزرگترین مزایای داشتن repository layer در برناممونه !
قطعا مزایا و معایب بیشتری داره ولی چیزایی به درد خودم خورد این موارد بودن.
- خب قبل توضیح دادنش طبق معمول باید سناریومون رو مطرح کنیم و بگیم اصلا چرا راجب این ؟ ما کلی دیزاین پترن داریم چرا راجب repository pattern صحبت کنیم ؟
- خب من داشتم یک orm کاستوم برای خودم میساختم برای یک هدف خاصی، توی requirment های پروژه دیدم که من مجبورم توی service layerd بیام به اصافه business logic بیام لاجیک های مربوط به database رو پیاده کنم ، اینکار نقض اصل SPR هست.
- خب راه حل چیه ؟ میایم یک لایه تعریف میکنیم وطیفش این باشه که مسائل مربوط به دیتابیس رو از business logic جدا کنه. یعنی ما میایم به جای اینکه 5 خط کوئری بنویسم و درگیر این بشم که چطور این Query رو بنویسم. پس من میام یک لایه طراحی میکنم که وطیفش پنهان کردن و ساده تر کردن این فراینذ باشه. یعنی بجای اون پنج خط query بیایم بنویسیم repo.create . خب حالا برنامه نویسی که توسعه دهنده ی اون برنامس ، بجای اینکه درگیر کوئری های پیچیده بشه میاد و رو لاجیک اصلی تمرکز میکنه
- علاوه بر این ، شما با اینکار وابستگی به انواع دیتابیس هارو کاهش دادی ! ، شما کافیه با کمترین تغییر برنامت رو روی دیتابیس جدید سوار کنی جای اینکه بری تک تک کوئریارو تست کنی ببینی درست کارمیکنه یا نه ، این یکی از بزرگترین مزایای داشتن repository layer در برناممونه !
قطعا مزایا و معایب بیشتری داره ولی چیزایی به درد خودم خورد این موارد بودن.
خب تو این پست میخوایم راجب یک design pattern صحبت کنیم به اسم event-sourcing. و طبق روالمون مرحله به مرحله ازش رو توضیح بدیم.
- خب سوال اصلی اینه که ما از چی به event-sourcing رسیدیم ؟ خب ما در بخش هایی از اپمون نیاز به تاریخچه و History راجب یک سری دیتا داشتیم ، مثلا اگه کاربر یک پروداکت رو اضافه کرده به سبد خرید خودش و سفارششو تکمیل نکرده ، تا چه مرحله ای پیش رفته که بعد اون مرحله تصمیم به تکمیل کردن سفارش نگرفته ؟ آیا واقعا محصول رو نمیخواسته یا دلیل دیگه ای داشته ؟ مسئله اصلیه ما تو این پست اینه که میخوایم حلش کنیم.
-خب حالا event sourcing چیه ؟ این دیزاین پترن میگه که شما در هر جایی که نیاز دارین یک History از دیتا ذخیره کنین و بدونین دقیقا در چه زمان هایی چه چیز هایی بهش اعمال شده( مثلا طرف ساعت ۱۲ امروز پروداکتو زده رفته تو سبد خریدش ولی فردا تازه رفته برای تکمیلش ) ، اینجاس که event sourcing میاد وسط و میگه من عین یک فیلم بردار از تک تک لحظات تولد تا مرگ یک انتیتی و موجودیت رو ذخیره میکنم.
- خب حالا ما فهمیدیم اگه میخوایم به این صورت اپمون کارکنه ، باید با دیزاین پترن event sourcing اپمون رو طراحی کنیم ، حالا بیایم چندین تا مبحث بنیادی راجبش صحبت کنیم.
- اینهمه نوشتیم و از معایبش نگیم ؟ نمیشه که😂 ، اولین عیب بزرگی که به ذهنتون میاد پیچیدگیه زیاده و این درسته ، شما برای قسمت هایی از اپتون باید یک سری پیچیدگی هارو به جون بخرین تا بتونین هر لحظه و هر event ای که اتفاق افتاد رو ذخیره کنین ، و یک مشکل بزرگتر اینه که migration رو نمیتونین به روش سنتی انجام بدین و کارتون سخت تر میشه
- در event-sourcing یک سری مفاهیم بنیادی و key word مربوط بهش وجود دارن ، که شامل command , event , event-store , replay projection هستن ، در پستای بعدی راجبش تخصصی تر صحبت میکنیم.
- خب سوال اصلی اینه که ما از چی به event-sourcing رسیدیم ؟ خب ما در بخش هایی از اپمون نیاز به تاریخچه و History راجب یک سری دیتا داشتیم ، مثلا اگه کاربر یک پروداکت رو اضافه کرده به سبد خرید خودش و سفارششو تکمیل نکرده ، تا چه مرحله ای پیش رفته که بعد اون مرحله تصمیم به تکمیل کردن سفارش نگرفته ؟ آیا واقعا محصول رو نمیخواسته یا دلیل دیگه ای داشته ؟ مسئله اصلیه ما تو این پست اینه که میخوایم حلش کنیم.
-خب حالا event sourcing چیه ؟ این دیزاین پترن میگه که شما در هر جایی که نیاز دارین یک History از دیتا ذخیره کنین و بدونین دقیقا در چه زمان هایی چه چیز هایی بهش اعمال شده( مثلا طرف ساعت ۱۲ امروز پروداکتو زده رفته تو سبد خریدش ولی فردا تازه رفته برای تکمیلش ) ، اینجاس که event sourcing میاد وسط و میگه من عین یک فیلم بردار از تک تک لحظات تولد تا مرگ یک انتیتی و موجودیت رو ذخیره میکنم.
- خب حالا ما فهمیدیم اگه میخوایم به این صورت اپمون کارکنه ، باید با دیزاین پترن event sourcing اپمون رو طراحی کنیم ، حالا بیایم چندین تا مبحث بنیادی راجبش صحبت کنیم.
- اینهمه نوشتیم و از معایبش نگیم ؟ نمیشه که😂 ، اولین عیب بزرگی که به ذهنتون میاد پیچیدگیه زیاده و این درسته ، شما برای قسمت هایی از اپتون باید یک سری پیچیدگی هارو به جون بخرین تا بتونین هر لحظه و هر event ای که اتفاق افتاد رو ذخیره کنین ، و یک مشکل بزرگتر اینه که migration رو نمیتونین به روش سنتی انجام بدین و کارتون سخت تر میشه
- در event-sourcing یک سری مفاهیم بنیادی و key word مربوط بهش وجود دارن ، که شامل command , event , event-store , replay projection هستن ، در پستای بعدی راجبش تخصصی تر صحبت میکنیم.
❤1🔥1
دوستان ما تو این چنل راجب مباحثی صحبت میکنیم که برای خودم مشکل به وجود اوردن و با شما به اشتراک میزارم که از وجودشون با خبر باشین.
ابزار ، پارادایم و .. برامون مهم نیست و صرفا میخوایم ذهنیت شمارو اماده کنیم برای یک سری موارد مثل پست بالا که راجبش حرف زدم. اینکه شما هر ابزاری کارمیکنین به من مربوط نیست ، راجب چیزای پایه ام صحبت نمیکنیم🤝❤️
ابزار ، پارادایم و .. برامون مهم نیست و صرفا میخوایم ذهنیت شمارو اماده کنیم برای یک سری موارد مثل پست بالا که راجبش حرف زدم. اینکه شما هر ابزاری کارمیکنین به من مربوط نیست ، راجب چیزای پایه ام صحبت نمیکنیم🤝❤️
🥰2❤1
تو این پست میخوام راجب موضوعی صحبت کنم که شاید وابسته به یک ابزار باشه ، ولی خب موضوع جالبیه lazy loading & eager loading in modules & services
- خب با یک سناریو شروع میکنیم ، فرض کنین شما یک اپ بزرگ دارین که سرویس هاش سه رقمی شدن و بالای ۱۰۰ تا سرویس دارین ، وقتی میخواین این پروژه رو بیلد بگیرین یک مقدار زمانی طول میکشه و یک سری منابع اشغال میشن تا همه اپلیکیشن شما ران بشه ، حالا فرض کنید که از اون ۱۰۰ تا سرویس ، ده تاشون سرویسایی هستن که نیاز نیست همیشه در دسترس باشن ! یعنی چی ؟ یعنی اقا من مثلا تو اپم زیاد نیازی به سرویس sms ندارم جز یسری مواقع خاص ، باید باشه ها ولی نه همیشه ، خیلی مواقع خاصی کاربرد داره، حالا سوال اصلی اینه ، ایا میتونم کاری کنم که هنگام بیلد شدن پروژه ، این سرویس sms بیلد نشه و هروقت نیازش داشتم بهش بگم ک داداش بیا لود شو تا ازت استفاده کنم ؟
- اینجاست ک مفاهیم lazy loading in service & modules به وجود میاد ، فرض کنید تو همین سناریو ، ما یک چیزی داریم به اسم sms-service که درون اون فقط یک سرویس به اسم SendSms وجود داره ، حالا فرض کنین ما فقط میخوایم هنگامی که میخوایم به یک کاربر اطلاع بدیم که از سایت ban شده بهش sms بدیم که اقا شما ban شدی ، خب ما در لحظه ی اولیه ک re-build میشه برنامه یا build میشه ، ما نیازی به sms-service نداریم ، پس ما اون رو lazy-load میکنیم که در اون لحظه ی اولیه ماژول sms و سرویسش لود نشن
- اینکار چه مزیتی داره ؟ اولین مزیتی ک همتون حدس زدین اینه ک سرعت build و run شدن پروژه زیاد تر میشه ، شما تعداد ماژولایی که برای ران کردن نیاز دارینو کم میکنین پس در لحظه ی start شدن برنامه ماژولای کمتری لود میشن ، و دومین چیزی که توجهمو جلب کرد مصرف منابع کمتره ! شما تصور کن این ۱۰۰ ماژول 50GB رم میخورن ، اگه شما ۱۰ تاشونو بتونی به صورت lazy load کنی ، مثلا ۵ گیگابایت از رم شما صرفه جویی میشه حین build گرفتن و در طول برنامه هم همینطور ! با اینکه شما ۱۰۰ تا سرویس داری ولی با استفاده ازش میتونین توی منابعتونم صرفه جویی کنین
ختم کلام : نمیدونم که این قابلیت داخل چه فریمورکایی هست و چه فریمورکایی نیست ولی خب موضوع جالبی بود و توی بهینه سازی اپم بهم کمک کرد
- خب با یک سناریو شروع میکنیم ، فرض کنین شما یک اپ بزرگ دارین که سرویس هاش سه رقمی شدن و بالای ۱۰۰ تا سرویس دارین ، وقتی میخواین این پروژه رو بیلد بگیرین یک مقدار زمانی طول میکشه و یک سری منابع اشغال میشن تا همه اپلیکیشن شما ران بشه ، حالا فرض کنید که از اون ۱۰۰ تا سرویس ، ده تاشون سرویسایی هستن که نیاز نیست همیشه در دسترس باشن ! یعنی چی ؟ یعنی اقا من مثلا تو اپم زیاد نیازی به سرویس sms ندارم جز یسری مواقع خاص ، باید باشه ها ولی نه همیشه ، خیلی مواقع خاصی کاربرد داره، حالا سوال اصلی اینه ، ایا میتونم کاری کنم که هنگام بیلد شدن پروژه ، این سرویس sms بیلد نشه و هروقت نیازش داشتم بهش بگم ک داداش بیا لود شو تا ازت استفاده کنم ؟
- اینجاست ک مفاهیم lazy loading in service & modules به وجود میاد ، فرض کنید تو همین سناریو ، ما یک چیزی داریم به اسم sms-service که درون اون فقط یک سرویس به اسم SendSms وجود داره ، حالا فرض کنین ما فقط میخوایم هنگامی که میخوایم به یک کاربر اطلاع بدیم که از سایت ban شده بهش sms بدیم که اقا شما ban شدی ، خب ما در لحظه ی اولیه ک re-build میشه برنامه یا build میشه ، ما نیازی به sms-service نداریم ، پس ما اون رو lazy-load میکنیم که در اون لحظه ی اولیه ماژول sms و سرویسش لود نشن
- اینکار چه مزیتی داره ؟ اولین مزیتی ک همتون حدس زدین اینه ک سرعت build و run شدن پروژه زیاد تر میشه ، شما تعداد ماژولایی که برای ران کردن نیاز دارینو کم میکنین پس در لحظه ی start شدن برنامه ماژولای کمتری لود میشن ، و دومین چیزی که توجهمو جلب کرد مصرف منابع کمتره ! شما تصور کن این ۱۰۰ ماژول 50GB رم میخورن ، اگه شما ۱۰ تاشونو بتونی به صورت lazy load کنی ، مثلا ۵ گیگابایت از رم شما صرفه جویی میشه حین build گرفتن و در طول برنامه هم همینطور ! با اینکه شما ۱۰۰ تا سرویس داری ولی با استفاده ازش میتونین توی منابعتونم صرفه جویی کنین
ختم کلام : نمیدونم که این قابلیت داخل چه فریمورکایی هست و چه فریمورکایی نیست ولی خب موضوع جالبی بود و توی بهینه سازی اپم بهم کمک کرد
🔥2🥰1
امروز میخوایم راجب یک موضوعی حرف بزنیم که اسمش رو شاید شنیده باشین ولی خب به ظاهر خیلی پیچیده میاد. CQRS
- خب میخوایم ببینیم چیشد که میخوایم راجبش حرف بزنیم ، از رو سرگرمی داشتم به این فکرمیکردم پلتفرمی مثل اینستاگرام وقتی یک فرد مشهور از خودش پستی رو منتشر میکنه ، میلیون ها نفر اون پست رو میبینن و لایک میکنن ، و همزمان هم اینستاگرام مقدار ویو و لایک و کامنت و ... رو بهتون نشون میده ، خب این چطور ممکنه ؟ شما چطور میتونی این حجم از دیتای عظیمو همزمان write کنی و بلافاصله بدون مشکل read کنی ، خب با چه منطقی اینستاگرام این رو هندل میکنه که این مشکل رو نداره ؟
- با یکم سوال و سرچ رسیدم به چیزی به اسم CQRS , مخفف Command query responsibility segregation ، بهمون میفهمونه که ما باید وظایف read , write رو جدا کنیم از همدیگه ، اما منظور این جداسازی چیه ؟ این چیزیه ک ما میخوایم بهش جواب بدیم.
- جوابش اینه که ما مدل های read , write از هم جدا باشن ، نه لزوما فیزیکی ، ممکنه از یک دیتابیس استفاده کنن ، ولی مدل ها و index ها و جزئیات دیگه ممکنه فرق کنن ، و بعضی مواقع کلا دیتابیس های جدا درنظر میگیریم ! یعنی شما دوتا مدل توی دوتا دیتاییس جداگونه برای خودتون دارین که این یک سری مزایا داره و یک سری معایب که راجبشون حرف میزنیم
- خب ما میتونیم تو سه سطح جداسازی انجام بدیم : ( mvp , mid-systems , enterprise-systems)
توی mvp ما میایم صرفا مدل هارو از هم دیگه جدا میکنیم ولی همچنان دیتابیس یکیه ، برای پروژه های کوچیک و نسخه های اولیه اپلیکیشنا بسیار مناسبه . توی mid-system میایم دیتابیسارو جدا میکنیم ، مثلا شما دیتابیسی ک میخوای توش عملیاتای write به خوبی و با پرفورمنس خوب انجام بشن رو postgres-sql درنظر میگیری و برای دیتابیسی که میخوای read رو با سرعت و بهینه انجام بدی ، از elasticSearch استفاده
میکنی( به عنوان دیتابیس عمومی نه )
توی enterprise-systems هر سرویس دیتابیس مربوط به خودشو داره که با Event ها باهم sync میشن.
- خب حالا اینهمه از مزایاش حرف زدیم که مشکلی رو ازمون حل میکنه ، ولی حالا میخوایم راجب معایبشم بگیم ، اونا چیا هستن ؟ اولین چیزی به ذهنتون میرسه اینه که سیستم بسیار پیچیده میشه ، مدل ها جدا میشن و لاجیک های خودشون رو دارن و sync کردن اونا باهم و همین باعث پیچیدگی بیشتر داخل سیستم میشه ، مشکل بعدی اینه که read model بلافاصله sync نیست ، چون زمان میبره که دیتابیسا باهم sync شن و ما بتونیم طبق انتظار read model بتونه بهمون response مورد نظرو بده ، مشکل خیلی چالش بر انگیزش debugging هست ، شما دیگه فقط یک state نداری که دیتاها داخلش جریان داشته باشن ، همونطور که گفتیم همه چیز جداس ، و شما برای دیباگ کردن باید چندین مسیر رو دنبال کنی و همین خودش هزینه های بسیار بالاییو به همراه داره
ختم کلام : اگه صرفا میخوای توی لاجیک ببینی cqrs چجوری کارمیکنه ، میتونی فقط مدل هارو جدا کنی ، از این بزرگتر رو حتما باید دلیل موجه براش داشته باشی وگرنه چیزی جز هزینه برات نداره
- خب میخوایم ببینیم چیشد که میخوایم راجبش حرف بزنیم ، از رو سرگرمی داشتم به این فکرمیکردم پلتفرمی مثل اینستاگرام وقتی یک فرد مشهور از خودش پستی رو منتشر میکنه ، میلیون ها نفر اون پست رو میبینن و لایک میکنن ، و همزمان هم اینستاگرام مقدار ویو و لایک و کامنت و ... رو بهتون نشون میده ، خب این چطور ممکنه ؟ شما چطور میتونی این حجم از دیتای عظیمو همزمان write کنی و بلافاصله بدون مشکل read کنی ، خب با چه منطقی اینستاگرام این رو هندل میکنه که این مشکل رو نداره ؟
- با یکم سوال و سرچ رسیدم به چیزی به اسم CQRS , مخفف Command query responsibility segregation ، بهمون میفهمونه که ما باید وظایف read , write رو جدا کنیم از همدیگه ، اما منظور این جداسازی چیه ؟ این چیزیه ک ما میخوایم بهش جواب بدیم.
- جوابش اینه که ما مدل های read , write از هم جدا باشن ، نه لزوما فیزیکی ، ممکنه از یک دیتابیس استفاده کنن ، ولی مدل ها و index ها و جزئیات دیگه ممکنه فرق کنن ، و بعضی مواقع کلا دیتابیس های جدا درنظر میگیریم ! یعنی شما دوتا مدل توی دوتا دیتاییس جداگونه برای خودتون دارین که این یک سری مزایا داره و یک سری معایب که راجبشون حرف میزنیم
- خب ما میتونیم تو سه سطح جداسازی انجام بدیم : ( mvp , mid-systems , enterprise-systems)
توی mvp ما میایم صرفا مدل هارو از هم دیگه جدا میکنیم ولی همچنان دیتابیس یکیه ، برای پروژه های کوچیک و نسخه های اولیه اپلیکیشنا بسیار مناسبه . توی mid-system میایم دیتابیسارو جدا میکنیم ، مثلا شما دیتابیسی ک میخوای توش عملیاتای write به خوبی و با پرفورمنس خوب انجام بشن رو postgres-sql درنظر میگیری و برای دیتابیسی که میخوای read رو با سرعت و بهینه انجام بدی ، از elasticSearch استفاده
میکنی( به عنوان دیتابیس عمومی نه )
توی enterprise-systems هر سرویس دیتابیس مربوط به خودشو داره که با Event ها باهم sync میشن.
- خب حالا اینهمه از مزایاش حرف زدیم که مشکلی رو ازمون حل میکنه ، ولی حالا میخوایم راجب معایبشم بگیم ، اونا چیا هستن ؟ اولین چیزی به ذهنتون میرسه اینه که سیستم بسیار پیچیده میشه ، مدل ها جدا میشن و لاجیک های خودشون رو دارن و sync کردن اونا باهم و همین باعث پیچیدگی بیشتر داخل سیستم میشه ، مشکل بعدی اینه که read model بلافاصله sync نیست ، چون زمان میبره که دیتابیسا باهم sync شن و ما بتونیم طبق انتظار read model بتونه بهمون response مورد نظرو بده ، مشکل خیلی چالش بر انگیزش debugging هست ، شما دیگه فقط یک state نداری که دیتاها داخلش جریان داشته باشن ، همونطور که گفتیم همه چیز جداس ، و شما برای دیباگ کردن باید چندین مسیر رو دنبال کنی و همین خودش هزینه های بسیار بالاییو به همراه داره
ختم کلام : اگه صرفا میخوای توی لاجیک ببینی cqrs چجوری کارمیکنه ، میتونی فقط مدل هارو جدا کنی ، از این بزرگتر رو حتما باید دلیل موجه براش داشته باشی وگرنه چیزی جز هزینه برات نداره
🔥5
امروز میخوایم به یک سوال نسبتا بزرگ جواب کلی بدیم ، و این طبق تجربه ی الانمه شاید یک ماه دیگه جوابم متفاوت باشه
سوال اصلی اینه که ما از کجا بفهمیم کی اینقدر پروژه بزرگ میشه که از معماری های پیچیده و پترنای مشخص استفاده کنیم ؟
- خودم این سوال برام بود که کی طرف مثلا میاد از repository-pattern استفاده میکنه ، میلیاردا ابزار و قاعده ی خاص برای مسائل گوناگون و متفاوت وجود دارن ، از کجا بفهمیم چیو کجا استفاده کنیم ؟
- تو این مرحله ، فقط میتونم بگم که شما دقیق نمیتونی بهترین ابزارو پیدا کنی ، ولی راهش اینه که اگه یک کار پیچیدرو داری بیش از دوبار انجام میدی یا هرچقدر پروژت میره جلوتر داره پیچیده تر و کثیف تر میشه و اصول رو زیر سوال میبره باید اونجا کار رو متوقف کنی ، و از خودت بپرسی آیا این بهترین راه حله ؟
- برای مثال من در پروژه های قبلیم میومدم توی سرویس validation انجام میدادم و ی حجم نسبتا بزرگی از سرویس به ولیدیشن اختصاص پیدا میکرد ، که اکثرشونم تکراری بودن(مثلا میخواستم ببینم ی ایدیی از یک انتیتیی برام فرستادن از قبل تو دیتابیس هست یا نه) ، اینجا باید مغزتون آلارم بده که راهو اشتباه میرین.
- راه اول خب مشخصه که ai تا حد خوبی میتونه کمکتون کنه ، اگر یک کاری بیش از حد پیچیده شده میتونین ازش کمک بخواین و اگرم جواب مورد نظرتونو نداد میتونین توی جاهایی مثل stack-overflow در قالب یک مشکل سرچش کنین و جواب هارو مطالعه کنین واقعا نتیجه های خوبی میگیرین.
- اگه یک جمع بندی کنیم ، هروقت دیدین ساختارتون بجای راحت تر کردن کارتون داره کارتونو پیچیده تر میکنه ( منظورم از لحاظ logic نیست ) باید یک جور code-smell بدونینش !
در کل به سنگ های تو مسیر عادت نکنید که یک جا شمارو میزنن زمین ، سنگ هارو بردارین نه اینکه باهاش کنار بیاین
سوال اصلی اینه که ما از کجا بفهمیم کی اینقدر پروژه بزرگ میشه که از معماری های پیچیده و پترنای مشخص استفاده کنیم ؟
- خودم این سوال برام بود که کی طرف مثلا میاد از repository-pattern استفاده میکنه ، میلیاردا ابزار و قاعده ی خاص برای مسائل گوناگون و متفاوت وجود دارن ، از کجا بفهمیم چیو کجا استفاده کنیم ؟
- تو این مرحله ، فقط میتونم بگم که شما دقیق نمیتونی بهترین ابزارو پیدا کنی ، ولی راهش اینه که اگه یک کار پیچیدرو داری بیش از دوبار انجام میدی یا هرچقدر پروژت میره جلوتر داره پیچیده تر و کثیف تر میشه و اصول رو زیر سوال میبره باید اونجا کار رو متوقف کنی ، و از خودت بپرسی آیا این بهترین راه حله ؟
- برای مثال من در پروژه های قبلیم میومدم توی سرویس validation انجام میدادم و ی حجم نسبتا بزرگی از سرویس به ولیدیشن اختصاص پیدا میکرد ، که اکثرشونم تکراری بودن(مثلا میخواستم ببینم ی ایدیی از یک انتیتیی برام فرستادن از قبل تو دیتابیس هست یا نه) ، اینجا باید مغزتون آلارم بده که راهو اشتباه میرین.
- راه اول خب مشخصه که ai تا حد خوبی میتونه کمکتون کنه ، اگر یک کاری بیش از حد پیچیده شده میتونین ازش کمک بخواین و اگرم جواب مورد نظرتونو نداد میتونین توی جاهایی مثل stack-overflow در قالب یک مشکل سرچش کنین و جواب هارو مطالعه کنین واقعا نتیجه های خوبی میگیرین.
- اگه یک جمع بندی کنیم ، هروقت دیدین ساختارتون بجای راحت تر کردن کارتون داره کارتونو پیچیده تر میکنه ( منظورم از لحاظ logic نیست ) باید یک جور code-smell بدونینش !
در کل به سنگ های تو مسیر عادت نکنید که یک جا شمارو میزنن زمین ، سنگ هارو بردارین نه اینکه باهاش کنار بیاین
🔥3
تو این پست میخوایم راجب مبحثی حرف بزنیم که شاید خیلیا مخالفش باشن ، go برای تازه کار ها مناسب نیست !
- خودم سرجمع ۹ ماهه که مفید کارمیکنم و شروع کردم کار حرفه ای کردن و تصمیم گرفتم ابزاری رو یادبگیرم که خیلی کارامد باشه و از لحاظ پرفورمنس کمکم کنه ، و رفتم سراغ go ببینم درونش چیه و چرا اینقدر داره فراگیر میشه
- زبان go یک زبان کامپایلری و سطح پایینه که میتونی ازش پرفورمنس بشدت خوبی بگیری در عین سادگی ! و در ظاهر فکرمیکنی خب اگه یک اپلیکیشن داشته باشم و go هم اینقدر سادس چرا با چیزی مثل laravel و express توسعش بدم ؟ میرم با go توسعه میدم که مزایای بسیار زیادی دریافت کنم ، این چیزیه ک درون ذهن شما میگذره
- ولی مشکل اینه که چیزی مثل laravel فریمورکیه که کامیونیتی عظیمی داره ، پروژه های خیلی زیادی باهاش بالا اومده ، کار باهاش راحته و زود میتونی خیلی کار هارو انجام بدی ، اما go چی ؟ درسته go خیلی سادس ولی هنوز خیلی از ابزارا برای go توسعه داده نشدن ، خیلی از قابلیتارو نداره و مثل laravel نمیتونین باهاش کارکنین و تایم بیشتری از شما میگیره
- درون go مباحثی هست که اگه اولین زبانتون باشه ممکنه یکم گیجتون کنه و نفهمین که چیکارمیکنین ، مثل conccurrency , memory که مباحث خیلی ساده ای نیستن و دلیلی هم نداره اینقدر اول کار پیچیده باشه
- الان شرکتای بزرگ همونطور ک دیدین یک سری از سرویس هاشونو دارن میبرن سمت اینکه جایگزینش کنن با go , ولی این نباید گولتون بزنه ! خودمونم میگیم شرکتای بزرگ ، یک شرکتی مثل اسنپ یا دیجیکالا نمیاد برنامه نویسای mid , junior رو استخدام کنه که براش کارکنن ، نیروی متخصص میخوان !
نتیجه میگیریم که باز هم ابزار شمارو متمایز نمیکنه دوسته من ، همون اول کار سراغ لقمه های بزرگ نرین که مثل من شکست نخورین از لحاظ فکری.
- خودم سرجمع ۹ ماهه که مفید کارمیکنم و شروع کردم کار حرفه ای کردن و تصمیم گرفتم ابزاری رو یادبگیرم که خیلی کارامد باشه و از لحاظ پرفورمنس کمکم کنه ، و رفتم سراغ go ببینم درونش چیه و چرا اینقدر داره فراگیر میشه
- زبان go یک زبان کامپایلری و سطح پایینه که میتونی ازش پرفورمنس بشدت خوبی بگیری در عین سادگی ! و در ظاهر فکرمیکنی خب اگه یک اپلیکیشن داشته باشم و go هم اینقدر سادس چرا با چیزی مثل laravel و express توسعش بدم ؟ میرم با go توسعه میدم که مزایای بسیار زیادی دریافت کنم ، این چیزیه ک درون ذهن شما میگذره
- ولی مشکل اینه که چیزی مثل laravel فریمورکیه که کامیونیتی عظیمی داره ، پروژه های خیلی زیادی باهاش بالا اومده ، کار باهاش راحته و زود میتونی خیلی کار هارو انجام بدی ، اما go چی ؟ درسته go خیلی سادس ولی هنوز خیلی از ابزارا برای go توسعه داده نشدن ، خیلی از قابلیتارو نداره و مثل laravel نمیتونین باهاش کارکنین و تایم بیشتری از شما میگیره
- درون go مباحثی هست که اگه اولین زبانتون باشه ممکنه یکم گیجتون کنه و نفهمین که چیکارمیکنین ، مثل conccurrency , memory که مباحث خیلی ساده ای نیستن و دلیلی هم نداره اینقدر اول کار پیچیده باشه
- الان شرکتای بزرگ همونطور ک دیدین یک سری از سرویس هاشونو دارن میبرن سمت اینکه جایگزینش کنن با go , ولی این نباید گولتون بزنه ! خودمونم میگیم شرکتای بزرگ ، یک شرکتی مثل اسنپ یا دیجیکالا نمیاد برنامه نویسای mid , junior رو استخدام کنه که براش کارکنن ، نیروی متخصص میخوان !
نتیجه میگیریم که باز هم ابزار شمارو متمایز نمیکنه دوسته من ، همون اول کار سراغ لقمه های بزرگ نرین که مثل من شکست نخورین از لحاظ فکری.
🔥3
تو این پست میخوایم راجب این حرف بزنیم که abstraction چیه و کجا کاربرد داره و چرا میخوایم راجبش حرف بزنیم
- طبق روال همیشگی ، به اواخر پروژه ی خودم رسیدم و دیدم که بعضی از نقاط برنامم ، یک کارو به دو روش انجام دادم ، فرض کنین یک سرویسی دارم که اطلاعات کاربرامو میگیره ، داخل یکیش یک سری اطلاعات اضافرو میگیرم داخل یکی دیگه نه ، و این بارها تکرار شده بود داخل برنامم ، هربار میومدم و select-fields رو مینوشتم و این خیلی رو مخ بود چون ممکن بود بین اون همه اطلاعات ، بعضیاش از دستم بره.
- اینجا بود که فهمیدم خب من باید از polymorphism استفاده کنم که در پستای قبلی راجبش صحبت کردیم. حالا به چه صورت ؟ یعنی من چجوری میتونم اون سرویسی که برام اطلاعات کاربر رو برمیگردوند رو polymorphic کنم که هم انعطاف داشته باشه هم کارمو راحتتر کنه ؟
- اینجا بود که با چیزی به اسم abstraction آشنا شدم ، یک روشی که در شئ گرایی(oop) خیلی بیشتر رایجه و وقتی اسمشو میشنویم یاد class ها و مفاهیم object-oriented میوفتیم
- این مفهوم میگه که بیایم مفاهیمی رو تعریف کنیم که خیلی کلی هستن ، وابسته به جزئیات نیستن ! یعنی ما بیایم بگیم که یک متدی داریم به اسم findUser و پیاده سازی این متد یک سری قوانین خاص داره و بستگی به شرایط شما این قوانین رو تغییر یا بازنویسی میکنی. یا فرضا یک class داریم به اسم db که دو حالت encrypt , raw داره ، میتونیم بگیم شما چندین تا متد داری مثل create , findAll , findOne , ... ولی جزئیاتش با شماس که encrypt شده دیتا برگردونی یا raw !
- ما بدون اینکه درگیر جزئیات بشیم ، اومدیم انعطاف پذیری رو به وجود اوردیم ، فقط در مثال db اجبار هست که متد هایی مثل create و findAll رو براش پیاده سازی کنم ، اما اینکه جزئیات داخلش چیه کاملا بستگی به شرایط اون کسی داره که ازش پیروی میکنه. یک نفر میاد encrypt شده برمیگردونه و یک نفر raw و یک نفر دیگه مقادیر دیگه !
پس با abstraction داخل oop میتونیم به polymorphism با sub-typing برسیم ! چیزی که قبلا توضیح داده بودیم ولی مثال نزده بودیم ، abstraction یکی از روشای رسیدن به polymorphism هست.
- طبق روال همیشگی ، به اواخر پروژه ی خودم رسیدم و دیدم که بعضی از نقاط برنامم ، یک کارو به دو روش انجام دادم ، فرض کنین یک سرویسی دارم که اطلاعات کاربرامو میگیره ، داخل یکیش یک سری اطلاعات اضافرو میگیرم داخل یکی دیگه نه ، و این بارها تکرار شده بود داخل برنامم ، هربار میومدم و select-fields رو مینوشتم و این خیلی رو مخ بود چون ممکن بود بین اون همه اطلاعات ، بعضیاش از دستم بره.
- اینجا بود که فهمیدم خب من باید از polymorphism استفاده کنم که در پستای قبلی راجبش صحبت کردیم. حالا به چه صورت ؟ یعنی من چجوری میتونم اون سرویسی که برام اطلاعات کاربر رو برمیگردوند رو polymorphic کنم که هم انعطاف داشته باشه هم کارمو راحتتر کنه ؟
- اینجا بود که با چیزی به اسم abstraction آشنا شدم ، یک روشی که در شئ گرایی(oop) خیلی بیشتر رایجه و وقتی اسمشو میشنویم یاد class ها و مفاهیم object-oriented میوفتیم
- این مفهوم میگه که بیایم مفاهیمی رو تعریف کنیم که خیلی کلی هستن ، وابسته به جزئیات نیستن ! یعنی ما بیایم بگیم که یک متدی داریم به اسم findUser و پیاده سازی این متد یک سری قوانین خاص داره و بستگی به شرایط شما این قوانین رو تغییر یا بازنویسی میکنی. یا فرضا یک class داریم به اسم db که دو حالت encrypt , raw داره ، میتونیم بگیم شما چندین تا متد داری مثل create , findAll , findOne , ... ولی جزئیاتش با شماس که encrypt شده دیتا برگردونی یا raw !
- ما بدون اینکه درگیر جزئیات بشیم ، اومدیم انعطاف پذیری رو به وجود اوردیم ، فقط در مثال db اجبار هست که متد هایی مثل create و findAll رو براش پیاده سازی کنم ، اما اینکه جزئیات داخلش چیه کاملا بستگی به شرایط اون کسی داره که ازش پیروی میکنه. یک نفر میاد encrypt شده برمیگردونه و یک نفر raw و یک نفر دیگه مقادیر دیگه !
پس با abstraction داخل oop میتونیم به polymorphism با sub-typing برسیم ! چیزی که قبلا توضیح داده بودیم ولی مثال نزده بودیم ، abstraction یکی از روشای رسیدن به polymorphism هست.
🔥3❤1
توی این پست میخوایم راجب چیزی حرف بزنیم که شاید خیلی اسمشو شنیدین ولی دقیقا نمیدونین کجاها باید این ویژگیو داشته باشه ، idempotency
- خب ببینیم اصلا چی هست و چرا میخوایم راجبش حرف بزنیم. ما یک مشکلی که تو سیستمای توزیع شده یا همون distributed system داریم اینه که ممکنه یک درخواست تکراری چندین بار ارسال شه ، یعنی یک کاربر میخواد کفش A رو بخره و پرداخت هم میکنه ولی دوبار request ارسال میشه و ممکنه دوتا payment-history ثبت شه ، خب این یک اشکال خیلی دردسر سازه ! باید چیکارکنیم ؟
- اینجاس که idempotency وارد عمل میشه ، میگه که گرچه یک عملیات چندین بار تکرار بشه ، ولی نتیجه باید مثل اولین عملیات باشه. حالا کی این موارد پیش میان ؟ ممکنه connection قطع بشه ، ممکنه مرورگر رفرش شه ، میکروسرویس ها self-retry میکنن و شاید کوبرنیتز یک کانتینر رو kill کنه و دوباره run کنه.
- حالا اگه بهش توجه نکنیم چی میشه ؟ چیزی که خودم توی پروژه خودم بهش برخوردم سر رزرو نوبت بود ، یک مشتری نوبتی رو ثبت میکرد و در بعضی مواقع self-retry میکرد سرویس نوبتم و ما به مشکلات گسترده ای خوردیم ، توی دنیای واقعی هم اینشکلیه که کاربر ممکنه دوبار پرداخت کنه چندین تا سفارش یکسان ثبت شه یا موجودی های انبار ها نابالانس شه ، پس این موضوع خیلی مهمه !
- خب حالا ما چجوری api ها و سرویس هامونو idempotent کنیم که به این مشکلات نخوریم ؟ کلاینت ما چیزی به اسم idempotency-key ارسال میکنه به ما ، ما چک میکنیم که آقا این درخواست آیا توی سیستم ما پردازش شده یا نه ، اگه شده بود که خب ریسپانس قبلی برمیگرده ، اگه نشده بود یعنی این یک عملیات جدیده و باید دوباره پردازش بشه !
- برای اینکه پیشگیری کنیم و api هامونو idempotent کنیم ، باید راجب یک سری مباحث مثل اینکه بفهمیم http از ریشه چجوری کارمیکنه ، راجب retry-mechanisms دقیق بدونیم و دیتابیسامونو درست مدل و تحلیل کنیم.
اگر api هایی که توسعه میدین ( در محیط اجرایی ) idempotent نباشه ، اسم خودتونو نباید بزارین برنامه نویس !
- خب ببینیم اصلا چی هست و چرا میخوایم راجبش حرف بزنیم. ما یک مشکلی که تو سیستمای توزیع شده یا همون distributed system داریم اینه که ممکنه یک درخواست تکراری چندین بار ارسال شه ، یعنی یک کاربر میخواد کفش A رو بخره و پرداخت هم میکنه ولی دوبار request ارسال میشه و ممکنه دوتا payment-history ثبت شه ، خب این یک اشکال خیلی دردسر سازه ! باید چیکارکنیم ؟
- اینجاس که idempotency وارد عمل میشه ، میگه که گرچه یک عملیات چندین بار تکرار بشه ، ولی نتیجه باید مثل اولین عملیات باشه. حالا کی این موارد پیش میان ؟ ممکنه connection قطع بشه ، ممکنه مرورگر رفرش شه ، میکروسرویس ها self-retry میکنن و شاید کوبرنیتز یک کانتینر رو kill کنه و دوباره run کنه.
- حالا اگه بهش توجه نکنیم چی میشه ؟ چیزی که خودم توی پروژه خودم بهش برخوردم سر رزرو نوبت بود ، یک مشتری نوبتی رو ثبت میکرد و در بعضی مواقع self-retry میکرد سرویس نوبتم و ما به مشکلات گسترده ای خوردیم ، توی دنیای واقعی هم اینشکلیه که کاربر ممکنه دوبار پرداخت کنه چندین تا سفارش یکسان ثبت شه یا موجودی های انبار ها نابالانس شه ، پس این موضوع خیلی مهمه !
- خب حالا ما چجوری api ها و سرویس هامونو idempotent کنیم که به این مشکلات نخوریم ؟ کلاینت ما چیزی به اسم idempotency-key ارسال میکنه به ما ، ما چک میکنیم که آقا این درخواست آیا توی سیستم ما پردازش شده یا نه ، اگه شده بود که خب ریسپانس قبلی برمیگرده ، اگه نشده بود یعنی این یک عملیات جدیده و باید دوباره پردازش بشه !
- برای اینکه پیشگیری کنیم و api هامونو idempotent کنیم ، باید راجب یک سری مباحث مثل اینکه بفهمیم http از ریشه چجوری کارمیکنه ، راجب retry-mechanisms دقیق بدونیم و دیتابیسامونو درست مدل و تحلیل کنیم.
اگر api هایی که توسعه میدین ( در محیط اجرایی ) idempotent نباشه ، اسم خودتونو نباید بزارین برنامه نویس !
🔥2❤1
توی این پست میخوام راجب موضوعی حرف بزنم که افرادی که تازه با میکروسرویس ها کارمیکنن ذهنشون رو درگیر میکنه
- فرض کنین که ما چندین تا سرویس داریم که شامل انبار(ware-house) ، سفارشات(orders) ، پرداخت ها(payments) چون داخل distributed systems یا همون سیستم های توزیع شده ما دیگه چیزی به اسم تراکنش توزیع شده نداریم(ACID) ، پس راه حل چیه ؟ اگه یکی از سرویسا به مشکل خورد باید چیکارکنیم ؟ این چیزیه ک میخوایم راجبش بحث کنیم
- اول بیاین ببینیم که ما قبل از اینکه وارد چالشای میکروسرویس بشیم ، این مسئلرو چطور حل کردیم ، ما اومدیم با استفاده از چیزی به اسم transaction ها این مشکل رو حل کردیم ، که به زبون ساده میگفت یا همه دستورات اجرا بشن یا هیچکدوم ! اگر ما داخل ی تراکنش میگیم یک سفارش ثبت شه بعد پرداخت شه بعد پرداخت سفارش تایید شه ، و حین پرداخت سیستم به مشکل بخوره ، باید مراحل قبل که شامل ثبت سفارشه هم برگرده به حالتی که سفارش اصلا ثبت نشده ! چون یا همه دستورات داخل تراکنس باهم اجرا میشن یا هیچکدوم نباید اجرا بشن
- خب ما چرا اینو داخل سیستم توزیع شده نداریم ؟ چون سرویس های مختلف داریم ! شاید db های متفاوت حتی داشته باشن و عملا در سیستم های توزیع شده ما نمیتونیم از ACIDاستفاده کنیم ، خب پس چاره چیه ؟ باید چیکارکنیم ؟ تو این نقطه یک پترنی به داد ما میرسه ، saga !
- این پترن چی میگه ؟ میگه اگر توی سیستم ما مشکلی پیش اومد ، برو مراحل قبل رو جبران کن ، یعنی بجای اینکه عین دیتابیس همه چیز رو برگردونیم به حالتی که قبل تراکنش بود ( rollback) ، بیایم خودمون برگردیم به مراحل قبل و کارایی که کردیم رو جبران کنیم ( مثلا اگه از موجودی انبار کم کردم و اضافه کردم به سفارش یک کاربر و کاربر حین پرداخت به مشکل خورد ، سفارششو لغو و پاک کنم و موجودی انبار رو برگردونم بهش ) که به این کار میگن compensation action
- حالا چجوری saga روی اپلیکیشن ما اعمال میشه ؟ به دو روش میتونیم اعمالش کنیم ، choreography و orchestraction . توی مدل اول سرویسای ما باهم حرف میزنن ، هرکسی میدونه کی نوبتشه و کی باید عملیات رو لغو کنه و undo بزنه ، مثل یک رقص دست جمعیه بدون رهبر که همه میدونن باید چیکارکنن ، توی مدل دوم ما یک centeral service داریم که عین یک رهبر همه چیز رو مدیریت میکنه ، که اون مشخص میکنه چه سرویسی در چه زمانی چه عملیاتی رو انجام بده و در صورت بروز مشکل کدوم سرویسا undo کنن به حالت قبلشون.
- حالا بحث اینه که کی از کدوم مدل استفاده کنیم ؟ اگه شما اپتون کوچیکه و step های کمتری رو طی میکنین برای انجام یک کار ، و ارتباط گرفتن بین سرویسا سادس بهتره که از choerography استفاده کنیم و بیخودی کار رو پیچیده نکنیم ، اگه سیستم ما بزرگه و برای یک مرحله، step های زیادیو پشت سر میزاریم ، و جبران و undo کردن هزینه زیادی داره میایم از orchestraction استفاده میکنیم ، برای سیستم های رزرو بلیط و مالی فوق العادس.
اگه بخوایم جمع بندی کنیم ، saga pattern یک راه حل برای مسئله ایه که ما چطور میتونیم مانند monolithic که اونجا از تراکنش ها یا همون ACID استفاده میکردیم برای اطمینان اجرا شدن تمامی دستورات پشت هم ، چجوری در distributed systems هم مطمئن باشیم که اگر سرویسی به مشکل خورد ، سرویسای قبلی هم برگردن به حالت قبل خودشون !
- فرض کنین که ما چندین تا سرویس داریم که شامل انبار(ware-house) ، سفارشات(orders) ، پرداخت ها(payments) چون داخل distributed systems یا همون سیستم های توزیع شده ما دیگه چیزی به اسم تراکنش توزیع شده نداریم(ACID) ، پس راه حل چیه ؟ اگه یکی از سرویسا به مشکل خورد باید چیکارکنیم ؟ این چیزیه ک میخوایم راجبش بحث کنیم
- اول بیاین ببینیم که ما قبل از اینکه وارد چالشای میکروسرویس بشیم ، این مسئلرو چطور حل کردیم ، ما اومدیم با استفاده از چیزی به اسم transaction ها این مشکل رو حل کردیم ، که به زبون ساده میگفت یا همه دستورات اجرا بشن یا هیچکدوم ! اگر ما داخل ی تراکنش میگیم یک سفارش ثبت شه بعد پرداخت شه بعد پرداخت سفارش تایید شه ، و حین پرداخت سیستم به مشکل بخوره ، باید مراحل قبل که شامل ثبت سفارشه هم برگرده به حالتی که سفارش اصلا ثبت نشده ! چون یا همه دستورات داخل تراکنس باهم اجرا میشن یا هیچکدوم نباید اجرا بشن
- خب ما چرا اینو داخل سیستم توزیع شده نداریم ؟ چون سرویس های مختلف داریم ! شاید db های متفاوت حتی داشته باشن و عملا در سیستم های توزیع شده ما نمیتونیم از ACIDاستفاده کنیم ، خب پس چاره چیه ؟ باید چیکارکنیم ؟ تو این نقطه یک پترنی به داد ما میرسه ، saga !
- این پترن چی میگه ؟ میگه اگر توی سیستم ما مشکلی پیش اومد ، برو مراحل قبل رو جبران کن ، یعنی بجای اینکه عین دیتابیس همه چیز رو برگردونیم به حالتی که قبل تراکنش بود ( rollback) ، بیایم خودمون برگردیم به مراحل قبل و کارایی که کردیم رو جبران کنیم ( مثلا اگه از موجودی انبار کم کردم و اضافه کردم به سفارش یک کاربر و کاربر حین پرداخت به مشکل خورد ، سفارششو لغو و پاک کنم و موجودی انبار رو برگردونم بهش ) که به این کار میگن compensation action
- حالا چجوری saga روی اپلیکیشن ما اعمال میشه ؟ به دو روش میتونیم اعمالش کنیم ، choreography و orchestraction . توی مدل اول سرویسای ما باهم حرف میزنن ، هرکسی میدونه کی نوبتشه و کی باید عملیات رو لغو کنه و undo بزنه ، مثل یک رقص دست جمعیه بدون رهبر که همه میدونن باید چیکارکنن ، توی مدل دوم ما یک centeral service داریم که عین یک رهبر همه چیز رو مدیریت میکنه ، که اون مشخص میکنه چه سرویسی در چه زمانی چه عملیاتی رو انجام بده و در صورت بروز مشکل کدوم سرویسا undo کنن به حالت قبلشون.
- حالا بحث اینه که کی از کدوم مدل استفاده کنیم ؟ اگه شما اپتون کوچیکه و step های کمتری رو طی میکنین برای انجام یک کار ، و ارتباط گرفتن بین سرویسا سادس بهتره که از choerography استفاده کنیم و بیخودی کار رو پیچیده نکنیم ، اگه سیستم ما بزرگه و برای یک مرحله، step های زیادیو پشت سر میزاریم ، و جبران و undo کردن هزینه زیادی داره میایم از orchestraction استفاده میکنیم ، برای سیستم های رزرو بلیط و مالی فوق العادس.
اگه بخوایم جمع بندی کنیم ، saga pattern یک راه حل برای مسئله ایه که ما چطور میتونیم مانند monolithic که اونجا از تراکنش ها یا همون ACID استفاده میکردیم برای اطمینان اجرا شدن تمامی دستورات پشت هم ، چجوری در distributed systems هم مطمئن باشیم که اگر سرویسی به مشکل خورد ، سرویسای قبلی هم برگردن به حالت قبل خودشون !
❤2🔥2🥰1
توی این پست میخوام راجب یک پترنی که تازه باهاش اشنا شدم صحبت کنم شاید به درد شما هم خورد ، retry pattern
- داخل پروژه ای که سه تا سرویس باهم میکروسرویس شده بودن ، خطاهای خیلی جالبی رخ میداد ، مثلا دیتابیس timeout میخورد یا connection reset میخورد ، این خطاها باعث میشدن که شخص فکرکنه اپ ما استیبل نیست و تجربه کاربری بدی براش بشه ، ریکوئستای درستی فرستاده میشده ولی خطا میگرفته !
- خب اینجا راه حل چیه ؟ خب اولین چیزی که به ذهن خودم رسید این بود که یک درخواست چندین بار تکرار بشه ، شما مشکلی که داری شاید یک یا دوثانیه طول بکشه ، و بعد دوثانیه سرویست دوباره بتونه درخواستارو درست پردازش کنه ، خب طبیعیه که اگه ثانیه اول درخواستمون رد شه شاید ثانیه دوم یا سوم درخواستمون قبول شه ، پس ما بجای یک درخواست چندین تا درخواست میدیم
- اما بحث اینه که آیا هر درخواستی که شکست میخوررو باید retry کنیم ؟ خب جواب مشخصه که نه ! وقتی شما بر فرض مثال ارور Access denied میگیری ، این مشخصه که ارور چیه و در هر زمانی با اون وضعیت ریکوئست بزنی همون ارور رو میگیری ، پس درخواست اضافیه و بار روی سرور رو بشدت زیاد میکنه ، خب چاره چیه ؟ اینکه بیایم هر درخواستی رو retry نکنیم و بررسی کنیم که اصلا مشکل چیه و آیا باید retry کنیم یا نه که در پستای بعدی راجب استاندارد هاش باهم صحبت میکنیم.
ختم کلام اینه که باید حواستون باشه خطاهای مقطعی و زود گذر ( قابل حل ) نباید روی تجربه کاربری اپلیکیشنتون و محصولتون تاثیر بزاره ، وگرنه همین خطاهای کوچیک باعث هزینه های سرسام آور میشه!
- داخل پروژه ای که سه تا سرویس باهم میکروسرویس شده بودن ، خطاهای خیلی جالبی رخ میداد ، مثلا دیتابیس timeout میخورد یا connection reset میخورد ، این خطاها باعث میشدن که شخص فکرکنه اپ ما استیبل نیست و تجربه کاربری بدی براش بشه ، ریکوئستای درستی فرستاده میشده ولی خطا میگرفته !
- خب اینجا راه حل چیه ؟ خب اولین چیزی که به ذهن خودم رسید این بود که یک درخواست چندین بار تکرار بشه ، شما مشکلی که داری شاید یک یا دوثانیه طول بکشه ، و بعد دوثانیه سرویست دوباره بتونه درخواستارو درست پردازش کنه ، خب طبیعیه که اگه ثانیه اول درخواستمون رد شه شاید ثانیه دوم یا سوم درخواستمون قبول شه ، پس ما بجای یک درخواست چندین تا درخواست میدیم
- اما بحث اینه که آیا هر درخواستی که شکست میخوررو باید retry کنیم ؟ خب جواب مشخصه که نه ! وقتی شما بر فرض مثال ارور Access denied میگیری ، این مشخصه که ارور چیه و در هر زمانی با اون وضعیت ریکوئست بزنی همون ارور رو میگیری ، پس درخواست اضافیه و بار روی سرور رو بشدت زیاد میکنه ، خب چاره چیه ؟ اینکه بیایم هر درخواستی رو retry نکنیم و بررسی کنیم که اصلا مشکل چیه و آیا باید retry کنیم یا نه که در پستای بعدی راجب استاندارد هاش باهم صحبت میکنیم.
ختم کلام اینه که باید حواستون باشه خطاهای مقطعی و زود گذر ( قابل حل ) نباید روی تجربه کاربری اپلیکیشنتون و محصولتون تاثیر بزاره ، وگرنه همین خطاهای کوچیک باعث هزینه های سرسام آور میشه!
❤2🔥1
امروز میخوام راجب موضوعی صحبت کنم که خیلی از دولوپرایی که دورمن ازش خبرندارن ، مشکلی که وجود نداررو نباید حل کرد !
- چیشد به این رسیدیم ؟ ما داشتیم روی یک محصولی کارمیکردیم که قبلا زده شده بود و ما فقط میخواستیم یکسری فیچر جدید و قابلیتایی که کارفرما خواسته بودو اضافه کنیم که یکی از بهترین بک اند کارامون گفت ، نیایم این سیستمی که با laravel نوشته شدرو با gin بازنویسی کنیم ؟ و خیلیارو قانع کرد که gin از خیلی جهات بهتر از laravel هست ، و همه قانع شدن که تصمیم درستیه
- ولی یک سوال مهمو از خودشون نپرسیدن ، که آیا محصول ما توی قسمت بک اندش نیاز به سرعت بالاتری داره ؟ آیا این محصولی که الان داریم از اون تعریفی ک ازش کردیم در سطح پایینتریه ؟ اصلا ما مشکل پرفورمنسی داشتیم که بیایم حلش کنیم ؟
- وقتی شما تصمیم بزرگی مثل re write کردن رو میگیری ، باید به این فکرکنین که در کدوم مرحله از توسعه هستین ، برای چی باید اینکارو کنین و دلایل منطقی داشته باشین و صرفا چون فریمورک A از فریمورک B بهتره نیایم یک تصمیم بزرگ مثل re write کردن یک پروژه بزرگ رو بگیریم. چون چیزی جز هزینه براتون نداره
- کسایی که فکرمیکنن اگر بک اند یک سایت یا اپلیکیشن رو با go بنویسن از چیزی مثل laravel و django بهتره آدمای متعصب و کوری هستن که نمیدونن اصلا چرا go رو انتخاب کردن و فقط همون محدوده ای که دوست دارنو میبینن.
- کسی که فکرمیکنه اگر مشکل پرفورمنس داریم پس بیایم همه چیزو اوپتیمایز کنیم و به بهترین شکل پیادش کنیم هم جزو همین دسته افراده ، کسایی هستن که مشکل رو دقیق نمیشناسن و میرن مشکل فرضی یا مشکل اشتباهیو حل میکنن که اصلا مشکل نبوده!
ختم کلام اینکه شما وقتی میتونی با یک بیل یک قبر رو بکنی نمیری براش تجهیزات خاکبرداری بیاری ، چیزی جز هزینه بیشتر براتون نداره پس هم به خودتون رحم کنید و اگه به خودتون رحم نمیکنید به کسایی که باهاتون کارمیکنن رحم کنید
- چیشد به این رسیدیم ؟ ما داشتیم روی یک محصولی کارمیکردیم که قبلا زده شده بود و ما فقط میخواستیم یکسری فیچر جدید و قابلیتایی که کارفرما خواسته بودو اضافه کنیم که یکی از بهترین بک اند کارامون گفت ، نیایم این سیستمی که با laravel نوشته شدرو با gin بازنویسی کنیم ؟ و خیلیارو قانع کرد که gin از خیلی جهات بهتر از laravel هست ، و همه قانع شدن که تصمیم درستیه
- ولی یک سوال مهمو از خودشون نپرسیدن ، که آیا محصول ما توی قسمت بک اندش نیاز به سرعت بالاتری داره ؟ آیا این محصولی که الان داریم از اون تعریفی ک ازش کردیم در سطح پایینتریه ؟ اصلا ما مشکل پرفورمنسی داشتیم که بیایم حلش کنیم ؟
- وقتی شما تصمیم بزرگی مثل re write کردن رو میگیری ، باید به این فکرکنین که در کدوم مرحله از توسعه هستین ، برای چی باید اینکارو کنین و دلایل منطقی داشته باشین و صرفا چون فریمورک A از فریمورک B بهتره نیایم یک تصمیم بزرگ مثل re write کردن یک پروژه بزرگ رو بگیریم. چون چیزی جز هزینه براتون نداره
- کسایی که فکرمیکنن اگر بک اند یک سایت یا اپلیکیشن رو با go بنویسن از چیزی مثل laravel و django بهتره آدمای متعصب و کوری هستن که نمیدونن اصلا چرا go رو انتخاب کردن و فقط همون محدوده ای که دوست دارنو میبینن.
- کسی که فکرمیکنه اگر مشکل پرفورمنس داریم پس بیایم همه چیزو اوپتیمایز کنیم و به بهترین شکل پیادش کنیم هم جزو همین دسته افراده ، کسایی هستن که مشکل رو دقیق نمیشناسن و میرن مشکل فرضی یا مشکل اشتباهیو حل میکنن که اصلا مشکل نبوده!
ختم کلام اینکه شما وقتی میتونی با یک بیل یک قبر رو بکنی نمیری براش تجهیزات خاکبرداری بیاری ، چیزی جز هزینه بیشتر براتون نداره پس هم به خودتون رحم کنید و اگه به خودتون رحم نمیکنید به کسایی که باهاتون کارمیکنن رحم کنید
🔥4👏2🖕1
تو این پست میخوام راجب اهمیت موضوعی حرف بزنم که شاید خیلیاتون با monitoring معمولی اشتباهش بگیرین، observability
- ببین ما عمدتا توی پروژه هایی که داریم ، به مرور زمان یک سری از قسمتا دچار مشکل میشن و به ارور میخورن ، حالا مبحث ما اینه که اگه اینقدری اپمون بزرگ بود که نتونستیم منبع خطارو پیدا بکنیم چی ؟ اگه چندین تا سرویس باهم میکروسرویس شده باشن چجوری میخوایم ریشه ی خطای خیلی مبهم مثل internal-server-error رو پیدا کنیم ؟
- اینجا observability به کمک ما میاد، ولی چرا گفتیم با مانیتورینگ فرق داره ؟ چون مانیتورینگ میاد میگه که یک اشکال در اپلیکیشن به وجود اومده ، یک بخشی کرش کرده ولی اینکه چرا و دقیقا کدوم نقطه این اتفاق افتادرو نمیتونه نشون ما بده، ولی observability میگه که این نقطه و به این دلیل برنامه دچار خطا شده یا کرش کرده برنامه و وقتی که فهمیدیم اهمیتش چیه باید بریم سراغ اینکه این رو به برناممون اضافه بکنیم.
- اولین چیزی ک خیلی شنیدین و به ذهنتون میرسه لاگ هست ، ولی نه هر لاگی! لاگی که ساختار درستی داشته باشه و بتونه بهمون کمک کنه تا خطارو ردیابی کنیم ، صرفا یک متن خالی نباشه که اون هیج کمکی بهمون نمیکنه ، مورد بعدی metrics هست ، اینکه اصلا نقطه ی شروع این مشکل کجاس ؟ از cpu یا ram یا latency ! این خیلی مهمه برای شروع خطایابی و tracing ، و مورد اخر هم که همین tracing هست که شما اگر لاگ و metrcis های خوبی نداشته باشین نمیتونین به راحتی خطا هارو trace کنی و در نتیجه باید برای پیدا کردن اون خطا فقط دست به دامن دعا بشین
ختم کلام اینکه مانیتورینگ ساده گولتون نزنه ، وقتی سیستمتون از یک حدی بزرگتر میشه که tracing سخت میشه باید لاگ هاتون رو ساختار بندی و metrics هاتون رو درست مشخص کنین تا بتونین یک سیستمی درست کنین که بشه بهش اعتماد کرد تا هر موقع مشکلی پیش اومد تو کوتاه ترین زمان ممکن حل شه
- ببین ما عمدتا توی پروژه هایی که داریم ، به مرور زمان یک سری از قسمتا دچار مشکل میشن و به ارور میخورن ، حالا مبحث ما اینه که اگه اینقدری اپمون بزرگ بود که نتونستیم منبع خطارو پیدا بکنیم چی ؟ اگه چندین تا سرویس باهم میکروسرویس شده باشن چجوری میخوایم ریشه ی خطای خیلی مبهم مثل internal-server-error رو پیدا کنیم ؟
- اینجا observability به کمک ما میاد، ولی چرا گفتیم با مانیتورینگ فرق داره ؟ چون مانیتورینگ میاد میگه که یک اشکال در اپلیکیشن به وجود اومده ، یک بخشی کرش کرده ولی اینکه چرا و دقیقا کدوم نقطه این اتفاق افتادرو نمیتونه نشون ما بده، ولی observability میگه که این نقطه و به این دلیل برنامه دچار خطا شده یا کرش کرده برنامه و وقتی که فهمیدیم اهمیتش چیه باید بریم سراغ اینکه این رو به برناممون اضافه بکنیم.
- اولین چیزی ک خیلی شنیدین و به ذهنتون میرسه لاگ هست ، ولی نه هر لاگی! لاگی که ساختار درستی داشته باشه و بتونه بهمون کمک کنه تا خطارو ردیابی کنیم ، صرفا یک متن خالی نباشه که اون هیج کمکی بهمون نمیکنه ، مورد بعدی metrics هست ، اینکه اصلا نقطه ی شروع این مشکل کجاس ؟ از cpu یا ram یا latency ! این خیلی مهمه برای شروع خطایابی و tracing ، و مورد اخر هم که همین tracing هست که شما اگر لاگ و metrcis های خوبی نداشته باشین نمیتونین به راحتی خطا هارو trace کنی و در نتیجه باید برای پیدا کردن اون خطا فقط دست به دامن دعا بشین
ختم کلام اینکه مانیتورینگ ساده گولتون نزنه ، وقتی سیستمتون از یک حدی بزرگتر میشه که tracing سخت میشه باید لاگ هاتون رو ساختار بندی و metrics هاتون رو درست مشخص کنین تا بتونین یک سیستمی درست کنین که بشه بهش اعتماد کرد تا هر موقع مشکلی پیش اومد تو کوتاه ترین زمان ممکن حل شه
👍1🔥1
تو این پست میخوام راجب این حرف بزنم که چرا index گذاری در انتیتیا تو مقیاس بالا اینقدر مهمه و صرفا نمیتونیم چون index گذاری رو یک فیلد سرعت سرچو میبره بالا اون رو قرار بدیم ، ایندکس بدون برنامه قاتل سرعت میشه !
- قبل اینکه بریم سراغش ببینیم که این مسئله از کجا طرح شد ، یک پروژه ای رو داشتم مطالعه میکردم و توی مشکلاتش به این برخوردم که api ها بشدت کند شدن و خیلی از توسعه دهنده هاشون ایده هایی مثل اوپتیمایز کردن و بهینه کردن کوئریای دیتابیس رو دادن و بهش عمل کردن ولی همچنان سایت سرعتش وحشتناک پایین بود ، اینجا بود که شاخکای کسایی که استاد حل مسئله ان جنبید.
- اومدن دیدن که توی فرایند api ها بخشی که مربوط به عملیات های دیتابیسی بود ۹۰ درصد وقت رو تلف میکرد ، یعنی اگه شما سرویست ۳ تا بخش داشته باشه و یک بخشش کلا مربوط به دیتاییس باشه و بقیش مربوط به بخشای دیگه ، کل وقت سر اون بخشی ک مربوط به دیتابیسه میره ، پس اینکه ما بیایم وقتمونو رو چیزی جز دیتابیس و فراینداش بزاریم یک احمقی بیش نیستیم.
- با بررسیه بیشتر فهمیدن که مشکل از index هاییه که دولوپر ها گذاشتن ، چرا این ایندکس گذاری ساده باعث شده اینقدر عملیاتای دیتابیسی وحشتناک کند بشن ؟ مسئله این بود که چند تا اصل رو برای ایندکس گذاری رعایت نکردن که راجب اون اصلا میخوایم حرف بزنیم.
- اولین و مهم ترین چیز اینه ک نباید ستون هاییو ایندکس گذاری کنیم ک تغییرات زیادی دارن ، یک چیزی مثل فیلد updatedAt یا status که قراره تغییرات زیادی کنن رو نباید ایندکس گذاری کرد ، چرا ؟ چون به همون اندازه ک read, search سریع میشه write هم کند میشه.
- مسئله بعدی چیزیه به اسم over-indexing ، ما گفتیم ایندکس گذاشتن روی فیلد ها search , read سریع تر میشه و write کند تر ، حالا اگه ما بیایم و روی هر ستونی که لزوما نیازی به ایندکس نیست ایندکس بزاریم ، دیتابیس حجیم تر میشه ، update هزینه برتر و کند تر میشه و insert کردن بشدت کند میشه ، پس ما اگه میخوایم ایندکس گذاری کنیم باید حواسمون باشه که چه فیلدی و چرا باید ایندکس گذاری بشه !
ختم کلام اینکه چون صرفا خوبیای یک ابزار و موضوعو میبینین دلیل نمیشه کورکورانه ازش استفاده کنین چون یک روزی تبدیل به یک گودال وحشتناک میشه ، برای تک تک کاراتون دلیل داشته باشید( اگر میخواید استاندارد کارکنین)
- قبل اینکه بریم سراغش ببینیم که این مسئله از کجا طرح شد ، یک پروژه ای رو داشتم مطالعه میکردم و توی مشکلاتش به این برخوردم که api ها بشدت کند شدن و خیلی از توسعه دهنده هاشون ایده هایی مثل اوپتیمایز کردن و بهینه کردن کوئریای دیتابیس رو دادن و بهش عمل کردن ولی همچنان سایت سرعتش وحشتناک پایین بود ، اینجا بود که شاخکای کسایی که استاد حل مسئله ان جنبید.
- اومدن دیدن که توی فرایند api ها بخشی که مربوط به عملیات های دیتابیسی بود ۹۰ درصد وقت رو تلف میکرد ، یعنی اگه شما سرویست ۳ تا بخش داشته باشه و یک بخشش کلا مربوط به دیتاییس باشه و بقیش مربوط به بخشای دیگه ، کل وقت سر اون بخشی ک مربوط به دیتابیسه میره ، پس اینکه ما بیایم وقتمونو رو چیزی جز دیتابیس و فراینداش بزاریم یک احمقی بیش نیستیم.
- با بررسیه بیشتر فهمیدن که مشکل از index هاییه که دولوپر ها گذاشتن ، چرا این ایندکس گذاری ساده باعث شده اینقدر عملیاتای دیتابیسی وحشتناک کند بشن ؟ مسئله این بود که چند تا اصل رو برای ایندکس گذاری رعایت نکردن که راجب اون اصلا میخوایم حرف بزنیم.
- اولین و مهم ترین چیز اینه ک نباید ستون هاییو ایندکس گذاری کنیم ک تغییرات زیادی دارن ، یک چیزی مثل فیلد updatedAt یا status که قراره تغییرات زیادی کنن رو نباید ایندکس گذاری کرد ، چرا ؟ چون به همون اندازه ک read, search سریع میشه write هم کند میشه.
- مسئله بعدی چیزیه به اسم over-indexing ، ما گفتیم ایندکس گذاشتن روی فیلد ها search , read سریع تر میشه و write کند تر ، حالا اگه ما بیایم و روی هر ستونی که لزوما نیازی به ایندکس نیست ایندکس بزاریم ، دیتابیس حجیم تر میشه ، update هزینه برتر و کند تر میشه و insert کردن بشدت کند میشه ، پس ما اگه میخوایم ایندکس گذاری کنیم باید حواسمون باشه که چه فیلدی و چرا باید ایندکس گذاری بشه !
ختم کلام اینکه چون صرفا خوبیای یک ابزار و موضوعو میبینین دلیل نمیشه کورکورانه ازش استفاده کنین چون یک روزی تبدیل به یک گودال وحشتناک میشه ، برای تک تک کاراتون دلیل داشته باشید( اگر میخواید استاندارد کارکنین)
❤1🔥1