介紹 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 中檢索儲存的影子,並將其與給定物件進行比較。為了符合差異同步流程,一旦生成補丁,給定物件將被複制到影子副本上。

值得注意的是,DiffSync 使用的 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,為其提供一個 PersistenceCallbackRegistry 和一個 ShadowStore

PersistenceCallbackRegistryPersistenceCallback 物件的登錄檔,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。

請關注該專案,並告訴我們您的想法。歡迎您 提交錯誤報告和改進建議,我們當然也歡迎您 分叉程式碼 並提交拉取請求。

如果您想了解更多關於 Spring Sync 的資訊,請檢視以下資源

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有