Skip to content

通道 (Channels)

@channel

typespec
extern dec channel(target: Interface | Namespace, address: valueof string, channelId?: valueof string);

宣告一個 channel。這個 channel 擁有直接寫在該 interface 或 namespace 裡的 operation。巢狀的 interface 與巢狀的 namespace 是各自獨立的範圍。它們可以各自帶自己的 channel。

address 是必填。沒給 channelId 時,channels map 的 key 就是 address 本身。用 Kafka 這類 broker 時,address 是 topic 名稱,讀者也是用 topic 名稱找 channel。要用別的名稱當 key 時,傳 channelId

typespec
@service(#{ title: "Orders" })
namespace Orders;

@message
model OrderCreated {
  orderId: string;
}

@channel("orders.created")
interface OrderChannel {
  publish(event: OrderCreated): void;
}
yaml
channels:
  orders.created:
    address: orders.created
    messages:
      OrderCreated:
        $ref: "#/components/messages/OrderCreated"

messages map 來自這個 channel 的 operation。emitter 會走訪每個頂層 operation 參數的型別與回傳型別。它會把 union 展開成各個成員,也會取出 array 或 record 的元素型別。帶 @message 的 model 成為一筆項目。走訪不進入 model 的屬性,因為巢狀的 model 屬於 payload 資料。收集不到任何 message 的 channel 會回報 channel-no-messages,且不輸出 messages 欄位。

address 在 decorator 執行期就檢查:

scheme 與 host 不檢查。完整 URL、純路徑片段、純 topic 名稱都是合法的 address。

一個 target 只能套用一次。第二次套用回報 duplicate-channel-decorator

address 參數

address 可以含 {name} 模板。每個名稱由這個 channel 的 operation 的頂層參數宣告。型別帶 @message 的參數屬於 message 宣告,不會宣告 address 參數。

typespec
@channel("orders.{region}.created")
interface OrderChannel {
  publish(
    @doc("下單的地區。")
    region: "eu" | "us",

    event: OrderCreated,
  ): void;
}
yaml
channels:
  orders.{region}.created:
    address: orders.{region}.created
    parameters:
      region:
        enum:
          - eu
          - us
        description: 下單的地區。
    messages:
      OrderCreated:
        $ref: "#/components/messages/OrderCreated"

AsyncAPI Parameter Object 只有五個欄位,沒有 schema。所以 channel 參數不帶型別資訊,值一律是字串。

Parameter Object 欄位TypeSpec 來源
enum字串字面值、字串字面值的 union、字串 enum
default該參數的預設值
description@doc
examples@example
location@parameterLocation

address 至少含一個模板時才輸出 parameters 欄位。這一層會回報五種錯誤:missing-channel-paramunused-channel-paramnon-string-channel-paramoptional-channel-paramconflicting-channel-param

描述欄位

channel 用的描述 decorator 與其他物件相同。@summarytitle@docdescription@tag@asyncTagtags,合併規則與 message 上相同。@externalDocsexternalDocs

AsyncAPI 的 channel 另有 summary 欄位。TypeSpec 沒有第三個文字來源,所以 emitter 不會輸出該欄位。

@dynamicChannel

typespec
extern dec dynamicChannel(target: Interface | Namespace, channelId?: valueof string);

宣告一個位址只有在執行期才決定的 channel。輸出的 channel 帶字面值 address: null,AsyncAPI 把它讀作「未知」。

沒給 channelId 時,channels map 的 key 用該 target 的宣告名稱。dynamic channel 沒有 address 可以當 key。

typespec
@message
model OrderAccepted {
  orderId: string;
}

@dynamicChannel("replies")
interface ReplyChannel {
  receive(response: OrderAccepted): void;
}
yaml
channels:
  replies:
    address: null
    messages:
      OrderAccepted:
        $ref: "#/components/messages/OrderAccepted"

這是獨立的 decorator,不是「省略 address 的 @channel」。位址未知的 channel 在語意上是另一種 channel。兩個 decorator 分開,「刻意要 null」與「忘記寫 address」才能在語法上區分。

dynamic channel 永遠不帶 parameters,因為它沒有可以放模板的 address。其餘行為與 @channel 相同。

一個 target 只能套用一次,也不能與 @channel 併用。兩種錯誤分別回報 duplicate-dynamic-channel-decoratorconflicting-channel-decorators

@parameterLocation

typespec
extern dec parameterLocation(target: ModelProperty, location: valueof string);

設定一個 channel address 參數的 location。值是 runtime expression,指出參數值在執行期位於 message 的哪裡。

typespec
@channel("users.{userId}.signedup")
interface UserChannel {
  publish(
    @parameterLocation("$message.payload#/user/id")
    userId: string,

    event: UserSignedUp,
  ): void;
}
yaml
channels:
  users.{userId}.signedup:
    address: users.{userId}.signedup
    parameters:
      userId:
        location: $message.payload#/user/id

這個運算式的文法與 @correlationId 相同。開頭是 $message.header#$message.payload#,後面可以接 JSON Pointer。emitter 只檢查格式。它不檢查該 pointer 是否指到 payload 或 headers schema 宣告過的欄位。超出文法的運算式回報 invalid-parameter-location

一個屬性只能套用一次。第二次套用回報 duplicate-parameter-location-decorator