برنامه نویسی

چگونه Next.js را در مقیاس ثابت کردیم: اسرار معماری DI و تمیز از تولید

فهرست مطالب

نمای کلی

تزریق وابستگی (DI) یکی از بحث و گفتگوترین الگوهای طراحی در توسعه نرم افزار است. در حالی که نظرات در مورد اینکه آیا این امر از نظر جهانی سودمند است متفاوت است ، یک چیز مسلم است: DI می تواند بسیار مفید باشد اگر به عنوان “چکش طلایی” رفتار نشود. استفاده بیش از حد از آن ، مانند تزریق کورکورانه هر وابستگی ، می تواند به جای کد پاک تر به پیچیدگی های غیر ضروری منجر شود.

در این مقاله ، ما بررسی خواهیم کرد که چگونه می توان DI را به طور مؤثر در Next.js اعمال کرد و تعادل مناسب بین قابلیت حفظ و سادگی را ایجاد کرد.

الزام

این مقاله بر اساس معماری ذکر شده در مخزن بعدی BoilerPlate است. برای درک کامل اجرای ، توصیه می کنم ابتدا این دو سند بنیادی را مرور کنید:

ما برای هر مثال ، منابع مستقیم پرونده به پیاده سازی های کلیدی در مخزن اصلی را درج کرده ایم.

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

یک دیگ بخار کامل NextJS ، بر اساس معماری تمیز ، MVVM و الگوی برنامه نویسی کاربردی.

فهرست محتوای

  • نمای کلی
  • فن آوری
  • معماری
  • ساخت پوشه
  • شروع
  • خط صنفی

نمای کلی

این پروژه یک نقطه شروع برای پروژه های متوسط ​​تا بزرگ شما با NextJS است ، تا مطمئن شوید که یک پایه ساختاری ، قابل استفاده و قابل استفاده مجدد برای پروژه شما بر اساس بهترین شیوه ها در معماری تمیز ، رویکرد DDD برای منطق تجاری ، کتاب پیشکسوت ، کتاب داستان و پایدار برای آزمایش اطلاعات و بخش UI و همچنین برنامه نویسی عملکردی با دست زدن به خطا در زمینه های مربوط به ورود به سیستم.

انگیزه

NextJS و بسیاری دیگر از ابزارهای جدید SSR یک رویکرد واقعاً خوب و جدید برای رسیدگی به برنامه های Frontend ارائه می دهند ، با ابزارهای جدید برای به وجود آوردن یک تجربه خوب جدید برای کاربران. اما از آنجا که آنها جدید هستند و آنها فقط سعی کردند ابزارها و ویژگی های جدیدی را به ارمغان بیاورند و همچنین جامعه Frontend ، در مورد مهندسی نرم افزار و رویکرد بهترین شیوه ها برای این ابزارها صحبت نکردند.

بنابراین در بسیاری از موارد ما می بینیم که بسیاری از تیم ها از NextJS استفاده می کنند …

usecase

در برنامه های بعدی. JS ، ما پروژه را در دو لایه اصلی سازماندهی می کنیم:

  • لایه ویژگی – شامل تمام منطق کسب و کار است.
  • Layer Application – ارتباط با API های Next.js و React را انجام می دهد.

جایی که ما از DI استفاده می کنیم

ما فقط بین مؤلفه هایی که:

  • به حداقل دانش یکدیگر نیاز دارید (جفت شل).
  • می تواند به طور مستقل کار کند اما یکپارچه ادغام شود.

ما از تزریق وابستگی (DI) به روش های مختلف در هر لایه استفاده می کنیم:

  1. لایه مشخصه
    • استفاده: DI متصل می شود UseCase وت Repository از طریق رابط ها ، به دنبال Adapter Patternبشر
  • هدف: تضمین می کند که منطق کسب و کار از منابع داده جدا می شود (به عنوان مثال ، بانکهای اطلاعاتی ، API).
  1. لایه کاربردی
    • استفاده: DI Bridges ViewModel (VM) وت View، پایبندی به Bridge Patternبشر
  • مورد خاص: هنگام عبور VM از a Server Component به Client Component، ما از یک منحصر به فرد استفاده می کنیم VM key برای سریال سازی
  1. همچنین وابستگی های جهانی و دیگر. ## لایه ویژگی همانطور که در ابتدا گفته شد ، ما از تزریق وابستگی (DI) در لایه ویژگی برای اتصال استفاده می کنیم UseCase وت Repository لایه ها از طریق رابط ها.

کلید DI: اتصال مبتنی بر رابط

برای پیوند یک USECase با مخزن مربوطه ، از یک کلید منحصر به فرد DI که به رابط مخزن گره خورده است استفاده می کنیم. این وضوح وابستگی از نوع ایمن را تضمین می کند.

مثال:

export default interface UserRepository {
  create(params: CreateUserParams): ApiTask<true>;
  update(params: UpdateUserParams): ApiTask<true>;
  delete(ids: string[]): ApiTask<true>;
}

export const userRepoKey = "userRepoKey";

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پرونده: user.repository.interface.ts

همانطور که در این پرونده می بینید ما userRepositoryKey که ما برای ثبت یک مخزن استفاده می کنیم و همچنین از آن در USECase برای استفاده از این مخزن در داخل آن استفاده می کنیم.

ماژول

در طراحی دامنه محور (DDD) ، یک ماژول به عنوان یک واحد سازمانی سطح بالا که مفاهیم مربوط به دامنه را گروه بندی می کند ، خدمت می کند. به عنوان مثال ، تمام منطق دامنه مربوط به کاربر (اشخاص ، مخازن ، خدمات) در یک ماژول کاربر سازماندهی می شوند ، جایی که ما همچنین ثبت نام های تزریق وابستگی آن (DI) را نیز متمرکز می کنیم.

مثال:

export default function userModule(di: DependencyContainer) {
  di.register(userRepoKey, UserRepositoryImpl);

  return di;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پرونده: ماژول کاربر.

بیایید جنبه های اصلی این پرونده را بررسی کنیم:

  1. *مسئولیت لایه معماری:

    این مؤلفه در هر دو رابط مخزن دامنه (I-REPO) و اجرای مخزن بتونی مشاهده می کند. با توجه به اصول معماری تمیز ، این دانش دوگانه به درستی آن را در آن قرار می دهد Data Layerبشر

  2. تنظیم تزریق وابستگی:

    ما یک کانتینر کودک DI دریافت می کنیم که تمام مخازن دامنه و اجرای وابستگی ها را ثبت می کند.

ویژگی های

برای سازماندهی موثر ، مدیریت و ثبت همه ماژول های دامنه ، ما به یک مؤلفه متمرکز نیاز داریم تا این مسئولیت ها را برطرف کنیم:

مثال:

/**
 * On adding new domain module, just add it to this list
 */
const moduleKeyToDi: Record<
  string,
  (di: DependencyContainer) => DependencyContainer
> = {
  [authModuleKey]: authModule,
  [userModuleKey]: userModule,
};

const memoizedDis: Record<string, DependencyContainer> = {};

export default function featuresDi(module: string): DependencyContainer {
  if (memoizedDis[module]) return memoizedDis[module];
  const moduleDiHandler = moduleKeyToDi[module];
  if (!moduleDiHandler)
    throw new Error(`Server Di didn't found for module: ${module}`);

  const moduleDi = moduleDiHandler(di.createChildContainer());
  globalModule(moduleDi);
  memoizedDis[module] = moduleDi;
  return moduleDi;
}

export function diResolve<T = unknown>(module: string, key: InjectionToken): T {
  return featuresDi(module).resolve<T>(key);
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پرونده: ویژگی.

این اجرای شامل روشی برای بازیابی تنظیمات تزریق وابستگی خاص دامنه (DI) با استفاده از unique module keysبشر برای اطمینان از انزوا و سازماندهی مناسب ، به هر دامنه یک شناسه مجزا اختصاص داده می شود.
برای مرجع ، به کلید ماژول دامنه دامنه کاربر در پرونده کلید ماژول کاربر مراجعه کنید.

همچنین در فایل DI ویژگی با افزودن ماژول جدید برای دامنه های جدید می توانیم ماژول دامنه جدید را به لیست اضافه کنیم moduleKeyToDi شیء.

استفاده

در پایان ، ما می توانیم مخزن را در لایه usecase مانند این دریافت کنیم:

مثال:

export default async function createUserUseCase(
  params: CreateUserParams,
): Promise<ApiEither<true>> {
  const repo = diResolve<UserRepository>(userModuleKey, userRepoKey);

  return repo.create(params)();
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پرونده: CreateUserUsecase

لایه کاربردی

بر خلاف لایه ویژگی ، لایه برنامه از یک رویکرد معماری متفاوت پیروی می کند. در اینجا ما الگوی MVVM (مدل-نمای-مشاهده -نمودل) را پیاده سازی می کنیم ، جایی که تزریق وابستگی نقش مهمی در عبور ViewModels از اجزای سرور به نماهای سمت مشتری دارد.

کلید VM

برای یک VM می توانیم یک کلید منحصر به فرد برای ثبت VM توسط یک رشته منحصر به فرد داشته باشیم

ماژول برنامه

در هر صفحه یا مسیری می توانیم یک ماژول را برای ثبت همه VM ها و هر قسمت دیگر در DI خود تعریف کنیم:

مثال:

/**
 * Each page can have its own di to connect all vms, usecases or controllers
 */
export default function dashboardAppModule() {
  const dashboardDi = di.createChildContainer();

  dashboardDi.register(
    createRandomInvoiceButtonVMKey,
    CreateRandomInvoiceButtonVM,
  );

  return dashboardDi;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پرونده: ماژول برنامه داشبورد

ارائه دهنده

ما می توانیم با اجرای یک الگوی ارائه دهنده ، ظرف تزریق وابستگی خود را در سراسر درخت مؤلفه توزیع کنیم. این امر هر مؤلفه ای را قادر می سازد ضمن حفظ انزوا وابستگی مناسب ، به View Models ثبت شده دسترسی پیدا کند.

مثال:


export default function Layout({ children }: { children: React.ReactNode }) {
  const di = useRef(dashboardAppModule());
  return (
    <ReactVVMDiProvider diContainer={di.current}>
      <div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
        <div className="w-full flex-none md:w-64">
          <SideNav />
        </div>
        <div className="flex-grow p-6 md:overflow-y-auto md:p-12">
          {children}
        </div>
      </div>
    </ReactVVMDiProvider>
  );
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پرونده: طرح داشبورد

در این مثال که ما از بسته ReactVVM استفاده می کنیم ، DI را به ارائه دهنده این کتابخانه منتقل کردیم تا بتواند نمای را به VM ما متصل کند ، به طور خودکار بر اساس انتقال VMKey به مؤلفه View.

استفاده

برای مثال اجرای بتن ، بیایید نحوه انتقال یک ViewModel را برای تولید فاکتور تصادفی به یک مؤلفه دکمه بررسی کنیم.

مثال:

export default async function LatestInvoices() {
  const latestInvoices = await latestInvoicesController();

  return (
    <div className="flex w-full flex-col md:col-span-4">
      ...
        <Button vmKey={createRandomInvoiceButtonVMKey} />
      ...
    div>
  );
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پرونده: آخرین فاکتور

همانطور که می بینید ما کلید VM را به نمای توسط VMKey Prop به مؤلفه دکمه منتقل کردیم.

پایان

در این مقاله ، ما بررسی کردیم Clean Architecture رویکرد با استفاده از Dependency Injection (DI) برای جدا کردن منطق کسب و کار ، استفاده DDD اصول برای اتصال UseCase وت Repository لایه ها از طریق رابط ها برای قابلیت حفظ و آزمایش.

برای Application Layer، ما یک اجرا کردیم MVVM pattern، با استفاده از کلیدهای VM منحصر به فرد برای پل سرور و مؤلفه های مشتری ضمن اطمینان از قابلیت استفاده مجدد و مسئولیت واحد از طریق Bridge Patternبشر این منطق UI را تمیز و مقیاس پذیر نگه می دارد Next.js برنامه ها

با ساختار ثبت نام DI در اطراف ماژول های دامنه و ارائه دهندگان متمرکز ، ما به یک معماری قابل انعطاف و قابل حفظ می رسیم که یکپارچه در هر دو زمینه سرور و مشتری کار می کند.

اگر این مقاله را مفید پیدا کردید ، اگر می توانستید واقعاً سپاسگزارم:
the مخزن را برای حمایت از دید آن ستاره کنید
thoughts افکار خود را در نظرات به اشتراک بگذارید – بازخورد شما به بهبود محتوا کمک می کند

هر تعامل به این بهترین شیوه ها کمک می کند تا به توسعه دهندگان بیشتری برسند!

نوشته های مشابه

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

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا