إنتقل إلى المحتوى الرئيسي

الملحق أ: أمثلة تطبيقية مرجعية

أنماط Fastify وNestJS عبر القطاعات الحالية

ملحق لـ: سياسة معمارية منصّة RaR-IT لأنظمة SaaS، الأقسام 5–6-1

:::note الغرض يوضّح هذا الملحق، بأسماء مكوّنات حقيقية من الأعمال المتراكمة الحالية لعَمَار وClinivio وEduSuite والنقل والسياحة، كيف يبدو النمطان المعتمَدان فعلياً في الشيفرة — نمط Fastify الافتراضي (القسم 5) وسماح NestJS المحدود النطاق (القسم 6-1) — بالإضافة إلى نمط واحد غير مسموح به دون اعتماد لجنة مراجعة المعمارية، مُعروض لكي يكون الخطر ملموساً لا نظرياً.

تشترك كل الأمثلة في خاصية واحدة غير قابلة للتفاوض: تقرأ آلية اكتشاف الوحدات في Entitlements نفس عقد البيان (Manifest) المستقل عن الإطار بغضّ النظر عن النمط المستخدَم داخل الوحدة. :::

أ-1 نمط Fastify — وحدة CRUD بسيطة

عَمَار: العقارات (Properties)

الحالة الافتراضية والأكثر شيوعاً — لا حاجة لحاوية حقن تبعيات لأنه لا يوجد ما يُحقَن.

verticals/amaar/modules/properties/manifest.ts
import { FeatureManifest } from '@rarit-kernel/entitlements';

export const manifest: FeatureManifest = {
key: 'properties',
displayName: { en: 'Properties', ar: 'العقارات' },
requiredPlan: ['starter', 'growth', 'enterprise'],
routes: ['/properties/*'],
};
verticals/amaar/modules/properties/index.ts
import fp from 'fastify-plugin';
import { z } from 'zod';
import { eq } from 'drizzle-orm';
import { requireEntitlement } from '@rarit-kernel/entitlements';
import { properties } from '../../db/schema';

const CreatePropertySchema = z.object({
title: z.string().min(3),
type: z.enum(['apartment', 'villa', 'office', 'land']),
price: z.number().positive(),
city: z.string(),
bedrooms: z.number().int().optional(),
});

export default fp(async (fastify) => {
fastify.addHook('preHandler', requireEntitlement('properties'));

fastify.get('/properties', async (req) => {
const { tenantId } = req.tenant;
return fastify.db.query.properties.findMany({
where: eq(properties.tenantId, tenantId),
});
});

fastify.post('/properties', { schema: { body: CreatePropertySchema } },
async (req, reply) => {
const { tenantId, userId } = req.tenant;
const [property] = await fastify.db.insert(properties)
.values({ ...req.body, tenantId, createdBy: userId })
.returning();
return reply.code(201).send(property);
});
}, { name: 'properties' });

أ-2 نمط Fastify — مستهلِك رقيق لمحرك مشترك

النقل: العمولات والتسوية (Commission & Settlement)

وحدة مهمتها الكاملة القراءة والكتابة مقابل محرك مشترك ذي عقد ثابت. لا يوجد منطق أعمال خاص بالقطاع معقّد بما يكفي لتبرير حاوية DI.

verticals/transport/modules/commission/index.ts
import fp from 'fastify-plugin';
import { requireEntitlement } from '@rarit-kernel/entitlements';
import { getLedgerForTenant, recordSettlement } from '@rarit-kernel/payment';

export default fp(async (fastify) => {
fastify.addHook('preHandler', requireEntitlement('commission-settlement'));

// إدخالات السجل (مستحق/معلّق/معتمد/مرفوض) تأتي مباشرة من آلية
// السجل المشتركة في محرك Payment -- النقل لا يحتفظ بنسخته الخاصة
// من حالة التسوية.
fastify.get('/commission/ledger/:packetShopId', async (req) => {
return getLedgerForTenant(req.tenant.tenantId, {
scope: 'packet_shop',
scopeId: req.params.packetShopId,
});
});

fastify.post('/commission/settle/:entryId', async (req, reply) => {
const settled = await recordSettlement(req.tenant.tenantId, req.params.entryId, {
approvedBy: req.tenant.userId,
});
return reply.send(settled);
});
}, { name: 'commission-settlement' });

أ-3 نمط Fastify — وحدة CRUD بسيطة (قطاع ثانٍ)

السياحة: إدارة علاقات العملاء (CRM)

مثال CRUD ثانٍ من قطاع مختلف، لتوضيح أن هذا النمط هو القاعدة، لا اتفاقية خاصة بعَمَار.

verticals/tourism/modules/crm/index.ts
import fp from 'fastify-plugin';
import { requireEntitlement } from '@rarit-kernel/entitlements';

export default fp(async (fastify) => {
fastify.addHook('preHandler', requireEntitlement('crm'));

fastify.get('/customers/:id', async (req) => {
return fastify.db.query.customers.findFirst({
where: (c, { eq, and }) => and(
eq(c.id, req.params.id),
eq(c.tenantId, req.tenant.tenantId),
),
with: { bookings: true },
});
});
}, { name: 'crm' });

أ-4 نمط NestJS — مزوّدات مشتركة عبر قوالب متعددة

Clinivio: محرك القوالب السريرية (Clinical Templates Engine)

هذه هي الحالة التي كُتب القسم 6-1 من أجلها: عشرة قوالب سريرية تخصصية، يتشارك عدد منها في مكوّنات بناء مشتركة — أداة رسم تشريحي، مُدقِّق قيم مُهيكَل، بحث في سجل القوالب. تبقى Fastify مالكة لمسار HTTP والبيان وخطّاف التحقق من الاستحقاقات؛ Nest غير مرئية من منظور Entitlements.

:::caution ملاحظة النطاق هذه الوحدة ضمن المرحلة 2 حالياً وفق نطاق الإطلاق المُقلَّص (مؤجَّلة إلى جانب المستشفيات/المراكز/الأسنان). أُدرِجت هنا كأوضح مثال حقيقي على النمط، لا كتأكيد أنها تُشحَن عند الإطلاق الأولي. :::

verticals/clinivio/modules/clinical-templates/nest/clinical-templates.module.ts
import { Injectable, Module } from '@nestjs/common';

@Injectable()
export class TemplateRegistryService {
private templates = new Map<string, ClinicalTemplateDefinition>();
register(template: ClinicalTemplateDefinition) {
this.templates.set(template.specialtyKey, template);
}
get(specialtyKey: string) {
return this.templates.get(specialtyKey);
}
}

@Injectable()
export class BodyDiagramWidgetService {
// مشتركة بين الجلدية وجراحة العظام والعلاج الطبيعي والتجميل
renderDiagram(regionMap: BodyRegionMap, markings: Marking[]) { /* ... */ }
}

@Injectable()
export class ClinicalValidationService {
constructor(private readonly registry: TemplateRegistryService) {}
validate(specialtyKey: string, payload: unknown) {
const template = this.registry.get(specialtyKey);
if (!template) throw new UnknownSpecialtyError(specialtyKey);
return template.schema.parse(payload);
}
}

@Module({
providers: [TemplateRegistryService, BodyDiagramWidgetService, ClinicalValidationService],
exports: [ClinicalValidationService, BodyDiagramWidgetService],
})
export class ClinicalTemplatesModule {}
verticals/clinivio/modules/clinical-templates/index.ts — Fastify تمتلك HTTP والبيان وخطّاف الاستحقاقات
import fp from 'fastify-plugin';
import { NestFactory } from '@nestjs/core';
import { requireEntitlement } from '@rarit-kernel/entitlements';
import { ClinicalTemplatesModule, ClinicalValidationService } from './nest/clinical-templates.module';

export default fp(async (fastify) => {
// Nest تُستخدَم فقط كسياق DI -- بلا مُوائم HTTP، بلا حرّاس/معترضات Nest،
// بلا توجيه مملوك لـNest. هذا هو نمط التكامل المطلوب بموجب القسم 6-1.
const nestCtx = await NestFactory.createApplicationContext(ClinicalTemplatesModule);
const validation = nestCtx.get(ClinicalValidationService);

fastify.addHook('preHandler', requireEntitlement('clinical-templates'));

fastify.post('/clinical-templates/:specialty/visit-note', async (req, reply) => {
const parsed = validation.validate(req.params.specialty, req.body);
return reply.code(201).send(parsed);
});
}, { name: 'clinical-templates' });

أ-5 نمط NestJS — كائنات استراتيجية قابلة للتبديل

عَمَار: التوقعات المالية (Financial Forecasting)

حالة ثانية حقيقية للسماح المحدود بـNest: خوارزميات تنبؤ متعددة قابلة للتبديل تُنفِّذ الواجهة نفسها، يُختار بينها وقت الطلب — نمط الاستراتيجية الكلاسيكي.

verticals/amaar/modules/financial-forecasting/nest/financial-forecasting.module.ts
import { Inject, Injectable, Module } from '@nestjs/common';

const FORECAST_STRATEGIES = Symbol('FORECAST_STRATEGIES');

@Injectable()
class LinearTrendStrategy { readonly key = 'linear'; forecast(h, m) { /* ... */ } }

@Injectable()
class SeasonalAdjustedStrategy { readonly key = 'seasonal'; forecast(h, m) { /* ... */ } }

@Injectable()
export class ForecastingService {
constructor(@Inject(FORECAST_STRATEGIES) private readonly strategies) {}
run(key, history, horizonMonths) {
const strategy = this.strategies.find((s) => s.key === key) ?? this.strategies[0];
return strategy.forecast(history, horizonMonths);
}
}

@Module({
providers: [
LinearTrendStrategy, SeasonalAdjustedStrategy,
{ provide: FORECAST_STRATEGIES, useFactory: (a, b) => [a, b], inject: [LinearTrendStrategy, SeasonalAdjustedStrategy] },
ForecastingService,
],
exports: [ForecastingService],
})
export class FinancialForecastingModule {}

أ-6 نمط مضاد — Nest تمتلك خط معالجة HTTP الخاص بها

:::danger غير مسموح دون اعتماد لجنة مراجعة المعمارية مُعروض هنا فقط لجعل الخطر المذكور في القسم 6-1 ملموساً — لا تُنسَخ هذه كقالب انطلاق. :::

المشكلة: بمجرد أن تمتلك Nest نسخة HTTP خاصة بها عبر مُوائم Fastify، لا تمرّ الطلبات الموجَّهة إليها أبداً عبر خطّاف التحقق من الاستحقاقات في العملية الأصلية.

verticals/tourism/modules/ai-intelligence/nest-app.ts — توضيحي، ليس نمطاً معتمَداً
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter } from '@nestjs/platform-fastify';

const nestApp = await NestFactory.create(AiIntelligenceModule, new FastifyAdapter());
await nestApp.init();

// تركيب هذا تحت العملية الأصلية لا يُمرّر هذه الطلبات عبر
// خطّاف requireEntitlement() الخاص بالعملية الأصلية:
fastify.register(async (instance) => {
instance.all('/ai-intelligence/*', (req, reply) =>
nestApp.getHttpAdapter().getInstance().routing(req.raw, reply.raw)
);
});
// <- يمكن لمستأجر بلا استحقاق 'ai-intelligence' الوصول لهذا المسار رغم ذلك.

أ-7 جدول ملخّص — اختيار النمط حسب الوحدة

القطاعالوحدةالنمط المستخدَمالسبب
عَمَارالعقارات✅ Fastify (مباشر)CRUD عادي؛ لا متعاونين داخليين يُحقَنون.
النقلالعمولات والتسوية✅ Fastify (مباشر)مستهلِك رقيق لمحرك Payment ذي العقد الثابت.
السياحةإدارة علاقات العملاء✅ Fastify (مباشر)CRUD عادي في قطاع مختلف — يؤكد أن النمط هو القاعدة.
Clinivioمحرك القوالب السريرية🟡 NestJS (سياق DI)مزوّدات مشتركة عبر 10 قوالب تخصصية — حالة DI حقيقية.
عَمَارالتوقعات المالية🟡 NestJS (سياق DI)استراتيجيات تنبؤ متعددة قابلة للتبديل — نمط الاستراتيجية الكلاسيكي.
السياحة (توضيحي)الذكاء الاصطناعي🔴 غير مسموح — Nest تمتلك HTTPمُعروض في أ-6 فقط لتوضيح سبب حاجة هذا النمط لاعتماد لجنة مراجعة المعمارية.
ملاحظة

هذا الملحق توضيحي وسيُوسَّع مع بناء وحدات حقيقية جديدة. لا يحمل بذاته قوة السياسة بشكل مستقل عن المستند الرئيسي — وحيثما يبدو تعارض بين هذا الملحق ونص السياسة، يحكم نص السياسة (الأقسام 1–10).