DeepSeek Harness: الخدمات والاعتماديات: صنف الخدمة الأساسي

آخر تحديث: 2026-08-31

إضافات الدوال تُسجّل قدرات "الفعل" عبر apply، بينما إضافات الخدمات تكشف قدرات "التوفير" عبر الأصناف. عندما تحتاج إضافتك الحفاظ على حالة داخلية، أو كشف APIs قابلة للاستدعاء، أو أن تكون أساس اعتماد لإضافات أخرى، صنف Service الأساسي هو الخيار الأفضل.

💡 نصيحة: القيمة الأساسية لـ Service هي "خدمات ذات حالة" — خصائص النسخة تحتفظ بالحالة، الدوال تكشف APIs، والإضافات الأخرى تستخدمها بعد التصريح باعتماديات inject. إذا كانت إضافتك تُسجّل فقط أدوات ومستمعين، شكل الدالة أبسط.

📋 المتطلبات المسبقة: أكمل 14-inject.md و 17-fiber.md

1. ما ستتعلمه

عزل ونطاق الخدمة


2. تعريف صنف Service

(1) ▶ مثال 1

TYPESCRIPT
import { Service } from '@deepseek-ai/cordis'

export default class MyService extends Service {
  constructor(ctx: Context) {
    super(ctx, 'my-service')
  }
}

مُنشئ صنف Service الأساسي يأخذ مُعاملين:

▶ مثال 2: خدمة الذاكرة المؤقتة

TYPESCRIPT
import { Service, Context } from '@deepseek-ai/cordis'

export default class CacheService extends Service {
  private cache = new Map<string, { value: any; expires: number }>()

  constructor(ctx: Context) {
    super(ctx, 'cache')
  }

  get(key: string): any {
    const entry = this.cache.get(key)
    if (!entry) return undefined
    if (Date.now() > entry.expires) {
      this.cache.delete(key)
      return undefined
    }
    return entry.value
  }

  set(key: string, value: any, ttlMs: number = 60000): void {
    this.cache.set(key, { value, expires: Date.now() + ttlMs })
  }

  delete(key: string): boolean {
    return this.cache.delete(key)
  }

  clear(): void {
    this.cache.clear()
  }
}

(3) التسجيل التلقائي

super(ctx, 'cache') في مُنشئ Service يُسجّل تلقائياً النسخة كخدمة cache. الإضافات الأخرى تصرّح بـ inject: ['cache'] لاستخدامها.


3. المُنشئ واسم الخدمة

(1) غرض اسم الخدمة

اسم الخدمة هو المفتاح في السجل العام:

TYPESCRIPT
super(ctx, 'cache')
// → الإضافات الأخرى تدخل نسخة هذه الخدمة عبر ctx.cache

(2) اصطلاحات التسمية

TYPESCRIPT
// ✅ مُوصى به: kebab-case أو camelCase
super(ctx, 'cache')
super(ctx, 'rate-limiter')
super(ctx, 'metricsCollector')

// ❌ غير مُوصى به
super(ctx, 'Cache')        // بداية بحرف كبير
super(ctx, 'cache_service') // شرطات سفلية

(3) تعارض أسماء الخدمات

إذا سجّلت خدمتان نفس الاسم، اللاحقة تتجاوز السابقة:

TEXT 📖 للعرض فقط
Plugin A تسجّل 'cache' → CacheServiceA
Plugin B تسجّل 'cache' → CacheServiceB
→ النهائي ctx.cache = CacheServiceB

(4) الوصول للخدمات

TYPESCRIPT
// إضافة المستهلك
export const inject = ['cache']

export function apply(ctx: Context) {
  ctx.cache.set('user:1', { name: 'Alice' }, 300000)
  const user = ctx.cache.get('user:1')
}

4. تصريحات static inject

(1) اعتماديات الخدمة

أصناف Service تصرّح باعتمادياتها عبر static inject:

TYPESCRIPT
export default class DatabaseService extends Service {
  static inject = ['fs']

  constructor(ctx: Context) {
    super(ctx, 'database')
  }

  async query(sql: string) {
    const schema = await ctx.fs.readFile('schema.json')
    // ...
  }
}

(2) الاعتماديات الاختيارية

TYPESCRIPT
static inject = ['fs', 'cache?']

نفس الصيغة كإضافات الدوال؛ ? تعني اختيارية.

(3) توقيت تهيئة الخدمة

100%
sequenceDiagram
    participant F as الإطار
    participant FS as FS Service
    participant DB as Database Service
    participant Tool as Tool Plugin

    F->>FS: تحميل → active
    F->>DB: inject ['fs'] ✅ → المُنشئ يُنفّذ → active
    F->>Tool: inject ['database'] ✅ → apply يُنفّذ → active

(4) استخدام الاعتماديات في المُنشئ

TYPESCRIPT
export default class MyService extends Service {
  static inject = ['tools']
  
  private defaultTool: string

  constructor(ctx: Context) {
    super(ctx, 'my-service')
    // ctx.tools جاهز (inject يضمن ذلك)
    this.defaultTool = ctx.tools.getDefault()
  }
}

5. تسجيل واكتشاف الخدمات

(1) آلية التسجيل

super(ctx, name) في مُنشئ Service يُحفّز التسجيل:

TYPESCRIPT
// المنطق الداخلي للإطار (كود زائف)
class Service {
  constructor(ctx: Context, name: string) {
    ctx.provide(name, this)
    // يُحفّز تفعيل الإضافات المنتظرة المعتمدة على هذه الخدمة
  }
}

(2) آلية الاكتشاف

الإضافات الأخرى تصرّح بالاعتماديات عبر inject؛ الإطار يُحقنها عندما تكون جاهزة:

TYPESCRIPT
// إضافة المستهلك
export const inject = ['cache']

export function apply(ctx: Context) {
  // ctx.cache مُحقن تلقائياً، آمن الأنواع
  ctx.cache.set('key', 'value')
}

(3) استعلام الخدمات وقت التشغيل

TYPESCRIPT
// التحقق من تسجيل خدمة
if (ctx.cache) {
  ctx.cache.get('key')
}

// عرض جميع الخدمات المُسجّلة
ctx.logger.info('available services:', ctx.serviceNames)

(4) رسم اعتماديات الخدمات

100%
graph TB
    FS[fs Service] --> DB[database Service]
    CACHE[cache Service] --> DB
    DB --> TOOL1[tool-a Plugin]
    DB --> TOOL2[tool-b Plugin]
    CACHE --> TOOL1

6. وصول آمن الأنواع للخدمات

(1) توسيع أنواع TypeScript

لإعطاء ctx.cache تلميحات أنواع صحيحة، صرّح بتوسيع نوع:

TYPESCRIPT
// في ملف تصريح أنواع الإضافة
declare module '@deepseek-ai/cordis' {
  interface Context {
    cache: CacheService
  }
}

(2) ▶ مثال 2

TYPESCRIPT
// cache-service.ts
import { Service, Context } from '@deepseek-ai/cordis'

export default class CacheService extends Service {
  private cache = new Map<string, any>()

  constructor(ctx: Context) {
    super(ctx, 'cache')
  }

  get(key: string): any {
    return this.cache.get(key)
  }

  set(key: string, value: any, ttl?: number): void {
    this.cache.set(key, value)
  }

  has(key: string): boolean {
    return this.cache.has(key)
  }

  delete(key: string): boolean {
    return this.cache.delete(key)
  }

  clear(): void {
    this.cache.clear()
  }
}

// توسيع الأنواع
declare module '@deepseek-ai/cordis' {
  interface Context {
    cache: CacheService
  }
}

(3) أمان الأنواع في جانب المستهلك

TYPESCRIPT
// إضافة المستهلك
import { Context } from '@deepseek-ai/cordis'

export const inject = ['cache']

export function apply(ctx: Context) {
  ctx.cache.set('key', 'value')   // ✅ نوع صحيح
  ctx.cache.invalid()             // ❌ خطأ ترجمة: الدالة غير موجودة
  ctx.cache.get('key').foo()      // ⚠️ نوع any، يحتاج كتابة إضافية
}

(4) خدمات عامة

TYPESCRIPT
export default class CacheService<T = any> extends Service {
  private cache = new Map<string, T>()

  get(key: string): T | undefined {
    return this.cache.get(key)
  }

  set(key: string, value: T): void {
    this.cache.set(key, value)
  }
}

7. مقارنة Service وإضافة الدالة

(1) مقارنة الميزات

البُعد إضافة Service إضافة دالة
الشكل صنف دالة/كائن
إدارة الحالة خصائص النسخة متغيرات الإغلاق
كشف الخدمة super(ctx, name) يُسجّل تلقائياً ctx.provide() تسجيل يدوي
تصريح الاعتماديات static inject export const inject
دورة الحياة المُنشئ/التدمير Apply/التنظيف التلقائي
التوريث ✅ مدعوم ❌ غير مدعوم
قابلية الاختبار ✅ سهلة المحاكاة ⚠️ تتطلب محاكاة ctx
حجم الكود أكثر أقل

(2) دليل الاختيار

TEXT 📖 للعرض فقط
اختر Service عندما:
  → تحتاج كشف APIs لإضافات أخرى
  → تحتاج الحفاظ على بيانات ذات حالة
  → تحتاج توريث وإعادة استخدام
  → تعمل كأساس اعتماد لإضافات متعددة

اختر إضافة دالة عندما:
  → فقط تسجيل أدوات ومستمعين
  → بدون حالة أو حالة بسيطة
  → كود أقل، تطوير سريع
  → لا تحتاج أن يعتمد عليك إضافات أخرى

(3) الاستخدام المختلط

كلا الشكلين يمكن أن يتواجدا في مشروع:

TYPESCRIPT
// CacheService — إضافة Service
export default class CacheService extends Service { ... }

// CacheTool — إضافة دالة، تستهلك CacheService
export const inject = ['cache', 'tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'cache_get',
    // ...
    async execute({ key }, ctx) {
      return ctx.cache.get(key)
    }
  }))
}

(4) الانتقال من إضافة دالة إلى Service

عندما تنمو إضافة دالة في التعقيد، انقلها إلى Service:

TYPESCRIPT
// قبل: إضافة دالة
export function apply(ctx: Context) {
  const cache = new Map()
  ctx.provide('cache', {
    get: (k) => cache.get(k),
    set: (k, v) => cache.set(k, v)
  })
}

// بعد: إضافة Service
export default class CacheService extends Service {
  private cache = new Map()
  
  constructor(ctx: Context) {
    super(ctx, 'cache')
  }
  
  get(k: string) { return this.cache.get(k) }
  set(k: string, v: any) { this.cache.set(k, v) }
}

❓ أسئلة شائعة

س هل يمكن لـ Service تسجيل أسماء خدمات متعددة؟
ج تقنياً يمكن استدعاء super عدة مرات في المُنشئ، لكن غير مُوصى به. يجب أن تسجل Service اسم خدمة واحد — مسؤولية واحدة.
س ما هو this.ctx الخاص بـ Service؟
ج this.ctx هو مُعامل ctx المُستقبل في المُنشئ، يُحفظ تلقائياً بواسطة صنف Service الأساسي. هو سياق الإضافة، يوفر الوصول لجميع الخدمات المُحقنة.
س كيف أكتب منطق تدمير Service؟
ج تجاوز الدالة dispose(): typescript export default class DbService extends Service { private pool: Pool dispose() { this.pool.end() }
س هل هناك فرق بين ctx.provide() في إضافات الدوال و Service؟
ج متكافئ وظيفياً. الفرق أن Service له نسخ أصناف ودعم توريث؛ ctx.provide() يسجل كائناً يدوياً فقط.
س هل يمكن لـ Service أن تعتمد على Service أخرى؟
ج نعم. صرّح بالاعتماديات بـ static inject وادخل عبر ctx.serviceName في المُنشئ.
س كيف أكتب توسيعات أنواع لـ Services طرف ثالث؟
ج صرّح بـ declare module '@deepseek-ai/cordis' في ملف .d.ts ووسّع واجهة Context. تأكد أن ملف التصريح هذا مُضمّن بواسطة مترجم TypeScript.

📖 ملخص


📝 تمارين

1. ⭐ أساسي: اكتب RateLimiterService بدالة check(key): boolean (60 استدعاء كحد أقصى في الدقيقة). سجّلها كخدمة rate-limiter واستخدمها في إضافة دالة أخرى عبر inject.

2. ⭐⭐ متوسط: أضف توسيع نوع (عبر declare module) لـ CacheService بحيث تعيد ctx.cache.get() قيماً مكتوبة الأنواع بشكل صحيح في إضافات المستهلك. اكتب إضافة مستهلك وتحقق من نجاح فحص أنواع TypeScript.

3. ⭐⭐⭐ تحدٍ: نفّذ MetricsService يجمع عدّادات استدعاءات الأدوات وإحصائيات التوقيت. يعتمد على خدمة tools ويسجل البيانات قبل وبعد تنفيذ الأداة. وفر دالة getStats(): Record<string, { count, avgMs }>. سجّل أمر metrics في واجهة الويب لعرض الإحصائيات.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%