فرهنگ و ساختار نسخه‌دهی در تیم‌های نرم‌افزاری (بخش دوم)

ترکیب ساختار و مدل؛ تصمیم واقعی اینجاست

در بخش اول این مطلب؛ دو محور رو جداگانه بررسی کردیم. حالا باید ببینیم این دو محور چطور با هم تعامل دارن. چون انتخاب مونوریپو به تنهایی هیچ چیزی رو تعیین نمی‌کنه؛ همونطور که انتخاب TBD بدون در نظر گرفتن ساختار ریپو، کامل نیست.

با مرور معمول‌ترین زمینه‌هایی که تیم‌ها باهاشون روبرو میشن شروع می‌کنم.


سناریو اول؛ یه تیم، یه محصول، monolith

این رایج‌ترین نقطه شروعه. تیم کوچیک، یه کدبیس، یه محصول. اینجا ساده‌ترین ترکیب می‌تونه بهترین ترکیب باشه:

Monorepo + GitHub Flow

دلیلش روشنه: وقتی یه تیم دارید و یه محصول، هیچ مشکل coordination بین تیمی وجود نداره (اگر وجود داره؛ باید آسیب‌شناسی رو از جای دیگری شروع کرد) که به ساختار پیچیده‌تری نیاز داشته باشه. مونوریپو همه چیز رو یه جا نگه می‌داره و GitHub Flow سرعت و سادگی میده.

تنها جایی که ممکنه Gitflow توجیه داشته باشه اینه که محصول‌تون نرم‌افزار desktop یا mobile باشه با چرخه release مشخص. مثلاً اپلیکیشنی که کاربر باید آپدیت دستی نصب کنه. اون‌وقت داشتن release branch و hotfix branch معنا پیدا می‌کنه.


سناریو دوم؛ چند تیم، monolith

اینجا جالب‌تره. monolith لزوماً به معنی یه تیم نیست. خیلی از سازمان‌ها با چند تیم روی یک codebase کار می‌کنن. این زمینه‌ایه که بیشتر تیم‌ها توش گیر می‌کنن.

Monorepo + Trunk-Based Development

این ترکیبیه که گوگل سال‌هاست روی اون کار می‌کنه. منطقش هم اینه که وقتی چند تیم روی یه codebase کار می‌کنن،عمر branch‌ها طولانی می‌شه و به merge conflict‌های سنگین منجر میشه، درست همون دردی که خیلی از تیم‌ها باهاش آشنان. TBD این درد رو از ریشه حل می‌کنه چون branch‌ها اصلاً فرصت diverge کردن ندارن.

این نکته هم مهمه که خیلی از سازمان‌ها بین monolith و microservice گیر می‌کنن، ولی راه‌حل درست، الزاماً شکستن به microservice نیست. برای چند تیم که روی یک monolith کار می‌کنند، فقط branching مشکل رو حل نمی‌کنه. میشه به module boundaries، ownership، build boundaries، package boundaries و dependency rules هم فکر کرد. یعنی اگر چند تیم روی یک monolith کار می‌کنند، قبل از تصمیم درباره repo و branching باید بپرسیم آیا monolith واقعاً modular است یا فقط یک codebase بزرگ است. Monorepo + TBD بدون module boundaries، فقط integration رو سریع‌تر می‌کنه، ولی coupling رو حل نمی‌کنه.

و این پیش‌نیازش رو هم جدی بگیرید: feature flag، CI سریع، و یه فرهنگ تیمی که commit کوچیک و مکرر رو عادی می‌دونه. بدون این‌ها، TBD روی یه monolith با چند تیم می‌تونه trunk رو به یک فاجعه تمام‌عیار تبدیل کنه. بدون کلی ابزار کمکی و یه فرهنگ و ساختار قوی، به سمت این روش رفتن رو اگر نگیم تصمیم اشتباه، حداقل یک ریسک جدیه.

گزینه میانه: اگر هنوز برای TBD آماده نیستید، GitLab Flow با branch‌های محیط (staging، production) یه انتخاب pragmatic‌تره. نه به سادگی TBD، ولی از Gitflow به مراتب کم‌دردسرتر.


سناریو سوم؛ microservice، تیم‌های مستقل

اینجا بحث جدی‌تر میشه. microservice معمولاً با multirepo میاد. هر سرویس، ریپوی خودش؛ و این یه سوال مهم ایجاد می‌کنه: وقتی ریپوها از هم جدا هستن، مدل branching هر کدوم چی باشه؟

Multirepo + GitHub Flow یا GitLab Flow (به ازای هر سرویس)

منطق اینه که هر سرویس باید بتونه با tempo (زمان‌بندی یا ضرب‌آهنگ) خودش deploy کنه. پس هر ریپو مدل branching مستقل خودش رو داره، و معمولاً GitHub Flow یا GitLab Flow گزینه‌های مناسب‌تری هستند.

ولی یه چالش مهم باقی می‌مونه: تغییرات cross-service. وقتی یه API contract تغییر می‌کنه و سه سرویس باید همزمان آپدیت بشن، چطور این رو هماهنگ می‌کنید؟ اینجا دیگه مدل branching کافی نیست؛ به یه استراتژی versioning برای APIها نیاز دارید؛ چیزی مثل API versioning صریح یا consumer-driven contract testing.

یه نکته که معمولاً نادیده گرفته میشه: microservice + multirepo لزوماً به معنی استقلال کامل نیست. اگر سرویس‌هاتون tight coupling دارن، یعنی تغییر یکی، مکرراً نیاز به تغییر بقیه داره، شاید مشکل اصلی در طراحی دامنه‌ست (domain boundaries)، نه در مدل branching. و branching strategy نمی‌تونه poor domain design رو پنهان کنه. در microserviceها، اصل طلایی این نیست که «همه چیز همزمان merge بشه»، بلکه اینه که تغییرات بین سرویس‌ها تا حد ممکن backward compatible و independently deployable باشن. هر کدوم از مفاهیم زیر باید گوشه زهنمون باشن:

– backward-compatible API changes
– expand-contract pattern
– schema evolution
– event versioning
– database ownership
– consumer-driven contracts
– deprecation policy
– observability برای cross-service failures


سناریو چهارم؛ microservice، ولی با monorepo

این ترکیبی که در نگاه اول متناقض به نظر میاد، ولی در عمل خیلی از تیم‌های موفق ازش استفاده می‌کنن؛ از جمله Uber و Airbnb. ایده اینه که می‌خواید autonomy سرویس‌ها رو داشته باشید، ولی visibility و atomic refactoring مونوریپو رو از دست ندید.

Monorepo + TBD (با ابزار مناسب)

اینجا tooling حیاتیه. بدون ابزاری مثل Nx، Turborepo یا Bazel که بتونه سرویس‌های تحت‌تأثیر رو شناسایی کنه و فقط اون‌ها رو build و test کنه، CI تبدیل به یه bottleneck سنگین میشه. با ابزار مناسب اما، این ترکیب می‌تونه بهترین دنیاها رو با هم بیاره. دقت کنید دارم در مورد شرکت‌هایی صحبت می‌کنم که نیروی متخصص، ساختار، و بودجه لازم برای تمرکز روی توسعه و پیاده‌سازی ابزارها رو دارن.


نسخه‌گذاری؛ شماره نسخه فقط تزئین نیست

یه نکته‌ای که معمولاً توی بحث branching و release گم میشه، خود مفهوم versioning است. اینکه ما به خروجی نرم‌افزار چه شماره‌ای می‌دیم، فقط یه قرارداد ظاهری نیست؛ بخشی از communication بین تیم توسعه، تیم عملیات، مصرف‌کننده‌های API، مشتری‌ها و حتی آینده‌نگری خودمونه. دو مدل رایج‌تر که ارزش شناختن دارن، Semantic Versioning و Calendar Versioning هستن.

Semantic Versioning؛ وقتی compatibility مهمه

Semantic Versioning یا همون SemVer معمولاً با این فرم شناخته میشه: MAJOR.MINOR.PATCH

مثلاً: 2.4.1

معنای کلی‌اش اینه:

  • MAJOR وقتی تغییر ناسازگار یا breaking change داریم
  • MINOR وقتی قابلیت جدید اضافه شده، ولی backward compatible است
  • PATCH وقتی bugfix داریم و رفتار عمومی سیستم نباید شکسته شده باشه

این مدل برای libraryها، SDKها، packageها، APIهای public یا هر چیزی که مصرف‌کننده بیرونی یا وابسته مشخص داره، خیلی ارزشمنده. چون مصرف‌کننده از روی شماره نسخه می‌فهمه با چه سطحی از ریسک طرفه.

مثلاً اگر یه package از نسخه 2.4.1 به 2.4.2 آپدیت میشه، انتظار داریم فقط bugfix باشد. اگر به 2.5.0 میره، یعنی قابلیت جدیدی اضافه شده ولی مصرف‌کننده قبلی نباید بشکنه. اما اگر به 3.0.0 میره، یعنی باید با احتیاط نگاه کنیم، چون احتمال breaking change وجود داره.

نکته مهم اینه که SemVer فقط شماره‌گذاری نیست؛ یه تعهد رفتاریه. اگر تیمی هر breaking change رو توی patch version منتشر کنه، عملاً SemVer رو فقط تزئینی استفاده کرده. اون‌وقت مصرف‌کننده‌ها دیگه به شماره نسخه اعتماد نمی‌کنن و کل قرارداد بی‌ارزش میشه.

Calendar Versioning؛ وقتی زمان مهم‌تر از compatibility است

مدل دوم Calendar Versioning یا CalVer است. اینجا نسخه بر اساس زمان ساخته میشه، نه الزاماً بر اساس سطح breaking change.

مثلاً: 2026.04.1 یا: 2026.16 یا حتی: 24.10

این مدل برای محصولاتی خوبه که release cadence زمانی دارن. مثلاً هر هفته، هر ماه، یا هر فصل release میشن. خیلی از سیستم‌های داخلی، SaaSها، توزیع‌های نرم‌افزاری، پلتفرم‌ها یا حتی تیم‌هایی که release train دارند، از چنین الگویی سود می‌برن.

مزیتش اینه که از روی نسخه سریع می‌فهمی این build یا release مربوط به چه بازه زمانی بوده. برای پشتیبانی، incident analysis، rollback، گزارش‌گیری و هماهنگی بین تیم‌ها هم خیلی کمک می‌کنه.

مثلاً وقتی می‌گیم release 2026.04.3، برای تیم روشنه که داریم درباره سومین release ماه April 2026 صحبت می‌کنیم. این شاید برای یک library public کافی نباشه، ولی برای یک سیستم داخلی یا محصولی که مصرف‌کننده مستقیم package نداره، می‌تونه بسیار عملی‌تر از SemVer باشد.

کدوم رو انتخاب کنیم؟

اگر خروجی شما library، SDK، package یا API عمومی است، SemVer معمولاً انتخاب بهتریه. چون مصرف‌کننده بیشتر از اینکه بدونه نسخه کی منتشر شده، می‌خواد بدونه آیا آپدیت کردن safe هست یا نه.

اگر خروجی شما یک web application، internal platform، SaaS، یا محصولیه که releaseهای زمانی و operational داره، CalVer می‌تونه خواناتر و عملی‌تر باشه. چون تیم‌ها بیشتر با زمان، sprint، release train، incident و rollback فکر می‌کنن.

گاهی هم ترکیب این دو منطقیه. مثلاً محصول اصلی با CalVer release میشه، ولی SDKها و packageهای مصرفی با SemVer نسخه‌گذاری میشن. یا artifact داخلی با build number و commit SHA trace میشه، ولی release note عمومی با SemVer یا CalVer منتشر میشه.

جدول پیشنهادی برای اضافه کردن:

زمینهمدل پیشنهادیدلیل
Library / SDK / PackageSemVercompatibility برای مصرف‌کننده مهم است
Public APISemVer یا API versioning صریحbreaking change باید قابل تشخیص باشد
Web app / SaaSCalVer یا build numberزمان release و traceability مهم‌تر است
Internal platformCalVer + commit SHAبرای incident، rollback و پشتیبانی عملی‌تر است
Mobile/Desktop appSemVer یا ترکیب SemVer + build numberکاربر و storeها نسخه قابل فهم می‌خواهند
Release train سازمانیCalVerهماهنگی با sprint، ماه یا هفته ساده‌تر می‌شود

انتخاب مدل نسخه‌گذاری باید با جنس محصول هماهنگ باشه. SemVer برای وقتی خوبه که compatibility قرارداد اصلی شماست. CalVer برای وقتی خوبه که زمان، release train و traceability اهمیت بیشتری دارن. اشتباه رایج اینه که تیم‌ها یه الگوی شماره‌گذاری انتخاب می‌کنن، بدون اینکه بدونن این شماره قرار است چه اطلاعاتی را منتقل کند. نسخه خوب، فقط عدد نیست؛ بخشی از زبان مشترک تیم و مصرف‌کننده‌هاست.


جمع‌بندی

زمینهساختار پیشنهادیمدل Branching پیشنهادیهشدار
تیم کوچیک، monolith، webMonorepoGitHub FlowCI قوی داشته باشید
تیم کوچیک، monolith، desktop/mobileMonorepoGitflowبرای چرخه release مشخص
چند تیم، monolithMonorepoTBD یا GitLab Flowبدون feature flag، TBD نرید
microservice، تیم مستقلMultirepoGitHub Flow / GitLab FlowAPI versioning فراموش نشه
microservice، visibility مهمMonorepoTBD + ابزار مناسبtooling ضعیف این ترکیب رو می‌کشه

فرهنگ مهم‌تر از ابزار است

یه نکته‌ای که دوست دارم با اون این بخش رو ببندم: هیچ‌کدوم از این ترکیب‌ها خودشون کار نمی‌کنن. مونوریپو با تیمی که ownership رو جدی نمی‌گیره، به یه انبار آشغال تبدیل میشه. TBD با تیمی که تست نمی‌نویسه، trunk رو دائماً می‌شکنه. Gitflow با تیمی که hotfix‌هاشون رو به develop برنمی‌گردوندن، codebase رو diverge می‌کنه.

ابزار و فرایند، ساختار می‌دن؛ ولی این فرهنگ تیمه که تعیین می‌کنه آیا اون ساختار زنده میمونه یا نه. و فرهنگ چیزی نیست که بشه import کرد؛ باید ساخته بشه.

و در ضمن، تک‌تک این مفاهیم مثل ownership در ذهن معمارها و تِک‌لیدها اگر تعریف و مختصات استاندارد و دقیقی نداشته باشه؛ مشکل‌آفرین خواهد شد. بی به تجربه شخصی من، یکی از مشکلات رایج اینه که همه از واژه ownership استفاده می‌کنن، اما تعریف مشترکی ازش ندارن. برای یک نفر ownership یعنی «این تیم کد رو review می‌کنه»، برای دیگری یعنی «این تیم روی SLA، roadmap، incident، کیفیت، مستندات و evolution اون دامنه هم مالکیت داره». همین اختلاف تعریف، بعداً خودش رو به شکل conflict، دوباره‌کاری و فرسایش تیمی نشون می‌دهد. من با تِک‌لیدها، CTOها و معمارهای زیادی برخورد داشتم که وقتی به عنوان مشاور، در مورد ownership دامنه‌های گفت‌وگو می‌کردیم، بعد از چند دقیقه گفت‌وگو متوجه می‌شدم که تعریفی که طرف مقابلم از ownership داره، اساسا با تعریفی که من توی ذهن دارم و بر اساس تجربه یاد گرفتم متفاوت بوده. پس اگر مفاهیم رو درست و عمیق نیاموزیم و تجربه نکنیم؛ دچار مشکلی نامرئی می‌شیم که پیدا کردن و رفع کردنش می‌تونه مدت‌ها زمان ببره و تیم رو دچار فرسایش و اضمحلال کنه.


چک‌لیست تصمیم‌گیری

این چک‌لیست رو طراحی کردم که تیم‌ها بتونن با جواب دادن به این سوال‌ها، از روی تشخیص، تصمیم‌گیری کنن، و نه از روی اینکه «فلان شرکت بزرگ اینجوری کار می‌کنه.»


بخش اول؛ شناخت زمینه

درباره محصول:

  • آیا نرم‌افزار شما چرخه release مشخصی داره؟ (نسخه ۱.۰، ۲.۰) یا continuous delivery است؟
  • آیا باید چند نسخه رو همزمان پشتیبانی کنید؟
  • کاربر نهایی آپدیت رو خودش نصب می‌کنه (desktop/mobile) یا شما deploy می‌کنید (web/SaaS)؟

درباره معماری:

  • معماری فعلی monolith است یا microservice یا modular monolith (modulith)؟
  • اگر microservice است، سرویس‌ها واقعاً مستقل‌اند یا tight coupling دارن؟
  • تغییرات cross-service چقدر پیش میاد؟

درباره تیم:

  • تیم چند نفره‌ست؟ یه تیم است یا چند تیم مستقل؟
  • tempo کار تیم‌ها یکسانه یا هر تیم چرخه خودش رو داره؟
  • فرهنگ تست توی تیم چقدر جا افتاده؟

بخش دوم؛ آمادگی infrastructure

  • آیا CI pipeline دارید که روی هر PR اجرا میشه؟
  • سرعت CI چقدره؟ بیشتر از ۵ دقیقه طول می‌کشه؟ (۵ دقیقه استاندارد نیست؛ به بیان دقیق‌تر: آیا به اندازه کافی سریع است؟)
  • آیا deploy فرایند مشخص و قابل اتکایی داره یا هنوز manual است؟
  • آیا rollback سریع ممکنه؟
  • آیا feature flag دارید یا می‌تونید راه‌اندازی کنید؟
  • آیا زیرساخت مدیریت feature flag رو دارید؟ (GrowthBook, LaunchDarkly, و…)
  • تخصص زیرساخت CI رو دارید؟ یا تجربی و یکی که فقط بیشتر بلده تلاش می‌کنه توی تیم راه بندازه؟

بخش سوم؛ انتخاب ساختار Repository

اگر به اکثر این‌ها «بله» بگید، Monorepo «احتمالاً» گزینه بهتریه:

  • تیم‌ها روی feature‌هایی کار می‌کنن که به هم وابسته‌ست
  • refactoring سراسری برای شما اهمیت داره
  • می‌خواید همه با یه clone محیط کامل داشته باشن
  • ابزار مناسب (Nx، Turborepo، Bazel) رو می‌تونید راه‌اندازی کنید

و اگر به اکثر این‌ها «بله» بگید، Multirepo احتمالاً گزینه بهتریه:

  • تیم‌ها کاملاً مستقل‌اند و tempo متفاوت دارن
  • هر سرویس چرخه deploy مستقل داره
  • تغییرات cross-repo نادره و یا با contract/versioning قابل مدیریته
  • API versioning صریح دارید یا آماده‌اید راه‌اندازی کنید

بخش چهارم؛ انتخاب مدل Branching

Gitflow می‌تونه انتخاب خوبی باشه، اگر:

  • چرخه release مشخص دارید
  • باید چند نسخه رو همزمان نگه دارید
  • کاربر آپدیت دستی نصب می‌کنه

و GitHub Flow می‌تونه انتخاب بشه، اگر:

  • روزانه یا چند بار در روز deploy می‌کنید
  • CI قوی دارید
  • نیازی به نگه داشتن چند نسخه موازی ندارید

GitLab Flow انتخاب کنید اگر:

  • چند environment دارید (staging، production)
  • گاهی نیاز به release branch دارید ولی نه پیچیدگی کامل Gitflow
  • می‌خواید جریان کد یه‌طرفه و قابل پیش‌بینی باشه

Trunk-Based Development انتخاب کنید اگر:

  • CI سریع و قوی دارید
  • فرهنگ تست در تیم جا افتاده
  • feature flag دارید یا می‌تونید راه‌اندازی کنید
    • آیا برای هر feature flag مالک مشخص دارید؟
    • آیا expiry date یا cleanup policy دارید؟
    • آیا flagها در production observable هستند؟
    • آیا می‌دانید کدام ترکیب flagها تست شده‌اند؟
  • تیم با commit کوچیک و مکرر راحته
  • merge conflict یه درد مزمن واقعی برای تیم‌تون شده

یه سوال آخر؛ برای هر انتخابی

قبل از نهایی کردن تصمیم، یه سوال صادقانه از خودتون بپرسید:

آیا این انتخاب با واقعیت فعلی تیم ما تناسب داره، یا داریم خودمون رو به جایی می‌رسونیم که هنوز آمادگیش رو نداریم؟

هیچ مدلی ذاتاً بهتر از بقیه نیست. بهترین مدل اونیه که تیم شما بتونه واقعاً و پایدار روش کار کنه؛ نه اونی که روی کاغذ زیباتر به نظر میاد. نه اونی رو که یک شرکت بزرگ با تیم مستقل ریلیزمنجمنت چندین نفره داره و ریلیز فقط توسط اون تیم و بعد از مرور دقیق مستندات ریلیز، اطمینان از عدم تداخل و یا breaking change در سایر سیستم‌ها و با دریافت چراغ سبز توسط ریلیز منجر صادر می‌شه رو برای تیمی که توسعه‌دهنده با موافقت ضمنی و شفاهی تک‌لید ریلیز رو انجام می‌ده استفاده کنیم… تصمیم آگاهانه خیلی مهم‌تر از انتخاب‌های به‌ظاهر مدرن و قشنگه…

دیدگاهتان را بنویسید