Actualmente existem várias abordagens que podemos usar para desenvolver serviços. Destacam-se duas:
1) Code-First Development: consiste em escrever o código de que vamos necessitar para implementar o serviço e/ou o cliente em primeiro lugar, e só depois de termos o código é que produzimos o WSDL do serviço. Regra geral em .NET e Java, este WSDL pode ser automaticamente produzido pelo framework a partir do código que escrevemos.
2) Contract-First Development: temos necessariamente de identificar em primeiro lugar os interfaces que vamos usar, antes de iniciar a escrita do código. Depois desta fase de identificação das operações (métodos) e respectivos parâmetros, estamos em condições de proceder à geração automática do código, tanto do serviço como do cliente, a partir do WSDL que produzimos (e é por isso que tem o nome de "Contract-First").
Tanto uma técnica de desenvolvimento como a outra possuem vantagens e desvantagens geralmente conhecidas, pelo que aqui fica apenas um resumo:
O Code-First favorece mais o Rapid Application Development do que o Contract-First. O Contract-First é geralmente mais moroso e exigente em relação a conhecimentos diversos, com destaque para XML Namespaces, XML Schemas e WSDL. Por outro lado, usando Contract-First, os serviços que produzimos tendem a ser potencialmente mais interoperáveis do que no Code-First. A razão de ser desta situação prende-se com o facto de que em Contract-First nos focamos mais nos interfaces que estamos a definir, e não nas potencialidades específicas de uma linguagem.
Nas SOA, um objectivo primário é a integração de sistemas garantindo a interoperabilidade dos serviços existentes, pelo que o Contract-First Development se apresenta à partida como a melhor opção. Isto significa que se começamos em primeiro lugar a pensar no XML Schema que vamos usar para definir um serviço, nos data types e na estruturação dos tipos complexos que vamos usar nesse schema, então estaremos no bom caminho para assegurar a interoperabilidade do nosso serviço.
Para ilustrar estas ideias, vou usar dois exemplos:
1) É necessário desenvolver um serviço que contém uma operação que aceita uma data-hora. Como implementar? Bom, se usarmos .NET, a primeira tentação é escrever um método que tem um DateTime como parâmetro, até porque em XML Schemas existe um dateTime. Tudo bem à partida, a operação fica a funcionar e por isso o serviço vai para produção. No entanto, algum tempo depois, somos informados que o nosso serviço vai necessitar de servir um parceiro da nossa organização que, por acaso, usa Java. Não há problema porque o cliente Java vai serializar sem problemas um DateTime e enviá-lo para o nosso serviço em .NET, certo? Ou haverá?
Resposta: podemos vir a ter problemas, porque ao contrário do .NET, em Java podemos atribuir o valor null a um objecto do tipo Date. Isto quer dizer que se o cliente Java enviar um Date com o valor null para o nosso serviço, o .NET não vai conseguir deserializar o pedido porque não tem maneira de o representar. Podem encontrar 3 possíveis workarounds para este problema recomendados pelo Simon Guest, aqui. Pessoalmente, também defendo a representação do dateTime numa string, desde que próxima do formato ISO 8601, e validada por uma expressão regular que conste no XML Schema do WSDL do serviço.
2) Fomos informados de que o nosso serviço necessita de mais uma operação, que terá que devolver uma lista de empregados que se encontra numa tabela da base de dados. Como existe já uma aplicação por camadas, somos levados a pensar que nos basta identificar qual o método do Data Layer que serve listas de empregados ao Business e só teremos que fazer um pequeno wrapper dessa funcionalidade, encapsulando-a na operação que nos foi solicitada. Neste processo, reparamos que o método em causa devolve um DataSet, mas como sabemos que o .NET Framework assegura a serialização automática de DataSets em XML, à partida não vamos ter qualquer problema de interoperabilidade... ou não?
Resposta: o DataSet é um objecto que só existe em .NET e apesar de ser serializável para XML, tem diversos problemas relacionados com a sua descrição em termos de XML Schemas e não é aconselhado o seu uso em parãmetros de serviços, a não ser nos casos em que conseguimos garantir que nunca vamos ter outros clientes que não sejam em .NET. Para uma descrição mais detalhada dos problemas de interoperabilidade do DataSet e uma possível solução, sugiro a leitura deste artigo do Aaron Skonnard.
Na minha opinião, em termos de desenvolvimento de serviços interoperáveis, faz de facto mais sentido desenvolvermos segundo as técnicas de Contract-First Development. No entanto, mesmo quando lidando directamente com XML Schemas em primeiro lugar, é aconselhável sermos bastante comedidos na utilização dos tipos disponíveis neste standard, sob pena de sacrificarmos a interoperabilidade dos nossos serviços, porque vamos caír no mesmo tipo de problemas do Code-First.
Note-se que temos um potencial problema de interoperabilidade sempre que um toolkit de SOAP tem que comunicar com outra implementação. Isto quer dizer que, mesmo dentro da mesma plataforma (Java, por exemplo), podem haver problemas se os toolkits de SOAP utilizados por clientes e servidores Java-based forem diferentes. E é claro, assegurar a interoperabilidade entre plataformas distintas como Perl, PHP, C/C++ ou Python restringe-nos ainda mais o leque de possibilidades.
Dito isto, e embora pudesse parecer exagerada (mas espero que não, depois do que ficou exposto) aqui fica a minha recomendação para um mínimo denominador comum que garante a interoperabilidade dos serviços:
1) Usar apenas os tipos primitivos de XML Schemas string e boolean;
2) Definir todos os tipos complexos (uma Factura ou um Cliente são exemplos de tipos complexos) usando para a sua composição apenas os tipos simples referidos em 1);
3) Sempre que possível, validar todos os tipos contra a sua XML Schema restriction (expressão regular, minValue/maxValue, etc.). Esta validação é altamente recomendada para alguns dos tipos primitivos não contemplados em 1). Por exemplo: nonPositiveInteger, double, float, dateTime. "Obrigatório", para os tipos complexos como: EMail, Invoice, Payment, etc.
Para uma boa discussão deste tópico (incluindo alguns workarounds com .NET 2.0 que nos permitem estender a lista que referi aos tipos dateTime e int), sugiro a leitura deste post de Anil John.
Qual é a vossa opinião/experiência?