اول سلام، دوم اینکه میخوایم در مورد یکی از پترنهای خوبِ مهندسیِ نرمافزار صحبت کنیم، و سوم هم اینکه اول یه پیشگفتار نسبتاً کوتاه داشته باشیم، بعد بریم سراغ متدولوژی 12فاکتور.
>>> شاید این مقاله یکم بنظرتون طولانی بیاد، ولی توصیه میکنم تایم بذارید و بخونیدش حتماً و کامل.
خیلی وقته که معماریها و روشهای قدیمیترِ توسعه نرمافزار جای خودشون رو دادن به تکنولوژیها و متدولوژیهای جدیدتر، و نحوه ارائهی خدمات نرمافزاری هم از حالت سادهی تولید و تحویل نرمافزار به مخاطب، تبدیل شدن به انواع خدماتی مثل SaaS یا PaaS. عملاً بجای اینکه صرفاً یه محصول یا نرمافزار تولید و تحویل مشتری داده بشه، نرمافزار یا محصول نهایی به عنوان یه سرویس و بدون نیاز به دانلود یا نصب در اختیار مصرفکننده قرار میگیره، و کاربران میتونن صرفاً با اتصال به بستر اینترنت یا شبکههای داخلی (که خدمات روشون پیادهسازی شدن) گاهی حتی کاملاً بصورت شخصیسازیشده از انواع خدماتِ قابلِ ارائه بهره ببرن. هدف این مقاله ای که دارین میخونین اینه که در مورد مفهومی به اسم Twelve-Factor App Methodology صحبت کنیم و با روشی بهینه برای پیادهسازی وباپلیکیشنا و نرمافزارهای اَبری مخصوصاً خدمات SaaS آشنا بشیم. برای اطلاعات بیشتر هم میتونین به آدرس 12factor.net نگاهی بندازین.

پیشاپیش بگم که من سعی کردم هم مفهوم رو برسونم هم برداشت و یسری جاها تجربه خودم رو چاشنی کار بکنم که قابل درکتر باشه و امیدوارم مفید فایده واقع شده باشه. اینم تا یادم نرفته بگم که بجز خود اون لینکی که گذاشتم، از منابع دیگهای هم توی نوشتن این مقاله کمک گرفتم مثل این لینک. قبل از ادامه هم عکس زیر رو ببینید که بصورت خلاصه میگه ما قراره در رابطه با چی صحبت کنیم.

توی ویکیپدیای این متدولوژی، نوشته شده که این روش توسط توسعهدهندههای شرکت Heroku معرفی و استفاده شده و اینکه Adam Wiggins سال 2011 اون رو معرفی کرده.
خب بریم سراغ اصل مطلب. میخوایم تا جایی که میشه، توسعهپذیری و مقیاسپذیری و انعطافپذیریِ توسعه نرمافزار رو بهتر کنیم و بتونیم هزینههای جانبیِ فعلی و آینده (چه مالی چه زمانی چه نیروی انسانی) رو تا حد ممکن کاهش بدیم. اگه یذره حرفهای تر بشیم میتونیم پروژههای قدیمیتر رو هم با صرف کمی وقت و البته حوصله و دقت، تا حد ممکن تحت بستر این متدولوژی تغییر بدیم. به این نکته هم توجه کنیم که هر تغییری اولش شاید سخت باشه، استفاده از این 12 فاکتور هم توی شروع به همین شکل شاید هم زمانبر باشه و هم کمی اعصاب خورد کن، ولی اگه کمی آیندهنگرتر باشیم متوجه میشیم که فایدهی آیندش خیلی بیشتر از هزینههای اولشه (البته با تأکید بیشتر روی پروژههای بزرگتر). این رو هم دقت کنیم که اگه شما یا تیمتون، کارِتون تولید و توسعه نرمافزاره، خواسته یا ناخواسته به احتمال زیاد حدأقل با یک یا چند مورد از این 12 فاکتور آشنا هستین یا حتی توی روند توسعه نرمافزارتون ازش استفاده کردین.
خب بریم ببنیم چی هستن این 12 فاکتور:
نرمافزار و کدها باید همیشه روی یه سیستم یا سامانه ورژنکنترل مثل git باشه. دقت کنیم که اگه قرار باشه چند تا اصطلاحاً codebase داشته باشیم دیگه عملاً یه distributed system یا همون سیستم توضیعیه و هر کامپوننت و قسمت از سیستم توضیعی توی متدولوژی 12فاکتور خودش به تنهایی یه محصول محسوب میشه و توصیه میشه codebase خودش رو داشته باشه. همچنین برخی جاها مثلاً زمانی که قراره نرمافزارای مختلف از کد مشترک استفاده کنن، پای مفهومی به اسم dependency manager میاد وسط که توی فاکتور دوم بیشتر باهاش آشنا میشیم. پس میشه گفت که باید یه کدبیس داشته باشیم و ورژنهای مختلف برنامه میتونن استقرارها (Deployهای) مختلف رو شامل بشن که هر deploy خودش یه نمونهی در حال اجرا از برنامس (به عنوان یه مثال خیلی ساده، هر برنامهنویس یه نسخه یا کپی از برنامه رو داره که روی سیستم خودش میتونه اجراش کنه و عملاً هر کدوم یه استقرار محصوب میشن).
اکثر زبونای برنامهنویسی که جای خودشون رو توی دنیای توسعهی نرمافزار باز کردن، برای خودشون چیزی به اسم packaging system (یا اصطلاحای مشابش رو) دارن که عملاً کتابخونههای موردِ استفاده رو مدیریت میکنه. فاکتور دوم میگه ما باید روی وابستگیها و لایبرریهایی که داریم مدیریت کامل داشته باشیم و تمامی مواردی که ازشون استفاده میکنیم، دقیقاً و بصورت کامل مشخص شده باشن. این کار معمولاً بصورت packageهای نصب شده روی سیستم یا پوشههایی به اسمهایی مثل vendor یا bundling شناخته میشه. مشخصات و تمامی وابستگیهایی که برنامه داره، باید برای هر دو اِستِیتِ توسعه و پروداکشن قابل استفاده و به شکل یکنواختی در دسترسی باشه. یکی از مزایای این کار اینه که وقتی یه توسعهدهندهی جدید به تیم اضافه میشه، دیگه درگیر وابستگیهای پروژه نمیشه (که شاید خیلی از ماها زمان زیادی رو درگیر اینجور چیزا بودیم، مثل ران کردن پروژهی یه نفر دیگه که کلی اذیتمون کرده صرفاً چون از لایبرریهای غیر استاندارد یا قدیمی یا از لینکایی استفاده کرده و فعلا برای ما در دسترسی نیستن یا هر نوع دیگهای). یه مثال دیگه اینه که توسعهدهنده اگه میخواد از کامندی مثل curl یا هر ابزار دیگهای استفاده کنه، نباید با این تفکر پیادهسازی بکنه که همهی سیستما اون ابزار رو دارن دیگه! باید ابزاری که میخواد استفاده کنه هر طور شده کنار برنامه قرار بگیره حالا هرچی که میخواد باشه (عملاً هر ابزارِ خارجی باید به هر شکلی شده، بصورت واضح و مشخص کنار برنامه باشه و برنامه ترجیحاً بتونه بدون وابستگی، روی هر سیستمی چه اون سیستم اون ابزار رو داره چه نداره اجرا بشه)
نکنین این کارو. تو رو خدا، به هرچی که میپرستین قسمتون میدم نیاین کانفیگ یا کانفیگای مختلف برنامه رو هارد کُد (hard code یا همون مستقیم توی کدتون بصورت استاتیک یا هر نوع دیگهای از این دست پیادهسازی) کنین. مثلاً چیزایی مثل اطلاعات پایگاهدادهها (دیتابیسها) یا سرویسهای دیگهای که استفاده میکنین (RabbitMQ یا هر ساختار دیگهای که واسه ارتباطاتتون در نظر گرفتین مثل پورت API و غیره). یه راهِ مرسوم اینه که از کانفیگفایل استفاده بشه ولی فاکتور سوم میگه بهتره از environment variables استفاده کنین و deploysهای مختلف، کانفیگهای خودشون رو داشته باشن (مثلاً تفاوت آدرس استوریج برای نسخههای تست و توسعه و پروداکشن). یه نکتهی دیگه این که این فاکتور، برای متغیرهای کانفیگ بصورت جدا و دونهدونه اهمیت قائل میشه نه اینکه مثلاً بیاد همشون رو به عنوان اسامیای مثل environments قبول کنه، از طرفی هم هر کدوم برای deployهای مختلف بصورت مستقل مدیریت میشن. یکی از مهمترین مزایای استفاده از متغیرهای سیستم بجای کانفیگفایل کنار برنامه اینه که برنامه برای اجرا شدن روی میزبانهای مختلف (مثلاً سیستم عاملها یا نسخههای مختلفشون) دیگه به تغییر یا ساخت فایل کانفیگ جدید نیاز نیست، فقط مهم اینه که بره از متغیر سیستمی، مقداری که لازم داره رو بخونه و ازش استفاده کنه (که عملاً مفهومش و یکی از مزیتهاش اینه که کانفیگتون وابستگیای به زبون برنامهنویسی یا سیستمعامل خاصی نداره و توی یه پروژه بزرگ که زبونای برنامهنویسی مختلفی قراره از کانفیگای مشترک استفاده کنن وابستگیای به فایل یا محیط خاصی ندارن). البته این قضیه نقاط ضعفی هم داره که گویا زورِ نقاط قوتش به نقاط ضعفش چربیده، مثل اینکه موقع پوش و پول کردن و استفاده از ورژنکنترل، باید حواس تیم به متغیرایی که اضافه یا حذف شدن و بصورت کلی نیازمندیهای سیستمی که قراره کد روش اجرا بشه باشه.
منظور از Backing services هر سرویسیه که برنامه حین روند طبیعی خودش از اونا استفاده میکنه. مثل دیتابیسها، راههای ارتباطی و سازوکارهای پیاممحور (مثل ربیت یا حتی وبهوکها)، چیزایی مثل سیستمِ کشینگ (مثل Memcached) یا هر سرویس دیگهای که برنامه باهاش سر و کار داره. تمامی این سرویسها باید روی بستر شبکه مثلاً به کمک آدرسدهی http قابل دسترس باشن، و تعیینکننده آدرس یا نحوه ارتباط همون کانفیگیه که به عنوان فاکتور سوم باهاش آشنا شدیم. عملاً هر کدوم از این موارد به عنوان ریسورس برنامه شناخته میشن، و چقدر خوبه که برنامه طوری نوشته شده باشه که اگه سیستم مَسِیجینگ خواست تغییر بکنه (مثلا از RabbitMQ به ZeroMQ) حتیالمقدور با یه تغییر توی کانفیگ این امر محقق بشه (طبیعتاً نمیشه همه حالات مختلف رو در نظر گرفت ولی حدأقل میشه توی کد در نظر گرفت لایهای که مثلاً وظیفه مسیجینگ رو مدیریت میکنه با کمتری تغییر ممکنه و به سرعت بتونه تغییرات لازمه رو اعمال کنه). یا به عنوان یه مثال دیگه، اگه به هر دلیلی مثلاً ادمین سیستم خواست دیتابیس رو تغییر بده (فرض کنید مشکلی برای دیتابیس فعلی بوجود اومده و برنامه خواست روی یه بکآپی که از دیتابیس قبلی گرفته شده و روی دیتابیس جدید نشسته ران بشه) نباید هیچ مشکلی بابت این قضیه بوجود بیاد و تغییر کد خاصی نباید صورت بگیره. این مورد نه فقط روی ورژن نهایی برنامه، بلکه در مورد تمامی deployهای برنامه صدق میکنه، به این مفهوم که برنامه نباید توی برنچها یا ورژنای مختلف، عملکردای متفاوتی نسبت به محیطی که داره روش ران میشه داشته باشه و اکثر عملکردها تنها وابستگیشون به ریسورسهاشون (که اینجا منظورمون همون backing service ها) هست باید صرفاً اطلاعاتی باشه که از کانفیگ و پارامترای سیستمی دریافت میکنن.
متدولوژی 12فاکتور خیلی نسبت به جداسازی روندهای توسعهنرم افزار مخصوصاً بیلد و ریلیز و ران سختگیر و حساسه. بصورت کلی روند خروجی گرفتن باید با ذخیرهی کدها روی دیتاسورس شروع بشه و بعدش بریم سراغ بیلد کردن وابستگیها (dependences) و بعد هم ریلیز و ران. به این نکته هم دقت کنیم که هر ریلیز باید شناسهای یکتا برای خودش داشته باشه، حتی اگه این ریلیز نسبت به ریلیز قبلی کوچیکترین تفاوت ممکنه رو داره بازم باید شناسه یونیک جدید خودشو بگیره. معمولاً هم توصیه میشه برای تولید شناسه جدید از timestamp (تاریخ و زمان) استفاده بشه یا بصورت عدد افزایشی باشه یا ترکیبی. بصورت کلی هم این روند سه قسمت در نظر گرفته میشه (همون اسمهای این فاکتور)، اول بیلد هست که یه ورژن از کد رو با تمام وابستگیهاش به یه برنامهی قابل اجرا تبدیل میکنه، دوم ریلیز هست که با توجه به کانفیگ موجود و محیطی که باید برنامه توش ران بشه عملیات لازمه رو انجام میده، و در نهایت هم رانکردن هست که همونطور که از اسمش پیداست، توی این مرحله برنامه (یا در مواردِ لازمه، زیر برنامههایی که برای اجرای این برنامه لازمه) با توجه به محیط و کانفیگ انتخاب شده، اجرا بشن. دقت کنیم که این روند بدون بازگشت هست و هر مرحله بصورت مجزا باید کار خودش رو بکنه، به این مفهوم که نباید مثلاً با تغییر توی کد در زمان اجرا مستقیم روی خروجی تأثیر داشته باشه یا اینکه روند کانفیگ و بیلد تحت تأثیر اکشنهای کاربر یا استفادهکننده از نسخهی ریلیز برنامه قرار بگیره. بصورت خلاصه این فاکتور رو میشه اینجوری تفسیر کرد: کد میشینه روی کدسورس، بیلد میشه و در کنار کانفیگ تبدیل میشه به نسخه ریلیز یکتا و در نهایت هم اجرا.
این فاکتور میگه برنامه باید روی پروسس یا پروسههای بدون اِستِیت اجرا بشن. مفهومش اینه که مثلاً اگه برنامه قراره با داده یا دادههایی کار کنه، باید یه جایی بطور مثال روی دیتابیس ذخیرهسازی صورت بگیره و دیتابیس خودش به عنوان یه ریسورس (فاکتور چهارم) در نظر گفته میشه. در حقیقت پراسسها باید مجزا باشن و چیزی بصورت مستقیم بینشون منتقل نشه حتیالمقدور (تا جای ممکن وابستگی بین پروسسها به کمترین حد خودش برسه). یکی از کاربردیترین مثالهای این فاکتور، عدم استفاده از سِشِنها برای نگهداری یسری اطلاعاته (که بعضی وقتا سِشِنی باز نگه داشته میشه و توی ادامه روندِ کارِ کاربر اگه اون سِشِن قطع بشه با اختلال مواجه میشه و این مورد عملاً نقض این فاکتور هست). به زبون دیگه، فاکتور ششم میگه که من فرض میکنم هیچ دیتایی که کاربر بعداً باهاش کار داره روی کش یا حافظهای که فعلا برنامه باهاش کار داره و ممکنه در آینده در دسترس نباشه یا توسط سایر سرویسها تغییر کنه، وجود نداره و هر اطلاعاتی که لازمه، یه جای مطمئن ذخیره میشه، اگرم قراره سشن یا کَشی در کار باشه، صرفاً روی دیتاهایی که بازه زمانی خاصی برای استفاده دارن و عملیات حیاتیای روشون صورت نمیگیره امکانپذیره (اونم تازه در شرایطی که واقعاً دلیل قانعکنندهای برای استفاده از سیستم کشینگ یا باز نگه داشتن سشن و موارد مشابه وجود داشته باشه).
برنامههایی که با متدولوژی 12فاکتور پیادهسازی میشن، همیشه باید بتونن کاملاً مستقل عمل کنن و هر عملیاتی باید بصورت ایزوله توی پراسس خودش اجرا بشه، و این مسئله نباید روی بقیه پروژه یا پروژهها تأثیرگذار باشه. برای درک بهترش، میشه به کانتینرای داکر اشاره کرد که وقتی میخوای رانِشون کنی، میتونی یه پرت از سیستم رو روی پورت کانتینر بندازی، مثلا بگی پورت 8090 بیفته روی پورت 80 داخل کانتینر، با این تفاسیر دیگه برای اپلیکیشن داخلی مهم نیست که روی سیستمای مختلف پورتاش تغییر کنه، سیستمی که میخواد اون رو ران کنه خودش پورتی که لازمه رو میندازه روی پورتی که اون برنامه بصورت ثابت فرض کرده ازش استفاده میکنه. این مورد خیلی مهمه، مثلاً قدیما یه برنامه نوشتین که روی یه پورتی سرویس میده، حالا میخواد ادغام بشه با برنامهی یا برنامههای دیگهای که از همین پورت برای سرویسدهی استفاده میکنن، و طبیعتاً به مشکل میخوریم، حالا بجای اینکه بریم کد رو تغییر بدیم، صرفاً با قرار دادن یه لایه میانی و انداختن دو تا پورت مجزا به دو تا اپلیکیشن، اونا هر کدوم فکر میکنن دارن روی همون پورت خودشون سرویس میدن در صورتی که از بیرون، درخواستدهنده با دو تا پورت طرفه. یه مثالِ واضحترش اینه که به هر دلیل منطقی یا غیرمنطیقیای شاید نیاز باشه دو تا سرویس رِدیس رو بصورت مجزا ولی روی یه سرور بالا بیارین، خب بصورت پیشفرض جفتشون میخوان 6379 استفاده کنن، پس یه راه اینه که ما باید بریم کانفیگ ران کردن ردیس رو دستکاری کنیم که 12فاکتور راه بهتری پیشنهاد میده اونم اینه که یه لایه بالاسری بیاد دو تا پورت دیگه رو بایند کنه روی مثلاً دو تا کانتینری که هر کدوم یه سرویس ردیس هستن.
همزمانی، اجرای همروند یا هر اسمی که میخواین روش بذارین، یکی از مفاهیم جدانشدنی از پروژههای درست حسابیه. امکان نداره شما بخواین به یعالمه یوزر سرویس بدین و نیاز نباشه که بصورت موازی یسری تسکارو انجام بدین. حالا این وسط یه داستانی هست، بعضی از اجراهای همزمان مثلاً برای زبون جاوا که ماشین JVM یعالمه ریسورس اِشغال میکنه، میتونن با منابع کمتری به نتیجه برسن. یعنی باید برنامه طوری پیادهسازی بشه که قابلیت مقایسپذیری داشته باشه و بتونه روی منابع مختلف کارایی خودش رو حفظ کنه. برای مثال وقتی برنامهی شما روی سرور قویتری اجرا میشه، نباید خودش بره منابع اضافی مثل رم و cpu بیشتری اشغال کنه، در عمل باید اصطلاحاً instanceهای بیشتری از خودش بسازه. یکی از بهترین مثالها داکر-سوارم هست که با توجه به ترافیک و عملکرد و تنظیمات، میتونه بار رو روی ماشینها توزیع کنه (که البته برنامه خودش باید طوری پیادهسازی شده باشه که این قابلیت رو داشته باشه و اگه 12فاکتور رعایت بشه به همین شکل خواهد بود). یا مثلاً کاری که لودبالانسرها میکنن (زمانی که تقاضا برای دسترسی به یه سرویس یا خدمت خیلی زیاد میشه، برای مثال مشاهده بازی فوتبال و حجوم کاربرای همزمان چندین برابر حالت عادی) میتونه بصورتی مدیریت بشه که وقتی لازمه ماشینهای جدیدی ران بشن و قسمتی از توزیع بار رو به عهده بگیرن و کارشون که تموم شد خاموش بشن. به زبون ساده و به شکل کاملاً خلاصه، برنامه باید طوری پیادهسازی بشه که افزودنِ همزمانیِ بیشتر (حالا از هر نوعی) عملیاتی ساده و امکانپذیر و قابل اعتماد باشه.
یجورایی برعکس حالت قبلی که میخواستیم همزمانی رو گسترش بدیم، گاهی اوقات لازمه که یسری از فیچرهای برنامه رو قطع کنیم تا باقی قسمتها بتونن سرویس بهتری ارائه بدن. بعضی وقتا هم شاید لازم باشه سرویس خاموش بشه و بلافاصله خودش یا سرویس دیگهای جایگزین بشن مجدد. به عبارت دیگه وقتی میخوایم یه برنامه رو خاموش کنیم (یا قطع کنیم یا موقتاً ارائهی سرویسش رو غیر فعال کنیم) باید کارای لازمه رو بکنه که موقع راهاندازی مجدد یا با بالا اوردن آپدیت جدیدش مشکلی برای کاربر بوجود نیاد. یه مثال ساده برای این مورد اینه که وقتی یه پراسسی کارش رو تموم کرد خیلی شیک و مجلسی منابعش رو تحویل بده و دیگه به سرویس دیگهای جواب نده یا منتظر نمونه تا زمانی که ممکنه حالا یکی بیاد صداش بزنه و کارش داشته باشه. یسری مواقع هم هست که برنامهی ما بصورت غیر منتظره (حالا به هر علتی، مثل قطع برق سرور یا هر مورد دیگهای) بسته میشه یا حتی شاید بصورت پیشبینی نشده برنامه کِرَش کنه، یا موارد مشابه دیگهای که ممکنه براتون رخ داده باشه، باید تا جای ممکن این موارد پیشبینی شده باشن و با راهاندازی مجدد، برنامه بتونه بصورت عادی کار خودش رو ادامه بده و سرویساش با اختلال مواجه نشن. یکی از بهترین مثالهایی که میشه برای این فاکتور زد، سیگنال SIGTERM هست و توی لینوکس زمانی که قراره یه پراسس بسته بشه بهش ارسال میشه، برنامهها هم باید ساز و کار مشابهی در نظر بگیرن، مثلاً یه API که داره سرویس میده باید اولاً تا جای ممکن زمانهارو کاهش بده و توی روالهایی که طولانیمدتتر هستند کاربر باید بصورت مداوم درخواست بده و از وضعیت مطلع بشه، ثانیاً اجازه بدیم کاربر بتونه درخواستاش رو لغو کنه یا درخواستهای جدید در صورت نیاز ایجاد کنه (حتی اگه اون لحظه نمیشه حتیالمقدور با پیادهسازی ساز و کارهایی مثل صف، کاربر رو ناامید نکنیم و نگیم برو بعداً بیا دوباره درخواست بده). یه مثال دیگه زمانیه که اپلیکیشنای اندروید (مخصوصاً روی نسخههای قدیمیتر که بیشتر مشکل مموری داشتن) خیلی وقتا بخاطر کم اوردن مموری بسته میشدن که باید طوری پیادهسازی بشن که بتونن از اینکه قراره بسته بشن مطلع بشن و کارای لازمه رو در صورت نیاز انجام بدن و علاوه بر اینکه دفعه بعدی که برنامه باز میشه مشکلی وجود نداشته باشه، تا جای ممکن کاربر احساس کنه روند قبلیش رو بدون اختلال خاصی میتونه ادامه بده و حتی حرفهایترش اینه که برگرده دقیقاً جایی که بوده.
یکی از نکات خیلی مهم توی روند توسعه نرمافزار اینه که نسخههای در حال توسعه، با نسخههای پروداکشن تفاوت فاحشی نداشته باشن. این قابل قبوله که شاید یسری فیچرا بعداً بخواد استفاده بشه یا اینکه یسری کارایی که در حال توسعه هست روی پرداکشن نره، ولی این که خیلی فاصله داشته باشن نسخههای در حال توسعه با نسخههای پروداکشن، فاکتور دهم رو نقض میکنه. حین توسعه نرمافزار ممکنه خواسته یا ناخواسته گپ زمانی بیفته بین توسعه و پروداکشن، مثلاً کاربر با یه دیتابیس کار کرده توی توسعه، بعداً به دلیل تغییر نیازمندی یا شرایط مشتری، سرویس قراره از یه دیتابیس دیگهای استفاده کنه. یا اینکه برنامهنویس یجوری کد رو نوشته، دوآپس میخواد یجور دیگه اون رو روی یه محیط دیگه ران کنه. برای مسائل اینچنینی 12فاکتور چند تا راه حل ساده داره، مثلاً بیاین تسکها رو حتیالمقدور بشکنید و سادشون کنید، محیطهای تست رو مشابه پروداکشن ایجاد کنید، یسری تسکها رو بندازین گردن آداپتورهایی که برای کار با یسری ابزارها در اختیار قرار دارین (مثلاً کاری نداشته باشین دیتابیس چیه، شما به یه لایه بگین این دیتا رو بریز توی دیتابیس و اون خودش از آداپتور، ORM یا هر چیزی که لازمه (برای ارتباط با هر دیتابیسی مثل MySQL یا PostgreSQL یا ...) استفاده کنه)، یا بجای اینکه خودتون سیستم صف پیادهسازی کنید از مواردی مثل Redis یا RabbitMQ استفاده کنید. بصورت کلی هم سعی کنید تا جای ممکنه سراغ سرویسهای دیگه و ریسورهاتون به صورت مستقیم نرید و از یه واسطه یا آداپتور استفاده کنید که پروژتون انعطافپذیری بیشتری داشته باشه. خلاصه اینکه هرطور شده از یه طرف مراقب باشین فیچرهایی که توی نسخه دِوِلوپ و پروداکشن دارین خیلی متفاوت نباشن، از یه طرف افرادی که با کدها و پروژه سر و کار دارن بفهمن هر کدوم چجوریه و چیکار کرده و میکنه و هماهنگ باشن، و از طرف دیگه از نظر زمانی هم اجازه ندین گپِ زیادی بین تسکاتون بیفته، و در نهایت هم ابزارایی که استفاده میکنین بصورت واضح و مشخص بین نسخهها و وضعیتهای مختلفِ روند توسعهی همزمانتون مشابه باشن.
ترجمه ی تحتاللفضی: با گزارشها به عنوان جریانهای رویداد رفتار کنید. نمیدونم تا چه حد این جمله منظور رو میرسونه واسه همین فکر کنیم این شکلی بگم بهتره: لاگها رو مثل استریمهای ایوِنت در نظر بگیرین. بازم اگه زیاد واضح نیست اینجوری ادامه بدم که قدیما (اوایل که لاگ هم نداشتیم ولی بعد از مدتی) فقط لاگهارو به کمک لاگر خود زبون برنامهنویسی مینوشتن، بعد از یه مدتی متوجه شدن که علاوه بر اینکه لاگها روی سیستمعامل ممکنه ذخیره بشن بهتره خودشون روی فایلی جداگانه یا یه محیط دیگه منتقل کنن. بصورت کلی بهتره که با لاگ به همین راحتی برخورد نکنین، اونو یه استریم ببینیم که داره همینجوری دیتا میاد روش، حالا میتونیم تصمیم بگیریم که این لاگ چه اتفاقی براش بیفته، اینکه این استریم رو به سادگی داخل یه فایل بریزیم، یا بدیمشون به سیستمای آنالیز لاگ مثل Splunk یا اینکه از لاگروترهایی مثل Logplex یا Fluentd استفاه کنیم، یا بدیم به انبار دادههایی (data warehousing system) مثل Hadoop/Hive یا هر تصمیمی دیگهای که میخوایم بگیریم. ولی یه نکته خیلی مهم اینه که هر تصمیمی در رابطه با نحوه برخورد با لاگها میگیریم، باید یکپارچه باشه و توی محیطای مختلف یا deployهای متفاوت عملکرد یکسان یا تقریباً یکسانی داشته باشه. در هر صورت بهتره لاگ انداختن داخل برنامه و تصمیمی که به نحوه برخورد با لاگها میگیریم، این امکان رو در اختیارمون بذاره که بتونیم به سرعت مشکلاتی که ممکنه پیش بیاد رو پیگیری رو رفع کنیم و بتونیم با توجه به مواردِ لازمه، گزارشاتِ مورد نیاز رو بگیریم.
آخرین فاکتور میگه که تسکهای مدیریتی و ادمینی رو از مابقی روند عادی برنامه جدا کنیم. این تسکها میتونن شامل هر چیز مهمی باشن، مثل انتقال و مهاجرت پایگاه داده یا بررسی سوابق عملکرد برنامه یا اجرا کردن اسکریپتهایی که یک بار جهت بهبود سیستم انجام میشن (مثل پاک کردن رکورودهای مشکلدارِ دیتابیس). البته وقتی میگیم روندشون جدا بشه، منظور این نیست که کلن بصورت برنامههای مجزا روی سیستمهای جداگانه اجرا بشن (که البته توی موارد خاصی ممکنه بشه استثنا قائل شد)، بلکه بهتره روی همون محیط و تحت همون بستر یا محیطهای مشابهِ محیطی که برنامهی عادی مشغولِ کاره پیادهسازی بشن. برای مثال نحوهی اجرای فرامین ادمینی نباید تفاوت زیادی با فرامین عادی داشته بشه (مثلاً نباید یکیش در حد فایل اجرا کردن باشه و برای اون یکی لازم باشه یه کامند با کلی پارامتر ران بشه). بروزرسانیها هم بهتره شامل هر دو نسخهی ادمین و غیرِادمین باشه که از مشکلات همگامسازی نسخههای مختلف جلوگیری بشه. دسترسی به تسکهای مدیریتی و ادمینی بهتره به راحتی و از راه دور هم امکانپذیر باشه و خیلی خوبه که برنامهنویسا و توسعهدهندهها قابلیتهایی مثل امکان استفاده از ssh یا مابقی کامندهای ریموت رو جهت بهینگی عملکرد و استفاده در زمان های حیاتی در نظر بگیرن. خلاصه این که، فرامین مدیریتی و ادمینی از اولویت بالاتری برخوردار هستن، واسه همین باید براشون بصورت مجزا مسیر جداگانهای برای اجرا و البته توی محیط خود برنامه یا مشابه اون و با نحوهی اجرای شبیه کار با خود برنامه در نظر گفت که همبستگیِ برنامه دچار مشکل نشه (چه از نظر استفاده انسانی چه روند توسعه چه بهینگیِ منابع چه هر مورد دیگهای). همچنین نباید این کار اختلالی توی روند عادی برنامه ایجاد کنه و سیستم فارق از اینکه تسک ادمین یا مدیریت در حال اجرا هست یا نه باید کارشو انجام بده (و البته توی موارد خاص این تصمیم باید گرفته بشه که کدوم اولویت بالاتری دارن).
منتشر شده در ویرگول توسط محمد قدسیان https://virgool.io/@mohammad.ghodsian
https://virgool.io/@mohammad.ghodsian/twelve-factor-app-methodology-blw5cti9tkem