الملحق أ: أمثلة تطبيقية مرجعية
أنماط Fastify وNestJS عبر القطاعات الحالية
ملحق لـ: سياسة معمارية منصّة RaR-IT لأنظمة SaaS، الأقسام 5–6-1
:::note الغرض يوضّح هذا الملحق، بأسماء مكوّنات حقيقية من الأعمال المتراكمة الحالية لعَمَار وClinivio وEduSuite والنقل والسياحة، كيف يبدو النمطان المعتمَدان فعلياً في الشيفرة — نمط Fastify الافتراضي (القسم 5) وسماح NestJS المحدود النطاق (القسم 6-1) — بالإضافة إلى نمط واحد غير مسموح به دون اعتماد لجنة مراجعة المعمارية، مُعروض لكي يكون الخطر ملموساً لا نظرياً.
تشترك كل الأمثلة في خاصية واحدة غير قابلة للتفاوض: تقرأ آلية اكتشاف الوحدات في Entitlements نفس عقد البيان (Manifest) المستقل عن الإطار بغضّ النظر عن النمط المستخدَم داخل الوحدة. :::
أ-1 نمط Fastify — وحدة CRUD بسيطة
عَمَار: العقارات (Properties)
الحالة الافتراضية والأكثر شيوعاً — لا حاجة لحاوية حقن تبعيات لأنه لا يوجد ما يُحقَن.
import { FeatureManifest } from '@rarit-kernel/entitlements';
export const manifest: FeatureManifest = {
key: 'properties',
displayName: { en: 'Properties', ar: 'العقارات' },
requiredPlan: ['starter', 'growth', 'enterprise'],
routes: ['/properties/*'],
};
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.
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 ثانٍ من قطاع مختلف، لتوضيح أن هذا النمط هو القاعدة، لا اتفاقية خاصة بعَمَار.
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 حالياً وفق نطاق الإطلاق المُقلَّص (مؤجَّلة إلى جانب المستشفيات/المراكز/الأسنان). أُدرِجت هنا كأوضح مثال حقيقي على النمط، لا كتأكيد أنها تُشحَن عند الإطلاق الأولي. :::
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 {}
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: خوارزميات تنبؤ متعددة قابلة للتبديل تُنفِّذ الواجهة نفسها، يُختار بينها وقت الطلب — نمط الاستراتيجية الكلاسيكي.
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، لا تمرّ الطلبات الموجَّهة إليها أبداً عبر خطّاف التحقق من الاستحقاقات في العملية الأصلية.
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).