RESTfulie com C# – O poder do dynamic
Por Luiz Costa em 26/02/10Há 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 DynamicObjectno 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 <Total>.
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?” 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.