Dectetando a desconexão

Um cenário muito comum em serviços, é a capacidade que os mesmos tem de conseguir invocar algum método do lado do cliente, em resposta ou notificação à algum acontecimento que ocorreu. Isso é conhecido no WCF como comunicação duplex, onde você cria um contrato e que o cliente será obrigado a implementá-lo para que, eventualmente, seja notificado de que algo aconteceu. Para maiores detalhes em como implementar isso, consulte esse artigo ou este vídeo.

Para extrair o canal de comunicação entre o serviço e o cliente, utilizamos o método genérico GetCallbackChannel<T> da classe OperationContext. Esse método retorna uma espécie de proxy, que também implementa a interface de callback, especificada através do parâmetro genérico T. Como a ideia é armazená-lo para mais tarde invocar, pode acontecer deste cliente já não estar mais online, e o canal de comunicação já encontra-se em um estado “inválido”.

Se você não se atentar à esse detalhe e, incondicionalmente, invocar o callback, você receberá uma exceção do tipo CommunicationObjectAbortedException, informando que o canal de comunicação com o respectivo cliente foi abortado. Antes de mostrar como dectectar a desconexão, precisamos conhecer um novo tipo fornecido pelo próprio WCF, e que tem um papel extremamente importante dentro deste framework: a interface ICommunicationObject.

Essa interface define um contrato para todos os objetos de comunicação dentro do WCF, gerenciando as conexões que são relizadas através dos métodos Open, Close e Abort (e suas versões assíncronas). Essa interface ainda fornece uma propriedade chamada State, que retorna uma das opções definidas no enumerador CommunicationState. Além disso, ela ainda fornece eventos que são disparados tão logo quando o canal entrar em um estado diferente. Entre os eventos, temos: OpenedClosed e Faulted.

Apesar do método GetCallbackChannel<T> retornar a instância tipo genérico especificado em T, ele pode ser convertido em ICommunicationObject, onde você terá acesso à todos os membros que vimos acima, correspondentes ao cliente que você deseja se comunicar. Tendo acesso a está interface, você tem duas opções para identificar a desconexão. A primeira delas é antes de invocar o callback, avaliar a propriedade State, e se ela ainda estiver aberta, você pode invocar sem maiores problemas. O exemplo abaixo ilustra isso:

ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();

if (((ICommunicationObject)callback).State == CommunicationState.Opened)
    callback.OnCallback(“Algum Parametro”);

A segunda opção consiste em assinar o evento Closed, ou dependendo da situação, o Faulted, que serão disparados quando o canal de comunicação for fechado ou quando ele entrar em um estado falho, respectivamente. Dessa forma você pode ser notificado quando isso acontecer, e tormar alguma decisão em cima disso. O código abaixo ilustra como proceder para assinar o evento Closed:

((ICommunicationObject)callback).Closed += (sender, args) => Console.WriteLine(“Fechou!”);

Esse tipo de técnica é comumente utilizada quando armazenamos do lado do serviço, a referência para um ou vários clientes, como é o caso de aplicações de chat ou algum modelo de publish-subscribe.

Serviços TCP no Silverlight

A cada dia que o passa, o Silverlight tem ganhado cada vez mais espaço nos sites que são construídos ao redor do mundo. Motivo para a Microsoft evoluir essa tecnologia rapidamente, adicionando cada vez mais funcionalidades dentro dele.

Os adeptos ao Silverlight não se limitam à aqueles construtores de sites institucionais, que na maioria das vezes, são criados para campanhas publicitárias e com poucas funcionalidades dinâmicas. Ele também está atingindo as empresas desenvolvem os softwares para uso interno, justamente porque a Microsoft incorpora nele, funcionalidades que permitem o uso em aplicações do tipo LOB (Line of Business).

Uma das novidades mais recentes, que afeta principalmente o uso de aplicações Silverlight em intranets, é que a partir da versão 4.0, teremos a possibilidade de referenciar e consumir serviços construídos em WCF, através do protocolo TCP (net.tcp).

Como disse no parágrafo acima, esse tipo de protocolo é útil em intranets, já que não haverá restrições de eventuais firewalls, e mesmo que tenha, tudo é negociável. É importante dizer, que as aplicações Silverlight estão condicionadas à utilizarem o seguinte intervalo de portas: 4502 à 4534, ou seja, somente conseguirá acessar um serviço WCF exposto através de uma delas.

Uma das principais características do protocolo TCP, é que possibilita a comunicação duplex, ou seja, permite que o serviço se comunique com o cliente, através de callbacks. Isso já era possível no Silverlight, através do Polling Duplex, que nada mais é do que uma especialização do protocolo BasicHttpBinding, mas que simula callbacks em cima do protocolo HTTP.

Outra grande vantagem do protocolo TCP quando comparado com o HTTP, é a performance, e mesmo em aplicações Silverlight, essa vantagem é bastante significativa, e um dos grande responsáveis por isso, é a codificação binária. Neste momento, para usar este protocolo, você deve abrir mão da segurança em nível de transporte, que não é suportado.

Os tipos necessários para acessar um serviço WCF através de TCP, estão contidos no assembly System.ServiceModel.NetTcp.dll, localizado em %Program Files%Microsoft SDKsSilverlightv4.0LibrariesClient. Mas isso não quer dizer que você terá uma classe chamada NetTcpBinding, assim como há no .NET Full. O Silverlight utiliza um CustomBinding, composto com os seguintes bindings elements: BinaryMessageEncodingBindingElement e TcpTransportBindingElement. Ao referenciar o serviço TCP em uma aplicação Silverlight 4.0, a IDE do Visual Studio 2010 já faz as seguintes entradas no respectivo arquivo de configuração:

<configuration>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name=”NetTcpBinding_IContrato”>
                    <binaryMessageEncoding />
                    <tcpTransport
                        maxReceivedMessageSize=”2147483647″
                        maxBufferSize=”2147483647″ />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint
                address=”net.tcp://localhost:4502/srv”
                binding=”customBinding”
                bindingConfiguration=”NetTcpBinding_IContrato”
                contract=”Servico.IContrato”
                name=”NetTcpBinding_IContrato” />
        </client>
    </system.serviceModel>
</configuration>

Ao contrário de outros tipos de comunicação e de aplicações, não basta simplesmente termos o cliente e o serviço funcionando para que eles possam se comunicar. Mesmo através de TCP, este protocolo também está condicionado às políticas de cross-domain.

Para comunicação HTTP, tudo o que precisamos é definir um arquivo XML na aplicação, que permite o acesso. Para suportar a comunicação através de TCP, o Silverlight resolve a questão da restrição de cross-domain de uma forma mais rebuscada. A Microsoft criou uma template de projeto chamada Silverlight TCP Socket Policy, que pode ser acessada a partir das templates Online do Visual Studio 2010 ou através do Visual Studio Gallery, neste endereço.

Essa template dá origem à um projeto do tipo Console, que expõe o arquivo de cross-domain (definido no arquivo SocketPolicy.cs), definindo o intervalo de portas que é permitido que aplicações Silverlight utilizem. Ao rodar as aplicações, certifique-se também de que este projeto esteja rodando, caso contrário, você receberá uma exceção do tipo CommunicationException, informando que você não tem permissões para efetuar o acesso ao serviço, sugerindo que talvez esteja faltando o arquivo de cross-domain.

Conclusão: Apesar de ainda estar em sua versão Beta, o Silverlight 4.0 traz uma série de melhorias e novas funcionalidades, que facilitará cada vez mais a criação de aplicações LOB, e com certeza, uma das principais limitações até então, era o consumo de serviços WCF através do protocolo TCP, que é fator determinante em aplicações deste tipo.

Mensagens Assíncronas com ChannelFactory

Uma das formas que temos para consumir serviços WCF em um cliente qualquer, é utilizando a classe ChannelFactory<TChannel>, assim como eu já mostrei um exemplo neste post. Geralmente utilizamos esta técnica quando optamos por compartilhar os tipos (incluindo a interface que representa o contrato) entre o serviço e o cliente, evitando a publicação do documento WSDL, a reconstrução destes tipos do lado do cliente e, principalmente, a necessidade de efetuar a referência do serviço através da IDE do Visual Studio .NET.

Atenção: Antes de prosseguir a leitura deste post, aconselho que leia os seguintes artigos:

Quando fazemos a referência ao serviço através do Visual Studio, há uma opção chamada “Generate asynchronous operations”, que para cada operação encontrada no serviço, um par de métodos BeginNomeDaOperacao e EndNomeDaOperacao serão criados, e que irão trabalhar em conjunto, para que assim, o cliente consiga invocar a respectiva operação de forma assíncrona. O problema disso é que muitas vezes o contrato não oferece suporte à chamadas assíncronas, o que nos obrigará a criar toda a infraestrutura do lado do cliente para suportar isso. Imagine que temos o seguinte contrato:

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    string FazAlgo(string value);
}

Como não temos a versão assíncrona do método FazAlgo, temos que recorrer à delegates para conseguir efetuar a chamada. Sendo assim, o código do lado do cliente, poderia ficar da seguinte forma:

private ChannelFactory<IContrato> _factory;
private IContrato _proxy;

public Form1()
{
    this._factory =
        new ChannelFactory<IContrato>(
            new NetTcpBinding(),
            “net.tcp://localhost:8722/srv”);

    this._proxy = this._factory.CreateChannel();
}

private void button1_Click(object sender, EventArgs e)
{
    string parametro = “algum valor”;

    Func<string, string> executor = p => this._proxy.FazAlgo(p);

    executor.BeginInvoke(
        parametro,
        result =>
        {
            this.textBox1.Invoke(
                new Action<string>(valor => this.textBox1.Text = valor),
                ((Func<string, string>)result.AsyncState).EndInvoke(result));

        },
        executor);
}

A diferença que vemos no código acima, é que invocamos o método FazAlgo através de um delegate. Ao invés de criarmos delegates a todo momento para uma necessidade específica, podemos recorrer aos delegates expostos pelo .NET Framework Action<> e Func<>. No caso acima, estamos fazendo uso do Func<string, string>, que coincide com a assinatura do método FazAlgo, ou seja, recebe e devolve uma string. No exemplo acima, estamos fazendo o uso de lambda ao invés de explicitamente criar a instância do delegate.

Todos os delegates dão suporte à chamada assíncrono para método que ele mantém a referência, e sendo assim, via BeginInvoke disparamos a execução da operação do serviço, utilizando uma segunda thread, e mantendo a aplicação disponível para outros trabalhos. Neste caso, o método BeginInvoke recebe três parâmetros: o primeiro é o parâmetro de entrada que o serviço recebe; o segundo é um delegate de callback, que será invocado quando o resultado voltar; e finalmente, o terceiro parâmetro é um System.Object que será passado para o callback, e que estará acessível através da propriedade AsyncState, e que no caso, passamos a instância do delegate criado para invocar o método.

Para recuperar o resultado, dentro do callback invocamos o método EndInvoke, que retornará o resultado do serviço, e como na maioria dos casos, precisamos exibir isso na tela. Como o callback sempre é disparado na thread que está executando o método assíncrono, você não pode tocar nos controles, já que eles tem afinidade com a thread de criação deles. Aqui entra em cena o método Invoke, exposto pelo do controle que deseja atualizar, e através dele, determinamos um método para ser executado na mesma thread que o criou, e aqui também utilizando lambda.

Se você tiver acesso ao contrato e poder alterá-lo, então você pode dar suporte ao processamento assíncrono ao mesmo, e grande parte do trabalho que vimos acima, do lado do cliente, será descartado. Para suportar o processamento assíncrono no contrato, temos que criar as versões assíncronas do método, e com isso, o nosso contrato de exemplo ficará da seguinte forma:

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    string FazAlgo(string value);

    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginFazAlgo(string value, AsyncCallback callback, object state);

    string EndFazAlgo(IAsyncResult result);
}

Ao contrário do que vimos anteriormente, ao utilizar essa técnica, você terá o processamento assíncrono tanto do lado do cliente, quanto do lado do serviço. Eu já discuti bastante sobre isso nestes outros artigos. Sendo assim, toda a complexidade da chamada assíncrona será movida para o serviço, que deveremos criar a versão assíncrona do método FazAlgo. O código do lado do cliente ficará relativamente mais simples:

private void button2_Click(object sender, EventArgs e)
{
    string parametro = “algum valor”;

    this._proxy.BeginFazAlgo(
        parametro,
        result =>
        {
            this.textBox1.Invoke(
                new Action<string>(valor => this.textBox1.Text = valor),
                this._proxy.EndFazAlgo(result));
        }, null);
}

Para ajudar no entendimento, você pode baixar o código de exemplo clicando aqui.

Caching via Providers

Assim como o membership, roles, profile, webparts ou health monitoring, a versão 4.0 do ASP.NET também permitirá trabalhar com o caching seguindo essa mesma arquitetura, ou seja, através de provider models. Com isso, teremos uma maior flexibilidade para elegermos o nosso repositório de caching, que nem sempre será a memória do próprio servidor onde a aplicação está hospedada. Provavelmente será através deste recurso que a Microsoft irá acoplar o “Velocity” ao pipeline do ASP.NET.

Para customizar, tudo o que precisamos fazer é criar uma classe que herde da classe abstrata OutputCacheProvider, e sobrescrever os seus métodos autoexplicativos: Add, Get, Remove e Set, assim como é mostrado abaixo.

public class TextCacheProvider : OutputCacheProvider
{
    private static readonly string PATH;

    static TextCacheProvider()
    {
        PATH = HttpContext.Current.Server.MapPath(“~/CachedPages/”);
    }

    public override object Add(string key, object entry, DateTime utcExpiry) { } 

    public override object Get(string key) { }

    public override void Remove(string key) { }

    public override void Set(string key, object entry, DateTime utcExpiry) { }
}

Depois desta classe criada, que seguirá as regras de caching customizadas, precisamos configurá-la para poder ser utilizada, e para isso recorremos ao arquivo Web.config. Tudo o que precisamos fazer para que essa nova classe funcione, é acoplá-la a execução, definindo-a como sendo o provider padrão de caching. O trecho de código abaixo ilustra essa configuração:

<?xml version=”1.0″?>
<configuration>
  <system.web>
    <compilation debug=”true” targetFramework=”4.0″/>
    <caching>
      <outputCache defaultProvider=”TextCacheProvider” enableOutputCache=”true”>
        <providers>
          <clear/>
          <add name=”TextCacheProvider” type=”TextCacheProvider”/>
        </providers>
      </outputCache>
    </caching>
</configuration>

E se desejar escolher, em tempo de execução, um dos providers de acordo com uma condição específica, você poderá fazer isso através do método GetOutputCacheProviderName, que é exposto pela classe HttpApplication (Global.asax), e retorna uma string que representá o provider a ser utilizado.

Compatibilidade dos Providers

Quando utilizamos qualquer uma das funcionalidades que são expostas pelo ASP.NET (membership, roles, profile, webparts ou health monitoring), geralmente recorremos ao SQL Server para armazenar essas informações. E para isso, tudo o que precisamos fazer é executar o utilitário aspnet_regsql.exe, que nos permite informar uma base de dados e as funcionalidades que desejamos instalar.

Uma vez instalado, fazemos todo o desenvolvimento em cima deste banco, e quando chega o momento de instalar a aplicação no servidor, muitas vezes um script com a estrutura da base de dados é gerado, e mais tarde, executado neste servidor que hospedará a aplicação. O problema é que o script somente levará a estrutura e não os dados. Com isso, ao executar a aplicação a partir do servidor, teremos a seguinte mensagem sendo disparada:

The ‘System.Web.Security.SqlMembershipProvider’ requires a database schema compatible with schema version ‘1’.  However, the current database schema is not compatible with this version.  You may need to either install a compatible schema with aspnet_regsql.exe (available in the framework installation directory), or upgrade the provider to a newer version.

Entre as tabelas que o utilitário cria para suportar as funcionalidades, temos a tabela aspnet_SchemaVersions. Essa tabela armazena a versão atual que está instalada, e é uma informação obrigatória, que o runtime do ASP.NET utiliza, e quando ela não estiver presente, o ASP.NET não conseguirá determinar qual versão é e, consequentemente, não rodará, disparando a mensagem que vimos acima.

Uma das opções que temos para resolver isso, é gerar um script com cláusulas INSERT INTO, criando um registro para cada funcionalidade habilitada, assim como podemos notar no exemplo abaixo:

INSERT INTO [dbo].[aspnet_SchemaVersions] ([Feature], [CompatibleSchemaVersion], [IsCurrentVersion]) 
    VALUES (N’common’, N’1′, 1)

INSERT INTO [dbo].[aspnet_SchemaVersions] ([Feature], [CompatibleSchemaVersion], [IsCurrentVersion]) 
    VALUES (N’health monitoring’, N’1′, 1)

INSERT INTO [dbo].[aspnet_SchemaVersions] ([Feature], [CompatibleSchemaVersion], [IsCurrentVersion]) 
    VALUES (N’membership’, N’1′, 1)

INSERT INTO [dbo].[aspnet_SchemaVersions] ([Feature], [CompatibleSchemaVersion], [IsCurrentVersion]) 
    VALUES (N’personalization’, N’1′, 1)

INSERT INTO [dbo].[aspnet_SchemaVersions] ([Feature], [CompatibleSchemaVersion], [IsCurrentVersion]) 
    VALUES (N’profile’, N’1′, 1)

INSERT INTO [dbo].[aspnet_SchemaVersions] ([Feature], [CompatibleSchemaVersion], [IsCurrentVersion]) 
    VALUES (N’role manager’, N’1′, 1)

Mas, o ideal é utilizar a opção -exportonly do utilitário aspnet_regsql.exe. Ao rodar este comando, ele também gerará um script SQL para adicionar ou remover as funcionalidades (e não executará), mas incluindo tudo o que é necessário, inclusive as respectivas inserções na tabela acima mencionada. De posse deste script, tudo que tem a fazer é rodar no servidor de destino.

Problemas em arquivos de dados

Até agora não entendi o motivo, mas repentinamente os arquivos que representam as estruturas de classes do LINQ To SQL e do Entity Framework deixaram de funcionar, ou melhor, o Visual Studio .NET deixou de exibir graficamente a estrutura de classes. O Server Explorer deixou de ser exibido; quando tentava criar um novo arquivo EDMX, o wizard simplesmente desaparecia; e quando tentava criar e/ou carregar um arquivo do LINQ To SQL, a seguinte mensagem era exibida: The operation could not be completed. The custom tool ‘MSLinqToSQLGenerator’ failed.  Could not retrieve the current project.

Depois de algumas pesquisas, cheguei à um blogue que dizia para excluir as sub-chaves que existiam dentro do seguinte path: HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio9.0Packages. Basicamente o que tinha ali é uma chave chamada SkipLoading, que estava definida como “1”. Antes de excluir, eu simplesmente mudei para “0”, e tudo voltou a funcionar.

Granularidade de Serviços

Independentemente de qual tecnologia estamos utilizando, uma das grandes perguntas que nos fazemos ao criar serviços, é o que realmente devemos disponibilizar em cada um deles, que ao meu ver, vai muito além os parâmetros e do resultado que cada operação recebe e/ou retorna. Existe uma série de aspectos que devemos nos atentar ao projetar ou construir um conjunto de serviços, que muitas vezes atenderão as aplicações que rodam dentro dessa mesma companhia.

O primeiro aspecto que temos que verificar é a questão da granularidade dos serviços, que é utilizada para mensurar a profundidade de abstração que foi aplicado. A granularidade pode ser dividida em duas partes, sendo: granularidade fina (fine-grained) e granularidade grossa (coarse-grained), onde granularidade fina determina que precisamos de muitos “grãos”, enquanto na granularidade grossa, teremos poucos “grãos”, bem maiores.

O que eu quero mostrar com o parágrafo acima, é que com a granularidade fina, teremos serviços com poucas operações, mas dividiremos essas operações por vários serviços. Já com a granularidade grossa, isso se inverte, ou seja, teremos poucos serviços, mas cada um deles conterá uma porção bem maior de operações. Cada uma das técnicas tem suas vantagens e desvantagens, e ao meu ver, quando temos uma granularidade fina, temos pequenos “blocos” de funcionalidades bem específicas e muitas vezes independentes, e que ficam bem mais fáceis de serem atualizadas, distribuídas e gerenciadas, mas isso pode se tornar complexo demais para aqueles que consomem os serviços, já que terão que compor e sincronizar suas operações, para que atinja um determinado objetivo. Por outro lado, a granularidade grossa pode tornar os serviços mais auto-suficientes, mas o problema disso é que cada serviço poderá, acidentalmente, fazer muito mais trabalho do que ele realmente deveria, e que muitas vezes precisará recorrer à outros serviços, para que, também, atinjam o seu objetivo, aumentando assim o acoplamento.

Acoplamento é um outro problema que pode ocorrer, que nada mais é do que a – forte – dependência que um serviço tem de outro. Isso infringe um dos princípios do SOA, que diz que os serviços precisam ser autônomos, ou seja, não depender de outros serviços. Mas em algumas situações isso pode ser benéfico, principalmente em um ambiente de composição. Imagine que você queira criar um serviço para resolver um problema maior, com uma complexidade muito grande. Ao invés de todos os consumidores acessarem esses serviços e ficar sob responsabilidade de cada um organizar isso, você poderá criar um serviço para compor essas tarefas que, como dissemos acima, precisarão recorrer à outros serviços.

Esses conceitos que vimos acima não são novidades. Eles já são (ou deveriam ser) aplicados na programação orientada à objetos, exatamente para termos os mesmos benefícios. Essas características não são exclusividades da computação, pois podemos adotar esses mesmos princípios no nosso dia-à-dia.

Outro grande ponto a ser considerado na construção de serviços, é a criação de serviços com interfaces CRUD (Create, Read, Update e Delete). A proposta destes tipos de serviços é permitir, na maioria das vezes, a manipulação de registros dentro de uma determinada base de dados. Nestes casos, o serviço será apenas uma espécie de wrapper para os dados, não fazendo nada além do que as operações básicas que todo banco de dados possui (INSERT, SELECT, UPDATE e DELETE).

Quando você trabalha com uma aplicação data-centric, onde a toda a regra se concentra em manipular a base de dados, talvez esses tipos de serviços sejam úteis. Considere aqui o uso do ADO.NET Data Services, que evitará conhecer e criar toda a estrutura necessária para expor via WCF.

Mas o ideal é não ter isso em mente ao construir serviços. Interfaces (contratos) CRUD induzem à granularidade fina, onde cada serviço será responsável por manipular uma determinada entidade/tabela. Como vimos acima, a granularidade fina em si não é o problema. A questão aqui é que muitas vezes, um cadastro de um cliente não consiste apenas em um INSERT na base de dados, ao contrário, vai muito além disso.

Imagine um novo cliente que deseja abrir uma conta bancária em um determinado banco, com um cartão de crédito vinculado. O processo de cadastro do cliente consistirá em:

  1. Validar os dados, como idade, renda, endereço, etc.;
  2. Consultar outras instituições financeiras para se certificar de que ele é um bom pagador;
  3. Definir o limite que ele terá no cheque especial;
  4. Inserir o cliente na base de dados;
  5. Criar a conta corrente para este cliente;
  6. Efetuar o lançamento da taxa de cadastro/abertura na conta corrente recém criada;
  7. Comunicar com o serviço de cartões de crédito, para que ele gere um novo cartão para este cliente;
  8. Notificar outros departamentos do banco de que uma nova conta foi aberta, para oferecimento de novos produtos.

Como podemos perceber, o cadastro de um novo cliente não consiste apenas em adicioná-lo na base de dados. Há muito mais do que isso. Se modelarmos nossos serviços orientado à dados, eu teria um serviço que manipula os clientes, outro serviço que manipula a conta corrente, outro de notificação e por aí vai. Quanto mais entidades/tabelas você tiver envolvidas em uma mesma tarefa, mais complicado ficará para gerenciar tudo isso, principalmente do ponto de vista daquele que consumirá esses serviços.

O consumo de serviço é um processo caro para se fazer à todo momento, e neste cenário, para efetuar o cadastro de um cliente, eu precisarei chamar, no mínimo, quatro serviços e tudo o que eu precisarei passar para eles, é exatamente os dados do cliente que eu desejo avaliar/cadastrar. Outro ponto importante é com relação ao fluxo de informações. Neste caso, fica sempre sob responsabilidade do cliente que consome esses serviços, configurar a ordem de chamadas, e em um ambiente onde múltiplas aplicações podem incluir clientes, eventualmente uma delas poderá alterar essa ordem, fazendo com que o processo fique em um estado inválido, comprometendo assim a veracidade e consistência das informações. Nada impedirá que uma pessoa maliciosa invoque apenas o serviço de cadastro de cliente diretamente, sem passar pelas políticas de validação necessárias.

Quando temos várias “sub-tarefas” que se juntam para algo maior, em muitos casos queremos garantir a atomicidade, que garantirá que todos os passos sejam efetuados com sucesso, ou tudo falhará. O que garante a atomicidade são as transações, e transações distribuídas são caras, e todas as chamadas para esses serviços devem estar envolvidas dentro dessa transação, que será, também, coordenada pelo cliente, que será o responsável por avaliar se tudo deu certo. Se sim, ele efetivará (Commit), do contrário, irá desfazer (Rollback).

Um segundo cenário que também ilustra isso: você possui clientes e cada um deles possui um flag que determina a situação dele dentro da sua empresa: Ativo, Bloqueado, EmProcessoJuridico, etc. Da mesma forma que vimos antes, alterar situação dele vai muito além de um simples comando de UPDATE na base de dados. Quando eu mover um determinado cliente para a situação de EmProcessoJuridico, eu terei que inserir um item no histórico deste cliente, desativar algumas opções que o mesmo tem site, e alterar a sua situação na tabela do banco de dados. Se movê-lo para Bloqueado, terei que inserir um item no seu histórico, efetuar um lockdown em todas as contas de acesso desse cliente no site, notificar o gerente responsável e, finalmente, efetuar o UPDATE da coluna onde armazeno a situação atual na tabela de clientes.

Como podemos perceber, interfaces CRUD definem os serviços como sendo data-centric, que modelando dessa forma, nós perderemos o contexto de negócio que será executado pelo cliente, tornando bem mais complicado de se entender o processo como um todo. Ao modelar os serviços, o ideal seria pensar em task-centric, ou seja, o serviço fornecerá operações que englobam grande parte do processo, não sendo o consumidor o responsável por isso. Nos dois cenários que vimos acima, teríamos um serviço de cliente, e que me forneceria uma operação para criar um novo cliente dentro do banco (IncluirNovoCliente), outra operação para bloquear o cliente (Bloquear), outro para criar um processo contra este cliente (AbrirProcessoJuridico), que o moverá para a situação EmProcessoJuridico, e assim por diante.

Note que as operações que serão expostas pelo serviço expressarão claramente o negócio, fazendo internamente tudo o que for necessário para atingir o respectivo objetivo. O interessante é que neste caso, não temos o overhead de chamar N serviços, problemas de fluxo e, principalmente, evitando transações distrubuídas.

Claro que poderá haver situações em que, mesmo que você utilize o modelo task-centric, os teus serviços estarão, coincidentemente, alinhados à interfaces CRUD, mas o importante é que isso apenas seja uma coincidência e não uma regra. Isso muitas vezes acontece quando as regras não estão tão aparentes. Por exemplo, se você mantém um cadastro de cidades e permite a alteração delas, provavelmente você poderá ter: 1 – Valinhos e 2 – Campinas. Os clientes cadastrados na sua base de dados e que são de Valinhos guardam o número 1, e os de Campinas, o número 2. Se agora, você diz que Valinhos será o 2 e Campinas o 1, você corromperá todos os clientes que fazem uso dessas cidades. Poderia haver aqui uma regra que, ao efetuar a alteração da cidade (swap), você atualizasse todos os clientes relacionados.

É importante dizer que serviços task-centric, em algum momento, precisarão efetuar operações de CRUD dentro da base de dados. Com isso, podemos criar duas categorias de serviços: Task Service e Entity Service. Task Services são os tipos de serviços que vimos acima, que serão responsáveis por orquestrar toda a regra de validação, fluxo, manipulação e persistência das informações. É neste ponto que entra em cena os Entity Services, que são responsáveis por gerenciar a vida/estado de uma entidade específica, incluindo seus respectivos relacionamentos, tendo esses serviços, uma interface semelhante à interface CRUD.

Os Entity Services levam o nome de uma entidade, como por exemplo: Cliente, ContaCorrente, PoliticasDeValidacao, etc., não fazendo nada além do que o nome diz, ou seja, disponibilizará apenas as informações referentes à respectiva entidade, não conhecendo nada sobre negócios. Já os nomes dos Task Services são voltados para o negócio em si: AdministracaoDeClientes, GestorDeCredito, CobrancaDeTitulos, etc. E ainda, os Task Services poderão utilizar um ou vários Entity Services para executar uma determinada tarefa, atentanto-se sempre aos conceitos que vimos acima, como é o caso do baixo/alto acoplamento e a granularidade.

Tudo o que foi falado aqui poderá, em alguns cenários, não ser a melhor opção, mas utilizar esse tipo de visão para a construção de serviços, ajudará a ter e criar uma representação muito mais consolidada de sua estrutura, e que será relativamente fácil de gerenciar, mas que refletirá, virtualmente, o seu negócio.

Nova versão do método Monitor.Enter

Desde a primeira versão do .NET Framework, temos a keyword lock. Com ela, podemos criar um bloco de código que será acessado por uma única thread de cada vez. Na verdade, depois de compilado, esse bloco é transformado em chamadas para os métodos Enter e Exit, respectivamente, expostos pela classe Monitor (System.Threading), e envolvido por um bloco try/finally. Imagine o seguinte código: 

lock (_meuLock)
{
    //código “protegido”
}

Ao ser compilado, esse código será transformado em:

Monitor.Enter(_meuLock);
try
{
    //código “protegido”
}
finally
{
    Monitor.Exit(_meuLock);
}

O problema disso é que, segundo o Joe Duffy, exceções podem acontecer entre o método Enter (que é responsável por adquirir o lock) e o bloco try; isso dará origem ao que ele chama de “bloqueios orfãos”, já que nunca serão liberados, pois o bloco finally não será disparado.

Para resolver isso, a Microsoft criou uma nova versão (overload) do método Enter, que além do objeto que representa o lock, recebe um parâmetro boleano indicando se o lock foi ou não adquirido com sucesso. O valor retornado por esse parâmetro boleano, será avaliado mais tarde, que determinará se o método Exit correspondente deverá ou não ser invocado. Além dessa mudança, o método Enter passa a ser chamado dentro do bloco try, garantindo assim que o bloco finally seja disparado e, consequentemente, liberando o lock. O mesmo código acima, a partir da versão 4.0 do .NET Framework, passa a ser compilado da seguinte forma:

bool taken = false;
try
{
    Monitor.Enter(_meuLock, ref taken);
    //código protegido
}
finally
{
    if (taken)
        Monitor.Exit(_meuLock);
}

Novas classes para inicialização de objetos

Quando desenvolvemos algum tipo de aplicação ou componente, é muito comum encontrarmos dentro do nosso código, classes que são extremamente custosas, ou melhor, que possuem um grande overhead na inicialização, como por exemplo, efetuam acesso à IO, cálculos complexos, etc. Dependendo da situação, instanciamos essas classes (pagando o alto preço da inicialização) e não utilizamos, já que, eventualmente, o teu sistema não precisará dela naquele momento.

Para melhorar isso, a Microsoft está disponibilizando no .NET Framework 4.0, uma classe genérica chamada Lazy<T> (namespace System). Basicamente, a finalidade desta classe é postergar, ao máximo, a criação do teu objeto, ou seja, isso somente acontecerá quando você realmente precisar dele. Por ser uma classe genérica e o parâmetro T não ter qualquer restrição, você pode definir T como qualquer tipo. Essa classe será um wrapper para o teu objeto custoso, efetuando a criação do mesmo somente quando for requisitado.

Ao instanciar a classe Lazy<T>, você tem algumas opções que variam de acordo com o overload do construtor que utiliza. Em um dos construtores, há um parâmetro boleano, que determina se a inicialização será ou não thread-safe. Se a instância da classe Lazy<T> pode ser acessada por um ambiente multi-threading, então definir este valor como True (que é o padrão), evitará problemas conhecidos, tal como as races conditions. Com isso, a primeira thread que entrar, incializará o objeto, e as threads subsequentes compartilharão o mesmo objeto, que já está criado. Mas se ambiente multi-threading não é o cenário, então definir esse parâmetro como False evitará processamentos extras, que são desnecessários neste caso.

Há também um overload do construtor, que recebe como parâmetro a instância de um delegate do tipo Func<T>. Esse delegate é referido como uma “factory”, ou seja, apontará para um método responsável por criar o objeto quando for solicitado, nos permitindo inicializá-lo de acordo com uma regra específica. Quando esse parâmetro não é informado, a classe Lazy<T> irá instanciar o tipo através do método CreateInstance da classe Activator, obrigando o tipo definido em T, a ter um construtor público sem parâmetros, caso contrário, uma exceção será disparada.

Além dos construtores, essa classe ainda expõe, publicamente, duas propriedades de somente leitura: IsValueCreated e Value. A primeira delas, retorna um valor boleano indicando se o objeto já foi ou não criado. Já a segunda, é a propriedade que utilizamos para extrair o objeto que foir criado e está sendo gerenciado pelo wrapper. É dentro desta propriedade que há toda a regra utilizada para determinar se o objeto já foi criado. E como vimos acima, caso ele ainda não tenha sido, invocará o método privado LazyInitValue, e me retornará a instância. Chamadas subsequentes, da mesma thread ou não, não entrarão mais neste método, reutilizando a instância criada. O código abaixo exibe um exemplo da utilização desta classe:

public class NotaFiscal
{
    public int Codigo { get; set; }
    public DateTime Data { get; set; }

    private Lazy<List<Item>> _itens;

    public NotaFiscal(int codigo)
    {
        this.Codigo = codigo;
        this._itens =
            new Lazy<List<Item>>(() => DataHelper.RecuperarItensDaNotaFiscal(this.Codigo));
    }

    public IEnumerable<Item> Itens
    {
        get
        {
            return this._itens.Value;
        }
    }
}

Como podemos perceber no código acima, a instância da classe representa uma Nota Fiscal. Muitas vezes carregamos a Nota Fiscal completa, incluindo seus respectivos itens, mas nem sempre eles são utilizados. Ao invés de carregar esses itens na criação da Nota Fiscal, iremos postergar essa tarefa, recorrendo a classe Lazy<T>. Como podemos ter vários itens, então o argumento T será definido como List<Item>. No construtor da classe Nota Fiscal, instanciamos a classe Lazy<List<Item>>, definindo em seu construtor, o método responsável por carregar os itens da Nota Fiscal. A propriedade que expõe os itens da Nota Fiscal, quando solicitada, recorre a propriedade Value do objeto _itens, que como vimos acima, é neste momento que o método (factoryRecuperarItensDaNotaFiscal será disparado.

Além da classe Lazy<T>, ainda temos a classe ThreadLocal<T> (namespace System.Threading). Assim como a anterior, não há nenhuma restrição quanto ao argumento T, ou seja, podemos definir qualquer tipo. A finalidade desta classe, é sanar alguns comportamentos de campos estáticos quando utilizados em conjuto com o atributo ThreadStaticAttribute. Ao aplicar esse atributo, cada thread terá a sua própria cópia do valor, mesmo que ele seja declarado como estático (static). O problema que ocorre ao utilizar esse atributo, é quando temos um campo que já é automaticamente inicializado, como por exemplo:

public class Teste
{
    [ThreadStatic]
    public static int Numero = 4;
}

A inicialização de todo membro estático ocorre apenas uma única vez, mesmo quando temos este atributo aplicado. Para ilustrar isso, vamos criar três threads diferentes, onde cada uma delas escreverá o valor do membro Numero:

new Thread(() => Console.WriteLine(Teste.Numero)).Start();
new Thread(() => Console.WriteLine(Teste.Numero)).Start();
new Thread(() => Console.WriteLine(Teste.Numero)).Start();

E o resultado é: 4, 0 e 0. A classe ThreadLocal<T> vai conseguir lidar com isso, ou seja, permitirá especificar um delegate de inicialização, que será invocado sempre que o valor for requisitado por uma nova thread. Ao invés da inicialização acontecer uma única vez, ele sempre rodará quando solicitado, e sempre trazendo a cópia da informação para dentro da thread corrente, não compartilhando o mesmo, assim como já acontecia anteriormente. Com essa nova classe, podemos reescrever o exemplo da seguinte forma:

public class Teste
{
    public static ThreadLocal<int> Numero = new ThreadLocal<int>(() => 4);
}

new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();
new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();
new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();

E agora, como já era de se esperar, temos como resultado: 4, 4 e 4. É importante dizer que se nada for informado no construtor desta classe, ela sempre construirá o tipo com o seu valor padrão.

Para finalizar, temos a classe estática LazyInitializer, que possui apenas um único método público: EnsureInitialized. Neste caso, ao invés de definir todos os membros como Lazy<T>, podemos recorrer a este método para inicializá-los individualmente, quando você achar necessário, sem a necessidade do wrapper. O código abaixo ilustra a utilização desta técnica, mas repare que informamos o objeto que será abastecido e o método (factory) que o construirá.

public IEnumerable<Item> Itens
{
    get
    {
        if (this._itens == null)
            LazyInitializer.EnsureInitialized(ref _itens, () => DataHelper.RecuperarItensDaNotaFiscal(this.Codigo));

        return this._itens;
    }
}

Conclusão: Criar aplicações multi-threading é bem simples, mas o grande problema sempre é a sincronização delas. Classes como essas que vimos aqui, auxilia bastante neste caso, tirando em algum pontos, a responsabilidade do usuário em gerenciar isso, podendo ele se preocupar cada vez mais com as regras de negócio. Essas classes que vimos aqui, estarão disponíveis a partir da versão 4.0 do .NET Framework, que já trará também uma grande API para suportar o desenvolvimento de código paralelo.

Persistência da linha selecionada

Quando temos a paginação e a seleção no GridView habilitadas, há um comportamento estranho. Ao selecionar uma linha em um GridView paginado, o ASP.NET armazena o índice da linha selecionada, e ao mudar de página, a mesma linha (porém outro registro) fica também marcada, e isso muitas vezes gera uma confusão.

Na versão 4.0 do ASP.NET, a Microsoft está adicionando uma propriedade boleana no GridView chamada EnablePersistedSelection. Ao definir como True, ele armazenará as datakeys ao invés do índice da linha no GridView, e com isso, ao mudar de página, nenhuma linha é marcada como selecionada, o que faz mais sentido. Ao voltar para a página que possui aquele registro, conseguimos visualizá-lo como marcado. Por padrão, essa propriedade é definida como False para efeitos de compatibilidade com as versões anteriores.