连接器 — 图示覆盖(注册侧 vs 调用侧)

更新于 · 在 sijie.xyz 查看原条目 ↗

父级:connector

连接器模块是共享一个包的两套设计:注册侧(连接器如何被安装、授予凭据、激活)和调用侧(消费者如何在完全看不到提供方或凭据的情况下调用一个类别动词)。用一张图恰好会模糊掉这个模块存在的意义所系的那条边界——所以,画两张。

1 · 注册侧 — 安装、凭据、激活

classDiagram
  class connectorService {
    <<connector.Service - the admin-facing side, service.go + svc_*.go>>
    Repo.SaveUploaded / Service.UpdateUploaded - plugin upload
    connect / oauth / activate / disconnect
    credential seal-unseal via cryptobox
  }
  class Hub {
    -conns map[string]Connector
    -mu sync.RWMutex
    +Register(c Connector)
    +Upsert(c Connector)
    +Resolve(name) (Connector, bool)
  }
  class Connector {
    <<interface>>
    +Name() string
    +Kind() string
    +Connected(ctx, ownerID) (bool, error)
  }
  class Verifier {
    <<interface - optional>>
    +Verify(ctx, ownerID) error
  }
  class cryptobox {
    AES-GCM seal and open
    creds ciphertext at rest
  }
  class owner_connectors {
    <<postgres>>
    per-owner connection rows
    credential ciphertext lives here
  }
  connectorService --> Hub : Register / Upsert on activate
  Hub o-- Connector : by name
  Connector <|-- Verifier : optionally also
  connectorService ..> cryptobox : seal creds
  cryptobox ..> owner_connectors : ciphertext

注册侧是凭据以明文形式存在的唯一地方,而且只是暂时的。Hub.Resolve 下游的一切看到的都只是一个 Connector——名称、类别、是否已连接;别无其他。

2 · 调用侧 — 类别动词,没有提供方,没有凭据

classDiagram
  class Consumer {
    agent tool / platform feature
    future IM-gateway, job-loop
  }
  class CalendarProxy {
    <<interface - connector/contract>>
    +Connected(ctx, ownerID) (bool, error)
    +FreeBusy(ctx, ownerID, FreeBusyReq) ([]BusyInterval, error)
    +InsertEvent(ctx, ownerID, *InsertEventReq) (InsertedEvent, error)
    +DeleteEvent(ctx, ownerID, eventID, attendeeEmail) error
  }
  class MailProxy {
    <<interface - connector/contract>>
    +Connected(ctx, ownerID) (bool, error)
    +Send(ctx, ownerID, MailMessage) (MailReceipt, error)
  }
  class DepRegistry {
    +AllConnected(ctx, ownerID, names) (bool, error)
    gate BEFORE exposure - fail-closed
  }
  class openapiRuntime {
    -spec *Spec
    -binding *Binding - JSONata
    -doer Doer
    -baseURL string
    +Call(ctx, op, input, dst, AuthInjector) error
  }
  class AuthInjector {
    <<func type>>
    func(req *http.Request) error
    built inside the connector pkg per call
  }
  class protocolImpl {
    built-in Go clients - SMTP, CalDAV
    telegram - token vault only, no contract
  }
  class StatusError {
    Code int
    Transient bool - retry per call-class
  }
  Consumer ..> DepRegistry : exposure gated first
  Consumer --> CalendarProxy : category verbs only
  Consumer --> MailProxy : category verbs only
  CalendarProxy <|.. openapiRuntime
  CalendarProxy <|.. protocolImpl
  MailProxy <|.. openapiRuntime
  MailProxy <|.. protocolImpl
  openapiRuntime ..> AuthInjector : injected per request
  openapiRuntime --> StatusError : transient vs fatal

调用方的世界里没有提供方名称,也没有凭据——代理接口(proxy interfaces)就是全部词汇。AuthInjector 是在包内部按调用时构造的;消费者甚至无法叫出它的名字。

覆盖清单(防遗漏关卡)

类型 / 区域 图示位置 已验证
Hub、Connector、Verifier 本页 §1 ✓ 代码
connector.Service 的职责(2026-07-26 之前叫 connectorsvc,1bc9ba8b0) 本页 §1 ✓ 名称,☐ 完整签名
cryptobox / owner_connectors 本页 §1 ✓ 机制,☐ 字段列表
CalendarProxy / MailProxy + DTO 本页 §2,connector-plugins ✓ 代码
openapi Runtime / AuthInjector / StatusError 本页 §2 ✓ 代码
DepProvider / DepRegistry / RequiresDeps connector-deps ✓ 代码
摄取(spec 解析、binding 校验) ☐ 尚未图示 —
egress guard 类型 connector-egress-guard(散文形式) ☐ 无类图

相关笔记