Hayo: a zákazník si napsal pravidla sám

To takhle přijde zákazník a řekne: „Chceme, aby si obchodníci mohli sami nastavit, za jakých podmínek se pošle upozornění." Nebo: „Každý tenant má trochu jiný výpočet ceny, chceme to mít konfigurovatelné." Nebo prostě: „Potřebujeme, aby to šlo měnit bez nasazení."

Jaké jsou možnosti? eval()? (ne!), Symfony Expression Language (to by šlo), vlastní parser (NIH), nebo to celé uzamknout do kódu a každou změnu řešit deploymentem (frustrující pro všechny).

Hayo je pro tyhle situace.

O co jde

Hayo je decentní skriptovací jazyk napsaný v PHP. Skripty jsou řetězce uložitelné do databáze, které pak jdou editovat přes admin rozhraní, verzovat je jako data. Na rozdíl od konkurence je možné je validovat na syntaktickou a typovou správnost. Engine skript přeloží do php (jakože) funkce, předají se data, a vypadne výsledek.

Klíčová vlastnost: jazyk je čistě funkcionální, bez vedlejších efektů. Skript nemůže číst soubory, volat HTTP, přistupovat k databázi ani k žádné PHP funkci, kterou mu sám explicitně nedáš. Bezpečnost není sandbox přidaný navrch — je to vlastnost architektury. Aby to šlo, je třeba pro to explicitně vytvořit rozhraní.

Kdy se to hodí

Konfigurovatelné notifikace

Představme si systém, kde různé týmy chtějí různá pravidla, kdy dostanou alert. Jeden tým chce upozornění, když chybovost překročí 5 %, jiný až při 10 % a jen v pracovní době, třetí to chce vázat na počet aktivních uživatelů.

Místo toho, aby každá úprava šla přes vývojáře a deployment, každý tým má v databázi uložený skript, který se vyhodnotí proti aktuálním metrikám:

errorRate > threshold and hour >= 8 and hour <= 18

nebo složitější:

errorRate > 0.05 or (errorRate > 0.02 and activeUsers > 1000)

Změna pravidla = editace v adminu, ne pull request.

Cenové kalkulace s výjimkami

E-shop, kde každý B2B zákazník má individuální ceník. Základní cena, množstevní slevy, speciální podmínky pro VIP, sezónní akce. Tohle v kódu rychle přeroste v nepřehledné if-else peklo nebo v tabulky výjimek v databázi, které stejně nestačí na složitější případy.

Se skriptovacím přístupem má každý zákazník přiřazenou cenovou funkci:

basePrice = unitPrice * quantity if customer.tier == "platinum" then basePrice * 0.75 elif quantity > 100 then basePrice * 0.85 elif customer.isLongterm then basePrice * 0.92 else basePrice

Přidat nového zákazníka s nestandardními podmínkami = přidat záznam do databáze.

Validační pravidla přizpůsobená per-tenant

SaaS produkt, kde každý zákazník (tenant) může mít jiná validační pravidla pro data, která do systému vkládá. Jeden chce povinné pole navíc, druhý chce jiné limity, třetí specifický formát.

Pravidla jsou skripty uložené per-tenant, spouštěné při každém uložení záznamu. Výsledek je boolean — validace prošla, nebo ne.

Str.len name >= 3 and Str.len name <= 100 and NOT (name INTERSECTS forbiddenWords)

Workflow podmínky

Systém schvalování, kde každý typ dokumentu může mít jiné podmínky postupu. Kdy jde faktura rovnou ke schválení, kdy potřebuje dvojí podpis, kdy se automaticky zamítne?

Místo hardcoded stavového automatu v kódu jsou přechody řízeny skripty:

amount < 10000 and submitter.department == approver.department

nebo:

amount >= 50000 or invoice.isInternational

Co to umí

Nebudu přepisovat dokumentaci. Krátce: Hayo zvládne výpočty, podmínky, práci se seznamy a slovníky, transformace dat přes map/filter/fold a řetězení operací přes pipe operátor. Type inference chytí většinu chyb při kompilaci, ne za běhu. Pro konkrétní syntaxi odkazuju na oficiální referenci.

Dvě věci stojí za zvláštní zmínku:

Typování a chyby při kompilaci. Jedna z největších obav při uživatelských skriptech je: co se stane, když to tam zákazník napíše špatně? U Hayo většina chyb vyplyne při kompilaci skriptu, ne až při jeho spuštění v produkci ve tři ráno. Type inference pozná, že předáváš číslo tam, kde se čeká řetězec, nebo voláš funkci s chybným počtem argumentů — a odmítne skript zkompilovat ještě předtím, než se dostane k uložení. Pro aplikace, kde skripty píšou netechničtí uživatelé, je to zásadní rozdíl: chyba se ukáže hned při uložení, ne až kdy se pravidlo poprvé reálně vyhodnotí.

Match výraz. Složitější větvení jde zapsat přehledněji než řadou if-elif-elif-else. Match umožňuje rozepsat varianty přímo proti hodnotě, bez opakování podmínky:

match customer.tier case "platinum" then basePrice * 0.75 case "gold" then basePrice * 0.85 case "silver" then basePrice * 0.92 else basePrice

Vhodně zvolená konstrukce pomáhá.

Symfony Expression Language by nestačil?

Symfony ExpressionLanguage je pro většinu PHP projektů první volba. Je součástí ekosystému, dobře zdokumentovaná, battle-tested, prostě klasika. Pro jednoduché výrazy jako user.age > 18 nebo product.price * 1.21 funguje skvěle. Takže stačil.

ale

Problém nastane ve chvíli, kdy logika trochu poroste. Je třeba projít seznam položek a spočítat součet? Lokální mezivýsledek, aby výraz nebyl třikrát? Nějaké čitelnější větvení s více podmínkami, ne jen ternární operátor?

Symfony Expression Language tohle nativně nezvládne. Buď je třeba vlastní rozšíření v PHP (a tedy kód, který měl být ve skriptu), nebo výraz v šabloně začne vypadat jako minifikovaný JavaScript... prostě nehezky.

Hayo je v tomto ohledu štědřejší. Jsou k dispozici lambdy, pipe operátor, if-elif-else a match konstrukce, práci se seznamy jako first-class součást, compile-time typování. Skript může mít víc řádků, mezivýsledky, lokální funkce. Stále jde o string uložený v databázi — ale ten string může být čitelný a udržovatelný i pro netechničtějšího zákazníka nebo operations tým.

Takže: pokud stačí jednoduché výrazy a navíc je již používán Symfony ekosystém, Expression Language je rozumná volba. Hayo ušetří spoustu ohýbání u netriviálnějších případů a podmínek.

Kde jsou hranice

Hayo se kompiluje do cílového jazyka, tudíž rychlost by nemusela být problém - ale přesto, stejně jako není dobrý nápad něco počítat v PHP, tak není dobrý nápad to počítat v Hayo.

Je schválně navržen na jednoduché konstrukce, takže není vhodný pro logiku která potřebuje přístup k externím zdrojům, ani pro skripty, které by měly měnit stav aplikace. Je to feature, ne bug.

Taky to není náhrada za plnohodnotný programovací jazyk. Pokud zákazník potřebuje psát komplexní algoritmy, Hayo pravděpodobně nebude dobrý nápad. Ale pro business pravidla, podmínky a transformace dat — tedy přesně pro ty věci, které se v reálných aplikacích mění nejčastěji a nejbolestnějším způsobem — na to je cílen.

Stav projektu

Verze 0.3, MIT licence, aktivně vyvíjený. Malý projekt jednoho autora, takže je třeba s tím počítat při rozhodování o kritické infrastruktuře.