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.

💡 Dica: O valor central do Service é "serviços com estado" — propriedades de instância mantêm estado, métodos expõem APIs, e outros plugins os usam após declarar dependências inject. Se seu plugin apenas registra ferramentas e listeners, a forma função é mais simples.

📋 Pré-requisitos: Ter completado 14-inject.md e 17-fiber.md

1. O Que Você Vai Aprender

Isolamento e Escopo de Serviço


2. Definição de Classe Service

(1) Estrutura Básica

TYPESCRIPT
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:

(2) Serviço de Cache

▶ Exemplo 1: CacheService com Auto-Registro

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) 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:

TYPESCRIPT
super(ctx, 'cache')
// → Outros plugins acessam esta instância de serviço via ctx.cache

(2) Convenções de Nomenclatura

TYPESCRIPT
// ✅ 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:

TEXT 📖 Somente leitura
Plugin A registra 'cache' → CacheServiceA
Plugin B registra 'cache' → CacheServiceB
→ Final ctx.cache = CacheServiceB

(4) Acessando Serviços

TYPESCRIPT
// 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:

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) Dependências Opcionais

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

Mesma sintaxe que plugins de função; ? significa opcional.

(3) Timing de Inicialização do Serviço

100%
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

TYPESCRIPT
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:

TYPESCRIPT
// 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:

TYPESCRIPT
// 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

TYPESCRIPT
// 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

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. 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:

TYPESCRIPT
// 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

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()
  }
}

// Extensão de tipo
declare module '@deepseek-ai/cordis' {
  interface Context {
    cache: CacheService
  }
}

(3) Segurança de Tipo no Lado do Consumidor

TYPESCRIPT
// 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

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. 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

TEXT 📖 Somente leitura
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:

TYPESCRIPT
// 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:

TYPESCRIPT
// 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

P Um Service pode registrar múltiplos nomes de serviço?
R Tecnicamente você poderia chamar super múltiplas vezes no construtor, mas não é recomendado. Um Service deve registrar um nome de serviço — responsabilidade única.
P O que é this.ctx do Service?
R 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.
P Como escrever lógica de destruição do Service?
R Sobrescreva o método dispose(): typescript export default class DbService extends Service { private pool: Pool dispose() { this.pool.end() }
P Há diferença entre ctx.provide() em plugins de função e Service?
R Funcionalmente equivalente. A diferença é que Service tem instâncias de classe e suporte a herança; ctx.provide() apenas registra manualmente um objeto.
P Um Service pode depender de outro Service?
R Sim. Declare dependências com static inject e acesse via ctx.serviceName no construtor.
P Como escrever extensões de tipo para Services de terceiros?
R Declare 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


📝 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%