對接現有系統
保留在跑的系統:即時讀取、同步進來,或者對它執行動作。
大部分業務早就跑在某個資料庫、ERP 或工單系統裡,沒有人會去重寫它。把它納入同一份定義有三條路:聯邦接入,資料留在原處,每次讀取都是即時的;單向同步,由作業把資料單向複製成一個普通物件,成員能讀、不能改;通過宣告的動作改動源系統,寫入發往源頭,下一次拉取再把變化帶回來。本頁講怎麼選——寫給做決定的人,也寫給編寫繫結的 Agent。
- 3 條路
- 聯邦接入、即時讀取;單向同步;或通過宣告的動作改動源系統
- 403
- 通過資料 API 編輯、新建或刪除同步行,一律得到這個答覆——拉取是它唯一的寫入方
- 1 條執行記錄
- 每次拉取在 sys_sync_run 上留一條:讀取、新建、更新、拒收的行數,以及到達的位置
完整的繫結
對映決定什麼落到哪裡,作業決定什麼時候。
這是同步的一半,取自在當前版本腳手架上實際執行的配置:一個現有 SQLite 資料庫裡的聯邦表,每 20 秒複製進一個標記為 synced 的本地物件。第一次執行複製了 25 行;源頭改了一行、新增一行之後,下一次執行從上一次提交的位置讀了 4 行,更新 3 行、新建 1 行。源鍵對映到 id,水位欄位只增不減。
// 副本:一個普通物件,對成員只讀。
export const OrderCopy = ObjectSchema.create({
name: 'app_order_copy',
managedBy: 'synced',
sharingModel: 'public_read', // 拉取來的行沒有個人所有者
fields: { order_no: Field.text(), status: Field.text(), /* … */ },
});
// 什麼落到哪裡:從聯邦物件分視窗讀取。
export const OrdersPull = defineMapping({
name: 'orders_pull',
targetObject: 'app_order_copy',
mode: 'upsert',
upsertKey: ['order_no'],
fieldMapping: [{ source: 'id', target: 'order_no' }, /* … */],
datasourceSource: {
object: 'app_legacy_order', // external.columnMap: { order_no: 'id' }
watermark: { field: 'updated_at', start: '2020-01-01T00:00:00.000Z' },
},
});
// 什麼時候:每次執行都從上次提交的位置接著讀。
export const OrdersPullJob = defineJob({
name: 'orders_pull_job',
schedule: { type: 'interval', intervalMs: 20000 },
pull: { mapping: 'orders_pull' },
}); 三條路
即時讀、複製進來,還是執行動作
要問的不是買哪個聯結器,而是複製會不會出錯。當複製會出錯時用聯邦:資料是按請求現算的、不允許落地的,或者變化快到同步跟不上。當複製只是更方便時就同步進來:報表、和自己的物件關聯、對資料提問、源系統又慢又脆弱。需要改動源系統裡的東西時,用動作。
聯邦接入
把源資料庫或 OData 服務宣告為外部資料來源,把它的表繫結成物件。資料留在源頭,每次讀取都即時轉發過去。預設只讀。
單向同步
由作業把對映的來源——REST 或 OpenAPI 聯結器,或者一個聯邦物件——拉進一個標記為 managedBy: 'synced' 的普通物件。成員像讀任何物件一樣讀它,但不能編輯。每次執行都留下記錄。
通過動作寫回
一個宣告的動作通過聯結器、用聯結器自己的憑據呼叫源系統。本地副本不被編輯,下一次拉取把變化帶回來。沒有雙向同步。
聯邦接入
資料留在原處,即時讀取
聯邦讀取一個平台並不擁有的成熟資料庫,走的是和原生物件相同的查詢引擎、REST 介面和權限檢查。ObjectStack 從不對它執行 DDL。
能接什麼
內建 SQL 驅動接 Postgres、MySQL 和 SQLite,Oracle 與 SQL Server 僅用於聯邦;內建 OData 驅動接 SAP S/4HANA、Dynamics 365 等 OData v4 與 v2 服務。
預設只讀
寫入需要兩處同時開啟:資料來源上的 allowWrites 和物件上的 writable。任何一處關閉,新增、修改、刪除都會被拒絕,返回 403 EXTERNAL_WRITE_FORBIDDEN。
報表直接彙總遠端表
基於聯邦物件的儀表盤和報表,會通過驅動自己的表解析,彙總到正確的遠端表和列上。
設計上的邊界
不支援跨資料來源關聯。經外掛驅動(包括 OData)讀取時,命中行數超過行數上限——預設 1,000,最多 10,000——的讀取會被拒絕,而不是隻返回一部分;按父記錄權限限定範圍的讀取也會被拒絕。源系統宕機時返回 503,絕不返回空列表。
單向同步
把資料複製成成員能讀、不能改的物件
同步宣告在目標上。一個 upsert 模式的對映說明資料從哪來、拉來的行如何匹配已存的行;一個 pull 指向該對映的作業說明什麼時候拉。複製進來之後它就是普通物件:每種查詢運算、報表和權限規則都適用,讀取不會碰到源頭。
兩種來源
connectorSource 讀取 REST 或 OpenAPI 聯結器,每次執行讀一個響應。datasourceSource 按水位欄位分視窗讀取聯邦物件,每次執行都從上一次提交的位置接著讀。
除了拉取,誰都不能寫
同步物件不開放新建、匯入、編輯或刪除。通過資料 API 編輯、新建或刪除都會被拒絕,返回 403——管理員也一樣——userActions 也打不開寫操作。CSV 匯出仍然可用。
每次執行都有記錄
每次拉取在 sys_sync_run 留一行:對映、驅動它的作業執行、讀取/新建/更新/拒收的行數、從哪裡開始讀、提交到哪個位置,以及結束狀態。記錄保留 30 天,持有各對映讀取位置的那一行除外。
權限從零配置
源系統的權限不會隨資料一起過來。拉取來的行沒有個人所有者,所以物件的共享模型和授權要在本地編寫;os validate 建議用 public_read,或者用 private 加一條授予讀取權限的共享規則。
寫回
通過宣告的動作改動源系統,下一次拉取把變化帶回來
對源資料的改動在源頭完成。執行改動的動作是宣告式中繼資料:在原始碼裡,它以 diff 的形式交到人手上;在 ObjectOS Cloud 上,對已上線且已有記錄的應用所做的變更會存為草稿,由人來發布。它和其他動作走同一個動作入口,並用聯結器自己的憑據呼叫源系統——Agent 從不拿到資料庫賬號。
宣告的動作呼叫源系統
一個 type 為 flow 的動作啟動流程,流程裡的 connector_action 節點把改動發往源系統。實測:動作向源系統發出了 PATCH /tickets/T-2,此時本地那一行仍是 open。
下一次拉取完成對賬
到了下一次定時拉取,作業重新讀取源頭,本地那一行變成了 closed。副本只在拉取寫入時才改變,源頭始終是記錄系統。
沒有雙向同步
不自動寫回、不做衝突合併、不在副本上疊加編輯層。被拒絕的編輯會明確告訴使用者:請通過宣告的動作改動源頭。
開發中
尚未包含在當前版本中
基於同一個同步核心開發。各項依據公開的設計記錄 ADR-0153 描述,兩項 Enterprise 能力依據 objectstack#31763 上的版本劃分。
標記源頭的刪除
源頭已經沒有的行會被標記為「源頭已刪除」,行回來時標記清除;拉取從不物理刪除任何行。deletePolicy 和 deletedField 兩個鍵已經宣告,目前還不改變任何行為。
每一行的來源資訊
每個同步行上的平台列:寫入它的對映、它在源頭的鍵、最近一次被拉取寫入的時間,以及對應的執行。_sync_ 字首已經為它們預留。
聯結器拉取的分頁
宣告分頁約定——頁碼、游標或偏移——讓聯結器拉取讀完每一頁,而不是隻讀一個響應。
Studio 同步嚮導
在資料來源頁面建立同步物件、對映和拉取作業,並設定共享模型、頻率,檢視執行歷史;目前拉取作業在程式碼裡宣告。
資料倉儲驅動
Snowflake 和 BigQuery 作為外掛驅動包,讓資料倉儲的表用自己的方言成為拉取來源。
同步指南
一份指南和配套的 Agent 技能,講清三條路的選擇規則、寫回的答案,以及對映與作業的寫法。
記錄級血緣
逐條記錄地說明每條資料從哪來、又流向了哪裡。
基於標籤的訪問控制
由資料上的標籤決定訪問,並對資料的使用目的加以限制。
決策表
聯邦還是同步,逐項對比
| 問題 | 聯邦接入 | 單向同步 |
|---|---|---|
| 即時還是複製? | 即時:每次讀取都發往源系統 | 按作業的頻率複製;每次執行從上次提交的位置接著讀 |
| 關聯與報表 | 報表直接彙總遠端表;不支援跨資料來源關聯 | 資料在平台自己的資料庫裡,篩選、彙總和報表都在本地執行 |
| 大批次讀取 | 經外掛驅動(包括 OData)讀取時,超過行數上限(預設 1,000,最多 10,000)會被拒絕 | 內建 SQL 來源分視窗讀取,沒有行數上限 |
| Agent 對資料提問 | 每個問題都是一次即時查詢,受源系統速度與上限約束 | 問題查詢本地副本,不觸碰源系統 |
| 權限 | 在 ObjectStack 裡設定在物件上,和任何物件一樣 | 在本地從零配置:源系統的權限不會隨資料過來 |
| 寫回 | 預設關閉;資料來源和物件兩處都開啟才能寫 | 成員不能編輯副本;宣告的動作改動源頭,下一次拉取把變化帶回來 |
| 源系統宕機時 | 讀取返回 503,絕不返回空列表 | 副本照常可用;下一次執行補上 |
評審清單
讓 Agent 寫繫結之前,先決定
- 複製會不會出錯——現算的、不能落地的、要求即時的,就用聯邦。
- 誰能讀副本:編寫它的共享模型和授權,源頭什麼都不會帶過來。
- 哪一列對映到 id,哪個水位欄位只增不減。
- 哪些動作可以改動源頭,誰可以執行它們。
- 拉取作業在哪個部署上執行,並且該部署開啟了包內定時任務。
FAQ
常見問題
這是雙向同步嗎?
不是。同步是單向的:拉取是同步物件唯一的寫入方。對源頭的改動通過呼叫源系統的宣告式動作完成,下一次拉取把變化帶回來。沒有衝突合併,也沒有編輯疊加層。
源系統的權限會隨資料一起過來嗎?
不會。同步物件的權限在本地從零配置。它的行沒有個人所有者,按所有者限定的共享模型會讓所有成員都看不到:請編寫 public_read,或者 private 加一條授予讀取權限的共享規則,再把物件授權給應當看到它的角色。
副本里的值錯了,能直接改嗎?
不能通過資料 API 改:編輯、新建或刪除同步行都會被拒絕,返回 403 PERMISSION_DENIED,管理員也一樣,提示資訊會讓你通過宣告的動作改動源頭。本地編輯會被下一次拉取悄無聲息地保留或覆蓋,這正是這類物件拒絕它的原因。
ObjectStack 會在資料落地前做轉換或關聯嗎?
不會。這裡不做資料管道與加工:跨來源的關聯、清洗和彙總是資料倉儲或 ETL 廠商的工作,它們是合作伙伴,ObjectStack 把結果同步進來。對映裡的逐欄位 transform 一次只轉換一個值,不會長成管道。
拉取作業需要什麼才能執行?
一個只有一個來源、upsert 模式且帶 upsertKey 的對映,目標是宣告為 managedBy: 'synced' 的物件;再加一個 pull 指向它、在程式碼裡宣告的作業。os validate 會拒絕指向不存在的對映或沒有來源的對映的 pull。部署只有在設定 OS_AUTOMATION_SCHEDULED_WORK_ENABLED=true 之後才執行包內宣告的作業,預設關閉。
ObjectOS 在其中是什麼角色?
聯邦接入、單向同步和聯結器動作都屬於開源的 ObjectStack 執行環境,Apache-2.0。為了執行它,ObjectStack 倉庫提供 ObjectOS Community —— ghcr.io/objectstack-ai/objectstack 映像、os start 和 Console,免費且自託管 —— 而 ObjectOS Cloud 是託管版本,ObjectOS Enterprise 是自管理、需許可的版本。ObjectOS 有三個版本:ObjectOS Community,開源、可免費自託管;ObjectOS Cloud,託管在瀏覽器中、無需安裝;ObjectOS Enterprise,自管部署在你自己的基礎設施上。你的本體是普通的 ObjectStack 中繼資料。從 ObjectOS Cloud 或 ObjectOS Enterprise 匯出本體、在開源的 ObjectStack 執行環境上執行,目前尚在計劃中。
下一步