Spring Sync 簡介

工程 | Craig Walls | 2014 年 10 月 22 日 | ...

今天稍早,我宣布了 Spring Sync 的第一個里程碑版本發佈,這是一個新專案,旨在透過採用基於修補程式的交換,來解決用戶端應用程式和 Spring 後端之間的有效通訊問題。由於這是一個新專案,我認為現在是時候向您展示 Spring Sync 的功能了。

這裡給出的範例參考了 Spring REST Todos 範例和/或該範例專案中的 Todo 類別。

建立與套用修補程式

在最低層級,Spring Sync 提供了一個程式庫,用於產生修補程式並將其套用到 Java 物件。 Patch 類別是此程式庫的核心,它捕捉可以套用到物件的變更,使其與另一個物件同步。

Patch 類別旨在成為通用的,不直接與修補程式的任何特定表示形式相關聯。也就是說,它的靈感來自 JSON Patch,而 Spring Sync 提供了建立和序列化 Patch 實例作為 JSON Patch 的支援。 Spring Sync 的未來版本可能會包含對其他修補程式表示形式的支援。

建立修補程式最簡單的方法是執行兩個 Java 物件之間的差異比較

Todo original = ...;
Todo modified = ...;
Patch patch = Diff.diff(original, modified);

在這裡,Diff.diff() 方法將比較兩個 Todo 物件,並產生一個 Patch,描述它們之間的差異。

一旦您有了 Patch,就可以透過將物件傳遞到 apply() 方法中,將其套用到物件

Todo patched = patch.apply(original, Todo.class);

請注意,diff()apply() 方法互為反向操作。因此,在這些範例中,修補後的 Todo 在將修補程式套用到原始物件後,應該與修改後的 Todo 相同。

正如我所提到的,Patch 與任何特定的修補程式表示形式解耦。但是 Spring Sync 提供了 JsonPatchMaker 作為一個實用類別,用於將 Patch 物件轉換為/從 Jackson JsonNode 實例轉換而來,其中 JsonNode 是一個 ArrayNode,根據 JSON Patch 規範包含零個或多個操作。例如,要將 Patch 轉換為包含 JSON Patch 的 JsonNode

JsonNode jsonPatchNode = JsonPatchMaker.toJsonNode(patch);

同樣地,可以從 JsonNode 建立 Patch 物件,如下所示

Patch patch = JsonPatchMaker.fromJsonNode(jsonPatchNode);

請注意,JsonPatchMaker 是一個暫時的解決方案,用於將 Patch 物件(反)序列化為 JSON Patch。在後續版本中,它將被更永久的解決方案取代。

套用差異同步

建立修補程式需要您同時擁有物件的「之前」和「之後」實例,才能從中計算差異。儘管 Neil Fraser 在一篇論文中描述的 差異同步 演算法並未將它們稱為「之前」和「之後」,但它本質上定義了一種控制器方式,藉此可以在兩個或多個網路節點之間(可能是用戶端和伺服器,但不一定僅適用於用戶端-伺服器情境)建立、共用和套用修補程式。

在套用差異同步時,每個節點維護資源的兩個副本

  • 本機節點自己的工作副本,它可以變更。
  • 陰影副本,它是本機節點對遠端節點工作副本外觀的理解。

節點可以對其資源的本機副本進行任何需要的變更。節點會定期透過比較本機節點與其維護的遠端節點的陰影副本來產生修補程式。然後,它將修補程式傳送到遠端節點。一旦修補程式傳送出去,節點就會將其本機副本複製到陰影副本上,假設遠端節點將套用修補程式,因此它對遠端節點資源的理解與本機資源同步。

在收到修補程式後,節點必須將修補程式套用到它為傳送修補程式的節點保留的陰影副本,以及它自己的本機副本(可能本身已進行變更)。

Spring Sync 透過其 DiffSync 類別支援差異同步。要建立 DiffSync,您必須為其提供 ShadowStore 和它可以套用修補程式的物件類型

ShadowStore shadowStore = new MapBasedShadowStore();
shadowStore.setRemoteNodeId("remoteNode");
DiffSync diffSync = new DiffSync(shadowStore, Todo.class);

一旦您手頭有了 DiffSync,您就可以使用它將 Patch 套用到物件

Todo patched = diffSync.apply(patch, todo);

apply() 方法會將修補程式套用到給定的物件以及同一個物件的陰影副本。如果尚未建立陰影副本,它將透過深度複製給定的物件來建立一個。

ShadowStoreDiffSync 維護其遠端節點陰影副本的地方。對於任何給定的節點,可能有多個陰影儲存區,每個儲存區對應一個它處理的遠端節點。正如您在範例中看到的,它的 remoteNodeId 屬性設定為唯一識別遠端節點。在用戶端-伺服器拓撲中,伺服器可以使用會話 ID 來識別遠端節點。同時,用戶端(可能只與一個中央伺服器共用資源)可以使用它們想要的任何識別符號來識別伺服器節點。

DiffSync 也可以用於從儲存的陰影副本建立 Patch

Patch patch = diffSync.diff(todo);

在建立修補程式時,將從 ShadowStore 檢索儲存的陰影副本,並與給定的物件進行比較。為了與差異同步流程保持一致,一旦產生修補程式,給定的物件將被複製到陰影副本上。

值得注意的是,DiffSyncPatch 物件一起運作,而 Patch 物件與任何特定的修補程式表示形式解耦。因此,DiffSync 本身也與修補程式表示形式解耦。

將 DiffSync 帶到 Web

在單一節點上建立和套用修補程式有點毫無意義。當兩個或多個節點共用和操作相同的資源,並且您需要每個節點保持同步(在合理的範圍內)時,差異同步才會真正發揮作用。因此,Spring Sync 也提供了 DiffSyncController,這是一個 Spring MVC 控制器,用於處理 HTTP PATCH 請求,將差異同步套用到資源。

配置 DiffSyncController 最簡單的方法是建立一個使用 @EnableDifferentialSynchronization 註解的 Spring 配置類別,並擴展 DiffSyncConfigurerAdapter 類別

@Configuration
@EnableDifferentialSynchronization
public class DiffSyncConfig extends DiffSyncConfigurerAdapter {

	@Autowired
	private PagingAndSortingRepository<Todo, Long> repo;
	
	@Override
	public void addPersistenceCallbacks(PersistenceCallbackRegistry registry) {
		registry.addPersistenceCallback(new JpaPersistenceCallback<Todo>(repo, Todo.class));
	}
	
}

除了其他事項外,@EnableDifferentialSynchronization 宣告了一個 DiffSyncController bean,為其提供了 PersistenceCallbackRegistryShadowStore

PersistenceCallbackRegistry 是一個 PersistenceCallback 物件的註冊表,DiffSyncController 將透過它檢索和持久化它修補的資源。 PersistenceCallback 介面使 DiffSyncController 能夠與資源的應用程式特定持久性選擇解耦。作為一個範例,以下是一個 PersistenceCallback 的實作,它與 Spring Data CrudRepository 一起運作,以持久化 Todo 物件

package org.springframework.sync.diffsync.web;

import java.util.List;

import org.springframework.data.repository.CrudRepository;
import org.springframework.sync.diffsync.PersistenceCallback;

class JpaPersistenceCallback<T> implements PersistenceCallback<T> {
	
	private final CrudRepository<T, Long> repo;
	private Class<T> entityType;

	public JpaPersistenceCallback(CrudRepository<T, Long> repo, Class<T> entityType) {
		this.repo = repo;
		this.entityType = entityType;
	}
	
	@Override
	public List<T> findAll() {
		return (List<T>) repo.findAll();
	}
	
	@Override
	public T findOne(String id) {
		return repo.findOne(Long.valueOf(id));
	}
	
	@Override
	public void persistChange(T itemToSave) {
		repo.save(itemToSave);
	}
	
	@Override
	public void persistChanges(List<T> itemsToSave, List<T> itemsToDelete) {
		repo.save(itemsToSave);
		repo.delete(itemsToDelete);
	}

	@Override
	public Class<T> getEntityType() {
		return entityType;
	}
	
}

至於提供給 DiffSyncControllerShadowStore,預設情況下它將是一個 MapBasedShadowStore。但是您可以覆寫 DiffSyncConfigurerAdapter 中的 getShadowStore() 方法,以指定不同的陰影儲存區實作。例如,您可以像這樣配置基於 Redis 的陰影儲存區

@Autowired
private RedisOperations<String, Object> redisTemplate;

@Override
public ShadowStore getShadowStore() {
	return new RedisShadowStore(redisTemplate);
}

無論您選擇哪種 ShadowStore 實作,都會宣告一個會話範圍的 bean,確保每個用戶端都收到它們自己的陰影儲存區實例。

由於它處理 PATCH 請求,DiffSyncController 將套用差異同步流程的一個週期

  1. 它會將修補程式套用到伺服器資源副本和傳送 PATCH 的用戶端的陰影副本。
  2. 它將透過將其本機資源與陰影副本進行比較來建立一個新的修補程式。
  3. 它會將陰影副本替換為資源的本機副本。
  4. 它會在對用戶端的響應中傳送新的修補程式。

就像 PatchDiffSync 一樣,DiffSyncController 與任何特定的修補程式格式解耦。但是,Spring Sync 確實提供了 JsonPatchHttpMessageConverter,以便 DiffSyncController 可以接收和響應 JSON Patch 格式的修補程式,前提是內容類型為 "application/json-patch+json"。

結論

正如您在這裡看到的,Spring Sync 旨在提供一種用戶端和伺服器(或任何共用資源的節點集合)之間有效通訊和同步的方法。它為產生和套用修補程式提供低階支援,並為使用差異同步提供高階支援。儘管它附帶了對 JSON Patch 的支援,但在很大程度上它獨立於任何特定的修補程式格式。

這僅僅是開始。在其他事項中,我們正在尋求...

  • 使用 WebSocket/STOMP 補充 DiffSyncController 基於 HTTP 的差異同步,以實現全雙工修補程式通訊。
  • 持續改進差異同步實作,以支援資源版本控制和其他技術,以避免修補程式衝突。
  • 支援在用戶端 Android 應用程式中使用 Spring Sync。

請密切關注該專案,並告訴我們您的想法。歡迎隨時提交錯誤報告和改進建議,我們當然歡迎您fork 程式碼並提交 pull request。

如果您想閱讀更多關於 Spring Sync 的資訊,請查看以下資源

取得 Spring 電子報

隨時掌握 Spring 電子報的最新資訊

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉辦的活動

查看 Spring 社群中所有即將舉辦的活動。

查看全部