Rychlý import dat v enterprise systémech: YAML přístup s Java a Vue
V moderních enterprise systémech se úkol rychlého a efektivního importu velkých objemů historických nebo zdrojových dat často stává vážnou výzvou. Tradiční přístupy, vyžadující psaní objemného kódu nebo složité konfigurace, mohou proces protáhnout na dny. Tento článek zkoumá inovativní metodu vytvoření odlehčené služby orientované na YAML, postavené na technologiích Java a Vue.js, která umožňuje zkrátit dobu nahrávání dat na pouhé minuty, čímž výrazně zjednodušuje interakci technických specialistů s platformou a jejím obsahem.
Vývoj nové platformy JMatrixPlatform, zaměřené na princip "vše je kód", odhalil kritický problém: nedostatek pohodlného nástroje pro jednorázové nahrávání dat. Na rozdíl od starších systémů, kde byl import dat triviální záležitostí, nová architektura vyžadovala psaní Java kódu pro každou operaci, což činilo proces náročným a nepružným. Analytici ztratili možnost spravovat data sami, zcela závislí na vývojářích.
Bylo zváženo několik standardních přístupů, z nichž každý měl podstatné nevýhody:
- Doplnění REST API batch službami: Vyžadovalo by složitou transformaci dat z Excelu do JSON polí, včetně escapování uvozovek v Excelových vzorcích, což je samo o sobě netriviální úkol. Použití externích nástrojů, jako je Postman, by proces zkomplikovalo.
- Použití
curl + jsonpro každý objekt: Vyžadovalo by dodatečné skriptování pro získání tokenů a neposkytovalo by odolnost proti chybám, přerušovalo by import při prvním nesprávném záznamu. - Přímý import přes SQL: Byl vyloučen, jelikož veškerá logika práce s daty je implementována na úrovni aplikace. Přímý zásah do databáze mimo obchodní logiku platformy by mohl vést k nekonzistenci dat a narušení integrity systému.
- Napsání nativního importu z Excelu pro každou entitu: Bylo by ekonomicky neefektivní a časově náročné řešení pro jednorázové importy.
Potřeba rychlého a flexibilního nástroje, který by technickým specialistům umožnil samostatně spravovat nahrávání dat, se stala zřejmou.
Řešení: Služba orientovaná na YAML pro dávkové nahrávání
Po uvědomění si omezení stávajících přístupů dospěl autor k závěru, že je zapotřebí minimalistická služba s jediným vstupním bodem (endpointem), schopná přijímat strukturovaná data pro provádění různých operací (vytvoření, změna, smazání). Namísto JSONu padla volba na YAML díky jeho stručnosti a pohodlí pro ruční generování nebo automatické vytváření pomocí jednoduchých Excel vzorců nebo LLM.
Příklad YAML dotazu demonstruje jeho jednoduchost a čitelnost:
#dotaz
- createObject:
type: ru.commons.matrix.schema.type.ATPPerson
policy: ru.commons.matrix.schema.policy.ALCPerson
- createObject:
type: ru.commons.matrix.schema.type.ATPPerson1
policy: ru.commons.matrix.schema.policy.ALCPerson
- createObject:
type: ru.commons.matrix.schema.type.ATPPerson
policy: ru.commons.matrix.schema.policy.ALCPerson
I odpovídající odpověď, včetně stavu provedení a identifikátoru objektu nebo chybové zprávy:
#odpověď
---
- createObject:
status: 200
message: null
oid: "f4ba679e-9253-4a83-a390-44daf7ac7756"
- createObject:
status: 500
message: "Admin typ ru.commons.matrix.schema.type.ATPPerson1 nebyl nalezen. Zadejte správné jméno nebo kontaktujte administrátora."
- createObject:
status: 200
message: null
oid: "d9c98e74-bd17-4b3c-ae07-d9c307151c74"
Takový formát umožňuje snadno generovat příkazy, například z Excelu, pomocí jednoduchých vzorců:
="- createObject:
id: "&K2&"
type: "&I2&"
policy: "&J2&"
code: "&B2&"
title: '"&C2&"'"
Tento přístup není plnohodnotným DSL (Domain Specific Language), ale využívá stávající DTO (Data Transfer Objects) REST API, zabalené do akčních příkazů, což zajišťuje flexibilitu a znovupoužitelnost.
Technická implementace v Java a Spring Frameworku
Základem služby je kontroler JQLController, který zpracovává příchozí YAML požadavky.
@RestController
@RequestMapping("jql")
@RequiredArgsConstructor
public class JQLController {
/**
* Provádí dávku příkazů.
*
* Používá LinkedHashMap pro zachování pořadí vstupních a výstupních příkazů
* - vstupní příkazy jsou zpracovány v pořadí, ve kterém jsou uvedeny v YAML
* - odpovědi jsou vráceny ve stejném pořadí jako požadavky
* - to je důležité pro scénáře, kde záleží na pořadí provádění (vytvoření → připojení)
* Jackson ve výchozím nastavení používá LinkedHashMap, ale explicitní specifikace
* chrání před náhodnou změnou implementace v budoucnu.
*/
@PostMapping(consumes = "application/yaml", produces = "application/yaml")
public ResponseEntity<List<LinkedHashMap<String, JQLResponseData>>> promote(@JPathContextVariable JContext ctx,
@RequestBody List<LinkedHashMap<String, Object>> commands) {
List<LinkedHashMap<String, JQLResponseData>> results = new ArrayList<>(commands.size());
for (Map<String, Object> command : commands) {
Map<JQLEnum, IJQLDTO> parsed = fromYaml(command);
//chyba v příkazu nevede k pádu celé dávky
//run to zohledňuje a zde není potřeba try catch
results.add(run(ctx, parsed));
}
return ResponseEntity.ok(results);
}
private static Map<JQLEnum, IJQLDTO> fromYaml(Map<String, Object> commands) {
Map<JQLEnum, IJQLDTO> command = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : commands.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
JQLEnum jqlEnum = JQLEnum.valueOf(key);
Class<? extends IJQLDTO> dtoClass = jqlEnum.getDTOClass();
IJQLDTO dto = JObjectJSON.MAPPER.convertValue(value, dtoClass);
command.put(jqlEnum, dto);
}
return command;
}
/**
* Provádí příkazy a vrací výsledek ve stejném formátu.
*
* Vstup: { "createObject": { "type": "...", "policy": "..." } }
* Výstup: { "createObject": { "status": 200, "id": "..." } }
*
* nebo
*
* Výstup: { "createObject": { "status": 500, "message": "..." } }
*/
private static LinkedHashMap<String, JQLResponseData> run(JContext ctx, Map<JQLEnum, IJQLDTO> commands) {
LinkedHashMap<String, JQLResponseData> response = new LinkedHashMap<>();
if (commands.isEmpty()) {
return response;
}
try {
ctx.getTxUpdate().executeWithoutResult(tx -> {
for (Map.Entry<JQLEnum, IJQLDTO> entry : commands.entrySet()) {
response.put(entry.getKey().name(), entry.getKey().execute(ctx, entry.getValue()));
}
});
} catch (JMatrixLocalizedError ex) {
commands.keySet().forEach(el -> {
response.put(el.name(), new JQLResponseData(500, ex.getLocalizedMessage(ctx.getLocale())));
});
} catch (Exception ex) {
commands.keySet().forEach(el -> {
response.put(el.name(), new JQLResponseData(500, ex.getMessage()));
});
}
return response;
}
}
Kontroler JQLController využívá anotace Spring pro zpracování POST požadavků s typem obsahu application/yaml. Důležitou vlastností je použití LinkedHashMap pro zachování pořadí příkazů, což je kritické pro scénáře, kde záleží na posloupnosti operací (například vytvoření objektu před jeho propojením). Metoda fromYaml je zodpovědná za deserializaci YAML objektů do odpovídajících DTO, přičemž používá JQLEnum k určení typu příkazu a třídy DTO. Metoda run provádí příkazy v rámci transakce, zpracovává chyby a generuje strukturovanou YAML odpověď.
Konfigurace Spring pro práci s YAML
Jelikož Spring Framework ve výchozím nastavení nepodporuje application/yaml jako typ obsahu, je nutná dodatečná konfigurace HttpMessageConverter.
@Configuration
public class YamlConfig {
@Bean
public YamlHttpMessageConverter yamlHttpMessageConverter() {
YAMLFactory factory = new YAMLFactory();
//.disable(YAMLGenerator.Feature.SPLIT_LINES)
//.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
YAMLMapper mapper = new YAMLMapper(factory);
return new YamlHttpMessageConverter(mapper);
}
public static class YamlHttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public YamlHttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper,
MediaType.parseMediaType("application/yaml"),
MediaType.parseMediaType("application/x-yaml"),
MediaType.parseMediaType("text/yaml"));
}
}
}
YamlConfig definuje bean YamlHttpMessageConverter, který rozšiřuje AbstractJackson2HttpMessageConverter a registruje YAMLMapper pro zpracování YAML formátů (application/yaml, application/x-yaml, text/yaml). To umožňuje Springu automaticky serializovat a deserializovat YAML data v objektech kontroleru.
Struktura DTO a Enum příkazů
Pro standardizaci odpovědí bylo vyvinuto základní DTO JQLResponseData:
@Getter
public class JQLResponseData {
private int status = 200;
private String message = null;
public JQLResponseData() {
}
public JQLResponseData(int status, String message) {
this.status = status;
this.message = message;
}
}
Tato třída zajišťuje jednotný formát pro stav provedení a chybové zprávy.
Původně byl pro MVP soubor příkazů implementován prostřednictvím enum JQLEnum, což zjednodušilo vývoj, ale ve zralejších systémech může být nahrazen flexibilnějším mechanismem registrace tříd příkazů.
public enum JQLEnum {
createObject {
@Override
JQLResponseData execute(JContext ctx, IJQLDTO value) {
JDTODomainObject dto = (JDTODomainObject) value;
JDomainObject object;
if (dto.getId() == null) {
object = new JDomainObject();
} else {
object = new JDomainObject(dto.getId());
}
dto.unmap(object);
object.create(ctx, JModel.getRequiredAdminByName(dto.getType()), JModel.getRequiredAdminByName(dto.getPolicy()));
return new CreateDomainRS(object.getId());
}
@Override
public Class<? extends IJQLDTO> getDTOClass() {
return JDTODomainObject.class;
}
},
deleteObject {
@Override
JQLResponseData execute(JContext ctx, IJQLDTO value) {
DeleteDomainRQ dto = (DeleteDomainRQ) value;
new JDomainObject(dto.getId()).delete(ctx);
return new JQLResponseData();
}
@Override
public Class<? extends IJQLDTO> getDTOClass() {
return DeleteDomainRQ.class;
}
},
//a tak dále
abstract JQLResponseData execute(JContext ctx, IJQLDTO value);
public abstract Class<? extends IJQLDTO> getDTOClass();
}
Každý prvek JQLEnum zapouzdřuje logiku provádění konkrétního příkazu (execute) a poskytuje metodu pro získání odpovídající třídy DTO (getDTOClass). To umožňuje centralizovaně spravovat dostupné operace a jejich zpracování.
Uživatelské rozhraní ve Vue.js
Pro interakci s YAML službou bylo vyvinuto jednoduché webové rozhraní ve Vue.js. Skládá se ze dvou oblastí: jedna pro zadávání YAML požadavků, druhá pro zobrazení výsledků. Použití knihovny ace-builds zajišťuje pohodlné editování YAML kódu s zvýrazněním syntaxe.
<template>
<div class="jql-base-div">
<div ref="refRequests" class="jql-requests-div" @keyup.alt.enter="handleAltEnter"></div>
<div ref="refResults" class="jql-response-div"></div>
</div>
</template>
<script setup>
import { useJServices } from '@/composables/useJServices';
const { serviceFetch } = useJServices()
import ace from 'ace-builds';
import 'ace-builds/src-noconflict/mode-yaml';
import 'ace-builds/src-noconflict/theme-chrome';
import { onMounted, ref } from 'vue';
ace.config.set('basePath', '/ace')
ace.config.set('workerPath', '/ace')
ace.config.set('themePath', '/ace')
const props = defineProps({
routeParams: Object,
routeQuery: Object,
requestBody: Object,
metaComponent: Object
})
const refRequests = ref(null)
const refResults = ref(null)
let aceEditorRequests = null
let aceEditorResponse = null
onMounted(() => {
document.title = 'JMatrix: JQL'
aceEditorRequests = ace.edit(refRequests.value)
aceEditorRequests.setTheme("ace/theme/chrome")
aceEditorRequests.session.setMode("ace/mode/yaml")
aceEditorRequests.setOptions({
fontSize: "13px",
showPrintMargin: false,
r
Rozhraní poskytuje interaktivní prostředí pro testování a provádění YAML příkazů, což umožňuje vývojářům a analytikům rychle ověřovat výsledky operací a upravovat požadavky. Použití ace-builds výrazně zvyšuje komfort práce s YAML strukturami.
Výhody a další rozvoj
Implementace YAML služby radikálně zkrátila dobu potřebnou pro nahrávání historických dat z dnů na minuty. Toto řešení nejen zvýšilo operativnost provádění předprodejních customizací, ale také výrazně rozšířilo možnosti analytiků, poskytujíc jim nástroj pro samostatnou práci s daty bez přímé účasti vývojářů.
Hlavní výhody tohoto přístupu:
- Rychlost a efektivita: Rychlé nahrávání dat díky zjednodušenému formátu a dávkovému zpracování.
- Flexibilita: Možnost snadno generovat požadavky z různých zdrojů (Excel, LLM) bez složitého kódování.
- Samostatnost analytiků: Snížení závislosti na vývojářích pro rutinní operace importu dat.
- Čistota architektury: Využití stávajících DTO a zachování obchodní logiky na úrovni aplikace.
Jako další rozvoj lze zvážit:
- Refaktorování
JQLEnumna dynamičtější mechanismus registrace příkazů, využívající reflexi nebo konfiguraci, pro zjednodušení přidávání nových operací. - Rozšíření UI o možnosti validace YAML schémat a složitější zpracování odpovědí.
- Integrace se systémy pro správu verzí pro sledování změn v importovaných datech.
Tento přístup ukazuje, jak lze pomocí promyšlené architektury a výběru vhodných technologií řešit složité úlohy importu dat, čímž se výrazně zvýší produktivita týmu a flexibilita systému.
Co je důležité
- Problém: Tradiční metody importu dat (REST API, SQL, nativní utility) jsou neefektivní pro jednorázové nahrávání historických dat v "code-first" enterprise systémech, vyžadují dny práce.
- Řešení: Vytvoření odlehčené služby orientované na YAML v Java (Spring Framework) a Vue.js pro dávkové zpracování příkazů.
- Volba YAML: Preferování YAML před JSONem je dáno jeho stručností, snadnou generací (z Excelu, LLM) a čitelností pro technické specialisty.
- Architektura: Služba využívá
JQLControllerpro zpracování YAML požadavků,YamlHttpMessageConverterpro integraci se Springem, standardizované DTO (JQLResponseData) aenumpro provádění příkazů. - Výsledek: Zkrácení doby importu dat z dnů na minuty, zvýšení samostatnosti analytiků a flexibility systému při zachování integrity obchodní logiky.
— Editorial Team
Zatím žádné komentáře.