Co že je to ten Wave?

Včera jsem měl volnou chvilku, tak jsem se se zájmem podíval na hodinovou přednášku o Google Wave. Takže se cítím být natolik expertem, abych o tom něco napsal.

O co tedy jde? Těžko se to popisuje, je to něco mezi Gmailem, gTalkem, Facebookem, Picasou, Androidem a GoogleDocs. V zásadě se na to můžete dívat jako na nadopovaný gTalk. Základním stavebním prvkem je jedna Vlnka (wave). Je to podobné jednomu diskuznímu vláknu, v němž si můžete povídat s několika lidmi, okamžitě vidíte jejich odpovědi, můžete vytvářet soukromé poddiskuze, zkrátka nic světoborného. Co je převratné jsou způsoby rozšířitelnosti. Diskuze se například mohou účastnit roboti. Jeden robot vám třeba může v reálném čase opravovat pravopis, druhý vaši diskuzi může překládat do Klingonštiny, třetí může poznávat vložené odkazy. Roboty si můžete napsat sami za použití přiloženého API a pustit je třeba na Google App Enginu.

Samozřejmě si také můžete napsat vlastní rozšíření samotné Wave aplikace (Gadgets). Je to něco jako plugin do prohlížeče nebo vlastní Androidí aplikace, akorát v tomto případě běží na serveru (alespoň jsem to tak pochopil). Takže do Vlnky můžete kromě textu vložit i třeba šachovnici a zahrát si šachy.

No a jelikož to má všechno běžet na otevřeném a jednoduchém protokolu, nemělo by být problémem napsat si vlastní server. Dokonce tvrdili, že část kódu zveřejní jako open source. Když to shrnu, tak Wave je rozšířitelný chat. Rozšířitelný tak, že do něm můžete vkládat fotky, mapy, aktivní obsah, hry a v podstatě co skoro všechno co vás napadne. Pro podrobnosti si pusťte tu prezentaci, nudit se u ní rozhodně nebudete.

Dva druhy kódu

Dnes budu zase psát o takové samozřejmosti, že ani nevím jestli s tím mám někoho obtěžovat. Navíc už jsem to před rokem předváděl na jedné konferenci. Možná ale nepatříte mezi těch deset šťastlivců, kteří mě tam viděli, tak to asi přeci jen napíšu. Začněme mojí oblíbenou ukázkou kódu:

public class PureJdbcBankDao implements Bank {
	public void setBalance(String accountNumber, BigDecimal balance) {
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			conn = ConnectionFactory.getConnection();
			ps = conn.prepareStatement("UPDATE ACCOUNT SET BALANCE = ? WHERE ACCOUNT_NUMBER = ?");
			ps.setBigDecimal(1, balance);
			ps.setString(2, accountNumber);
			ps.execute();
		} catch (SQLException e) {
			throw new ApplicationException("JDBC failure", e);
		} finally {
			if (ps != null) {
				try {
					ps.close();
				} catch (SQLException e) {
					// what to do here?
				}
			}
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					// what to do here?
				}
			}
		}

	}

Je to metoda pro zápis do databáze pomocí čistého JDBC. Toto je nádherná ukázka míchání dvou druhů kódu. Jednomu druhu říkám hezky česky „business kód”. To je kód, který je užitečný pro zákazníka. V našem příkladu po nás někdo chce, abychom změnili hodnotu v databázi. To obvykle děláme pomocí SQL příkazu, který vidím jenom na jednom řádku. Zbytek je „infrastrukturní kód”. Tak říkám kódu, který někdo udělat musí, ale zákazníkovi je v podstatě k ničemu. V našem příkladě to jsou všechny řádky kromě jednoho. Ano opravdu, zákazníkovi je naprosto jedno, že potřebuji připojení do databáze, je mu dokonce jedno, že ho musím po sobě zavřít, on po mě chce abych něco zapsal do databáze. Popravdě řečeno, často po mě nechce ani to, chce jen, aby jeho důležitá data byla dobře uložena.

Ale zpátky k věci. Když to přeformuluji tak mohu říci, že business kód je ten, co dělá něco zajímavého pro zákazníka, infrastrukturní kód je ten, který zajímá jen programátora. Samozřejmě potřebujeme oba druhy, je ale důležité si uvědomit který je který a přistupovat k nim rozdílně. Úplně nejdůležitější je držet oba druhy odděleně. Pokud máte JDBC kód v business vrstvě nebo nedej bože v kontroleru, tak vám nepomůže ani svěcená voda. Pokud ho máte schovaný v DAO vrstvě, tak jste v trochu lepší situaci. Ale i tak je obtížné takový moloch testovat a měnit. Představte si, že bych si vzpomněl, že potřebuji transakce. Musel bych upravit všechny metody v DAO vrstvě. To nezní jako dobrý nápad.
Existují i další argumenty proč infrastrukturní kód nepsat. Nejenže je takový kód nečitelný, on je navíc náchylný na chybu. Když náhodou zapomenu zavřít PreparedStatement nemusí to nějaký čas vadit. Po změně databáze ale můžu začít dostávat veselé chyby a může mi trvat pěkných pár dní než najdu jejich zdroj.

Já jsem obecně přesvědčen, že programátoři, kteří píší kód pro zákazníky, by se měli infrastrukturnímu kódu co nejvíce vyhýbat. Myslím si že je mnohem lepší psát kód takto:

public class JdbcBankDao extends SimpleJdbcDaoSupport implements Bank {

	public void setBalance(String accountNumber, BigDecimal balance)
	{
		getSimpleJdbcTemplate().
			update("UPDATE ACCOUNT SET BALANCE = ? WHERE ACCOUNT_NUMBER = ?", balance, accountNumber);
	}
}

Nejen, že rovnou vidím, co daná metoda dělá, já tam ani nemám moc co zkazit. Téměř všechen infrastrukturní kód zmizel. Samozřejmě že tam stále je, ale napsal ho za mě někdo jiný. V tomto případě autoři Springu. Ano mohl jsem si napsat vlastní framework, ale tím bych si moc práce neušetřil. Navíc bych do něj musel přidat podporu transakcí, podporu testování a podobně. Takže bych víc času strávil vývojem frameworku než něčím, co přináší zákazníkovi užitek.

Příklad s JDBC je samozřejmě trošku extrémní, málokdy se nám stane, že bychom motali JDBC kód s business kódem. Často ale vyvíjíme jiné typy infrastruktury a často nás ani nenapadne, že bychom to dělat neměli. Nebo nás to napadne, ale najdeme si nějakou výmluvu. Ze své praxe pamatuji několik případů, které jsem spáchal já nebo některý z mých kolegů.

  1. Asi nejčastější jsou všelijaké implementace vyrovnávací paměti (cache). Znáte to, máte číselníková nebo jiná málo se měnící data v databázi. Místo toho abyste je četli při každém dotazu tak si je přednačtete do mapy a pak je už jen taháte z paměti. Klasické míchání infrastrukturního a business kódu a také klasický kandidát na vlastní framework popřípadě knihovnu. Přitom stačí vhodně použít cache přes AOP (spring-modules nebo AOP cache).
  2. Načítání konfigurace je také oblíbené. S nástupem Springu už se s tím tak často nesetkáte, ale doby kdy si každý implementoval svoji variantu konfigurační knihovny neminuly zas tak dávno. Občas ještě v business kódu tu a tam zahlédnu načítání properties souboru a vytahování příslušné hodnoty.
  3. Logování je další klasický případ. Zde bych udělal výjimku z pravidla a řekl bych, že míchání logování a business kódu není na škodu, někdy může být i užitečné. Logy mohou pěkně zastupovat komentáře. Když ale říkám že logování samotné škodlivé není, psaní si vlastní knihovny na logování už škodlivé je. Myslím si, že zrovna logovacích knihoven máme v Javě dost.
  4. Skoro vždy se setkáme s vlastními utility třídami. Tedy třídami, které usnadňují věci, které v Javě nejsou zrovna jednoduché. Ať už se jedná o práci s časem a datem, kopírování input streamu do output streamu, načítání obsahu souboru do stringu a podobně. Ano, standardní knihovny jsou v tomto ohledu neohrabané, to ale neznamená, že si to musíme psát sami. Jakarta commons, Joda nebo i Spring nám v tomto ohledu rozhodně dokáží pomoci.
  5. Jednou jsem strávil krásný týden implementací skoro transparentního šifrování dat na úrovni Hibernate. Tzn. v Javě se s daty pracovalo normálně, ale i při ukládání a načítání do databáze se data automagicky šifrovala a dešifrovala. Měl jsem z toho projektu docela radost. Tu mi zkazily dvě věci. Týden po tom, co jsem to dopsal jsem zjistil, že už podobné řešení existuje. Druhou vadou na kráse bylo, že kolegové začali přes Hibernate dávkově zpracovávat desetitísíce záznamů a moje geniální knihovna na to nebyla stavěná a dost celé zpracování zpomalovala.
  6. Jsem si jist, že další příklady z praxe si doplníte sami.

Já jsem přesvědčen, že v dnešní době už člověk sežene knihovnu skoro na všechno. Pokud má to štěstí a knihovna mu umožní zbavit se infrastrukturního kódu, měl by ji použít. Pokud štěstí nemá a musí si opravdu napsat infrastrukturní kód, měl by ho důsledně oddělit od business kódu. Ale jak říkával kolega, každý, kdo se rozhodne napsat si vlastní knihovnu, by měl před tím desetkrát oběhnout barák, aby si to dobře rozmyslel. Ono to totiž není o tom ji jen napsat. Ono je potřeba ji zdokumentovat, otestovat, verzovat, donutit kolegy aby se ji naučili, dávat si pozor jestli máme dobře nadefinované API a spoustu dalších věcí.

Podle mě je jen jeden případ, kdy se vyplatí psát si vlastní knihovnu. A to v případě kdy do ní dávám kód specifický pro daného zákazníka. Například nějaké specifické výpočty, které chci sdílet mezi různými moduly, kód pro volání jeho backendu a podobně. Ale i tak je potřeba počítat s tím, že údržba takové knihovny zabere neuvěřitelné množství času a vaši kolegové z jejího používaní nebudou moc nadšení.

Takže abych to shrnul. Ať už píšete cokoliv, je dobré uvědomit si, který typ kódu to je. Pokud se jedná o business kód, tak je vše v pořádku. Pokud zrovna píšete infrastrukturní kód a není to hlavní náplní vaší práce (zdravím chlapce ze Sunu), zamyslete se nad tím, jestli je to opravdu potřeba. Zamyslete se jestli to už náhodou někdo nevyřešil za vás. Já vím, naučit se novou knihovnu je mnohem menší legrace než si ji napsat. Ale připomíná mi to jedno staré rčení, které říkalo něco v tom smyslu, že “Půl roku v laboratoři vás dokáže uchránit od deseti minut v knihovně.”

Spring WS fault detail

Spring WS project provides nice and versatile exception handling tools. But in some scenarios predefined Exception Resolvers are not sufficient. For example if you want to provide additional error info in the soap:fault detail like in this example:


   
   
      
         SOAP-ENV:Client
         Something wrong happened
         
            Error
            BigTrouble
         
      
   

Fortunately it is quite easy to add similar behavior using Spring WS. You can easily extend existing SoapFaultMappingExceptionResolver and customize the fault (please note that EndpointExeption is project specific exception that provides necessary data):

 public class EndpointExceptionResolver extends SoapFaultMappingExceptionResolver {
	private static final QName CODE = new QName("code");
	private static final QName SUB_CODE = new QName("sub-code");

	@Override
	protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
		logger.warn("Exception processed ",ex);
		if (ex instanceof EndpointException) {
			EndpointException ee = (EndpointException) ex;
			SoapFaultDetail detail = fault.addFaultDetail();
			detail.addFaultDetailElement(CODE).addText(ee.getCode());
			detail.addFaultDetailElement(SUB_CODE).addText(ee.getSubCode());
		}
	}
}