DeepSeek Harness: الخدمات والاعتماديات: صنف الخدمة الأساسي
آخر تحديث: 2026-08-31
إضافات الدوال تُسجّل قدرات "الفعل" عبر apply، بينما إضافات الخدمات تكشف قدرات "التوفير" عبر الأصناف. عندما تحتاج إضافتك الحفاظ على حالة داخلية، أو كشف APIs قابلة للاستدعاء، أو أن تكون أساس اعتماد لإضافات أخرى، صنف Service الأساسي هو الخيار الأفضل.
📋 المتطلبات المسبقة: أكمل 14-inject.md و 17-fiber.md
1. ما ستتعلمه
- تعريف صنف Service
- constructor(ctx, 'serviceName')
- تصريحات static inject
- تسجيل واكتشاف الخدمات
- وصول آمن الأنواع للخدمات
- مقارنة Service وإضافة الدالة
2. تعريف صنف Service
(1) ▶ مثال 1
import { Service } from '@deepseek-ai/cordis'
export default class MyService extends Service {
constructor(ctx: Context) {
super(ctx, 'my-service')
}
}
مُنشئ صنف Service الأساسي يأخذ مُعاملين:
ctx: سياق CordisserviceName: مُعرّف الخدمة؛ الإضافات الأخرى تشير إليها بهذا الاسم
▶ مثال 2: خدمة الذاكرة المؤقتة
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) غرض اسم الخدمة
اسم الخدمة هو المفتاح في السجل العام:
super(ctx, 'cache')
// → الإضافات الأخرى تدخل نسخة هذه الخدمة عبر ctx.cache
(2) اصطلاحات التسمية
// ✅ مُوصى به: kebab-case أو camelCase
super(ctx, 'cache')
super(ctx, 'rate-limiter')
super(ctx, 'metricsCollector')
// ❌ غير مُوصى به
super(ctx, 'Cache') // بداية بحرف كبير
super(ctx, 'cache_service') // شرطات سفلية
(3) تعارض أسماء الخدمات
إذا سجّلت خدمتان نفس الاسم، اللاحقة تتجاوز السابقة:
Plugin A تسجّل 'cache' → CacheServiceA
Plugin B تسجّل 'cache' → CacheServiceB
→ النهائي ctx.cache = CacheServiceB
(4) الوصول للخدمات
// إضافة المستهلك
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:
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) الاعتماديات الاختيارية
static inject = ['fs', 'cache?']
نفس الصيغة كإضافات الدوال؛ ? تعني اختيارية.
(3) توقيت تهيئة الخدمة
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) استخدام الاعتماديات في المُنشئ
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 يُحفّز التسجيل:
// المنطق الداخلي للإطار (كود زائف)
class Service {
constructor(ctx: Context, name: string) {
ctx.provide(name, this)
// يُحفّز تفعيل الإضافات المنتظرة المعتمدة على هذه الخدمة
}
}
(2) آلية الاكتشاف
الإضافات الأخرى تصرّح بالاعتماديات عبر inject؛ الإطار يُحقنها عندما تكون جاهزة:
// إضافة المستهلك
export const inject = ['cache']
export function apply(ctx: Context) {
// ctx.cache مُحقن تلقائياً، آمن الأنواع
ctx.cache.set('key', 'value')
}
(3) استعلام الخدمات وقت التشغيل
// التحقق من تسجيل خدمة
if (ctx.cache) {
ctx.cache.get('key')
}
// عرض جميع الخدمات المُسجّلة
ctx.logger.info('available services:', ctx.serviceNames)
(4) رسم اعتماديات الخدمات
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 تلميحات أنواع صحيحة، صرّح بتوسيع نوع:
// في ملف تصريح أنواع الإضافة
declare module '@deepseek-ai/cordis' {
interface Context {
cache: CacheService
}
}
(2) ▶ مثال 2
// 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) أمان الأنواع في جانب المستهلك
// إضافة المستهلك
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) خدمات عامة
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) دليل الاختيار
اختر Service عندما:
→ تحتاج كشف APIs لإضافات أخرى
→ تحتاج الحفاظ على بيانات ذات حالة
→ تحتاج توريث وإعادة استخدام
→ تعمل كأساس اعتماد لإضافات متعددة
اختر إضافة دالة عندما:
→ فقط تسجيل أدوات ومستمعين
→ بدون حالة أو حالة بسيطة
→ كود أقل، تطوير سريع
→ لا تحتاج أن يعتمد عليك إضافات أخرى
(3) الاستخدام المختلط
كلا الشكلين يمكن أن يتواجدا في مشروع:
// 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:
// قبل: إضافة دالة
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) }
}
❓ أسئلة شائعة
this.ctx هو مُعامل ctx المُستقبل في المُنشئ، يُحفظ تلقائياً بواسطة صنف Service الأساسي. هو سياق الإضافة، يوفر الوصول لجميع الخدمات المُحقنة.dispose(): typescript export default class DbService extends Service { private pool: Pool dispose() { this.pool.end() } static inject وادخل عبر ctx.serviceName في المُنشئ.declare module '@deepseek-ai/cordis' في ملف .d.ts ووسّع واجهة Context. تأكد أن ملف التصريح هذا مُضمّن بواسطة مترجم TypeScript.📖 ملخص
- Service هو شكل الإضافة القائم على الأصناف في Cordis؛
super(ctx, 'serviceName')يُسجّل الخدمة تلقائياً static injectيصرّح باعتماديات Service، يضمن جاهزية الخدمات على ctx في المُنشئ- توسيعات الأنواع عبر
declare moduleتجعل TypeScript يتعرف على أنواعctx.serviceName - Service يناسب سيناريوهات ذات حالة، كاشفة APIs، تحتاج توريث؛ إضافات الدوال تناسب سيناريوهات بدون حالة وخفيفة
- كلا الشكلين يمكن خلطه: Service يوفر خدمات أساسية، إضافات الدوال تستهلك الخدمات وتُسجّل أدوات
📝 تمارين
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 في واجهة الويب لعرض الإحصائيات.