Best Practices-First Development

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?



Published Sunday, July 23, 2006 2:07 AM by António Cruz
Filed under , , , ,

Comments

 

Paulo Morgado said:

Não estarás a fazer alguma confusão entre Contract-First e WSDL-First (ou XML Schema First)?

Referes várias limitações das plataformas e/ou ferramentas que não serão resolvidas por WSDL-First (como o caso das datas vazias/nulas).

Para mim, o WSDL é o contracto escrito num determinado dialecto.

Imagina que precisas que te façam um serviço para consumires. Tu estás a desenvolver em WCF e o serviço vai ser feito em ASP.NET 1.1 ASMX. Se fosse eu, fazia um stub em WCF e enviava o WSDL para quem vai fazer o serviço. Para mim isto é Contract-First e eu não preciso de estar a olhar para o WSDL nem quem vai desenvolver o serviço.

Já agora, não se poderá resolver o problema das datas nulas em .NET com um array e minOccurs=0 e maxOccurs=1?
July 26, 2006 12:02 AM
 

António Cruz said:

Paulo,

Fazer Contract-First Development em SOA implica necessariamente começar pelo contracto, que em Web Services se representa usando WSDL. Por sua vez, criar um WSDL implica também necessariamente produzir o seu XML Schema que define pedidos e respostas, pelo que considero Contract-First e Schema-First a mesma abordagem para quem desenha os interfaces, embora sob designações diferentes.

Em relação ao exemplo de preferires produzir o stub em WCF (Code-First) e enviar de seguida o contracto, compreendo que te pareça mais fácil à partida e que com as ferramentas actualmente disponíveis seja a abordagem generalizada, mas é precisamente por causa dos perigos dessa abordagem que existe Contract-First. Transcrevo aqui dois parágrafos do artigo de Aaron Skonnard (http://msdn.microsoft.com/msdnmag/issues/05/05/ServiceStation/) sobre a justificação de não fazer as coisas da forma que descreveste (embora aplicado ao desenvolvimento de .asmx os conceitos são os mesmos):
"ASMX automatically maps the class and the [WebMethod] signatures to a WSDL contract. The ASMX approach encourages a remote procedure call (RPC ) programming style, where developers are focused more on methods and parameters than XML messages and elements. (...) Experience has shown that services implemented using code-first techniques, like the default ASMX model, are less likely to interoperate cleanly in mixed-language environments. However, the difference with SO is that mixed environments are much more common—practically the norm—making interoperability a much greater design concern. The problem is compounded by the fact there are more programming languages and type systems involved in this new world than before in the component days. Now it's even more important to consider all possible implementation environments during the contract design stage.

Code-first can also complicate versioning because the developer isn't in direct control of the contract, but must rely on the translation layer throughout development."

À partida, o WSDL é de facto "apenas mais um dialecto", mas como sabes, no caso dos Web Service é *o* dialecto que se standardizou para descrever os seus interfaces, bindings, port types e endpoints. Mas não fiques com a ideia de que o mais importante é usarmos todos o mesmo standard e está tudo resolvido. Não é isso que eu pretendo dizer. É importante que usemos o mesmo standard mas mais importante ainda é como o usamos, daí eu sugerir sermos comedidos na utilização de XML Schemas, sempre, mesmo quando fazendo COntract-First.

A abordagem que sugeriste para o DateTime não funciona (já testaste?) porque o facto de dizeres que pode não ocorrer um dateTime na mensagem (minOccurs="0") não resolve o problema de quando ele ocorre sim, mas tem o valor null (xsi:nil).

Desde já grato pelos comentários,

António Cruz
July 26, 2006 10:35 AM
 

Paulo Morgado said:

Eu continua a achar que é uma questão de semântica.

No exemplo que eu dei, estava a definir o contracto da forma que era mais fácil para mim. Não considero Code-First porque o código que fiz foi só para gerar o WSDL. A partir daí fiquei foi com o WSDL e "deitei o código fora".

É a minha opinião sobre isto e não tenho argumentos para contrariar os teus.

Acho que o problema é as plataformas/ferramentas não respeitarem ou implementarem todos os standards (ou estes não são totalmente implementáveis).

Seja qual for a aproximação acho que, nesta fase da tecnologia, o contracto que devemos trocar (mesmo entre plataformas/ferramentas iguais/compatíves) deve ser o WSDL.

O facto do XML ser o novo assembly não quer dizer que o devemos desconhecer (ainda um dia destes tive de explicar porque >0 não é a mesma coisa que >=1).

Quanto à data, pensei com as mãos (escrevi enquanto "pensava2), e saíu asneira.
July 26, 2006 12:18 PM