برنامه نویسی

NestJs: اجرای DDD – EN

شما می توانید نسخه اسپانیایی را اینجا بخوانید

سلام! در این مقاله قصد داریم به طور خاص در مورد پیاده سازی Clean Architecture صحبت کنیم طراحی دامنه محور (DDD) در NestJS. ما جنبه های مختلفی از ساختار پوشه تا تزریق وابستگی بین لایه ها را پوشش خواهیم داد. ما از ODM mongoose استفاده خواهیم کرد، اما از ORM/ODM دلخواه خود استفاده کنید!

کل مقاله را رد کنید و کد منبع را در اینجا بررسی کنید!

چرا NestJS؟

NestJS یک فریمورک Node.js است که جعبه ابزار جامعی را برای توسعه برنامه های کاربردی Backend در اختیار ما قرار می دهد. این یک معماری لایه ای (مبتنی بر ماژول) برای تفکیک مسئولیت ها در برنامه ما ارائه می دهد. بدون شک، این چارچوبی است که ارزش آن را در هنگام ایجاد یک پروژه مبتنی بر Node.js دارد.

چرا DDD؟

طراحی دامنه محور یک متدولوژی طراحی نرم افزار است که بر درک و مدل سازی دامنه یک برنامه کاربردی تمرکز دارد و نرم افزار را حول مفاهیم کلیدی کسب و کار ساختار می دهد.
علاوه بر این، “الگو” بودن معماری، پیاده سازی و خوانایی کد را آسان تر می کند.
اگر اطلاعات قبلی در مورد DDD ندارید، خواندن این مقاله را توصیه می کنم.

معماری پوشه

.
└──src
   ├──application
   |  ├──cat (module)
   |  └──organization (module)
   ├──domain
   |  ├──entities
   |  └──interfaces
   └──infrastructure
      ├──schemas
      └──services
وارد حالت تمام صفحه شوید

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

با مشاهده این موضوع، باید وابستگی های بین لایه های تعریف شده در معماری DDD را به خاطر بسپاریم.

تصویر ddd

لایه دامنه

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

بیایید موجودیت ها را ایجاد کنیم:

export enum CatStatus {
    AVAILABLE = 'available',
    PENDING = 'pending',
    ADOPTED = 'Adopted',
}

export class CatEntity {
    id?: string;
    name: string;
    age: number;
    color: string;
    status: CatStatus;
}
وارد حالت تمام صفحه شوید

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

حالا رابط ها:

import { CatEntity } from "../Entities/Cat.entity";

export interface ICatRepository {
    findById(id: string): Promise<CatEntity>;
    create(cat: CatEntity): Promise<string>;
    delete(id: string): Promise<boolean>;
}

export const ICatRepository = Symbol('ICatRepository');
وارد حالت تمام صفحه شوید

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

همانطور که می بینید، علاوه بر ایجاد رابط، در حال ایجاد یک نماد نیز هستیم. ما این کار را انجام می دهیم زیرا این رابط را به لایه برنامه خود تزریق می کنیم. در ادامه با جزئیات بیشتر به این موضوع خواهیم پرداخت.

لایه زیرساخت

در این لایه به تعریف طرحواره ها (در این مورد) و سرویس های مربوط به پایگاه داده می پردازیم. همه این عناصر ویژگی های خود را از موجودیت ها و رابط هایی که در لایه دامنه ایجاد کرده ایم به ارث می برند.

ما طرحواره ها را با پیاده سازی فیلدهای موجودیت ایجاد می کنیم CatEntity:

@Schema()
export class Cats implements CatEntity {
    @Prop({
        type: String,
        required: true
    })
    name: string;

    @Prop({
        type: Number,
        required: true
    })
    age: number;

    @Prop({
        type: String,
        required: true
    })
    color: string;

    @Prop({
        type: String,
        required: true
    })
    status: CatStatus;
}

export type CatDocument = Cats & Document;

export const CatSchema = SchemaFactory.createForClass(Cats);
وارد حالت تمام صفحه شوید

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

اکنون ممکن است این سوال برای شما پیش بیاید که آیا باید یک موجودیت جداگانه برای مدیریت پایگاه داده ایجاد کنیم؟ پاسخ بله است. ما باید ویژگی های موجودیت های خود را به زبانی ترسیم کنیم که ORM/ODM ما بتواند آن را تفسیر کند. می توانید آنها را به عنوان فایل های پیکربندی در نظر بگیرید.

اکنون زمان کار بر روی خدمات است.

@Injectable()
export class CatMongoRepository implements ICatRepository {
    constructor(
        @InjectModel(Cats.name)
        private catModel: Model<CatDocument>
    ) { }

    async create(cat: CatEntity): Promise<string> {
        const result = await this.catModel.create(cat);
        return result._id;
    }

    async findById(id: string): Promise<CatEntity> {
        const cat = await this.catModel.findById(id);
        return cat;
    }

    async delete(id: string): Promise<boolean> {
        const result = await this.catModel.deleteOne({ _id: new Types.ObjectId(id) });
        return result.deletedCount > 0;
    }

}
وارد حالت تمام صفحه شوید

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

در این سمت، ما تمام پرس و جوهای پایگاه داده را مدیریت خواهیم کرد.
قوانین تجارت را از اینجا دور نگه دارید!

تزریق وابستگی

عالی است، ما اکنون لایه های دامنه و زیرساخت را راه اندازی کرده ایم. اکنون فقط باید تزریق های وابستگی را پیکربندی کنیم.

اول از همه، ما باید خدمات را در لایه زیرساخت خود و همچنین طرحواره ها را صادر کنیم:

const mongooseSchemas = [{
    name: Cats.name,
    schema: CatSchema
}, {
    name: Organizations.name,
    schema: OrganizationSchema
}];

@Module({
    imports: [
        MongooseModule.forRoot(`mongodb://admin:password123@mongo-ddd:27017/accounts?authSource=admin&readPreference=primary&ssl=false&directConnection=true`),
        MongooseModule.forFeature(mongooseSchemas)
    ],
    controllers: [],
    providers: [
        OrganizationMongoRepository,
        CatMongoRepository
    ],
    exports: [
        MongooseModule.forFeature(mongooseSchemas),
        OrganizationMongoRepository,
        CatMongoRepository,
    ]
})
export class InfrastructureModule { };
وارد حالت تمام صفحه شوید

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

اکنون، در لایه دامنه، برای مدیریت تزریق وابستگی، باید لایه زیرساخت را وارد کنیم و سرویس‌های زیرساخت را پیکربندی کنیم تا با رابط‌های لایه دامنه تزریق شوند:

@Module({
    imports: [InfrastructureModule],
    controllers: [],
    providers: [{
        provide: ICatRepository,
        useClass: CatMongoRepository
    }, {
        provide: IOrganizationRepository,
        useClass: OrganizationMongoRepository
    }],
    exports: [{
        provide: ICatRepository,
        useClass: CatMongoRepository
    }, {
        provide: IOrganizationRepository,
        useClass: OrganizationMongoRepository
    }]
})
export class DomainModule { };
وارد حالت تمام صفحه شوید

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

اگرچه ممکن است متناقض به نظر برسد، اما ما یکی از قوانین DDD را زیر پا می گذاریم: هیچ کس نباید به لایه زیرساخت وابسته باشد. با این حال، به دلیل محدودیت‌های NestJS، این تنها راهی است که برای انجام این کار پیدا کرده‌ام.
با این وجود، وابستگی حداقل است و فقط به پیکربندی موجود در آن محدود می شود domain.module.ts.

با استفاده از خدمات

در داخل ما cat ماژول، در لایه برنامه، باید ماژول دامنه را وارد کنیم. به این ترتیب، ما می توانیم از اینترفیس ها از طریق تزریق وابستگی استفاده کنیم!:

@Injectable()
export class CatService {
    constructor(
        @Inject(ICatRepository)
        private catRepository: ICatRepository
    ) { }

    async create(data: CatCreateDto): Promise<string> {
        const cat = new CatEntity();
        cat.name = data.name;
        cat.age = data.age;
        cat.color = data.color;
        cat.status = CatStatus.AVAILABLE;

        return await this.catRepository.create(cat);
    }

    async findById(id: string): Promise<any> {
        const result = await this.catRepository.findById(id);
        return result;
    }

    async delete(id: string): Promise<boolean> {
        const result = await this.catRepository.delete(id);
        return result;
    }
}
وارد حالت تمام صفحه شوید

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

خلاصه

این یک نمای کلی از پیاده‌سازی DDD در NestJS است، که برای پایگاه داده‌ای که استفاده می‌کنید، چه SQL باشد و چه نباشد، آگنوستیک است.
بدون شک، چیزهای بیشتری برای بررسی وجود دارد، مانند این واقعیت که ما هنوز مدل های دامنه کم خون را مدیریت می کنیم و فقدان رویدادها برای جلوگیری از وابستگی دایره ای بین ماژول ها در لایه برنامه. با این حال، من مقالات بیشتری در این زمینه منتشر خواهم کرد. امیدوارم این به شما در درک بهتر اجرای این طرح معماری در NestJS کمک کند. می بینمت، فرنگرز!

فقط یک یادآوری است که می توانید با کد اینجا بروید و مخزن را بررسی کنید.

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

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

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

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