برنامه نویسی

Node Abstract Repository برای MongoDB

node-abstract-repository یک کتابخانه سبک وزن و ایمن برای ایجاد یکپارچه مخازن پایگاه داده سفارشی برای برنامه های Node.js است. این برنامه برخی از عملیات CRUD داخلی را در اختیار توسعه دهندگان قرار می دهد و آنها را قادر می سازد تا بر روی افزودن عملیات خود تمرکز کنند و جدایی تمیز بین پایداری و منطق دامنه را تضمین می کند. علیرغم اینکه طبیعتاً فناوری پایگاه داده آگنوستیک است، در حال حاضر شامل پیاده سازی برای MongoDB است.

توجه داشته باشید

این مقاله معرفی می کند node-abstract-repository، اما شامل تمام جزئیات آن نمی شود. ممکن است اسناد کامل و چند نمونه استفاده را در این مخزن GitHub بیابید، از جمله مثالی از نحوه استفاده از این کتابخانه در یک برنامه مبتنی بر NestJS.

چرا یک کتابخانه مخزن دیگری بسازیم؟

پیوستن به ClimatePartner باعث شد که وسایلم را عوض کنم و شروع به توسعه برنامه های وب مبتنی بر JS/TS کنم. اولین وظیفه من اتصال یک برنامه کوچک NestJS با یک پایگاه داده MongoDB بود. ما برنامه های بزرگی برای برنامه خود داشتیم و به زودی متوجه شدیم که مدل دامنه متعلق به تیم من به سرعت و پیچیدگی رشد می کند. علاوه بر این، دیدیم که این واقعیت در سایر تیم‌ها نیز وجود داشت که می‌خواستند از همین راه‌حل استفاده کنند.

همه در نظر گرفته شده، ما تصمیم گرفتیم یک ابزار دسترسی به پایگاه داده را ترکیب کنیم که الگوی Repository را پیاده سازی می کند تا پایداری را از منطق دامنه جدا کند و در عین حال تکرار منطق پرس و جو را به حداقل برساند. ما هم می خواستیم از آن باشد خلاصه طبیعت به طوری که بتوانیم منطق دسترسی به پایگاه داده را که برای همه مخازن سفارشی مشترک است (یعنی عملیات CRUD بر روی هر شی دامنه پایدار) دوباره استفاده کنیم. در نهایت، ما به آن نیاز داشتیم تا الگوی Polymorphic را برای پشتیبانی از مدل‌های دامنه مبتنی بر subtyping پیاده‌سازی کنیم.

بنابراین ما شروع به تحقیق در مورد جدیدترین ها کردیم و چندین کتابخانه را کشف کردیم که می توانستند با نیازهای ما مطابقت داشته باشند. بهترین کاندیداهایی که توانستیم پیدا کنیم Mongoose، Typegoose و TypeORM بودند. Mongoose یک کتابخانه معروف Node.js برای MongoDB است که الگوی Data Mapper را پیاده‌سازی می‌کند و به توسعه‌دهندگان اجازه می‌دهد طرح‌واره‌هایی را برای محدود کردن مدل‌های داده مرتبط با اشیاء دامنه خود تعریف کنند. با این حال، Mongoose با مدل‌های داده بتن کار می‌کند، که در سناریوی مدل دامنه پیچیده منجر به تکرار منطق پرس و جو می‌شود. Typegoose یک Wrapper Mongoose ایمن است که امکان اعلان محدودیت طرحواره را در سطح فیلد شی دامنه از طریق دکوراتورهای JS می دهد. متأسفانه، همان دکوراتورها منطق پایداری را به مدل دامنه نشت می دهند. علاوه بر این، Typegoose الگوی Data Mapper را نیز پیاده‌سازی می‌کند، بنابراین همان اشکالات Mongoose را به اشتراک می‌گذارد. از طرف دیگر TypeORM الگوی Repository را پیاده سازی می کند و برخی از پشتیبانی های اساسی برای MongoDB ارائه می دهد. با این حال، TypeORM در مقایسه با Mongoose محدودیت‌های متعددی دارد.

اگرچه هیچ یک از گزینه های موجود دقیقاً با نیازهای ما مطابقت نداشت، ما ارزشی را در مجموعه ویژگی های مستحکم، مستند و کامل Mongoose دیدیم. در راس آن، ما سبک‌ترین راه‌حل ممکن را می‌خواستیم. به همین دلیل تصمیم به ساخت گرفتیم node-abstract-repository.

بریم سر اصل مطلب

فرض کنید یک مدل دامنه داریم که مشخص می کند Book به عنوان شی دامنه supertype و PaperBook و AudioBook به عنوان زیرگروه در اینجا یک تعریف ممکن برای آن مدل دامنه وجود دارد:

class Book implements Entity {
  readonly id?: string;
  readonly title: string;
  readonly description: string;
  readonly isbn: string;

  constructor(book: {
    id?: string;
    title: string;
    description: string;
    isbn: string;
  }) {
    this.id = book.id;
    this.title = book.title;
    this.description = book.description;
    this.isbn = book.isbn;
  }
}

class PaperBook extends Book {
  readonly edition: number;

  constructor(paperBook: {
    id?: string;
    title: string;
    description: string;
    isbn: string;
    edition: number;
  }) {
    super(paperBook);
    this.edition = paperBook.edition;
  }
}

class AudioBook extends Book {
  readonly hostingPlatforms: string[];
  readonly format?: string;

  constructor(audioBook: {
    id?: string;
    title: string;
    description: string;
    isbn: string;
    hostingPlatforms: string[];
  }) {
    super(audioBook);
    this.hostingPlatforms = audioBook.hostingPlatforms;
  }
}
وارد حالت تمام صفحه شوید

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

اکنون، می‌خواهید بتوانید نمونه‌های آن را ادامه دهید و بازیابی کنید Book و هر یک از زیرگروه های آن از پایگاه داده MongoDB. MongooseBookRepository یک مخزن سفارشی است که مشخص می کند Book-عملیات پایگاه داده مرتبط در اینجا تعریف آن است:

class MongooseBookRepository
  extends MongooseRepository<Book>
  implements BookRepository
{
  constructor() {
    super({
      Default: { type: Book, schema: BookSchema },
      PaperBook: { type: PaperBook, schema: PaperBookSchema },
      AudioBook: { type: AudioBook, schema: AudioBookSchema },
    });
  }

  async findByIsbn<T extends Book>(isbn: string): Promise<Optional<T>> {
    if (!isbn)
      throw new IllegalArgumentException('The given ISBN must be valid');
    return this.entityModel
      .findOne({ isbn: isbn })
      .exec()
      .then((book) => Optional.ofNullable(this.instantiateFrom(book) as T));
  }
}
وارد حالت تمام صفحه شوید

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

Voilà! MongooseBookRepository یک سری عملیات پایگاه داده CRUD را از آن به ارث می برد MongooseRepository و خود را اضافه می کند یعنی، findByIsbn. اکنون می توانید به سادگی نمونه سازی کنید MongooseBookRepository و هر یک از عملیات پایگاه داده را به صورت زیر اجرا کنید:

const bookRepository = new MongooseBookRepository();
const books: Book[] = bookRepository.findAll();
وارد حالت تمام صفحه شوید

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

دیگر خبری از نشتی منطق پایداری در منطق دامنه/برنامه شما نیست!

برخی از جزئیات اجرایی مهم

ممکن است در مورد عناصر خاصی که در مثال قبلی در معرض دید قرار گرفته اند تعجب کنید. از قسمت بالایی شروع می شود MongooseBookRepository تعریف، اولین مورد از این است MongooseRepository<Book>. این یکی از دو عنصر اصلی در معرض است node-abstract-repository: یک الگوی عمومی که چندین عملیات رایج پایگاه داده CRUD را پیاده سازی می کند. علاوه بر این، این کلاس همچنین ایجاد یک مدل داده Mongoose را انجام می دهد که قادر به مدیریت اشیاء داده کتاب مبتنی بر تایپ فرعی است. این همه پیچیدگی از شما پنهان است. تنها چیزی که باید بدانید این است که دارید entityModel در اختیار شما برای پیاده سازی عملیات پایگاه داده خود، همانطور که در نشان داده شده است findByIsbn.

تنها تلاش اضافی که از شما لازم است این است که نقشه ای را تعریف کنید که انواعی را که مدل دامنه شما و طرحواره های Mongoose مربوط به آنها را تشکیل می دهند، همانطور که در سازنده مشخص شده است، مشخص می کند. MongooseBookRepository. همچنین مهم است که توجه داشته باشید که کلید ورودی که سوپرتایپ شی دامنه شما را مشخص می کند باید نامگذاری شود. Default، و کلید هر ورودی دیگر باید بر اساس نام نوع شیء دامنه فرعی که به آن اشاره دارد نامگذاری شود. این یک قرارداد نامگذاری است که توسط جزئیات پیاده سازی داخلی مورد نیاز است MongooseRepository. اگر در مدل دامنه خود هیچ گونه زیرنوع شی دامنه ندارید، به سادگی یک نقشه را با یک ورودی منحصر به فرد که مشخص می کند نمونه سازی کنید. Default کلید و مقادیر طرحواره نوع شی دامنه شما.

عنصر دیگری که ممکن است توجه شما را جلب کند این است BookRepository. این یک رابط اختیاری است که گسترش می یابد Repository، دومین عنصر اصلی در معرض در node-abstract-repository: یک رابط عمومی آگنوستیک فناوری پایگاه داده که تمام عملیات پایگاه داده پشتیبانی شده CRUD را مشخص می کند تا توسط هر مخزن اجرا شود. قطعه زیر این رابطه بین این دو رابط را نشان می دهد:

interface BookRepository extends Repository<Book> {
  findByIsbn: <T extends Book>(isbn: string) => Promise<Optional<T>>;
}
وارد حالت تمام صفحه شوید

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

مدیریت نمونه‌های انواع رابط به جای کلاس‌ها یک روش خوب در برنامه‌های شی گرا است. بسته به انتزاعات به جای پیاده سازی، کد شما را مقیاس پذیر و برای تغییر مناسب تر می کند. بنابراین، من به شدت توصیه می کنم که رابط های مخزن خود را تعریف کنید، یا در صورتی که فقط به افشای عملیات CRUD علاقه مند هستید، مخازن سفارشی خود را به عنوان اشیاء از نوع نمونه سازی کنید. Repository<T>، جایی که T سوپرتایپ شی دامنه شما است.

به عبارتی دیگر، Book یک رابط به نام پیاده سازی می کند Entity. این رابط هر نوع شی دامنه پایدار را مدل می کند. رابط یک اختیاری را تعریف می کند id رشته. اختیاری بودن فیلد به این دلیل است که مقدار آن به صورت داخلی توسط Mongoose تنظیم شده است. اگر نمی خواهید مدل دامنه شما شامل این وابستگی باشد، فقط می توانید اطمینان حاصل کنید که اشیاء دامنه شما یک id?: string رشته. و هیچ اشتباهی در انجام این کار وجود ندارد id کلید اصلی متعارف در اسناد MongoDB است.

در نهایت، بسیاری از عملیات پایگاه داده اشیاء از نوع را برمی گرداند Optional. این نوع در جاوا الهام گرفته شده است Optional نوع هدف آن مدل سازی مفهوم “بدون نتیجه” است. این نوع یک جایگزین عالی برای بازگشت است null اشیاء یا پرتاب یک استثنا. استفاده از Optional ایمن تر و واضح تر از نیاز به مشخص کردن است T | null به عنوان نتیجه نوع یا مجبور به رسیدگی به استثنایی که یک شرط استثنایی برنامه را نشان نمی دهد. بنابراین بله، این ویژگی است که باعث می شود node-abstract-repository کمی متفکر، اما من معتقدم که ارزشش را دارد.

نتیجه

این node-abstract-repository کتابخانه ابزاری را برای ایجاد مخازن سفارشی برای برنامه های کاربردی مبتنی بر Node.js به روشی سریع و آسان فراهم می کند. این تصور برای دستیابی به جداسازی مؤثر بین پایداری و منطق دامنه است. پیاده سازی فعلی آن یک Wrapper Mongoose است که شامل تمام کدهای دیگ بخار مورد نیاز برای انجام عملیات CRUD رایج بر روی اشیاء دامنه چندشکلی در MongoDB است. بنابراین، توسعه دهندگان می توانند بر روی نوشتن عملیات پایگاه داده خاص دامنه در مخازن سفارشی تمرکز کنند.

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

تصویر جلد توسط Alev Takil در Unsplash

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

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

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

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