Míra abstrakce API

Školení, která pořádám

Komunikuji s jednou webovou službou, která přijímá data v parametrech URL stejně jako je posílají například formuláře metodou GET. Pro komunikaci s touto službou jsem si napsal jednoduché API, které řeší například přihlašování. Otázka je, jak moc má být toto API abstraktní. V zásadě vidím dvě možnosti:

  1. Metodě se předá přímo řetězec, který se použije na místě parametrů.
  2. Metodě se předá pole, ze kterého si řetězec v požadovaném tvaru sama vytvoří, nejspíš funkcí http_build_query.

Druhý způsob je samozřejmě čistší, protože je nezávislý na tom, jakým způsobem webová služba data přijímá, navíc sám řeší ošetřování dat, takže na něj nejde zapomenout.

První způsob se zase kupodivu lépe používá, což je do značné míry způsobeno tím, že většina parametrů je číselných nebo obsahují konstantní řetězec, takže je není potřeba ošetřovat, některé parametry je navíc potřeba občas nepředat.

<?php
// předávání parametrů ve tvaru řetězce
metodaRetezec("jmeno=" . urlencode($jmeno)
    . "&pozice=1"
    . "&typ=nakup"
    . (isset($cena) ? "&cena=$cena" : "") // $cena je číselná
);

// předávání parametrů ve tvaru pole
$parametry = array(
    "jmeno" => $jmeno,
    "pozice" => 1,
    "typ" => "nakup",
);
if (isset($cena)) {
    $parametry["cena"] = $cena;
}
metodaPole($parametry);
?>

S druhým způsobem je prostě víc psaní, navíc se dá jen velmi těžko obejít bez pomocné proměnné.

Jakub Vrána, Řešení problému, 30.11.2009, diskuse: 24 (nové: 0)

Diskuse

PHX:

A co neco mezi? Kdyz bude vstupem pole tak se zpracuje pole, kdyz retezec tak se proste pouzije. Nebo jeste lepe, udelat podporu nekolika parametru, ktere se za sebe spravne poskladaji. (inspirovano v nette)

ikona The Zero:

Na tohle je jednoduchá odpověď: http://c2.com/xp/YouArentGonnaNeedIt.html :)
Detekce vstupního typu má IMHO smysl pouze v případě, že se jedná o rozdílný subset dat (jako například v ZF - nejdůležitější parametr vs. seznam všech)

Já bych to určitě dělal polem. Nepotřebuju vědět, jak služba funguje vnitřně, abych ji mohl používat navenek (separation of concerns). Způsob předávání informací (http query) je odkrývání implementace.

Radek:

Osobně zastávám názor, že api knihovny by mělo být odstíněno od jejího vnitřního fungování natož pak od použitého komunikačního protokolu. Protokol se jinak zadrátuje na místo, kde tu knihovnu použiješ a dohledávání je pak pěkný opruz. Já bych navíc jako klíče předávaného pole použil třídní konstanty, čímž se dále oddělí vnitřní prostor knihovny. Místo pole jako jediného všeobsažného parametru mám raději přetěžování funkcí.

ikona Jakub Vrána OpenID:

Vidím jasný pokus o samoúčelnou komplikaci, která pouze přidělá obrovské množství práce. Ta webová služba má desítky funkcí s desítkami parametrů, které navíc v čase přibývají. Moje řešení je nadefinovat jednu metodu __call, která pošle dál předané parametry a která v případě chyby (nepodporovaná funkce nebo parametr) vyvolá výjimku. Navrhované řešení by bylo pro každou funkci definovat vlastní metodu a pro její parametry definovat konstanty. Kód by tedy byl několikanásobně delší, s žádnou výhodou a s nevýhodou toho, že nově přidané funkce a parametry by API nepodporovalo.

Bednee:

Co když najednou, z nějakého důvodu, najednou bude vyžadovat webová služba parametry třeba podle abecedy. V prvním případě to bude znamenat přepsat všechen kód, který metodu volá. V druhém případě se přepíše pouze metoda. Já tedy volím pole.

ikona Jakub Vrána OpenID:

Zkus se na to podívat z jiného úhlu: URL-zakódovaný řetězec je legitimní způsob předání dat (stejně třeba jako JSON), jen není tak nativní jako PHP pole. To, že je shodný s výstupním formátem, lze brát jen jako shodu okolností.

Takže pokud by došlo ke zmíněné situaci, tak by i v tomto případě stačilo přepsat vnitřek metody (parse_str, ksort, http_build_query).

LesTR:

Podle mě jsou oba způsoby špatné, protože tím vystrkuji vnitřní implementaci mimo API. Co má venkovní kód zajímat, co je potřeba zavolat, když chci něco nakoupit?
Vytvořil bych třídu, která bude mít metodu "nakup" a parametry potřebné k nákupu. To co je potřeba provést při nákupu bych schoval právě do ní, případně ještě lépe do nějakého adaptéru, to už ale záleží na dalších aspektech.

Pokud se jedná o vnitřní implementaci, tak bych použil jistě http_build_query, což ale okolní kód nezajímá a až se poskytovatel rozhodne používat nějaké sofistikovanější API např. soap, tak jen upravím vnitřní implementaci a ničeho kolem se to nedotkne.

ikona David Grudl:

První se lépe používá? Ehm, to snad ne.

metodaPole(array(
    "jmeno" => $jmeno,
    "pozice" => 1,
    "typ" => "nakup",
    "cena" => isset($cena) ? $cena : NULL,
));

ikona Jakub Vrána OpenID:

Bohužel nastává i situace, kdy je potřeba na základě nějaké podmínky nastavit buď jeden klíč nebo druhý. S řetězcem tedy ($dlouhaPodminka ? "a=1" : "b=1"), s polem jen s dočasnou proměnnou nebo krkolomně.

ikona David Grudl:

Tak bych napsal

metodaPole(array(
    "jmeno" => $jmeno,
    "pozice" => 1,
    $dlouhaPodminka ? "a" : "b"=>1,
));

ikona Jakub Vrána OpenID:

Zvolil jsem špatný příklad, ve skutečnosti to je ($dlouhaPodminka ? "a=1" : "b=2").

ikona David Grudl:

Tak potom:

metodaPole(array(
    "jmeno" => $jmeno,
    "pozice" => 1,
) + ($dlouhaPodminka ? array("a" => 1) : array("b"=>2))
);

Šachování s řetězci a urlencode mi připadá tak špatné, že to nemůže omluvit ani o pár znaků kratší zápis. Nehledě na to, že PHP nemá spolehlivou fci na parsování těchto řetězců.

ikona Jakub Vrána OpenID:

Ano, to je ten krkolomný zápis. Osobně bych použil dočasnou proměnnou, jak je naznačeno v článku.

Já vím, že je pole čistší, ale musí se toho víc napsat a zpracovat jak na straně knihovny, tak na straně kódu. A to je smutné – ideálně by ten čistý způsob měl být i co nejpříjemnější na použití.

ikona David Grudl:

No čistší, nečistší... spíš bych řekl normální a prasácké. Vždyť se na to podívej v rámci celého životního cyklu aplikace. Vstup zvenčí se musí rozebrat a ošetřit, má tohle dělat každá metodaPole? Co se může stát, pokud zapomenu zavolat urlencode nebo napsat ampersand? Vznikne těžko odhalitelná chyba. Nehledě na to, že je docela sporné, který zápis je kratší.

PHX:

Osobne to resim tak, ze tu hodnotu, ktera byva cesteji v poli tam proste dam a v pripade, ze tam byt nema ji proste unset().

optik:

D.G. má pravdu, Ty preferuješ návrhově špatné řešení kvůli tomu, že PHP má dost nešťastnou syntaxy pro práci s polem. Vždyť to se o PHP dávno ví, že jeho syntaxe je strašně ukecená, neefektivní a i ta implementace práce s poli je dost hrůza.

ikona tiso:

Tiež preferujem pole, lepšie sa mi s ním pracuje ako so stringom. Sem-tam použijem nejaký unset, väčšinou nastavujem pár parametrov. A následne to jednou funkciou prevediem na query string... Ale ideálne riešenie to nie je. Kam z toho von?

Tomáš Fejfar:

Tu první metodu snad nemůžeš myslet vážně. Co když bude potřeba ty partametry validovat? Co když některý bude moct být jen 1|2 to budeš všechny ty URL přepisovat a kontrolovat předem? Nebo je budeš "snadno"  parsovat a kontrolovat? Fuj.

ikona Jakub Vrána OpenID:

Ale tady žádné tvoje "co když" neplatí. Webová služba přijímá parametry a když se jí nelíbí, tak skončí chybou. O validaci se tedy stará sama a není potřeba tuto práci duplikovat v API. Byla by to zbytečná práce, která by vedla jen k tomu, že by API vůči službě zastarávalo.

API v tomto případě nemusí (a nemělo by) validovat ani existenci volaných metod. Pokud by to dělalo, opět by to byla samoúčelná a kontraproduktivní práce.

První způsob má samozřejmě zásadní nevýhodu, ale ty jsi ji nevystihl. Nicméně jsi to alespoň nahradil agresivním tónem.

ikona Pavel Stehule:

Pokud zrovna nepisu jednoucelovy prototyp, pak kazda validace, asertace je vitana - zvlast kdyz volam cizi kod - cizi sluzbu, o jejiz kvalite nevim nic. Navic casna validace muze v pripade web service zrychlit odezvu aplikace.

Nerikam, ze je nutne neustale opakovat kontroly vstupu - nicmene nemel bych zapominat na asertaci - a nikdy bych se nemel absolutne spolehat na kvalitu ciziho kodu.

ikona Jakub Vrána OpenID:

Pro jistotu ještě jednou popíšu situaci: Máme dejme tomu sto funkcí webové služby, každá z nich má průměrně deset parametrů s rozličnými omezeními. Funkce i parametry v čase přibývají, řekněme co týden. Když se webové službě pošle neplatná funkce nebo parametr, tak skončí chybou.

Opravdu navrhuješ vzít stávající API, udělat na funkce a parametry validace a každý týden to upravovat? Já se ptám – jaký to bude mít přínos?

ikona Pavel Stehule:

Prinosem je robustnejsi kod. Zvlast v situaci, kterou popisujes. Pokud budu mit intenzivne vyvijenou web servisu, meni se parametry a funkce kazdy tyden, tak se tezko mohu spolehnout, ze bude mit kvalitne osetrene vstupy. Navic se nemohu spolehnout ani na stabilitu chybovych hlaseni - tudiz muze se mi stat, ze pri pozdejsim parsovani chyby, tak abych zjistil pricinu, narazim. Navic - misto toho, abych nestabilni API izoloval od zbytku kodu, tak jej rozsirim po cele aplikaci.

Kde mas zaruceno, ze kdyz posles neplatny parametr, tak sluzba skonci chybou? U ciziho kodu. To ti preci nikdo negarantuje na 100%.

ikona Jakub Vrána OpenID:

Z mého pohledu je tebou navrhovaný přístup anti-robustní. Jakákoliv změna ho rozbije.

Argumentovat tím, že ošetření je u cizího kódu, takže se mu nedá věřit, je absurdní. Dá se věřit dokumentaci, která je z cizího kódu nejspíš vygenerovaná a podle které budeš psát svojí validaci? Podle stejné logiky ne.

ikona Pavel Stehule:

Kdyz nepouziji primo validaci, tak alespon asertaci. Ano - existuji duveryhodne knihovny. Ale alespon pro mne, duveryhodne knihovny nejsou ty, ktere tyden co tyden zmeni interface.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.