Jak Deedspan funguje
Deedspan přenáší data mezi e-shopem a jeho ERP, skladem, účetnictvím, PIM nebo CRM. Každý systém se napojuje na společný datový model nezávislý na e-shopu. Každé prostředí obchodníka má vlastní instalaci, staging a produkce jsou dvě instalace stejného projektu.
Co se stane se záznamem
- Spouštěč zahájí běh flow.
- Načte se zdroj: soubory, výsledek dotazu, odpověď API nebo data z webhooku.
- Každý řádek se namapuje na záznam společného modelu, třeba
stock_level. - Volitelné PHP hooky záznamy upraví, rozdělí nebo přeskočí.
- Nezměněné záznamy se označí a neposílají se.
- Cíl ověří, že odkazovaná SKU existují.
- Konektor zapisuje hromadně, nejvýš 1 000 záznamů v dávce.
- U každého záznamu se uloží výsledek.
Z čeho se instalace skládá
| Část | K čemu slouží |
|---|---|
| Aplikace | Jádro (deedhold/deedspan, Symfony bundle) a váš projekt |
| Workery | Spouštějí běhy, zpracovávají dávky a běhy dokončují; může jich běžet víc |
| Plánovač | Spouští flow podle cronu; běží právě jeden |
| MariaDB | Běhy, výsledky záznamů, co se naposledy odeslalo, data zpráv, šifrovaná připojení |
| RabbitMQ | Povinný; nese všechny fáze běhu |
| Archiv souborů | Šifrované soubory na disku nebo v úložišti kompatibilním s S3 |
| Modul pro Magento | Zachytávání změn, podepsaná upozornění, hromadný zápis skladu, sloupec v přehledu objednávek |
Co obsahuje projekt
Malý projekt pro Composer založený z kostry. Je v git repozitáři obchodníka a nasazuje ho CI.
acme-integrations/
deedspan.yaml připojení a balíčky typů záznamů
flows/ jeden YAML soubor na každý flow
records/ vlastní typy záznamů a pole navíc
connectors/ konektory jen pro tento projekt
lookups/ převodní tabulky v CSV
src/ PHP hooky, operace, přenosy
samples/ vzorové soubory pro zkoušky nanečisto
composer.json jádro a doplňky; composer.lock je v gitu
Dockerfile runtime image a vaše balíčky
compose.yaml app, worker, scheduler, MariaDB, RabbitMQ
deedspan.yaml
Připojení k ostatním systémům a vypnuté balíčky typů záznamů. U přístupových údajů je jen název proměnné prostředí, takže se hesla nedostanou do gitu, do flow ani k hookům.
deedspan.yaml
connections:
erp-ftp:
settings: { host: sftp.erp.example, user: deedspan, root: /upload }
secrets: { password: ERP_FTP_PASSWORD }
shop:
settings: { url: "https://shop.example.com" }
secrets:
consumer_key: MAGENTO_CONSUMER_KEY
consumer_secret: MAGENTO_CONSUMER_SECRET
token: MAGENTO_TOKEN
token_secret: MAGENTO_TOKEN_SECRET
records:
disabled: [pricing]
Převodní tabulky
CSV s hlavičkou key,value, ze kterého čte lookup().
lookups/warehouses.csv
key,value
01,main
02,east
Základní pojmy
Flow a spouštění
Každá integrace se skládá z jednoho nebo více flow. Flow přenáší jeden typ záznamů jedním směrem: zdroj, mapování, volitelné hooky a cíl. Uvádí, kterému systému data patří (owner:), a dva flow nesmí zapisovat stejné pole do stejného cíle.
flows/prices-from-erp.yaml
flow: prices-from-erp
owner: erp
record: price
triggers:
schedule: "0 * * * *"
source:
transport: sftp
connection: erp-ftp
path: /export/prices_*.csv
format: { type: csv, delimiter: ";" }
map:
sku: "trim(row['sku'])"
channel: "lower(trim(row['channel']))"
base: "decimal(row['price'], ',')"
currency: "upper(trim(row['currency']))"
target:
connector: magento
operation: price.update
connection: shop
channels: { eu: eu_store, uk: uk_store }
rules: { unknown_references: fail, compare_with: target }
Flow se spouští jedním ze čtyř způsobů, které se zapisují pod triggers:.
| Spouštěč | Flow se spustí, když |
|---|---|
schedule | nastane čas podle cronu, třeba "*/15 * * * *" |
shop_signal | modul v Magentu ohlásí změněné objednávky nebo produkty |
webhook | jiný systém zavolá podepsaný POST /webhooks/<name> |
| ručně | vždycky: z příkazové řádky, přes API, nanečisto nebo jako opakování |
Webhook má dva režimy. V režimu signal jen oznámí, co se změnilo, a flow si aktuální data načte sám, takže duplicitní nebo opožděná volání nevadí. V režimu payload nese data přímo volání; uloží se a flow je čte přes source: { transport: webhook }. Webhooky se občas ztratí, proto k nim nechte běžet i pomalý plán:
triggers
triggers:
webhook: { name: payments, mode: payload }
schedule: "0 3 * * *" # noční kontrola
| Klíč | Obsahuje |
|---|---|
source | Přenos a formát, nebo stream konektoru. group_by spojí sousední řádky do jednoho záznamu. |
map | Jeden výraz na každé pole. |
hooks | PHP třídy v bodech after_read, after_map, before_send, on_error. |
target | Operace konektoru, nebo přenos, který zapíše soubory, zavolá webhook nebo naplní mezitabulku. |
rules | unknown_references, compare_with a bezpečnostní limity jako max_change_share. |
Každý flow se kontroluje při načtení projektu, takže chyby zachytí už CI.
Záznamy
Záznam je jedna přenášená položka, třeba stav TENT-200 na skladovém místě main. Typy záznamů jsou definice v YAML s klíčem a typovanými poli.
| Typ | Klíč | Hlavní pole |
|---|---|---|
stock_level | sku + location | množství, skladem, datum naskladnění |
price | sku + channel | základní cena, měna, množstevní a akční ceny |
product | sku | typ, sada atributů, kanály, kategorie, viditelnost, varianty, související |
category | název | přeložené názvy |
order | číslo objednávky | zákazník, adresy, položky, součty, platba, doprava, stav |
order_update | číslo objednávky | stav, zásilka se sledovacím číslem, faktura |
- Záznam nese jen pole, která flow mapuje. Chybějící pole zůstane beze změny,
nullho vymaže a prázdná buňka v CSV data nikdy nesmaže. - Pole může mít hodnotu pro každý prodejní kanál nebo jazyk.
extends:přidá pole k typu z jádra.references: productzajistí kontrolu, že SKU v cíli existuje.
Konektory
| Část | Možnosti |
|---|---|
| Přenos | sftp, ftp, local, http, soap, mssql a další databáze, email, s3, webhook |
| Formát | csv (jakékoli kódování), xml, json, jsonl, pevná šířka |
| Konektor systému | magento (jádro); Pohoda, Helios, Fakturoid (doplňky); vlastní |
Operace pro Magento: stock.update, price.update, product.update, product.upsert, category.update. Zapisují přes REST API Magenta podepsané OAuth 1.0a a přes tenký modul Deedspanu. Do databází cizích systémů se zapisuje jen do mezitabulek nebo uložených procedur.
Výrazy
Každé mapované pole má jeden výraz nad zdrojovým řádkem row. Výrazy se nedostanou k souborům, síti ani databázi a kontrolují se při načtení projektu.
| Skupina | Funkce |
|---|---|
| Text | trim, upper, lower, replace, split, extract, substr, pad, join, regex_match, regex_replace |
| Čísla | int, decimal, round, abs, min, max |
| Datum | date, format_date, add_days, now |
| Logika | coalesce, empty, in, ? :, ?? |
| Převody | lookup, xref, setting |
| Kolekce | map, filter, first, count, sum |
Když výraz selže, skončí chybou jen jeho záznam:
záznam s chybou
map.quantity: int(): "many" is not a whole number. (expression: max(0, int(row['qty'])))
Běhy a výsledky
Každé spuštění flow je běh. Dělí se na fáze v RabbitMQ a na dávky nejvýš po 1 000 záznamech.
- Zápis je idempotentní podle klíče, třeba
TENT-200|main. - Posílají se jen změněné záznamy;
compare_with: targetporovnává s aktuálním stavem v cíli. - Od jednoho flow běží vždy jen jeden běh.
- Běh, který by toho změnil nebo smazal příliš, počká na schválení.
| Výsledek | Význam |
|---|---|
written | Odesláno a přijato |
unchanged | Stejné jako minule, neodesláno |
skipped | Vynechal ho hook nebo pravidlo |
failed | Odmítnuto; uloží se pole a chyba |
deleted | Smazáno, protože ve zdroji už není |
Běh skončí jako completed, completed_with_failures nebo failed.
Jak začít
Stačí Docker, PHP běží v kontejneru.
-
Založte projekt
terminál
$ composer create-project deedhold/deedspan-skeleton acme-integrations $ cd acme-integrations $ cp .env.example .env.local # doplňte nastavení a hesla $ docker compose up -d --build -
Napište flow
Začněte s
target: { transport: none }: záznamy se namapují a zkontrolují, ale nikam se nepošlou.flows/stock-from-erp.yaml
flow: stock-from-erp owner: erp record: stock_level source: transport: sftp connection: erp-ftp path: /export/stock_*.csv format: { type: csv, delimiter: ";", encoding: windows-1250 } map: sku: "trim(row['sku'])" location: "lookup('warehouses', row['warehouse'])" quantity: "max(0, int(row['qty']))" target: { transport: none } -
Zkontrolujte projekt
terminál
$ bin/console deedspan:flow:validate Record types: category, price, product, stock_level Connectors: magento Flows: stock-from-erp -
Vyzkoušejte ho nanečisto
Na cíl ani databázi nesáhne. Když některý záznam selže, skončí s kódem 1, takže se hodí do CI.
terminál
$ bin/console deedspan:flow:dry-run stock-from-erp samples/stock.csv TENT-200|main: sku=TENT-200, location=main, quantity=15 BOOT-42|east: sku=BOOT-42, location=east, quantity=3 -
Spusťte ho dvakrát
Druhý běh už nic nového nenajde.
terminál
$ bin/console deedspan:flow:run stock-from-erp --sync Run 01a1d5f1 completed: written 2, unchanged 0, skipped 0, failed 0. $ bin/console deedspan:flow:run stock-from-erp --sync Run 01a1d5f9 completed: written 0, unchanged 2, skipped 0, failed 0. -
Posílejte sklad do Magenta podle plánu
flows/stock-from-erp.yaml
triggers: { schedule: "*/15 * * * *" } target: connector: magento operation: stock.update connection: shop locations: { main: default, east: east } # zdroj MSI pro každé místo
Příkazová řádka
Každý příkaz nejdřív načte celý projekt. Logy jdou na stderr jako JSON, výstup na stdout.
| Příkaz | Co dělá |
|---|---|
deedspan:flow:validate | Zkontroluje typy záznamů, konektory a flow |
deedspan:flow:dry-run <flow> <sample> | Ukáže, co by se zapsalo; --json vypíše celý report |
deedspan:flow:run <flow> | Zařadí běh do fronty; --sync ho provede hned, --force i přes běžící běh |
messenger:consume control bulk | Worker |
messenger:consume scheduler_flows | Plánovač (jen jeden) |
debug:scheduler | Časy dalších běhů |
messenger:stats | Počet zpráv ve frontách |
messenger:failed:show / retry | Zprávy, které vyčerpaly pokusy |
doctrine:migrations:migrate | Tabulky v databázi; po každém upgradu |
Stejným nástrojem se dají spravovat připojení, nasazovat a vracet verze flow, znovu odesílat chyby, schvalovat pozastavené běhy a exportovat celou instalaci. Totéž umí API pro vaše nástroje i portál Deedholdu.
Provoz
- Nasazení
- Docker Compose na linuxovém serveru. CI zkontroluje a vyzkouší flow, sestaví image a nasadí ho. Přístup k placeným doplňkům jde jako build secret.
- Prostředí
- Nejdřív staging, produkce dostane stejný image po výslovném schválení.
- Verze
- Každé nasazení uloží verzi flow, vrátit ji jde jedním příkazem. Upgrade jádra:
composer update deedhold/deedspan. - Hesla
- Šifrovaně v databázi instalace; nikdy v gitu, hooky k nim nemají přístup.
- Logy
- JSON na stderr s během, flow, verzí, fází, dávkou a klíčem záznamu.
- Upozornění
- Podepsané webhooky a e-mail:
run.failed,record.failed,flow.silent,approval.neededa další. - Telemetrie
- OpenTelemetry přes OTLP, heartbeat URL pro každý flow, Sentry SDK.
- Administrace Magenta
- Sloupec Integrace v přehledu objednávek; u neúspěšné objednávky chyba a tlačítko pro opakování.
- Chyby
- Vadný řádek shodí jen svůj záznam. Neúspěšné zprávy se zkusí znovu třikrát (po 1, 3 a 9 s). Neúspěšné záznamy čekají na seznamu, odkud je pošlete znovu.
- Uchovávání
- Data a soubory 30 dní šifrovaně, potom historie a výsledky.
- Odchod
- Export přidá k repozitáři zálohu databáze a archiv; stejný Compose soubor spustíte kdekoli.
stderr
{"level_name":"WARNING","message":"Record failed.","context":{"event":"record.failed"},
"extra":{"run_id":"01a1d668-1076-746d-9a19","flow":"stock-from-erp","stage":"send","chunk":3,"record_key":"STOVE-9|east"}}
Rozšíření
Nejdřív YAML a výrazy, PHP jen tam, kde nestačí. Instalace patří jednomu obchodníkovi, takže PHP z projektu běží bez sandboxu.
Vlastní konektory
Manifest connector.yaml nad obecnými přenosy, PHP jen v pojmenovaných místech. Konektor je ve složce connectors/, nebo jako balíček pro Composer.
connectors/trailhub/connector.yaml
schema_version: 1
connector: trailhub
version: 1.0.0
connection:
url: { type: string }
token: { type: string, secret: true }
api:
base_url: "{{ connection.url }}/api"
auth: { type: bearer, token: "{{ connection.token }}" }
operations:
claim.create:
method: POST
path: /claims
class: App\Connector\CreateClaim
record: warranty_claim
Třída operace dostane přihlášeného klienta API, nikdy samotné heslo, a vrací výsledek pro každý záznam:
RecordOperation
interface RecordOperation
{
public function checkOptions(array $options): void;
/** @return list<RecordResult> one per record, in the same order */
public function send(array $records, OperationContext $context): array;
}
Volitelně: ComparesWithTarget pro compare_with: target, #[AsReferenceIndex] pro kontrolu odkazů, #[AsTransport] a #[AsFormat] pro nové přenosy a formáty.
PHP hooky
Hook dostane jeden záznam a vrátí ho, vrátí několik záznamů, nebo null, když se má přeskočit. K dispozici má nastavení, převodní tabulky, tabulku křížových odkazů a logger, nikdy ne připojení.
src/Hook/SkipZeroStock.php
final class SkipZeroStock implements Hook
{
public function __invoke(Record $record, HookContext $context): ?Record
{
return '0' === $record->get('quantity') ? null : $record;
}
}
Ve flow ho zapíšete jako hooks: { before_send: App\Hook\SkipZeroStock }.
Vlastní typy záznamů
Běhy, opakování, porovnání změn i zkoušky nanečisto fungují pro vlastní typy stejně.
records/warranty_claim.yaml
schema_version: 1
record: warranty_claim
version: 1.0.0
key: [claim_no]
fields:
claim_no: { type: string, required: true }
order_no: { type: string, required: true }
sku: { type: string, references: product }
created_at: { type: date }
Typy polí: string, decimal, int, bool, date, enum, object, list.
Správa od Deedholdu
Integrace vám nastavíme a staging i produkci provozujeme za pevný měsíční poplatek za každý napojený systém. Kód zůstává vám.