RESTfulie com C# - O poder do dynamic
Há um mês lançamos uma versão inicial do projeto Restfulie para C#, projeto que tem ganhado bastante visibilidade. Para deixa-lo tão fácil quanto a versão original em Ruby, utilizamos as novas características dinâmicas da versão 4.0 do C#, alterando a estrutura de objetos em tempo de execução. Algumas pessoas acham esse recurso perigoso demais, outras acham a escolha certa.
Para enxergar o uso da nova palavra chave dynamic
, consideremos um recurso REST que representa um pedido com sua representação em XML como a que segue:
<pedido> <data>26/12/2009 11:40</data> <total>300.00</total> <atom:link rel=”refresh” href=”http://www.caelum.com.br/pedidos/1″ xmlns:atom=”http://www.w3.org/2005/Atom”/> <atom:link rel=”update” href=”http://www.caelum.com.br/pedidos/1″ xmlns:atom=”…”/> <atom:link rel=”pagar” href=”http://www.caelum.com.br/pedidos’/1/pagar” xmlns:atom=”…”/> <atom:link rel=”excluir” href=”http://www.caelum.com.br/pedidos/1″ xmlns:atom=”…”/> <atom:link rel=”obterCliente” href=”http://www.caelum.com.br/pedidos/1/clientexmlns:atom=” xmlns:atom=”…”/> </pedido>
É interessante notar que neste XML temos os dados (data, total) e temos ações que podem ser executadas sobre este recurso. Por exemplo, depois que obtivemos a representação deste recurso, é possível executar algumas ações, descritas através de links no próprio xml: pagar
, excluir
, obterCliente
. Para obter a representação de um recurso no restfulie C# fazemos da seguinte maneira:
dynamic pedido = Restfulie.At(“www.caelum.com.br\\pedidos\\1”).Get();
Repare que o primeiro passo que fazemos ao declarar a variável pedido é ignorar o seu tipo. Ou melhor, dizer explicitamente que ela é dinâmica, através do tipo dynamic. Isso nos trás uma série de vantagens como, por exemplo, simplesmente acessar suas properties:
Console.WriteLine(string.Format(“A data do pedido é: {0}”, pedido.Data)); Console.WriteLine(string.Format(“O valor total do pedido é: {0}”, pedido.Total));
Além de podermos acessar suas properties, podemos também seguir os links que estão disponíveis na representação, utilizando invocações de métodos:
pedido.Pagar(); pedido.Excluir();
dynamic cliente = pedido.obterCliente(); Console.WriteLine(string.Format(“Nome do cliente: {0}”, cliente.Nome));
Isso é bastante poderoso: perceba que do lado cliente não precisamos definir nada, apenas dizer que uma referência é dinâmica. Mas como esta mágica acontece com o C#?
Além de existir a palavra reservada dynamic
existe uma classe DynamicObject no C#. Diferente de Ruby, em C#, nem todos os objetos podem ser modificados em Runtime. Para que seja possível adicionar comportamento dinâmico em a um objeto, precisamos estender a classe DynamicObject
.
Ao estender esta classe, ganhamos a oportunidade de alterar o comportamento do objeto. Quando fizermos então uma invocação a pedido.Total
, o que de fato tem que ocorrer aqui? De acordo com o xml de representação do recurso, devemos obter o valor que está na tag
Para implementar isso utilizamos um recurso bem parecido com o method_missing em Ruby e, de certa forma, como as dynamic proxies do Java (apesar destas precisarem de interfaces explícitas). Vejamos a classe DynamicXmlResource
:
class DynamicXmlResource : DynamicObject { private XElement xmlRepresentation;
public override bool TryGetMember(GetMemberBinder binder, out object result) { //pseudo código que encontra a tag xml de acordo //com o nome da property informada object = XmlRepresentation.FindXMLTagWithName( binder.Name) .ReadTheValue() return result != null; } }
Esta classe estende DynamicObject
e tem um atributo que é a representação do recurso em XML (xmlRepresentation). O mais interessante é o método TryGetMember
que intercepta qualquer invocação a um Get
de uma property e nos permite fazer o que for necessário. No nosso caso, o que é feito é buscar o valor da da property no xml, que nesse caso tem o nome igual ao da tag do xml. Com isso, toda vez que fizermos pedido.Total
, o que está acontecendo é a invocação de TryGetMember
da classe DynamicXmlResource
.
Da mesma forma que existe o método TryGetMember
, existem outros que nos permitem alterar o comportamento do objeto. No nosso projeto, também fizemos o uso do TryInvokeMember
:
class DynamicXmlResource : DynamicObject { private XElement xmlRepresentation;
public override bool TryGetMember(GetMemberBinder binder, out object result) { result = XmlRepresentation.FindXMLTagWithName(binder.Name).ReadTheValue() return result != null; }
public override bool TryInvokeMember(InvokeMemberBinder binder, object\[\] args, out object result) { // pseudo código para pegar o link no xml a partir do nome do método object link = XmlRepresentation.FindRelAttributeWithName(Binder.Name).ReadTheLink(); if (link == null) throw new ArgumentException (string.Format("There is not method defined with name:", binder.Name));
// faz a chamada remota para o servidor através do link. HttpRemoteResponse response = (HttpRemoteResponse) this.InvokeRemoteResource(value.ToString(), binder.Name); return result != null; } }
Este método também é parecido com o method_missing
, mas neste caso ele intercepta a chamada de um método. No caso do restfulie, quando chamamos um método qualquer, o que ele faz é, procurar na representação XML e descobrir o link relacionado com o nome do método. Depois disso é só fazer uma requisição http. Novamente, quando fizermos a invocação pedido.Pagar(), ela será interceptada pelo TryInvokeMember
e executará uma invocação remota para o Restfulie Server.
Utilizando estas características do C# conseguimos alcançar algo muito próximo do que se faz com o Restfulie Ruby, como comparamos a seguir. Em Ruby:
pedido = Restfulie.at(“http://restfulie-test.heroku.com/orders/14”).get puts pedido.customer-name pedido.cancel
E, em C#, temos o código tão sucinto quanto:
dynamic pedido = Restfulie.At(“http://restfulie-test.heroku.com/orders/14.xml”).Get(); Console.WriteLine(pedido.customer\_name); pedido.Cancel();
Uma das consequências da utilização de tipos dinâmicos é que perdemos as vantagens da fase da tipagem estática: não há code complete, e pode ser que a gente erre o nome de um membro, como customer_name
, e só viremos a saber disso em tempo de execução. Nada que testes unitários bem colocados não resolvam.
Como podemos ver, a utilização de tipos dinâmicos facilita bastante a utilização da api do Restfulie, assim como diversos outros frameworks vem tirando vantagem com o auxílio desse recurso.
Reforçando a tendência, o Java também anda nesse caminho, com a JSR 292, que melhora o suporte de linguagens dinâmicas na JVM. Em abril de 2003 Robert C. Martin escreveu o artigo "[As linguagens dinâmicas vão substituir as estáticas?"](http://www.artima.com/weblogs/viewpost.jsp?thread=4639 "Are Dynamic Languages Going to Replace Static Languages?"") e, no final deste artigo, ele faz seguinte pergunta: Estaremos todos nós programando numa linguagem dinamicamente tipada em 2010?. Isso não aconteceu, mas com certeza cada vez mais cenários de utilização estão surgindo, e a tendência é cada vez mais forte.