Jiří Hradil blog

o software


Desatero pro vývoj software

1. Ideální software je takový, který neexistuje.
2. Pokud existuje, ať není vidět.
3. Pokud je vidět, ať v něm pracují jen roboti.
4. Pokud v něm musí pracovat člověk, ať tam tráví minimum času s maximální efektivitou.
5. Minimum času musí být zábava.
6. Časem rozumíme vteřiny, největší jednotkou budiž minuty.
7. Efektivitou budiž chytrost.
8. Software pomáhá, software neotravuje. Ani člověka, ani robota.
9. Pokud software otravuje, nesmí existovat.
10. Pokud nechápeme některé z pravidel, držme se pouze pravidla č. 1 a raději nic nepišme.

Publikoval Jiří Hradil • 08.05.2011 v 18:05 • pod kategorií NezařazenéŽádné komentáře

CZJUG: Ruby on Rails: zapomeňte na Javu

Publikoval Jiří Hradil • 13.03.2011 v 21:03 • pod kategorií NezařazenéŽádné komentáře

Fulltext v Ruby on Rails a Apache Solr

Fulltextové vyhledávání je příkladem technologie, kterou řešíme téměř v každé aplikaci. Požadavky a vize systému určují složitost celého řešení a naším cílem je použít nejjednodušší možné řešení.

Pro jednoduché a zlehka používané aplikace můžeme “fulltext” řešit jednoduchými SQL dotazy typu “SELECT * FROM contacts WHERE name LIKE ‘neco%’”, což není žádná ostuda, pokud to aplikaci a klientovi stačí.

Pokud tvoří fulltext páteř našeho systému, nebo máme požadavky na vyhledávání typu “chceme synonyma”, “váhy”, “vyhledávat v obsahu dokumentů” a nám se nepodaří klienta přesvědčit, že to opravdu nepotřebuje, musíme použít skutečný fulltext, čímž si zajistíme práci na několik týdnů či měsíců. Kvalitní fulltextové vyhledávání je složitý problém, který je vždy o kompromisech.

Nejdříve doporučuji zkusit fulltext, který nabízí přímo relační databáze. Například v PostgreSQL je to modul TSearch, který je od verze 8.3 přímo součástí databáze a nemusí se instalovat externě. TSearch lze nakonfigurovat tak, aby používal české a slovenské slovníky z Open Office, řešil stemování (převod slova na kořen), stoplist, apod. Integrace s databází nám zároveň vyřeší spoustu nepříjemných problémů jako přístupová práva k objektům (chci vyhledat pouze dokumenty, ke kterým má určitý uživatel právo), stabilitu, nezávislost na další vrstvě, testování, apod. Konkrétně s TSearch mám vynikající zkušenosti. Pokud můžete a neočekáváte mnoho, použijte databázový fulltext.

Jestliže potřebujeme vyhledávání dle částí slov, wildcards na začátku či na konci, podrobné váhy, n-gramy, škálování, distribuované vyhledávání, zřejmě musíme použít externí řešení a náš fulltext delegovat na něj. Google je nám tady vhodnou inspirací, ke které se můžeme blížit.

Přestože jsem railsista, jsem také javista, takže znám Apache Lucene. Perfektní a enormně složitá technologie, jejíž ovládnutí sice znamená několik měsíců těžkého studia, ale dokážete z ní těžit několik let. Na vlastní implementaci v Lucene zapomeňme, je to objevování kola a zbytečná práce. Pokud chceme řešení v Javě a používáme Hibernate, pak vřele doporučuji Hibernate Search, který jsem zkusil se skvělými výsledky.

HTTP servletem nad Lucene je Apache Solr, skvělá technologie, která nás odstíní od složité práce se samotným Lucene a s indexem pracuje přes HTTP požadavky. Protože HTTP lze použít snad z každého normálního jazyka, máme řešení, které je nezávislé na použitém programovacím jazyce či frameworku. Samotný Solr je stále docela složitý, ale poskytne nám mnoho. Doporučuji zakoupit knihu [SOLR] a prostudovat ji. Minimálně tím získáte přehled, co je možné a co raději nechceme.

Pro napojení Apache Solr na Ruby on Rails lze použít plugin acts_as_solr a mít super fulltext v aplikaci v řádu minut.

1. Instalace pluginu

script/plugin install git://github.com/mattmatt/acts_as_solr.git

2. Nastartujeme Solr

rake solr:start

Solr by teď měl odpovídat na ttp://localhost:8982/solr/, kde najdeme přehledné admin rozhraní.

3. Integrace do modelu


class Address < ActiveRecord::Base
#z modelu chceme indexovat ulici, město, psč
acts_as_solr :fields => [:street, :town, :zip]
end

Od teď se při každém save adresy provede zápis do Lucene indexu v Solr.

4. Reindex

Pokud máme plnou databázi a chceme reindexovat všechny modely najednou, v console spustíme:

Address.rebuild_solr_index

5. Používání


Address.find_by_solr("Praha")

Solr umí také vyhledávat přes více modelů najednou, používat wildcards, facets, syntax highlighting, našeptávat, apod. Zájemce odkazuji na zdroje pod článkem.

I když plugin acts_as_solr vypadá opravdu jednoduše, je vhodné nastudovat minimálně doporučený [SOLR] a ještě lépe [LUCENE]. Musíme mít na paměti, že pokud si Solr pustíme do infrastruktury, budeme s ním muset pracovat a podporovat ho řadu let, takže je třeba vědět, jak vlastně technologie funguje. Minimálně pro debugování, až jednoho dne fungovat přestane :).

Zdroje:

Publikoval Jiří Hradil • 02.10.2010 v 13:10 • pod kategorií Ruby on RailsŽádné komentáře

Ruby on Rails z pohledu odběratele

V návaznosti na minulý článek Rails and the Enterprise a první otázku “jak byste definovali enterprise ?”.

Pokud budeme uvažovat o “enterprise” jako o “podnikovém” software, tak je otázka postavena “jsou vhodné Ruby on Rails pro používání v podnicích”? Pak je třeba definovat kritéria, která rozhodují o vhodnosti používání technologie v podnikovém prostředí. Uvažujme podnik/společnost, která je příjemcem, nikoli dodavatelem software. Podnik chce:

1. Aby software řešil problémovou domému podniku. Pokud podnik potřebuje dostat jakoukoli logiku na web, nepotřebuje specifika jako např. real-time software a tato logika se dá nacpat do klasického request+response+relační db, jsou Ruby on Rails skvělou volbou. Jako programátoři pomocí Ruby skvěle popíšeme problémovou doménu, můžeme vytvořit DSL (domain specific language) a jazyk si v podstatě ohnout podle sebe. Příjemci software je v podstatě jedno, jaký jazyk je v pozadí. Je na schopnostech poskytovatele/dodavatele dokázat, že použitá technologie prostě “bude fungovat”.

2. Aby software fungoval dostatečně dlouho, ideálně aby přežil (časově) potřeby podniku. Tento požadavek je pro příjemce jasný. Pro poskytovatele to znamená přijmout dostatečně agilní model vývoje, který bude rychle reagovat na změny. Podnik se mění společně se světem okolo něj a neexistuje zadání, které by bylo časově neměnné a bez nutných modifikací. Opět je na schopnostech poskytovatele dokázat, že jeho technologie je časově odolná. Ruby on Rails mají za sebou dostatečně dlouhou historii a pokud stále fungují a jsou nasazovány, pak testem odolnosti prošly. Tady je nutno chápat, že 17 let existence Ruby a 6 let existence Rails jsou v IT tak dlouhé etapy, že neschopná technologie by již dávno zanikla. Takže ano, Ruby on Rails jsou časově odolné a nejsou žádný prázdninový hype.

3. Aby měla technologie “zázemí”. Některé podniky se rády šťourají v referencích na použitou technologii a rády slyší, že existuje tzv. “zázemí velké společnosti”. To připomíná známý vtip “Nikdo ještě neudělal chybu tím, že vybral IBM”. Tento pocit bezpečí je falešný, jak jsme nedávno viděli na příkladu Sunu a Javy. Molochy, které poskytují zázemí nefungují. Firmy platí miliony za SLA, konzultanty, metodiky a procesy velkým korporacím, které jako celek degradovaly do bezpohlavních bytostí, jejichž jediným cílem je dosahovat zisku. Mám z 6-leté historie Kyberie bezpočet příkladů, kdy spoléháni na “velkého” dodavatele nefungovalo. Počínaje HW supportem, SW konzultacemi, návrhy na metodiky vývoje, řešení infrastruktury a konče poskytováním SLA… Nejlepší, co lze dle mého názoru udělat je spolehnout se sám na sebe a příjemci/odběrateli poskytnout vlastní zázemí. Ono “velké zázemí” či zvučné jméno může fungovat jen jako pojistka či reference. Ne jako základní stavební kámen.

4. Aby šel software napojit na existující systémy. Možná dostaneme rozhraní, na které se musíme napojit a je možné, že třeba v Javě to půjde jednodušeji. Tady nelze předjímat, vše záleží na konkrétní situaci. Ovšem i s RESTem, který Ruby on Rails používají, případně s web hooks lze dokázat divy.

5. Aby software stál rozumné peníze. Pokud přijmeme klasický platební model “cena za člověkoden”, tak tady jsou Ruby on Rails v obrovské výhodě. Jak jsem uvedl, v Kyberii jsou Ruby on Rails při vývoji asi 10x rychlejší než stack Java+Spring+Hibernate+Wicket+dalších X špaget. Pokud budeme uvažovat jednoduše linárně, tak oproti konkurenci můžeme poskytnout software 10x levněji.

A to už stojí za to vyzkoušet Ruby on Rails, ne?

Publikoval Jiří Hradil • 05.09.2010 v 23:09 • pod kategorií Ruby on Rails4 komentářů

Rails and the Enterprise

Posílám skvělý článek Rails and the Enterprise, který je povinností pro každého programátora v Ruby on Rails.

Zajímá mě:

  1. jak byste s ohledem na obsah článku definovali “enterprise”?
  2. kde je hranice, od které považujete systém za “velký”?
  3. proč a jak jsou některé jazyky či frameworky vhodnější pro vývoj “velkých” systémů?
  4. kdo či co je autorita, která definuje tuto “vhodnost”?
Publikoval Jiří Hradil • 25.08.2010 v 22:08 • pod kategorií Ruby on RailsŽádné komentáře

Active Record a transakce

Použití transakcí je v Active Record opravdu triviální. Samotná dokumentace k modulu ActiveRecord::Transactions::ClassMethods je jednoduchá a ovládnutelná za 5 minut. Zapomeňme na dlouhá studia románů typu Spring transaction management, zapomeňte na AOP, advisory a jiné ptákoviny. Nepoužíváme technologii pro technologii. Transakce potřebujeme jednoduše proto, aby data v databázi byla v každém okamžiku konzistentní. Pro polévku kolem odkazuji třeba na Wikipedii.

Automatické transakce pro create, save a destroy

Pokud voláme jen jednu metodu create, save nebo destroy, transakce nemusíme řešit, Active Record volání metod obalí transakcí automaticky:

Contact.create(:name=>'Jirka Hradil')

Vygeneruje:

BEGIN
INSERT INTO "contacts" ("name") VALUES('Jirka Hradil') RETURNING "id"
COMMIT

Je to logické - 1 volání create, save nebo destroy buď je nebo není provedeno. Toto volání už nemáme jak kouskovat. Samozřejmě pokud je insert, update nebo delete pouze 1, nebylo by třeba vůbec transakci používat, samostatný sql statement je atomický sám o sobě.

Stejně můžeme uložit více objektů najednou, pokud se ovšem vejdeme do volání 1 metody:

Contact.create(:name=>'Jirka Hradil', :addresses=>[Address.new(:address=>'Pod Valhallou 1')])

Vygeneruje:

BEGIN
INSERT INTO "contacts" ("name") VALUES('Jirka Hradil') RETURNING "id"
INSERT INTO "addresses" ("address", "contact_id") VALUES('Pod Valhallou 1', 4) RETURNING "id"
COMMIT

Ruční transakce přes více metod

Pokud potřebujeme zavolat více metod, které musí být provedeny všechny najednou nebo žádná z nich, obalíme je metodou transaction, která je jak metodou třídy, tak instance každého modelu (třídy jsou v Ruby také objekty):

c = Contact.new(:name=>'Jirka Hradil')
a = Address.new(:address=>'Pod Valhallou 1', :contact=>c)

Contact.transaction do #tady by klidně mohlo být c.transaction nebo Address.transaction
c.save!
a.save!
end

Vygeneruje stejné:

BEGIN
INSERT INTO "contacts" ("name") VALUES('Jirka Hradil') RETURNING "id"
INSERT INTO "addresses" ("address", "contact_id") VALUES('Pod Valhallou 1', 5) RETURNING "id"
COMMIT

Pokud používáme jen jedno databázové spojení (default nastavení Rails), je jedno, ze kterého modelu metodu transaction použijeme, tuto metodu každý model dědí z ActiveRecord::Base, který includuje modul ActiveRecord::Transactions::ClassMethods.

Transakce napříč databázovými spojeními

Tuto možnost jsem nikdy nezkoušel, ale dle dokumentace stačí vnořit volání metod transaction do sebe napříč modely, které jsou ukládány do různých databází:

c = Contact.new(:name=>'Jirka Hradil')
a = Address.new(:address=>'Pod Valhallou 1', :contact=>c)

Contact.transaction do
Address.transaction do
c.save!
a.save!
end
end

Plně distribuované transakce napříč různými databázemi Active Record nepodporuje. Já osobně jsem tohle nikdy nepotřeboval a dávám si moc záležet, abych o distribuované transakce ani nezavadil, ale pro někoho jejich absence může být omezením.

A co rollback a commit?

Commit neřešíme. Pokud žádné volání metod uvnitř transakčního bloku nevyhodí výjimku, na konci bloku se provede commit automaticky.

Rollback také nemusíme řešit. Vyvolá se sám, pokud některá z metod vyhodí výjimku.

Na co si dávat pozor při použití save namísto save!

Pokud voláme více metod, které obalíme do transakčního bloku a pro ukládání změn voláme save namísto save!, pak pozor - metoda save (bez vykřičníku) nevyhazuje výjimku a tedy se ani neprovede rollback v případě neuložení objektu (třeba když neprojde validace). Výsledkem je nekonzistentní stav, který je na konci potvrzen “sprostým” commitem:

Contact.transaction do
c.save #nevyhodí výjimku, ale nemusí se uložit do db, pokud např. neprojde validace
a.save #nevyhodí výjimku, ale nemusí se uložit do db, pokud např. neprojde validace
end

Pokud třeba selhala validace u adresy, vygeneruje se jen insert pro kontakt:

BEGIN
INSERT INTO "contacts" ("name") VALUES('Jirka Hradil') RETURNING "id"
COMMIT

…a databázi máme v nekonzistentním stavu. Což je logické, protože jak jsem uvedl, rollback se zavolá jen tehdy, pokud něco v transakčním bloku vyhodí výjimku.

Publikoval Jiří Hradil • 24.08.2010 v 10:08 • pod kategorií Ruby on RailsŽádné komentáře

Active Record a propojení objektů

Propojení objektů je v Active Record velmi jednoduché a pokud znáte ORM, zabere vám pochopení několik minut. Nemá smysl přepisovat napsané a zájemce odkazuji na ActiveRecord::Associations::ClassMethods. Active Record má pro asociace opravdu hodně možností, ale pro základní použití vám bude stačit jen minimum z nich.

Pro jednoduchost si projdeme vztahy one-to-one a one-to-many.

One to one

“Kontakt má jednu adresu” definujeme pomocí metod modulu ActiveRecord::Associations::ClassMethods a to has_one a belongs_to. Netřeba se obávat, nic nemusíme importovat, includovat, requirovat ani nic podobného. Vše potřebné již udělal Active Record. Rozdíl mezi has_one a belongs_to metodami je v tom, že strana, která má belongs_to obsahuje cizí klíč v db tabulce. Teoreticky nemusíme vůbec has_one definovat, ale potom se připravíme o možnost jednoduše objektově dosáhnout na adresu z kontaktu.


class Contact < ActiveRecord::Base
has_one :address
end


class Address < ActiveRecord::Base
belongs_to :contact
end

Databázové tabulky:


CREATE TABLE contacts (
id integer NOT NULL PRIMARY KEY,
name varchar(255)
);


CREATE TABLE addresses (
id integer NOT NULL PRIMARY KEY,
contact_id integer NOT NULL,
address varchar(255)
);

Ukládáme:

c = Contact.new(:name=>'Jirka Hradil', :address=>Address.new(:address=>'Valhalla'))
c.save

Tento kód nám vygeneruje potřebné inserty:

BEGIN
INSERT INTO "contacts" ("name") VALUES('Jirka Hradil') RETURNING "id"
INSERT INTO "addresses" ("address", "contact_id") VALUES('Valhalla', 1) RETURNING "id"
COMMIT

Všimněte si automatické transakce okolo obou insertů. Vzpomínáte si, že bychom je někde definovali? Ne, Active Record to za nás udělal automaticky. Na kontaktu jsme zavolali metodu save, což je 1 “atomické” volání metody, tudíž se vše obalilo do 1 transakce.
Samozřejmě bychom mohli nejdřív uložit kontakt a teprve pak adresu, pak bychom si ovšem hranice transakce museli řídit sami. To si ukážeme příště.

A teď vyhledáváme:

c = Contact.first #najdeme první kontakt
puts c.address #a vypíšeme jeho adresu

Což nám generuje selecty:

SELECT * FROM "contacts" LIMIT 1
SELECT * FROM "addresses" WHERE ("addresses".contact_id = 1) LIMIT 1

Tady si všimněte, že nikde nedefinuji eager, lazy ani nic podobného. Active Record standardně používá lazy inicializaci a prostě pokud mu chybí adresa, tak si ji při prvním použití dotáhne přes samostatný select. Podle mě v Active Record 2.3.x neexistuje default způsob, jak zajistit, aby se při výběru kontaktu VŽDY joinovala automaticky jedna jeho adresa (lze však použít Contact.find_by_sql a select si napsat dle libosti). Eager inicializace se používají při vztahu has_many a has_and_belongs_to_many a fungují na principu dotažení všech podřízených entit najednou v samostatném subselectu, jak si ukážeme.

One to many

“Kontakt má hodně adres” definujeme pomocí has_many a adresy přepíšeme do množného čísla na addresses.

class Contact < ActiveRecord::Base
has_many :addresses
end

Třída Address zůstává stejná (má jen belongs_to), stejně tak databázové schéma.

Teď uložíme kontakt s více adresami najednou. Všimněte si použití slova addresses jako množného čísla. Adresy jsou rovněž pole, nikoli samostatný objekt:

c = Contact.new(:name=>'Jirka Hradil', :addresses=>[Address.new(:address=>'První adresa'), Address.new(:address=>'Druhá adresa')])
c.save

Vygenerované inserty:

BEGIN
INSERT INTO "contacts" ("name") VALUES('Jirka Hradil') RETURNING "id"
INSERT INTO "addresses" ("address", "contact_id") VALUES('První adresa', 2) RETURNING "id"
INSERT INTO "addresses" ("address", "contact_id") VALUES('Druhá adresa', 2) RETURNING "id
COMMIT

Opět, vše automaticky zabaleno do transakce, protože jsme volali jen 1 “atomické” save.

Vyhledáváme:

c = Contact.first #najdeme první kontakt
puts c.addresses #pole adres

Vygenerované selecty:

SELECT * FROM "contacts" LIMIT 1
SELECT * FROM "addresses" WHERE ("addresses".contact_id = 1)

Lazy vs eager

Active Record je při načítání podřízených objektů velmi chytrý. Zapomeňte na nějakou LazyInitializationException nebo podobné ptákoviny, které vás jen stojí spoustu času. Podřízené objekty se natahují vždy přes samostatný select.

Contact.all.each {|c| puts c.addresses} # vypíšeme si všechny adresy

Generované selecty:

SELECT * FROM "contacts"
SELECT * FROM "addresses" WHERE ("addresses".contact_id = 1)
SELECT * FROM "addresses" WHERE ("addresses".contact_id = 2)

…což nám vede ke známému “N+1″ select problému - máme 2 adresy a udělají se celkem 3 dotazy.
Eliminace je jednoduchá pomocí atributu :include

Contact.all(:include=>:addresses).each {|c| puts c.addresses}

Pak se selecty transformují do 1+1, kdy je jeden select na všechny kontakty a další select na všechny adresy kontaktů, načtených v předchozím selectu:

SELECT * FROM "contacts"
SELECT "addresses".* FROM "addresses" WHERE ("addresses".contact_id IN (1,2))

Jednoduché a efektivní.

Publikoval Jiří Hradil • 15.08.2010 v 13:08 • pod kategorií Ruby on Rails5 komentářů

Active Record a automatické findery

Jako další pěknou vlastnost Active Record vypíchnu automatické findery.

Zděděním modelu (Ruby třídy, která reprezentuje “business object”) od ActiveRecord::Base získáváme automatické findery na všechny atributy, načtené reflexí z databázové tabulky i včetně jejich kombinací.

Příklad:

Stejně jako v minulém příspěvku použijeme třídu Contact:

class Contact < ActiveRecord::Base
#to je vsechno
end

A její tabulku contacts:

CREATE TABLE contacts (
id integer NOT NULL PRIMARY KEY,
name varchar(255),
address varchar(255)
);

Atribut id je default primární klíč (ve třídě jsme tohle nikde neurčili, je to default chování Active Record). Pak můžeme kontakt najít jednoduše dle id:

c = Contact.find(1) #najde kontakt s id 1

Metodu find třídy Contact jsme nikde nenapsali, je zděděna z ActiveRecord::Base. Zajímavé jsou však další “automatické findery” dle atributů tabulky, aniž bychom je kdekoli definovali:

c = Contact.find_by_name('Jirka') #metodu find_by_name jsem nikde nepsal
c = Contact.find_by_address('Valhalla') #tuhle jsem taky nikde nepsal

Fungují dokonce kombinace atributů. Je jedno v jakém pořadí, všimněte si spojky “and” mezi atributy:

c = Contact.find_by_name_and_address('Jirka', 'Valhalla') #nejdriv jmeno, pak adresa
c = Contact.find_by_address_and_name('Valhalla', 'Jirka') #naopak

Tohle je vývojářův sen. Žádné rozhraní, žádné implementace, při změně atributů v tabulce nemusíme findery měnit či připisovat nové. Změníme pouze schéma tabulky a nové findery a jejich kombinace máme okamžitě k dispozici. Samozřejmě bez redeploy aplikace či restartu serveru.

Vypadá to jako magie, ale Active Record využívá Ruby metodu method_missing, která se zavolá vždy, když metoda v objektu neexistuje.  V tomto případě si method_missing sáhne do schématu tabulku a za běhu přidá novou metodu dle kombinace atributů.

Protože máme logiku i data pěkně pohromadě, pak se nemusíme rozpomínat či studovat, jaké rozhraní používat pro hledání, jaké pro ukládání, co pro změnu či mazání, apod. Prostě potřebuju pracovat s kontaktem, tak použiju třídu Contact či některou z jejich instancí.

Příklad:

c1 = Contact.new(:name=>'Hradil')
c1.save #ulozime novy kontakt
c1.name = 'Jirka Hradil' #zmenime jmeno
c1.save #ulozime zmeny, provede se UPDATE, protoze zaznam uz byl persistovan a ma id
c2 = Contact.find_by_name('Jirka Hradil') #najdeme si kontakt
c2.delete #smazeme kontakt, provede se DELETE

Zkuste si to a uvidíte, jak neradi se budete vracet k Hibernate :).

Publikoval Jiří Hradil • 08.08.2010 v 17:08 • pod kategorií Ruby on RailsŽádné komentáře

Active Record je nejlepší ORM

Při vývoji webových aplikací pomocí Ruby on Rails se okamžitě setkáme s potřebou ukládat objekty do databáze. Ruby on Rails používají Active Record, což je první ORM, které je opravdu radost používat. Ve srovnání třeba s Toplinkem nebo Hibernate mě nijak nebrzí a neuvěřitelně urychluje vývoj.

Za pozornost stojí:

Konvence před konfigurací (Convention over Configuration)

A to narozdíl třeba od Springu bez keců. Žádné XML, žádné mapování databázových atributů do pofidérních XML objektů či anotací, nic. Stačí Ruby business objekt podědit od třídy ActiveRecord::Base Mapování se bere rovnou z databázové tabulky a v Ruby objektu o něm není ani zmínka.

Příklad:

Třída

class Contact < ActiveRecord::Base
#to je vsechno
end

…si všechny atributy včetně datových typů načte dle databázové tabulky contacts:

CREATE TABLE contacts (
id integer NOT NULL,
name varchar(255),
address varchar(255)
);

Protože se používá CoC, nemusíme mít nikde žádný soubor, který definuje, že třída Contact se má podívat do contacts, spojení je definováno automaticky (Contact se automaticky převede na malá písmena a dá se do množného čísla).

Protože je použita reflexe dle struktury tabulky, ve třídě Contact máme opět automaticky k dispozici gettery a settery na všechny atributy z tabulky, takže můžeme klidně hned použít:

c=Contact.new
c.name='Jirka Hradil'
c.address='Valhalla'

nebo ještě rychlejší:

c = Contact.new(:name=>'Jirka Hradil', :address=>'Valhalla')

Tohle je, vážení kolegové, jednoduchost v ryzí formě. Napadá někoho, jak to udělat ještě líp?

Data a logika pohromadě

Klasický Java patternista s tímhle bude mít asi problém. Dobré je přece mít servisní vrstvu, pod ní DAO vrstvu, logika je v servisní vrstvě striktně oddělená od DAO, vše pro jistotu přes rozhraní, pak implementovat… Proč? Pokud byste někdy potřebovali změnit implementaci, tak “jen” implementujete rozhraní. Já osobně jsem si ale vždy vystačil pouze s jedinou implementací a tyto patterny mě jen zdržovaly. ActiveRecord::Base nám do třídy přidá rovnou instanční metody pro uložení. Takže jakmile máme vytvořen náš kontakt, ukládáme:

c.save #to je vsechno

Tímto se vygeneruje klasický INSERT INTO… a záznam je persistován. Žádný service locator, žádné vytváření session, nic. Pokud potřebujeme ukládat třeba více objektů najednou nebo dát před ukládání nějakou logiku, vytvoříme si vlastní metodu a v ní si uděláme, co je třeba.

YAML místo XML či properties souborů

YAML je formát pro serializaci či definici dat a v Rails se používá pro konfigurační soubory. Někde přece jen musí být napojení Rails aplikace do databáze definováno a tímto souborem je database.yml:

...
development:
adapter: postgresql
encoding: unicode
database: book_development
pool: 5
username: uzivatel
password: heslo
...

To je v kostce veškeré napojení Active Record na konkrétní databázi a všechna potřebná konfigurace. Opět - jde to jednodušeji?

V některém z dalších článků se zmíním o tom, jak je to s transakcemi, asociacemi a s lazy vs eager.

Publikoval Jiří Hradil • 03.08.2010 v 22:08 • pod kategorií Ruby on RailsŽádné komentáře

Java vs Ruby on Rails - cesta tam a zase zpátky

Historie

Než se dostanu k jádru věci, dovolte mi malou cestu časem.

Někdy kolem roku 1999 jsem se jako první jazyk začal učit PHP. Zadáním bylo vytvoření interního web systému pro hodnocení zaměstnanců. Protože jsem se v jazycích nevyznal, vzal jsem první knihu, která mi přišla pod ruku - “ PHP - Hypertextový preprocesor” od Jirku Koska. Už tehdy mě překvapila neuvěřitelná rychlost vývoje. Za týden jsem zpatlal první verzi, napojenou na MySQL databázi. Žádné šablony, žádné navrhové vzory, žádné transakce, žádná 3-vrstvá architektura. Logika pěkně zmatlaná s HTML, pár stránek. Autorizace spočívala ve vsunutí kouzelného atributu do HTML requestu s hodnotou 1, což bylo dle mé naivní představy dostatečné.

Jirka Kosek mi pak stačil pro další 3 roky, abych se uživil jako PHP programátor. Postupně jsem se naučil, že je dobré oddělit logiku od aplikační vrstvy, že existuje něco jako “normální forma”, “sql inject”, “databázové transakce”, apod. Měl jsem však pocit, že musí existovat něco “jednoduššího”, více výkonného, standardního. Tak jsem se dostal k Javě, která byla v té době (kolem roku 2002) opravdu moc sexy. Pomocí Netbeans bylo docela rychlé napsat jednoduchou aplikaci ve Swingu přes GUI editor. Co na tom, že metody v kódu byly zamknuté, nedalo se do nich zasáhnout ručně, třída byla pořád delší a Netbeans byly opravdu pomalé. Psal jsem v Javě, takže jsem byl na špičce, to bylo jasné ;).

Od Swingu jsem se chtěl dostat k Java web aplikacím. A tady jsem poprvé narazil. Napsat Java web aplikaci nebylo vůbec jednoduché. Najednou jsem toho měl umět opravdu dost. JSP, JSTL, EJB, JDBC. Samé stupidní zkratky a co bylo nejhorší - pořad přibývaly. Jen jsem se začal zhruba orientovat, co je vlastně JSP, tak přišlo JSTL se sdělením “jsi idiot, že píšeš v JSP, použij mě”. Jen co jsem zkusil JDBC, dočetl jsem se “JDBC je pro lamy, EJB, to je budoucnost”. Ale protože jsem byl čínský pionýr, který si sám dává překážky, aby je mohl zdolávat, do všech těch buzz jsem se zakousl. Když něco dělám, tak to dělám pořádně a tak jsem to vzal a dal včetně certifikace. Alespoň jsem se naučil základy Javy. To mi ovšem na psaní web aplikace nestačilo. Musel jsem projít všemi těmi Sun srandami. Než jsem byl schopen web aplikaci napsat, musel jsem studovat několik měsíců. To mi nevadilo, protože na konci přece čekala nirvána - něco se naučím a pak už budu web aplikace sekat jako Baťa cvičky.

Jenže Java je nevěrná mrcha. Něco se naučíte a hned přijdou další frameworky, které vše řeší lépe a radostněji. Takhle jsem se postupně dostal k ORM (Hibernate, Toplink), dalším  prezentačním frameworkům (JSF, Stripes, Wicket, Spring MVC), must-have nástrojům (Ant, Maven, Spring, JUnit) a dalšímu, občas dobrému balastu (princip nepřetržité integrace, statická analýza kódu, normy psaní zdrojových kódů…). Tohoto období nelituji, určitě mě neustálé studium posunulo dál. Javu jsem také několik let školil a všechny své kolegy (a později zaměstnance či klienty) dostal, jemně nasměroval či dokopal k Sun certifikacím.

Poslední rok jsem však začal pochybovat ohledně celé Java platformy. Nikoli z pohledu nefunkčnosti, spíše z pohledu efektivity vývoje. V Kyberii používáme striktně agilní vývoj, což je komplikované označení pro “nevím-co-bude-zítra-vývoj”. Agilní vývoj (a jeho implementace XP a Scrum) stále považuji za nejefektivnější možný způsob vývoje software. A tady mě Java brzdila.

Největší překážky v Javě při vývoji webových aplikací:

Složitost jazyka

Java není jednoduchý jazyk. Ze začátku to vypadá, že ano, ale pár tříd aplikaci nedělá. Po “hello world” následuje polymorfismus, modifikátory přístupu, kontejnery, knihovny ze základního API, chuťovky jako reflexe či vlákna… Jistě, nemusíme znát všechno, ale většinu ze standardního API bychom znát měli. Obecně trvá vyškolení Java začátečníka na programátora, který projde standardní certifikací SJCP, asi 20 týdnů (zdroj: moje zkušenosti). Po této době ovšem tento programátor umí jen základy jazyka a není schopen psát produkční aplikaci (nezná ORM, JSP, apod…). Celý proces vyškolení nováčka do produkčního programátora trvá asi rok. Zkuste si to přepočítat na peníze. To byl IMHO také důvod, proč byli a jsou Java experti přeplacení. Samotný vývoj software je pak pro klienty zbytečně drahý a pokud budeme předpokládat, že Java je trend, pak trh trpí nedostatkem kvalitního software.

Širokost platformy

Sun nasměruje budoucí vývoj platformy přes JCP, vznikne nějaké JSR a pak se začne implementovat. Z různých implementací si vývojový tým musí vybrat. A právě možnost výběru vývoj komplikuje. Začínáme pochybovat, zda jsme si vybrali správně, porovnáváme, zkoušíme. U nás jsme třeba začali s JPA přes Toplink, ale později jsme migrovali na Hibernate, protože měl prostě větší komunitní základnu. Byla to jistě správná volba, ale stálo nás to nemalé úsilí a spoustu času.

Špagetování

Pokud už vyberete správné frameworky (u nás to byla kombinace Hibernate JPA - Wicket - Maven2 - Spring - Hudson CI - JUnit-Lucene-Hibernate Search), tak celou aplikaci musíte provázat spoustou špaget, aby vám držela pohromadě. Na špagetování se ukázal vhodný Spring, který je pro default použití jednoduchý, ale při bližším koketování neuvěřitelně složitý. Křivka učení je hlavně ze začátku velmi nestrmá.

Kompilace a redeploy

Tohle byla největší překážka. Při každé změně třeba i v prezentační vrstvě jsme museli aplikaci redeployovat. Servlet kontejner (Tomcat nebo pro vývoj Jetty) namísto nabušeného aplikačního serveru (Glassfish, apod.) proces dost urychlil, ale stejně jsme museli čekat typicky desítky vteřin, než se po reloadu stránky změna projevila (počet tříd v naší aplikaci šel do několika stovek či tisíc). Zkoušeli jsme i hot-redeploy, Java Rebel, apod. Ale stejně jsme museli čekat. Se slzami v očích jsem vzpomínal na PHP, které sice bylo vedle Javy jako ošlivý nepříbuzný, ale zato pekelně rychlý.

Nepřetržitá integrace a testování

V Javě není na testování ani nepřetržitou integraci žádný standard či implementace, která by proces napříč různě použitými frameworky zjednodušila a urychlila. Všechno jsme museli vybudovat ručně - Maven, Hudson CI, scripty pro naplnění testovací databáze, vyhodnocení, pokrytí testy, posílání mailem, apod. Což jsme zvládli v řádu týdnů, ale samozřejmě jsem se musel ptát - copak to nejde jednodušeji?

Ruby on Rails - zpátky na koleje

A ono to jde. Po několika týdnech experimentování, setkání s Jiřím Fabiánem z JetMinds a vytvoření pilotního projektu jsme na konci roku 2009 z Javy migrovali na Ruby on Rails. Když jsem viděl, jak rychle jsme dokázali vytvořit nový projekt a udržovat klienty spokojené tím, že nemusí na výsledky čekat hodiny či dny, ale vteřiny či minuty, bylo rozhodnuto. Efektivita vývoje je dle našich interních měření asi 10x větší. Vrátili jsme se na začátek. Jen místo PHP teď jedeme v Ruby :).

Ale o tom zase v příštím článku.

Publikoval Jiří Hradil • 26.07.2010 v 16:07 • pod kategorií Ruby on Rails Tags: , Žádné komentáře