DeepSeek Harness: Serviços e Dependências: Service Base Class
Última atualização: 2026-08-31
Plugins de função registram capacidades de "fazer" via apply, enquanto plugins de serviço expõem capacidades de "prover" através de classes. Quando seu plugin precisa manter estado interno, expor APIs chamáveis, ou servir como base de dependência para outros plugins, a classe base Service é a melhor escolha.
📋 Pré-requisitos: Ter completado 14-inject.md e 17-fiber.md
1. O Que Você Vai Aprender
- Definição de classe Service
- constructor(ctx, 'serviceName')
- Declarações static inject
- Registro e descoberta de serviços
- Acesso a serviço com tipagem segura
- Comparação Service vs plugin de função
2. Definição de Classe Service
(1) Estrutura Básica
import { Service } from '@deepseek-ai/cordis'
export default class MyService extends Service {
constructor(ctx: Context) {
super(ctx, 'my-service')
}
}
O construtor da classe base Service recebe dois parâmetros:
ctx: Contexto CordisserviceName: Identificador do serviço; outros plugins referenciam por este nome
(2) Serviço de Cache
▶ Exemplo 1: CacheService com Auto-Registro
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) Auto-Registro
super(ctx, 'cache') no construtor do Service automaticamente registra a instância como o serviço cache. Outros plugins declaram inject: ['cache'] para usá-lo.
3. Construtor e Nome do Serviço
(1) Propósito do Nome do Serviço
O nome do serviço é a chave no registro global:
super(ctx, 'cache')
// → Outros plugins acessam esta instância de serviço via ctx.cache
(2) Convenções de Nomenclatura
// ✅ Recomendado: kebab-case ou camelCase
super(ctx, 'cache')
super(ctx, 'rate-limiter')
super(ctx, 'metricsCollector')
// ❌ Não recomendado
super(ctx, 'Cache') // Início maiúsculo
super(ctx, 'cache_service') // Underscores
(3) Conflitos de Nome de Serviço
Se dois Services registram o mesmo nome, o último sobrescreve o anterior:
Plugin A registra 'cache' → CacheServiceA
Plugin B registra 'cache' → CacheServiceB
→ Final ctx.cache = CacheServiceB
(4) Acessando Serviços
// Plugin consumidor
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. Declarações static inject
(1) Dependências de Serviço
Classes Service declaram suas dependências via 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) Dependências Opcionais
static inject = ['fs', 'cache?']
Mesma sintaxe que plugins de função; ? significa opcional.
(3) Timing de Inicialização do Serviço
sequenceDiagram
participant F as Framework
participant FS as FS Service
participant DB as Database Service
participant Tool as Tool Plugin
F->>FS: Carregar → active
F->>DB: inject ['fs'] ✅ → construtor executa → active
F->>Tool: inject ['database'] ✅ → apply executa → active
(4) Usando Dependências no Construtor
export default class MyService extends Service {
static inject = ['tools']
private defaultTool: string
constructor(ctx: Context) {
super(ctx, 'my-service')
// ctx.tools está pronto (inject garante)
this.defaultTool = ctx.tools.getDefault()
}
}
5. Registro e Descoberta de Serviços
(1) Mecanismo de Registro
super(ctx, name) no construtor Service aciona o registro:
// Interno do framework (pseudocódigo)
class Service {
constructor(ctx: Context, name: string) {
ctx.provide(name, this)
// Aciona ativação de plugins pendentes que dependem deste serviço
}
}
(2) Mecanismo de Descoberta
Outros plugins declaram dependências via inject; o framework as injeta quando prontas:
// Plugin consumidor
export const inject = ['cache']
export function apply(ctx: Context) {
// ctx.cache auto-injetado, com tipagem segura
ctx.cache.set('key', 'value')
}
(3) Consulta de Serviço em Runtime
// Verificar se um serviço está registrado
if (ctx.cache) {
ctx.cache.get('key')
}
// Listar todos os serviços registrados
ctx.logger.info('serviços disponíveis:', ctx.serviceNames)
(4) Grafo de Dependência de Serviços
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. Acesso a Serviço com Tipagem Segura
(1) Extensão de Tipo TypeScript
Para dar a ctx.cache dicas de tipo adequadas, declare uma extensão de tipo:
// No arquivo de declaração de tipos do plugin
declare module '@deepseek-ai/cordis' {
interface Context {
cache: CacheService
}
}
(2) Exemplo Completo
▶ Exemplo 2: CacheService com Extensão de Tipo
// 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()
}
}
// Extensão de tipo
declare module '@deepseek-ai/cordis' {
interface Context {
cache: CacheService
}
}
(3) Segurança de Tipo no Lado do Consumidor
// Plugin consumidor
import { Context } from '@deepseek-ai/cordis'
export const inject = ['cache']
export function apply(ctx: Context) {
ctx.cache.set('key', 'value') // ✅ Tipo correto
ctx.cache.invalid() // ❌ Erro de compilação: método não existe
ctx.cache.get('key').foo() // ⚠️ Tipo any, precisa de tipagem adicional
}
(4) Serviços Genéricos
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. Comparação Service vs Plugin de Função
(1) Comparação de Recursos
| Dimensão | Plugin Service | Plugin de Função |
|---|---|---|
| Forma | Classe | Função/objeto |
| Gerenciamento de estado | Propriedades de instância | Variáveis de closure |
| Exposição de serviço | super(ctx, name) auto-registra | ctx.provide() registro manual |
| Declaração de dependência | static inject | export const inject |
| Ciclo de vida | Construtor/destruição | Apply/auto-limpeza |
| Herança | ✅ Suportado | ❌ Não suportado |
| Testabilidade | ✅ Fácil de mock | ⚠️ Requer mock de ctx |
| Volume de código | Maior | Menor |
(2) Guia de Seleção
Escolha Service quando:
→ Precisa expor APIs para outros plugins
→ Precisa manter dados com estado
→ Precisa de herança e reuso
→ Serve como dependência base para múltiplos plugins
Escolha plugin de função quando:
→ Apenas registra ferramentas e listeners
→ Sem estado ou estado simples
→ Código mínimo, desenvolvimento rápido
→ Não precisa ser dependência de outros plugins
(3) Uso Misto
▶ Exemplo 3: Service e Plugin de Função Coexistindo
Ambas as formas podem coexistir em um projeto:
// CacheService — Plugin Service
export default class CacheService extends Service { ... }
// CacheTool — Plugin de função, consome 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) Migração de Plugin de Função para Service
Quando um plugin de função cresce em complexidade, migre para Service:
// Antes: Plugin de função
export function apply(ctx: Context) {
const cache = new Map()
ctx.provide('cache', {
get: (k) => cache.get(k),
set: (k, v) => cache.set(k, v)
})
}
// Depois: Plugin 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) }
}
❓ Perguntas Frequentes
this.ctx é o parâmetro ctx recebido no construtor, automaticamente salvo pela classe base Service. É o contexto do plugin, fornecendo acesso a todos os serviços injetados.dispose(): typescript export default class DbService extends Service { private pool: Pool dispose() { this.pool.end() } static inject e acesse via ctx.serviceName no construtor.declare module '@deepseek-ai/cordis' em um arquivo .d.ts e estenda a interface Context. Garanta que este arquivo de declaração seja incluído pelo compilador TypeScript.📖 Resumo
- Service é a forma de plugin baseada em classe do Cordis;
super(ctx, 'serviceName')auto-registra o serviço static injectdeclara dependências do Service, garantindo que serviços em ctx estão prontos no construtor- Extensões de tipo via
declare modulepermitem ao TypeScript reconhecer tipos dectx.serviceName - Service é adequado para cenários com estado, exposição de API, necessidade de herança; plugins de função para cenários sem estado, leves
- Ambas as formas podem ser misturadas: Service fornece serviços base, plugins de função consomem serviços e registram ferramentas
📝 Exercícios
1. ⭐ Básico: Escreva um RateLimiterService com método check(key): boolean (máx. 60 chamadas por minuto). Registre-o como serviço rate-limiter e use-o em outro plugin de função via inject.
2. ⭐⭐ Intermediário: Adicione uma extensão de tipo (via declare module) para CacheService para que ctx.cache.get() retorne valores adequadamente tipados em plugins consumidores. Escreva um plugin consumidor e verifique que a verificação de tipos TypeScript passa.
3. ⭐⭐⭐ Desafio: Implemente um MetricsService que coleta contagens de chamadas de ferramentas e estatísticas de timing. Ele depende do serviço tools e registra dados antes e depois da execução da ferramenta. Forneça método getStats(): Record<string, { count, avgMs }>. Registre um comando metrics na Web UI para exibir estatísticas.