Portál AbcLinuxu, 30. dubna 2025 11:32
Právě jsem vydal novou verzi čtečky kanálu RSS Guard. Nutno říct, že se jedná o docela velké vydání, ve kterém jsem v zásadě dofinalizoval množinu podporovaných služeb do podoby, která mi vyhovuje.
V průzkumech, které jsem si nechal dělat na Google Forms, hodně lidí chtělo podporu pro Feedly. Feedly používá pro autentizaci uživatele OAuth, a pro připojení ke službě je tedy potřeba použít "client ID" a "client SECRET" řetězce. Bohužel, Feedly produkční variantu těchto řetězců takřka NEposkytuje OSS projektům. Musí se jednat o: "Inovativní projekt, který by Feedly přinesl na nové platformy." Jelikož moc klientů Feedly pro Windows, potažmo Linux či OS/2 (!!) není, kontaktoval jsem Feedly a ti napsali, že mi teda tokeny dají, pokud je v binárce nějak zobfuskuju, aby nebylo možné je jednoduše vylámat.
Po nějaké komunikace se ale Feedly bohužel odmlčelo a neodpovídá mi na mé dotazy a tokeny nakonec neposlali, proto vydávám podporu pro Feedly také s možností používat tzv. developer access token, který má ale tu potíž, že je tuze osekaný. Umožňuje jen 250 API dotazů denně a je nutné ho jednou za měsíc ručně obnovit. No, co se dá dělat.
Mezi další novinky patří:
Vzhledem k tomu, že se jedná o vydání s mnoha novými věcmi, kde bylo docela dost společné funkcionality poupraveno, tak celkem očekávám, že se něco podstatného někde rozbije. Uvidíme co.
4.x
Udělal jsem k tomu krásný tiket. Je to k nevíře, ale v řadě 3.x
bylo vydáno snad 40 verzí a už je to 5 let od verze 3.0.0
. Během té doby se naakumulovalo několik problémů, které pramenily z mých debilních rozhodnutí, snahy o zachování zpětné kompatibility a špatného předvídání a plánování budoucího vývoje programu.
Například databázová vrstva programu je "verzována" a je tvořená základní skriptem pro inicializaci struktur "from scratch" a dále sadou rozdílových skriptů pro případ aktualizace už existujícíh starších verzi. Za tu dobu se struktura měnila 20x, a tak už máme těch skriptů celou řádku. To chci změnit, provést větší zpětně nekompatibilní DB změny, víc to zjednodušit a začít trošku odznova. Bohužel je nutné DB struktury navrhnout tak, aby byly "přátelské" k datům z různých pluginů (standardní RSS kanály, různe online služby jako Inoreader, TT-RSS atp.). Perličkou je třeba Nextcloud News (tedy online čtečka z balíku Nextcloud), která má dosti nekonzistentní API a jednotlivé zprávičky nemají pouze jedno unikátní id
ale hned DVĚ! Už asi tak 4 roky to plánují předělat a zatím nic. Takže jen kvůli tomuto pluginu je nutno v DB tabulce pro zprávy držet extra Nextcloud-specific sloupec.
Další zajímavá věc jsou ty OAuth tokeny. V současné době RSS Guard podporuje out-of-box tři služby, které OAuth používají - Feedly, Inoreader a Gmail. U Feedly už jsem situaci popsal, tokeny asi nedostanu, uživatelé si tedy musí poradit sami skrze DAT. Gmail má naprosto debilně složité GUI na vygenerování těch OAuth tokenů, kde se musí nastavit X věcí od "scope" po různé další údaje - pro normálního uživatele peklo. Jediný Inoreader má naprosto jednoduché GUI, kde se dá "client ID/SECRET" vygenerovat naprosto nádherně na jeden jedinej klik. Ponaučení - snažit se ty tokeny předgenerovat a dát je přímo do binárek jako skrytý fallback. Plánuji to udělat přes Github Actions jako ten jejich "encrypted secret". Blbý je, že se tak tyto předchystané tokeny dostanou pouze do balíčků, které se generují na Actions a linuxová repa, která kompilují ze zdrojáků, mají smůlu.
Poslední zmínka snad o tiketu #302, který žádá snad 50% uživatelů, ačkoliv nechápu, jak je to možné. Prostě v seznamu kanálů jim nestačí seřazování kanálů dle abecedy či dle počtu nepřečtených zpráv. Oni chtějí ruční řazení. Navíc ten seznam je hierarchický, tedy víceúrovňový. Je možno mít libovolně zanořenou strukturu složek a teprve v těch kanály, takže budu muset imlementovat do DB vrstvy nějakou šikovnou reprezentaci "pořadí" prvku v seznamu a k tomu odpovídající SQL operace.
Tož tak.
Tiskni
Sdílej:
Případně je tu Liquibase – dá se použít buď přímo jako hotový produkt, nebo pro inspiraci při psaní vlastního řešení.
Liquibase jsme používali mj. na projektu, kde se nasazovalo na více různých databázových systémů – přidává to tam určitou portabilitu a abstrakci. Když ty změny definuješ v XML a ne ve formě SQL skriptu pro specifický systém, tak pro tebe Liquibase vygeneruje SQL skripty pro jednotlivé systémy. Pokud by automatika nestačila, tak si tam vždycky můžeš udělat podmíněný blok, který se pustí jen na určitém DBMS.
To je jen jedna z mnoha výhod.
Na druhou stranu souhlasím, že je to poměrně komplexní řešení. Pokud by šlo o můj vlastní projekt, kde mám na všechno dost času, tak bych pravděpodobně šel taky cestou vlastních skriptů – je to tedy trochu NIH, ale zase minimální komplexita a řešení přesně dle mých potřeb – za cenu toho, že si s tím musím pohrát a nestačí stáhnout z internetu hotový nástroj a začít ho používat.
Jinak, co jsem viděl u zákazníků různé jejich upgradovací skripty, tak to bylo většinou slabší – napsané kdysi někým, kdo už tam není, a ostatní se na to bojí sáhnout… někdy se pak právě přecházelo na ten Liquibase.
Oboje má svoje. Jak jsem psal, pro vlastní projekt, o který se budu starat pořád já, spíš vlastní řešení. Ale někam do větší firmy, kde je pravděpodobná vyšší fluktuace lidí, bych doporučil spíš ten Liquibase. Ono už jen zdokumentovat to vlastní řešení, aby ho mohli používat ostatní a ne jen jeho autor, zabere dost času – a ten si přepočti na peníze.
up()
a down()
, kam si napíšeš ty SQL dotazy (ať už přímo, nebo tím udělátkem, co tam je na manipulaci schématem, nebo obojím) a do tabulky si píše, co už bylo provedeno. Laravel má cca totéž. Phinx je samostatný nástroj, který dělá to samé. Liquibase vypadá jako poněkud overengineered věc, ale tak nějak v hromadě zbytečností tam vykukuje ten samý koncept …
Tak to mluvíš zase spíš o ORM – ano, můžeš si napsat třídy a pak si pod nimi nechat automagicky vygenerovat tabulky… ale to mi nepřijde jako nejšťastnější cesta. Je to takové rychlé a špinavé řešení, někdy použitelné. Ale já přeci jen radši začínám od toho modelu – databázi beru jako něco trvalého, zatímco aplikace, knihovny a frameworky jsou pomíjivé – v čase se mění, nahrazují nebo jich může být současně nad jednou databází několik. Proto se nechci tolik vázat na konkrétní programovací jazyk nebo framework.
Pak je tu otázka rolí/lidí – kdo by ten datový model měl navrhovat a udržovat. Když se to děje ad-hoc v rámci běžného vývoje a implementace jednotlivých úkolů nebo dokonce oprav chyb, tak to nedopadá moc dobře. Někdy je na to vyhrazena separátní role. Nebo to dělá aspoň seniorní vývojář/architekt, který má blízko k relačním databázím. Případně když je část logiky v procedurálním SQL, tak se o ten model nejspíš budou starat lidé, kteří píší to procedurální SQL, protože k relačním databázím mají bližší vztah než běžný javista, phpčkář atd.
vždy je vyzkouším oproti různým předchozím verzím relačního schématuTo je něco, co by nikdy nemělo nastat. Máš mít lineární posloupnost změn (SQL skriptů), které jdou vždy z jedné definované verze do druhé. Takže ať se spustí migrace nad jakoukoliv verzí schematu databáze, tak se jen vyberou ty správné SQL skripty a ty krok po kroku projdou všemi verzemi až do té aktuální. Tedy každá změna pracuje jen s jedním předchozím stavem a vyprodukuje jeden následující stav. Pokud máš různé DB, tak budeš mít různé sady SQL skriptů. Pokud chceš přepínat, tak spustíš v cílové databázi migrace z nuly, čímž vytvoříš prázdnou databázi s aktuálním schematem, a pak budeš mít skript na export a import, či zálohu a obnovu. Pokud někde máš jiné kódovaní, jiný typ sloupce, nebo něco takového, tak tam prostě máš bordel a první co máš udělat je, že bordel uklidíš a databázi dostaneš do jednoho z definovaných stavů (verzí). Pokud je nějaká část schematu proměnlivá, tak má být zdokumentováno jak se může měnit a migrace s tím musí počítat. Lepší ale je mít pár zbytečných sloupečků pro nepoužívané funkce (v dané instalaci), než proměnlivé schema.
Takové ty plugin-specific nuance jsem v zásadě jen v tom, že synchronizované pluginy k zprávičkám přiřazují service-specific ID, ale každá zpráva má ID, jen jeho generátorem je vždy někdo jiný - tudíž sloupec je pro všechny společný.To je jeden textový sloupec dostatečné délky, např. varchar(255). To není v ničem problém.
Nejvíc těch plugin-specific věcí je právě v tabulce "Accounts", kde některé účtu mají jméno/heslo, některé ne, některé umí [...]Na to je nejlepší textový sloupec, kam se parametry serializují v JSON. Z akademického pohledu je to děsná prasárna, ale z praktického je to to nejlepší, co jde udělat. Na typické časté věci (např. uživatelské jméno a heslo) si tam necháš nullable sloupeček a na ten zbytek blob s JSON. Z pohledu databáze to bude sloupec trochu exotického typu a tobě to ušetří spousty potíží.
Obecně by modul (plug-in) měl záviset na jádře, ale nikoli jádro na modulu. Moduly by pak mělo jít psát a přidávat, aniž by se v jádře cokoli měnilo. Na to je potřeba tam mít připravená rozhraní. Co se týče dat, tak modul může mít vlastní schéma (pokud ho DB, např. PostgreSQL, podporuje) nebo v horším případě ten prefix tabulek. Případně může ukládat data do nějakých generických sloupečků třeba v XML (dá se to i indexovat, ale o ty indexy se pak musí někdo ručně starat). Nebo i do EAV – přestože to mnozí považují za „anti-pattern“, jsou případy, kdy je to funkční a smysluplné řešení.
Závislost směrem z jádra k modulu by neměla být ani na úrovni datového modelu. Cizí klíče by tam buď neměly být nebo by se mělo mazat kaskádovitě (modul přijde o svoje data, když se smaže nadřazená entita v jádře, ale to by nemělo vadit). Určitě by modul neměl narušovat funkci jádra tím, že si tam nasmolí nějaké svoje klíče nebo jiná integritní omezení a pak bude blokovat dříve běžně prováděné akce.
To je něco, co by nikdy nemělo nastat. Máš mít lineární posloupnost změn (SQL skriptů), které jdou vždy z jedné definované verze do druhé. Takže ať se spustí migrace nad jakoukoliv verzí schematu databáze, tak se jen vyberou ty správné SQL skripty a ty krok po kroku projdou všemi verzemi až do té aktuální.
Tohle funguje dobře u malých projektů, kde máš těch změn jen pár. Pokud jde o něco většího (spousty tabulek, pohledů, procedur, funkcí…) a s delší historií (každý měsíc se nasazuje řada změn), tak to přestává být použitelné. Kdyby sis tímto způsobem chtěl vytvořit třeba vývojové/testovací prostředí, tak bys musel pokaždé sjíždět inkrementy za posledních deset nebo víc let… To by ses jen tak nedočkal a bylo by to obrovsky neefektivní. Ideál je, když nové prostředí můžeš vytvořit během pár vteřin, pustit na něm nějaké testy, a pak to klidně celé zahodit.
Proto se pak dělají záchytné body (snapshoty), které obsahují kompletní stav k nějaké verzi – a inkrementálně se donasadí jen těch pár posledních změn.
Pak je dobré mít ověřené, že se na aktuální verzi dostaneš z libovolného záchytného bodu a i úplně od nuly tím, že projdeš celou historií – a výsledek bude vždy stejný.
Pak je dobré mít ověřené, že se na aktuální verzi dostaneš z libovolného záchytného bodu a i úplně od nuly tím, že projdeš celou historií – a výsledek bude vždy stejný.Toto je velmi podstatné. Testy, které ověří konzistenci snapshotů jsou v podstatě nutnost. Je pak záležitost politiky podpory starých verzí, kolik se toho bude testovat – např. jen od snapshotu předminulé major verze, nebo klidně celou historii u malé/mladé aplikace. Stále však platí, že snapshoty jsou jen optimalizace. Primární zdroj je posloupnost změn.
Muselo by to ale umět i generování "aktualizačních" SQLTo je právě ta komplikovaná věc. Nejlepší řešení je použít to, co v aplikaci tak jako tak používáš. Každá migrace pak bude třída, která má název a metodu na provedení změn (je rozumné jednotlivé migrace automaticky zabalit do transakcí). Při kompilaci si vygeneruješ jejich seznam. Migrační nástroj pak vypadá tak, že koukne, které migrace byly provedeny a provede ty, které nebyly (v definovaném pořadí). Potřebuješ na to vylistovat seznam migrací, udělat select do tabuky s provedenými migracemi a odečíst provedené od dostupných. To je celé. Nějakých 200 řádek kódu. Výhodou je, že migrace jsou plně integrované v aplikaci a nemusíš řešit externí nástroje. Při spuštění spustíš migrace a hotovo. Představa jednotného schématu a generování změn je sice lákavá, ale praxe ukazuje, že to za to nestojí. Když se podíváš na různá ORM, tak ty to dělají, ale také schovávají většinu z toho, co databáze umí, aby to tak nějak šlo smysluplně udělat. Navíc to pořád neřeší změny v datech. Osvědčilo se mi používat query builder. Když se nějaké dotazy vyskytují často, tak si uděláš metodu, která takový dotaz sestaví. Vedle častých selectů a insertů můžeš mít třeba i přidávání sloupečků. Když pak migrace je běžná (C++) třída, tak máš k dispozici ten samý query builder jako ve zbytku aplikace. Můžeš i zkoumat, zda něco bylo či nebylo provedeno (např. přidej sloupec, pokud chybí a tím uklízet bordel).
custom_data_1 ... custom_data_20Jo, dej tam custom_data_json (jak píši o nějaké to vlákno výše) a při selectu to deserializuj do
QHash<QString, QString>
, tedy hloupé key-value úložiště.
Pokud bys měl nutkání tam dávat další tabulku s EAV, tak se na to vybodni.
Navíc, to není asi OSS jestli se dobře dívám
To se bohužel trochu změnilo od posledně, co jsem to používal. Zdrojáky jsou pořád k dispozici (pod svobodnou licencí Apache 2.0), ale teď tě mnohem víc tlačí k tomu, aby sis koupil placenou verzi. Pro jistotu ty zdrojáky zálohuji.
přijde mi to i takové trošku jak tu někdo psal overengineered.
Tady to chce znát míru a proporce. Když budu mít velký projekt, kde už tak je hodně komplexních knihoven a frameworků, tak ten Liquibase už nepředstavuje významný nárůst komplexity. Ale zatáhnout Liquibase jako závislost do nějakého minimalistického softwaru – z toho bych neměl dobrý pocit.
Muselo by to ale umět i generování "aktualizačních" SQL
Příkazy končící na SQL, jako updateSQL, neprovádí změny v databázi, ale generují SQL skript. Někdy se to hodí, protože jsou prostředí, kde tě nenechají spouštět nějaký tvůj nástroj (Liquibase) nebo ti tam ani nedají přístup a jen řeknou, ať jim dáš SQL skript a že si ho pustí sami.
Nicméně pokud by nastaly nějaké komplikace nebo bylo potřeba změny vrátit zpět, tak si to pak člověk musí řešit sám. (jinak Liquibase má i příkaz rollback
, kterým jde změny vracet zpátky).
pro případ, kdy už nějaké struktury mám, ale potřebuji přidat sloupec, upravit typ sloupce atp. a to s důrazem na zachování dat - tohle by se mi zejména hodilo pro SQLite, kde není možno nedestruktivně přidávat sloupce, musí se vždy zkopírovat stávající tabulka.
Pokud už databáze existuje a dosud nebyla spravovaná přes Liquibase, tak k tomu je tu příkaz generateChangeLog, který ze stávající DB vygeneruje to XML – a na něj se pak dá navázat dalšími inkrementy.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.