Jmix 如何將外部資料直接顯示在介面?

2023-04-07 12:00:17

企業級應用中,通常一個業務系統並不是孤立存在的,而是需要與企業、部門或者是外部的已有系統進行整合。一般而言,系統整合的資料和介面互動方式通常有以下幾種:

  1. 檔案傳輸:通過檔案傳輸的方式將資料傳遞給其他系統,例如使用 FTP 或 SFTP 等協定傳送檔案。這種互動方式適合單向或批次資料傳輸。
  2. Web 服務 API:使用 API 與其他系統進行通訊,一般採用 SOAP 或 REST 等通訊協定。這種互動方式可用於實現單向或雙向資料傳輸。
  3. 資料庫互動:使用資料庫共用的方式,或者通過資料庫連線進行資料交換。這種互動方式在雙向資料同步和實時資料交換方面非常有效。但是一定要注意資料許可權控制和資料安全。
  4. 訊息佇列:通過訊息佇列進行資料的收發,而無需與其他系統直接通訊。訊息佇列可用於處理大量資料以及非同步資料傳輸的情況。

不論使用那種方式進行通訊和傳輸,在主系統(即,正在實施的系統)中,還需要考慮的一個問題是,第三方系統過來的資料需不需要儲存?如果主系統要基於接入的資料進行進一步處理,則通常需要儲存資料。而有時候由於資料安全方面的原因,亦或是考慮到本地儲存資料後還存在資料同步與重複儲存的問題,第三方系統的資料過來後,主系統並不需要儲存資料,只是提供展示和操作介面。

本文中,我們以常見的 REST API 通訊為例,看看 Jmix 應用是如何直接使用外部資料的(這裡我們不儲存外部資料)。

外部資料來源

我們假設外部資料來源通過 REST API 提供關於專案(project)和任務(task)的 CRUD 介面。

定義 DTO 和 Service

首先,我們在主系統中定義兩個 DTO 實體:ProjectTask,用 Jmix Studio 可以直接建立 DTO 實體:

然後,在主系統中我們需要定義兩個 Services,專門用來對 ProjectTask 實體進行 CRUD 操作,而這些操作裡面,其實是呼叫了外部系統提供的 REST 介面,以 TaskService 為例:

@Component
public class TaskService {

    public static final String TASKS_BASE_URL = "http://localhost:18080/tasks";

    @Autowired
    private RestTemplate restTemplate;

    public List<Task> loadTasks() {
        Task[] tasks = restTemplate.getForObject(TASKS_BASE_URL, Task[].class);
        return Arrays.asList(tasks);
    }

    public Task saveTask(Task task) {
        String url = task.getId() != null ?
                TASKS_BASE_URL + "/"  + task.getId() :
                TASKS_BASE_URL;
        ResponseEntity<Task> response = restTemplate.postForEntity(url, task, Task.class);
        return response.getBody();
    }

    public void deleteTask(Task task) {
        restTemplate.delete(TASKS_BASE_URL + "/" + task.getId());
    }
}

Project DTO 的建立過程和 ProjectService 的內容與上面步驟類似,這裡就不再贅述。

第一種方式:使用代理

第一種方式是使用資料載入代理和提交代理方法,將原本使用 DataManager 進行資料載入和寫入的相應方法替換為使用我們自定義的服務:

這裡,我們選擇 Task DTO 和它的列表頁和編輯頁作為範例。

首先,在列表頁新增資料載入的代理,在介面選中資料載入器後,雙擊代理方法中的 <empty> 標籤,Studio 會自動生成方法並跳轉到方法定義,新增自定義邏輯:

然後,在編輯頁新增資料提交代理,這裡需要在 XML 中選中 data 節點,然後雙擊生成 commitDelegate

這樣就完成了我們需要實現的功能。是不是很簡單?

第二種方式:自定義資料儲存

Jmix 中,資料儲存可以進行自定義,通過自定義的資料儲存,可以像處理 JPA 實體一樣,使用 DataManager 處理 DTO 實體。在檢測到 DTO 實體關聯到某個自定義儲存後,DataManager 會將 CRUD 操作都通過代理執行,並且能處理對 DTO 實體的參照。具體實現框架如下:

可以看到,使用這種方式不需要對介面中的實體操作進行攔截,而是將所有對於外部系統的介面呼叫都交給 DataManager 通過資料儲存進行分發。

這裡,我們選擇 Project DTO 作為範例,建立相應的資料儲存:

  1. 建立 ProjectDataStore 實現 DataStore 介面:
@Component("sample_ProjectDataStore")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ProjectDataStore implements DataStore {

    // 注入 ProjectService 用於具體的操作
    @Autowired
    private ProjectService projectService;

    // 後面需要實現介面中的方法,主要是通過 projectService 對資料進行 CRUD,這裡省略。
    ...
}
  1. 建立一個實現 StoreDescriptor 介面的類。必須是一個 Spring 單例 bean,其中 getBeanName() 方法必須返回上一步建立的 bean 的名稱:
@Component("sample_ProjectDataStoreDescriptor")
public class ProjectDataStoreDescriptor implements StoreDescriptor {

    @Override
    public String getBeanName() {
        return "sample_ProjectDataStore";
    }

    @Override
    public boolean isJpa() {
        return false;
    }
}
  1. application.properties 中新增對資料儲存的設定:
# 如果有多個,則以逗號分隔
jmix.core.additional-stores = projectds
# 設定名稱為 jmix.core.storeDescriptor_<store_name>
jmix.core.store-descriptor_projectds = sample_ProjectDataStoreDescriptor

  1. Project 實體新增 @Store 註解:
@Store(name = "projectds")
@JmixEntity
public class Project {
    ...
}

通過這幾步,我們完成了資料儲存的實現和設定。Project 的列表頁和編輯也不需要做任何改動,並且,任何通過 DataManagerProject DTO 的操作就像操作 JPA 實體一樣方便,可以在服務層和 UI 層呼叫。

結論

如果外部 API 提供了豐富的操作介面,比如 CRUD、分頁、排序甚至支援某種查詢語言,那麼我們推薦建立一個自定義的資料儲存。這種為資料操作 Service 提供自定義資料儲存的方式,更貼近 Jmix 原生的開發方式。另外,如果需要的話,自定義的資料儲存也可以繼承 AbstractDataStore 類,這個類是 Jmix 內建 JpaDataStore 的父類別。通過這個類派生可以使用框架提供的一些機制,比如資料存取安全和對外部資料的審計。

但是如果外部 API 只提供了幾個簡單的介面,這種情況我們建議直接在 UI 層使用資料讀寫代理的方式。

範例的完整程式碼請存取 GitHub