12Factor Appبه زبان فارسی، برای مطالعه تیم‌های فنی

۱۲Factor App مجموعه‌ای از اصل‌ها برای ساخت Software-as-a-Service است؛ Appهایی که قابل Deploy، قابل Scale، قابل نگهداری و قابل انتقال بین محیط‌های مختلف باشند.

این نسخه برای کتابخانه مرتب شده است: متن کوتاه و فصل‌محور، فهرست سریع، مثال‌های بد و خوب، و برداشت عملی برای Docker و Kubernetes. اصطلاحات رایج فنی مثل Codebase، Deploy، Config، Runtime، Backing Service و Process همان‌طور نگه داشته شده‌اند.

تصویر مفهومی عدد ۱۲ برای 12Factor App
01

Factor 01

Codebase

یک Codebase، چند Deploy

تصویر مفهومی Codebase واحد که به چند Deploy متصل شده است

هر App باید یک Codebase اصلی داشته باشد؛ معمولاً یک Git repo. همین Codebase می‌تواند در local، staging و production به شکل Deployهای مختلف اجرا شود.

چرا مهم است؟

وقتی برای هر محیط یا هر مشتری کد جدا داشته باشیم، بعد از مدتی چند نسخه از حقیقت داریم. Bug fixها جا می‌مانند، Release سخت می‌شود و تیم به جای ساخت محصول، با نسخه‌ها کشتی می‌گیرد.

برداشت برای Kubernetes / Docker

در Kubernetes هر Service باید Image مشخصی داشته باشد که از همان Codebase ساخته شده. تفاوت dev/staging/prod باید در ConfigMap، Secret، Helm values یا GitOps values باشد، نه در سورس‌کد.

مثال بد

  • برای production یک repo جدا داشته باشیم.
  • برای staging یک branch دائمی با تغییرات خاص محیط نگه داریم.
  • برای یک مشتری خاص، کد را copy کنیم و دستی تغییر بدهیم.

مثال خوب

  • یک repo اصلی برای هر App داشته باشیم.
  • تفاوت محیط‌ها با Config و Release مدیریت شود، نه با تغییر کد.
  • کد مشترک بین چند App به package/library جدا تبدیل شود.
02

Factor 02

Dependencies

Dependencyها را explicit تعریف و isolate کن

تصویر مفهومی Dependencyهای مشخص و ایزوله‌شده داخل کانتینر اپلیکیشن

App نباید به چیزی که «احتمالاً روی سرور نصب است» وابسته باشد. همه Dependencyها باید دقیقاً در manifest پروژه مشخص شوند.

چرا مهم است؟

اگر App فقط روی لپ‌تاپ یک نفر یا یک VM قدیمی اجرا شود، یعنی سیستم قابل‌اعتماد نیست؛ فقط خوش‌شانس بوده. Dependency پنهان یعنی deploy شانسی.

برداشت برای Kubernetes / Docker

در دنیای container، Dockerfile و lockfile باید حقیقت Dependencyها باشند. اگر Pod فقط روی یک Node خاص بالا می‌آید، بوی Dependency پنهان می‌دهد؛ و این بو معمولاً بوی Incident است.

مثال بد

  • استفاده از package نصب‌شده روی OS بدون ثبت در پروژه.
  • فرض گرفتن وجود ابزارهایی مثل curl یا ImageMagick روی همه سرورها.
  • اجرای پروژه با نسخه نامشخص runtime یا library.

مثال خوب

  • استفاده از package.json، go.mod، requirements.txt، Gemfile یا ابزار مشابه.
  • pin کردن نسخه‌ها تا build قابل‌تکرار باشد.
  • اجرای App داخل container یا محیط isolate شده.
03

Factor 03

Config

Config را در Environment نگه دار

تصویر مفهومی Config، Environment Variable و Secretهای جدا از کد

هر چیزی که بین Deployها فرق می‌کند، Config است: آدرس DB، credential، hostname، feature flag و مقادیر وابسته به محیط.

چرا مهم است؟

Code باید بین محیط‌ها ثابت بماند. اگر برای تغییر production مجبور به تغییر سورس‌کد هستیم، مرز Code و Config را خراب کرده‌ایم.

برداشت برای Kubernetes / Docker

در Kubernetes، ConfigMap برای config غیرحساس و Secret برای credential استفاده شود. تغییر Config باید Release/rollout قابل‌ردیابی داشته باشد، نه SSH دستی داخل سرور.

مثال بد

  • قرار دادن password یا token داخل repository.
  • داشتن config فایل‌های پراکنده و دستی روی سرورها.
  • تعریف environmentهای زیاد مثل qa2، ali-staging، test-prod و هیولای چندسر.

مثال خوب

  • استفاده از Environment Variable برای مقادیر محیطی.
  • نگهداری Secretها در Secret manager یا Kubernetes Secret با کنترل دسترسی.
  • هر Config را مستقل و granular مدیریت کنیم، نه در گروه‌های مبهم.
04

Factor 04

Backing Services

Backing Serviceها را attached resource ببین

تصویر مفهومی Backing Serviceهای قابل اتصال مثل دیتابیس، کش، صف و ذخیره‌ساز

Database، Queue، Cache، SMTP، Object Storage و سرویس‌های خارجی باید برای App مثل resource متصل باشند، نه چیزی که App به پیاده‌سازی داخلی‌اش گره خورده باشد.

چرا مهم است؟

اگر عوض کردن MySQL محلی با RDS یا SMTP داخلی با سرویس بیرونی نیاز به تغییر کد داشته باشد، App زیادی به زیرساخت چسبیده است.

برداشت برای Kubernetes / Docker

در Kubernetes، Service discovery و Secret/ConfigMap باید جایگزین آدرس‌دهی دستی شوند. App نباید بداند Redis روی کدام VM است؛ فقط باید resource handle درست را بگیرد.

مثال بد

  • آدرس DB یا Queue در کد hardcode شده باشد.
  • App بین سرویس local و third-party رفتار متفاوت داشته باشد.
  • تعویض provider بدون تغییر code ممکن نباشد.

مثال خوب

  • اتصال به همه Backing Serviceها از طریق URL/credential در Config.
  • امکان detach/attach کردن resource بدون تغییر code.
  • تعریف واضح ownership، SLA و backup برای هر resource.
05

Factor 05

Build, Release, Run

Build، Release و Run را جدا نگه دار

تصویر مفهومی جدایی مراحل Build، Release و Run در مسیر اجرای اپلیکیشن

Code اول تبدیل به Build می‌شود؛ بعد Build با Config ترکیب می‌شود و Release ساخته می‌شود؛ بعد همان Release در Runtime اجرا می‌شود.

چرا مهم است؟

وقتی وسط Runtime روی سرور تغییر دستی بدهیم، دیگر نمی‌دانیم چه چیزی واقعاً در production اجرا می‌شود. این دقیقاً همان جایی است که rollback تبدیل به فال قهوه می‌شود.

برداشت برای Kubernetes / Docker

در Kubernetes، container image باید Build immutable باشد. Release می‌تواند با image tag/digest و Helm chart version مشخص شود. هر rollout باید قابل audit و rollback باشد.

مثال بد

  • SSH به سرور و تغییر دستی فایل.
  • Build کردن asset یا dependency در زمان بالا آمدن production.
  • Release بدون شناسه مشخص و بدون امکان rollback.

مثال خوب

  • هر Release یک ID مشخص داشته باشد.
  • Build immutable باشد.
  • Rollback یعنی برگشت به Release قبلی، نه حدس‌زدن تغییرات دستی.
06

Factor 06

Processes

App را به شکل Processهای Stateless اجرا کن

تصویر مفهومی Processهای Stateless با State پایدار خارج از کانتینر

App باید در قالب یک یا چند Process اجرا شود و هیچ state مهمی را در memory یا filesystem محلی نگه ندارد.

چرا مهم است؟

اگر request بعدی باید حتماً به همان Process قبلی برسد، scaling و failover سخت می‌شود. Stateless بودن یعنی آزادی در جابه‌جایی، restart و scale.

برداشت برای Kubernetes / Docker

Podها ephemeral هستند. هر چیزی داخل container ممکن است با restart از بین برود. برای state واقعی باید PVC، DB، Redis یا Object Storage داشته باشیم، نه امید و دعا.

مثال بد

  • نگهداری session فقط در memory همان Process.
  • ذخیره فایل‌های مهم روی filesystem داخل container.
  • وابستگی به sticky session.

مثال خوب

  • State پایدار در Backing Service مثل DB، Redis یا Object Storage ذخیره شود.
  • Processها share-nothing باشند.
  • cache محلی فقط موقت و قابل‌ازدست‌رفتن باشد.
07

Factor 07

Port Binding

Service را از طریق Port ارائه کن

تصویر مفهومی اپلیکیشنی که خودش روی یک Port سرویس ارائه می‌کند

App باید خودش سرویس را با bind شدن به یک Port ارائه کند؛ مثلاً HTTP را روی یک Port listen کند.

چرا مهم است؟

App self-contained راحت‌تر deploy، تست و scale می‌شود. محیط اجرا فقط باید بتواند traffic را به port-bound process route کند.

برداشت برای Kubernetes / Docker

در Kubernetes، containerPort، Service و Ingress همین مدل را پیاده می‌کنند. App روی Port listen می‌کند؛ Service آن را stable می‌کند؛ Ingress/Nginx traffic بیرونی را route می‌کند.

مثال بد

  • وابستگی سخت به Apache/Tomcat/Nginx به‌عنوان container اجرای App.
  • فرض گرفتن runtime injection از سمت سرور.
  • App بدون قرارداد مشخص port اجرا شود.

مثال خوب

  • App خودش روی port مشخص listen کند.
  • Port از Config/Env گرفته شود.
  • Routing بیرونی جدا از خود App مدیریت شود.
08

Factor 08

Concurrency

با مدل Process scale کن

تصویر مفهومی Scale شدن مستقل Process typeهای web، worker و scheduler

برای workloadهای مختلف، Process typeهای جدا تعریف کن؛ مثلاً web برای HTTP و worker برای jobهای background.

چرا مهم است؟

Scale فقط زیاد کردن CPU نیست. باید نوع workload مشخص باشد. بعضی بارها request-driven هستند، بعضی queue-driven، بعضی scheduled.

برداشت برای Kubernetes / Docker

در Kubernetes، web Deployment و worker Deployment جدا بساز. HPA برای web می‌تواند بر اساس CPU/RPS باشد؛ worker می‌تواند بر اساس queue length یا custom metric scale شود.

مثال بد

  • همه کارها داخل یک Process بزرگ انجام شود.
  • Background job داخل web request اجرا شود.
  • Scale فقط vertical و با بزرگ‌تر کردن یک VM انجام شود.

مثال خوب

  • Process typeهای جدا مثل web، worker، scheduler تعریف شود.
  • هر Process type مستقل scale شود.
  • Crash و restart توسط process manager یا orchestrator مدیریت شود.
09

Factor 09

Disposability

Startup سریع و Graceful Shutdown داشته باش

تصویر مفهومی Startup سریع و Shutdown امن همراه با بازگرداندن Job به صف

Process باید سریع بالا بیاید و هنگام shutdown هم کار جاری را سالم تمام کند یا قابل‌تکرار به queue برگرداند.

چرا مهم است؟

در production، restart و reschedule عادی است. اگر App با هر restart قهر کند، یعنی برای cloud-native آماده نیست.

برداشت برای Kubernetes / Docker

در Kubernetes باید readinessProbe، livenessProbe، preStop hook و terminationGracePeriodSeconds درست تنظیم شود. Worker باید روی SIGTERM job را درست ack/nack کند.

مثال بد

  • Startup طولانی و پر از کارهای سنگین.
  • نادیده گرفتن SIGTERM.
  • jobهایی که اگر وسط اجرا قطع شوند، داده خراب می‌کنند.

مثال خوب

  • Startup سبک و سریع.
  • Graceful shutdown برای web و worker.
  • Idempotent کردن jobها و transaction درست.
10

Factor 10

Dev/Prod Parity

Dev، Staging و Production را شبیه نگه دار

تصویر مفهومی شباهت محیط‌های توسعه، staging و production

فاصله بین محیط توسعه و production باید کم باشد: زمان deploy، افراد درگیر و ابزارهای استفاده‌شده.

چرا مهم است؟

هرچه dev با production فرق بیشتری داشته باشد، bugهای بیشتری فقط در production دیده می‌شوند؛ و production بدترین جا برای کشف واقعیت است.

برداشت برای Kubernetes / Docker

با Docker Compose، dev container، namespaceهای staging و production و GitOps می‌توان parity را بهتر کرد. هدف یکی شدن کامل نیست؛ هدف حذف تفاوت‌های خطرناک است.

مثال بد

  • SQLite در local ولی PostgreSQL در production.
  • Queue متفاوت در dev و prod.
  • توسعه‌دهنده کد می‌نویسد ولی هیچ‌وقت رفتار production را نمی‌بیند.

مثال خوب

  • استفاده از نوع و نسخه نزدیک Backing Serviceها در همه محیط‌ها.
  • Deployهای کوچک و مکرر.
  • درگیر بودن developer در مشاهده رفتار production.
11

Factor 11

Logs

Logها را Event Stream ببین

تصویر مفهومی جریان Logها از اپلیکیشن به Pipeline مشاهده‌پذیری

App نباید خودش درگیر ذخیره و مدیریت فایل log شود. Process باید eventها را به stdout/stderr بنویسد و محیط اجرا آن‌ها را جمع‌آوری کند.

چرا مهم است؟

Log برای مشاهده رفتار سیستم است. وقتی هر سرویس log را یک‌جور و یک‌جا ذخیره کند، observability تبدیل به باستان‌شناسی می‌شود.

برداشت برای Kubernetes / Docker

در Kubernetes، stdout/stderr هر container توسط runtime قابل جمع‌آوری است. Log pipeline باید بیرون از App باشد. App فقط خوب و ساختاریافته log کند؛ بقیه با platform.

مثال بد

  • نوشتن log فقط داخل فایل local container.
  • چرخاندن، آرشیو و ارسال log توسط خود App.
  • نداشتن correlation id یا ساختار قابل جست‌وجو.

مثال خوب

  • نوشتن log به stdout/stderr.
  • جمع‌آوری توسط Fluent Bit، Fluentd، Vector یا ابزار مشابه.
  • ارسال به Elasticsearch، Loki، Splunk یا data warehouse.
12

Factor 12

Admin Processes

Taskهای مدیریتی را One-off Process اجرا کن

تصویر مفهومی اجرای Task مدیریتی یک‌باره با همان Release و Config

کارهایی مثل migration، script اصلاح داده یا console باید در همان Release و همان Config اجرا شوند؛ ولی به شکل Process یک‌باره.

چرا مهم است؟

Admin task اگر با کد یا config متفاوت اجرا شود، می‌تواند production را خراب کند؛ آن هم با اعتمادبه‌نفس کامل.

برداشت برای Kubernetes / Docker

در Kubernetes، migration یا data fix بهتر است به شکل Job/CronJob یا task کنترل‌شده اجرا شود. image باید همان Release باشد، نه image دست‌ساز اضطراری شب حادثه.

مثال بد

  • اجرای script دستی از لپ‌تاپ با config نامعلوم.
  • داشتن migration خارج از repo.
  • اجرای task مدیریتی روی سرور با dependency متفاوت.

مثال خوب

  • Admin code داخل همان repo باشد.
  • با همان dependency isolation اجرا شود.
  • روی همان Release و Config محیط هدف اجرا شود.