OIO REST ActiveResource March 31, 2008 02:24 over 2 years ago
Jeg sider dybt nede i en applikation og happy codeslapper. Ca. 10 procent af objekterne er mappede fra relationelle tabeller og resten er indviklede og helt unikke arkitekturer som jeg selv har fundet på og alle de gode argumenter er væk. En maneger nærmer sig forsigtigt og begynder med at rømme sig. Øh,, kan du tilgå en web service? For bare et par år siden ville den forspørgsel betyde en del kode men det rigtige teknologivalg kan gøre arbejdet. Rails 2.0 har ActiveResource som er en wrapper til RESTful services. Denne pakker tillader tilgang til webservices på samme elegante måde som ActiveRecord tillader adgang til persistente data.
Rails er opbygget omkring konventioner og best practice indenfor softwareudvikling, hvilken betyder at der kan være forskelle set i forhold til Roy Fieldings dissertation om REST. Måske vil Davids implementering bliver bedre og bedre i den respekt efter deres møde til railsconf i Berlin 2007.
REST er en arkitektoniske stil som giver succes til world wide web. Formålet med at anvende REST i webservice er at have samme fordele i system til system, menneske til system kommunikation. De mest åbenlyse fordele er interoperabilitet, performance, skalerbarhed og enkelhed.
h3. REST baserede webservices
Jeg har valgt at attakere de nye REST baserede webservices i det Digitale Danmark. OIOREST interface. Dels er det fedt at det offentlige, med Henrik Hvid, seniorkonsulent hos Devoteam og storfortaler for SOA, bla i forbindelse med elektronisk tinglysning, i spidsen for en række eksempler på hvordan offentlige data kan udstilles så de kan tilgås fra forskellige platforme.
Formålet med denne blog er at eksperimentere med Rails “conventions over configuation” ActiveResource og REST design. Hvordan Rails fortolker REST design og hvordan de REST baserede web services i OIO regi er fortolket.
OIO interfacet er super let.
- http://oiorest.dk/danmark/postdistrikter => :all
- http://oiorest.dk/danmark/postdistrikter/3100 => 3100 Hornbæk
- http://oiorest.dk/danmark/postdistrikter?q=rosk => 4000 Roskilde
Men naturligvis gider jeg ikke skulle lave alle de URI, parse resultatet og mappe til de respektive objekter som jeg endelig kan arbejde med. Hvorfor ikke gøre noget objektorienteret og springe direkte til arbejdet. Som det første vil jeg lave en ruby klasse som håndterer alt kommunikation med OIO servicerne.
class Postdistrikt < ActiveResource::Base self.site = 'http://oiorest.dk/danmark/' end
Denne klasse behandler alle funktioner ved servicen postdistrikt på domain http://oiorest.dk med context danmark. Den seriøse læser vil hurtigt opdage er der mangler (er) på klassen definitionen. Her er bare et af mange steder hvor det lyser ud af rails at der er gjort meget for at sproget lækkert og smidigt. Ikke kun når det gælder syntaks men også det rent sproglige. Fx kan rails finde flertals versionen af octopi, nemlig octopus. Det bunder i det faktum at Rails udviklerne har brugt masser af tid på at diskutere detaljer så arbejdet bliver lækkert mht. ental og flertals udtale. Men Rails kender ikke den danske term postdistrikt hverken i ental eller flertal. Før nu.
Inflector.inflections do |inflect| inflect.irregular 'postdistrikt', 'postdistrikter' end
Så er der kun tilbage at kalde services. ActiveResource giver finder metoden find(). Ups, stadig et problem. Rails benytter rails en extention på REST api som er denne notation, protocol://domain/model.[format] Klienten specificer retur formatet enten med content header information eller med extention som fx .json, .js, .yaml, .xml eller standart html. Men OIO bruger ikke en extention endnu. Sikkert fordi de kun understøtter et format, nemlig xml. Så jeg må altså tilrettet ruby klassen til at reagere specielt imod OIO.
Open Ruby, arv eller komposition
Ruby sproget er nemt at tilrette til specielle forhold. Man kan til enhver tid åbne en klasse og tilføre, overrule eller omdøbe metode navnet. I denne løsning er to specielle problemer som må adresseres på to forskellige fremgangmåder. Hele applikationen skal gennem en proxy og de servicekald som rammer OIO skal benytte et URI format uden ententions. Først proxy opsætningen som glæder gennem hele applikationen.
module ActiveResource
class Connection
def http
http = Net::HTTP.new(@site.host, @site.port, 'localhost', 8888)
end
end
end
Det store spørgsmål er om det altid er smart at gribe ind i de basale strukturer som ofte ligger på et lavere niveau. Ofte kræver små tiltag nøje design overvejelser, ikke alle simple tilretninger løses bedst med den største power i ruby.
Lad mig tage eksemplet med at OIO ikke understøtter ”.xml” notationen for at fortælle serveren i hvilet format respons ønskes. I denne situation ville det være nærliggende at benytte open ruby og lave et modul indeholdende en klasse som overrule metoder i den rigtige source. Men dette vil jo så gælde for alle andre, også for dem som rammer andre services end OIO.
Desuden tilfører den ActiveResource klassen transparent adfærd. Mit personlige koncentrations niveau ligger på lige over 26 sekunder ad gange og jeg vil med det samme glemme hvilken speciel adfærd jeg har påført ActiveResource instanser. Jeg behøver eksplicit at udtrykke denne adfærd på en visuel måde i koden.
Den næste fælde er arv. Næste alle kodehacker jeg kender er tilbøjeligt til at lave denne: Fælles funktionalitet er lig en superklasse! Men det er jo ikke sådan den essentielle form for arv ser ud, og igen bliver klassens enlige funktion transparent for brugeren.
module ActiveResource
class OIO < ActiveResource::Base
class << self
## remove .xml from the url
def element_path(id, prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
"#{prefix(prefix_options)}#{collection_name}/#{id}#{query_string(query_options)}"
end
## Remove .xml from the url.
def collection_path(prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
"#{prefix(prefix_options)}#{collection_name}#{query_string(query_options)}"
end
## this method override makes it possible to retrieve arrays of results
def instantiate_collection(collection, prefix_options = {})
if collection.is_a? Array
collection.collect! { |record| instantiate_record(record, prefix_options) }
elsif collection.is_a? Hash
# debugger
if collection.size == 3
value = collection.values[2]
if value.is_a? Array
# Assume the value contains an array of elements to create
return value.collect! { |record| instantiate_record(record, prefix_options) }
elsif value.is_a? Hash
# Assume the value contains a single row result
result = []
result << instantiate_record(value, prefix_options)
else
# puts "#{value.class} " puts "#{value.size} " puts "#{value.inspect} "
raise ArgumentError("Expected a nested Array or Hash but got #{value.inspect}")
end
else
# Assume the result was a single row, so encapsulate it in an array
result = []
result << instantiate_record(collection, prefix_options)
return result
end
elsif
raise ArgumentError, "Expected an Array or Hash, but got #{collection.inspect}"
end
end
end
end
end
Klassen definition herunder giver en klar indikation af Postdistrikt arver fra ActiveResource og OIO mens Blog arver fra ActiveResource og Base, hvilket vil sige at OIO syntaksen kun influere OIO objekter.
Denne kombination af at mixe et Mixin modul og traditionel arv giver et fornuftigt design uden for meget forurening. Men kunne med fordel flytte self.site definitionerne op i superklassen men efter min mening passer de logisk til de enkelte klasser. Det mønster er helt sikkert bibeholdt i Rails pga. klarhed og læsbarhed.
class Postdistrikt < ActiveResource::OIO self.site = 'http://oiorest.dk/danmark/' end class Blog < ActiveResource::Base self.site = 'http://www.someblog.dk/' end class Adresse < ActiveResource::OIO self.site = 'http://oiorest.dk/danmark/' end
Men kan tilføre en masse validering og andet i ActiveResource klasserne men nu skal de bruges. Et mål i REST er at undgå kobling mellem server og klient. Så i teorien kan jeg blot begynde at bruge OIO servicen. REST services er stateless på den måde at der ikke findes en tilstand på serveren som klienten skal bruge ved næste interaktion. Hvis klienten har en tilstand ligger den i URI’erne.
Hvis man ser bort fra sikkerhedsaspekter kan alle services bruges blot med kendskab til REST og API’et. REST forskriver at man ligger URI’er til andre resource i de enkelte services, og på dette område virker OIO fint. Man kan finde en ref og traverser gennnem model objekterne.
Postdistrikt.find(4100)
# GET /danmark/postdistrikter/4100
puts Postdistrikt.find(:all, :params=>{:q=>"ros"} )
# GET /danmark/postdistrikter?q=ros
puts Adresse.find(:all, :params => { :postnr => "1620", :vejnavn => "vesterbrogade", :husnr => "149" })
# GET /danmark/adresser?husnr=149&postnr=1620&vejnavn=vesterbrogade #
Alle service kald her returneres HTTP/1.1 200 OK plus resultat i form af XML. Eneste ting som ikke er cool er at fejlbeskederne både returneres i formateret HTML og HTTP status codes. Ikke særligt hensigtsmæssigt.
HTTP methods
Lad os gøre det lidt vanskeligt. Jeg vil oprette en ny by til mig selv med postnummer og alting. Det har de helt sikkert ikke tænkt på hos OIO. Rails folkene bag ActiveResource har til gengæld tænkt en del over HTTP verbs operationerne GET, PUT, POST, DELETE, HEAD og pakket pænt objektorienteret ind i service kald, ActiveResource instanserne føles som ActiveRecord objekter.
postdistrikt = Postdistrikt.new postdistrikt.nr = 4110 postdistrikt.name = ”Ny Borgmesterby” postdistrikt.save # HTTP/1.1 405 Method Not Allowed
Denne codesnippet skaber et nye postdistrikt. Metoden save fyre en HTTP POST afsted mod serveren. Nej jeg fik ikke lov og det var vel forventeligt. Men faktisk forventede at få en 401 Unauthorized eller 403 Forbidden, men jeg fik en 405 Method Not Allowed, hvilket vil sige at OIO ikke understøtter POST til denne service idet det netop er verbum’et POST som fejlbeskeden henvender sig imod. Hvis jeg har lov til at skabe nye entiteter ville jeg ha modtaget HTTP/1.1 201 Created eller HTTP/1.1 200 OK.
Et godt eksemple med løs kobling på service niveau. Mens jeg skiver denne blog har den jeg skrevet med Finn Jordal som implementer løsningen og han har ændret løsningen til at kunne klare både med og uden .xml ententions. Så jeg kan faktisk fjerne et par metoder i OIO modulet. Det fede her at det kun drejer sig absolutte detaljer da vi jo på forhånd er meget enige om selve protokollen vi benytter til transport(TCP/IP) og kommunikation(http).
NICE.
By Frank Vilhelmsen - 3 tags: architecture rest ruby - Add comment