Architects Delight: Enforcing Layers and Project Boundaries با Nx

معرفی
زمانی که من به عنوان یک توسعه دهنده نرم افزار سفر خود را آغاز کردم، به عمق معماری نرم افزار پرداختم. کتابهای معروفی مانند «معماری پاک» و «طراحی دامنه محور (DDD)» بینشهای ارزشمندی را درباره مفاهیمی مانند مرزهای پروژه و لایههای نرمافزار (اعم از عمودی و افقی) ارائه میکنند. با این حال، چیزی که به شدت گم شده بود، ابزاری سریع و کارآمد برای اجرای این اصول بود.
سپس، Nx، به طور خاص آن را کشف کردم @nx/enforce-module-boundaries
پلاگین ESLint که نحوه اجرای این مرزها را متحول کرده است.
سمت پنهان کوه یخ
جالب است که اگر چه @nx/enforce-module-boundaries
پلاگین ELLint در کد تولید شده هر Nx-Monorepo یکپارچه تعبیه شده است، به نظر می رسد زمانی که مکالمه به شایستگی های Nx تبدیل می شود، زیر رادار پرواز می کند، که در واقع بسیار زیاد است. حتی ویدیوی Nx، “The Nx Iceberg”، علیرغم تمرکز بر ویژگی های کمتر شناخته شده این ابزار، به آن اشاره ای نمی کند.
چندین نویسنده از جمله مانفرد اشتایر، لارس گیروپ برینک نیلسن و من به این موضوع پرداختهاند. با این حال، بسیاری از توسعه دهندگانی که از Nx استفاده می کنند تا حد امکان از این ویژگی استفاده نمی کنند. این باعث شد که این مقاله را بنویسم و قدرت Nx را در اجرای مرزهای پروژه برجسته کنم.
مرزها و لایه های پروژه چیست؟
قبل از اینکه به اعمال مرزهای پروژه بپردازیم، ضروری است که بدانیم آنها چیست. این قوانین یا اصول نحوه تعامل بخش های مختلف نرم افزار ما را دیکته می کنند.
به عنوان مثال، معماران نرم افزار اغلب از فلسفه هایی مانند معماری پیاز و معماری تمیز برای تعریف لایه های افقی استفاده می کنند. سپس این لایه ها با استفاده از اصولی مانند طراحی استراتژیک از Domain-Driven Design به برش های عمودی یا مرزها گروه بندی می شوند.
با این حال، بدون ابزارهای خودکار، حفظ این مرزها دشوار می شود. توسعه دهندگان ممکن است به طور ناخواسته از این مرزها عبور کنند که منجر به مشکلات نگهداری و مقیاس پذیری شود.
ایجاد پروژه ها و مرزها با Nx
اولاً، قبل از شروع اعمال محدودیتهای مرزی، باید پروژهها و مرزها داشته باشیم. Nx این فرآیند را با ژنراتورهای خود که به سرعت برنامه ها و کتابخانه ها را ایجاد می کنند، ساده می کند. در اینجا یک دستور ساده برای ایجاد یک کتابخانه وجود دارد:
nx generate @nrwl/js:library feature-booking
اما یک عنصر اصلی از دست رفته است. تگ های پروژه Nx در اینجا بسیار مهم هستند. آنها به عنوان تعاریف مکتوب از مرزهای ما عمل می کنند. برای مثال:
// project.json
{
...
"tags": ["bounded-context:booking", "type:feature"],
...
}
در مثال فعلی، یک برش عمودی یا لایه عمودی را بر اساس زمینه محدود حاوی کتابخانه تعریف می کنیم: رزرو بافت محدود نه تنها این، بلکه یک لایه افقی را نیز بر اساس نوع کتابخانه تعریف می کنیم: The ویژگی نوع
علاوه بر این، میتوانیم از گروهبندی پوشهها با Nx برای سادهتر کردن ساختار استفاده کنیم. من معتقدم که باید یک ارتباط مستقیم بین برچسب ها و پوشه های گروه بندی وجود داشته باشد. من این الگوها را به طور گسترده در مقاله خود “گروه بندی معنایی پوشه ها با Nx” مورد بحث قرار داده ام.
اجرای مرزهای پروژه با Nx
با وجود پروژه ها و مرزهای خود، می توانیم از قدرت Nx استفاده کنیم @nx/enforce-module-boundaries
پلاگین ESLint برای تعیین محدودیت بین لایه ها.
به عنوان مثال، اگر بخواهیم آن را محدود کنیم، یک کتابخانه متعلق به رزرو زمینه محدود فقط می تواند کتابخانه ها را از همان زمینه محدود وارد کند. یا به عبارت دیگر، اگر بخواهیم مرز عمودی تعریف شده توسط تگهای بافت محدود را اعمال کنیم، میتوانیم کارهای زیر را انجام دهیم:
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
...
"depConstraints": [
...
{
"sourceTag": "bounded-context:booking",
"onlyDependOnLibsWithTags": ["bounded-context:booking"]
},
...
]
}
]
همین کار را می توان برای لایه های افقی ما نیز انجام داد. در مثال زیر موارد زیر را اجرا خواهیم کرد:
- کتابخانه ها با نوع ویژگی فقط می تواند کتابخانه هایی با نوع وارد کند ویژگی، دامنه، رابط کاربری
- کتابخانه ها با نوع رابط کاربری فقط می تواند به کتابخانه هایی با نوع بستگی داشته باشد رابط کاربری و دامنه
- کتابخانه ها با نوع دامنه فقط می تواند به کتابخانه هایی با نوع بستگی داشته باشد دامنه.
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
...
"depConstraints": [
...
{
"sourceTag": "type:feature",
"onlyDependOnLibsWithTags": ["type:feature", "type:domain", "type:ui"]
},
{
"sourceTag": "type:ui",
"onlyDependOnLibsWithTags": ["type:domain", "type:ui"]
},
{
"sourceTag": "type:domain",
"onlyDependOnLibsWithTags": ["type:domain"]
},
...
]
}
]
با وجود این محدودیت ها، اگر بخواهید کتابخانه ای را وارد کنید که یکی از این مرزهای اجباری را می شکند، با خطای ESLint مواجه می شوید. در اینجا یک وضعیت فرضی وجود دارد:
// File: libs/boundary/domain/types.ts
import { Button } from '@my-org/ui';
error A project tagged with "type:domain" can only depend on libs tagged with "type:domain" @nx/enforce-module-boundaries
این پیام خطا نشان می دهد که ما سعی کرده ایم از یک مرز عبور کنیم، بنابراین به ما کمک می کند تا یکپارچگی معماری خود را حفظ کنیم.
و این همه چیز نیست. ما حتی می توانیم واردات خارجی را همانطور که در اسناد توضیح داده شده محدود کنیم. اینو پیدا کردم ابزار بسیار مفید برای استفاده از فناوری هایی که شبیه به React، Qwik و SolidJS هستند. یا حتی بک اند و فرانت اند مانند Angular و NestJS.
نتیجه
اجرای مرزها و لایههای پروژه به راحتی رویایی بود که فکر میکردم خیلی زیاد است.
Nx ها @nx/enforce-module-boundaries
پلاگین ESLint یک راه حل قدرتمند ارائه می دهد. هرچه بیشتر پتانسیل آن را درک کنیم و یاد بگیریم از قدرت آن استفاده کنیم، بیشتر به سمت ایجاد راه حل های نرم افزاری قوی، مقیاس پذیر و قابل نگهداری پیش می رویم. Nx واقعاً در آینده معماری نرم افزار پیشرو است.