quinta-feira, 10 de abril de 2014

Desenvolvimento Self-Service por KB

Eu estava lendo um email quando um anúncio me chamou a atenção (coisa rara de acontecer). Era um anúncio sobre o Ironmq. O Ironmq é um serviço de filas, assim como existe um na Amazon (SQS - Simple Queue Service), por exemplo.

Não foi exatamente o Ironmq que me chamou a atenção. Comecei a pensar na evolução de como os sistemas foram/são/serão desenvolvidos. Cada vez mais os diferentes aspectos de um sistema (persistência, segurança, integração, performance, escalabilidade, etc.) estão ficando mais especializados e de melhor qualidade, e novos nichos em desenvolvimento estão se delimitando mais claramente.

Eu realmente acredito que daqui há pouco profissionais de desenvolvimento terão títulos como "Definidor de Métodos de API para Filas" e esse cara vai ter que ter um conhecimento profundo sobre como definir contratos; "Integrador Persistência/API de Serviço" e ela vai ter que saber qual é o melhor jeito de uma API (como serviço) acessar um banco de dados não relacional, por exemplo. Esses dois exemplos são de funções muito técnicas. Aí, a maioria dos desenvolvedores das consultorias de hoje, no futuro, serão basicamente usuários desses serviços -- vai ser mais uma questão de montar os sistemas do que de codificá-los. O desenvolvedor "normal" de hoje, no futuro, vai passar muito mais a configurar do que codificar ("onde vamos persistir os dados dessa solução?", "qual o endereço do autorizador, mesmo?", "descobri ontem um serviço novo pra processamento pesado, eles estão dando 50% de desconto pra quem for usuário do XPTO").

Antigamente, você cultivava, fazia a colheita e preparava sua comida. Hoje, a gente não cultiva mais -- pelo menos, as pessoas do meu círculo de convivência. Alguns preparam e outros nem preparam mais, só sentam, são servidos ou se servem e comem. Eu acho que essa é uma boa analogia do que está acontecendo com sistemas. Desenvolvimento self-service por KB.

Você concorda que o caminho seja esse?

sábado, 15 de fevereiro de 2014

ZeroMQ: Request/Reply com Balanceamento de Carga

Se você já entendeu o básico para fazer um request/reply com dois componentes; está na hora de elaborar um pouco mais o negócio. Vamos criar um request/reply com um balanceador no meio. Na verdade, nós vamos trabalhar imaginando escalar nossa solução horizontalmente com mais de um serviço que responda as requisições, afinal é moda e essa moda é boa ;)

O exemplo a seguir, é baseado nesse trecho do guia do ZeroMQ: http://zguide.zeromq.org/page:all#Shared-Queue-DEALER-and-ROUTER-sockets (figura 16, a segunda desse tópico).

Código


Se você seguiu todo os passos dos posts anteriores ou já conhece como configurar seu projeto com ZeroMQ, ótimo. Se você não sabe do que estou falando, procure pelos posts sobre ZeroMQ que ensinam como incluir o ZeroMQ em seu projeto. Se você esta sem paciência, use o comando seguinte do Package Manager Console: PM> Install-Package clrzmq -pre

Vamos lá, crie 3 projetos do tipo console, .NET 4.5, C#.

O primeiro projeto é o do serviço que realiza a resposta, ou reply, ou servidor. Nós vamos chamá-lo de Worker, porque é o componente que faria o processamento de fato; o trabalho pesado. Eu dei o nome dele de zeromq.lab.worker. Os nomes das variáveis, mensagens, etc. estão em inglês nesse exemplo porque era uma POC antiga e às vezes cismo de aderir ao inglês -- mantive para não ter que traduzir tudo.

Direto ao ponto: use o trecho a seguir para seu Worker:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ZeroMQ;

namespace zeromq.lab.worker
{
 class Worker
 {
  static void Main(string[] args)
  {
   Console.Title = "Worker";
   Console.WindowHeight = 10;
   Console.WindowWidth = 70;

   using (var context = ZmqContext.Create())
   using (var replier_sckt = context.CreateSocket(SocketType.REP))
   {
    replier_sckt.Connect("tcp://127.0.0.1:5001");
    Message("connected...");

    while (true)
    {
     ZmqMessage request = replier_sckt.ReceiveMessage();
     string request_payload = Encoding.Unicode.GetString(request[0]);

     Message("request received: {0}", request_payload);

     string reply_payload = request_payload.ToUpper().Replace("-", string.Empty);

     ZmqMessage resposta = new ZmqMessage(new List<byte[]> 
     {
      Encoding.Unicode.GetBytes(reply_payload)
     });

     replier_sckt.SendMessage(resposta);
     Message("reply sent: {0}", reply_payload);
    }
   }
  }

  static void Message(string message, params object[] arg)
  {
   Console.WriteLine(
    string.Format("{0} - {1}",
     DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
     message), arg);
  }
 }
}



O segundo projeto é o projeto do cliente. Num cenário real de uma aplicação web, é este componente que estaríamos elaborando. Mas como um console é mais rápido para se fazer, vamos usá-lo. Novamente, use o trecho a seguir para seu cliente.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ZeroMQ;

namespace zeromq.lab.client
{
 class Client
 {
  static void Main(string[] args)
  {
   Console.Title = "Client";
   Console.WindowHeight = 10;
   Console.WindowWidth = 50;

   using (ZmqContext ctx = ZmqContext.Create())
   using (ZmqSocket request_sckt = ctx.CreateSocket(SocketType.REQ))
   {
    request_sckt.Connect("tcp://127.0.0.1:5000");

    while (true)
    {

     string request_id = Guid.NewGuid().ToString();

     ZmqMessage request = new ZmqMessage(new List<byte[]> 
     {
      Encoding.Unicode.GetBytes(request_id)
     });


     request_sckt.SendMessage(request);
     Message("request sent");

     ZmqMessage reply = request_sckt.ReceiveMessage();
     Message("reply received: {0}", Encoding.Unicode.GetString(reply[0]));

     Thread.Sleep(new Random().Next(5000));
    }

   }

  }

  static void Message(string message, params object[] arg)
  {
   Console.WriteLine(
    string.Format("{0} - {1}",
     DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
     message), arg);
  }
 }
}


O último projeto é o nosso broker, ou balanceador de carga, mais especificamente. Este componente é responsável por repassar as requisições aos serviços de forma uniforme e circular. Essa estratégia de distribuição se chama round robin. Use o trecho a seguir de código para completar nosso exemplo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ZeroMQ;

namespace zeromq.lab.broker
{
 class Broker
 {
  static void Main(string[] args)
  {
   Console.Title = "THE BROKER";
   Console.WindowHeight = 10;
   Console.WindowWidth = 50;

   using (ZmqContext context = ZmqContext.Create())
   using (ZmqSocket
    frontend = context.CreateSocket(SocketType.ROUTER),
    backend = context.CreateSocket(SocketType.DEALER))
   {
    frontend.Bind("tcp://*:5000");
    backend.Bind("tcp://*:5001");

    Message("ports binded");

    frontend.ReceiveReady += (sender, e) => 
    {
     ZmqMessage request = e.Socket.ReceiveMessage();
     backend.SendMessage(request);
    };

    backend.ReceiveReady += (sender, e) =>
    {
     ZmqMessage reply = e.Socket.ReceiveMessage();
     frontend.SendMessage(reply);
    };

    Poller poller = new Poller(new List<ZmqSocket>
    { 
     frontend, 
     backend
    });

    while (true)
    {
     poller.Poll();
     Message("polled");
    }
   } 
  }

  static void Message(string message, params object[] arg)
  {
   Console.WriteLine(
    string.Format("{0} - {1}", 
     DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), 
     message), arg);
  }
 }
}

Agora, inicie mais de uma instância do projeto do Worker (para poder ver a distribuição de carga entre eles). Inicie o broker e o cliente em seguida.

Pronto! Espero que tenha gostado desse exemplo.

segunda-feira, 10 de fevereiro de 2014

ZeroMQ: Request Síncrono/Reply Assíncrono

Agora é a vez de invertemos a situação em relação ao post anterior. Vamos criar um cliente síncrono e um servidor assíncrono.

Vamos lá, crie um projeto C# do tipo console, versão 4.5 do .NET com o nome zeromq.testes.sync-request. Instale o ZeroMQ via NuGet com install-package clrzmq -pre.

Vamos fazer o mesmo para o nosso servidor assíncrono. Crie um projeto com as mesmas características e dê o nome de zeromq.testes.async-reply. Instale o ZeroMQ como no projeto anterior.

Código


Use o trecho de código a seguir para o cliente -- request.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using ZeroMQ;

namespace zeromq.testes.sync_request
{
 class Program
 {
  static void Main(string[] args)
  {
   using (var context = ZmqContext.Create())
   using (var socket = context.CreateSocket(SocketType.REQ))
   {
    socket.Connect("tcp://127.0.0.1:5555");

    while (true)
    {
     string request_body = Guid.NewGuid().ToString();
     
     ZmqMessage request = new ZmqMessage(new List<byte[]>
     { 
      Encoding.Unicode.GetBytes(request_body),
     });

     socket.SendMessage(request);

     Console.WriteLine("{0} - requisição enviada: {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), request_body);
     
     ZmqMessage resposta = socket.ReceiveMessage();
     
     Console.WriteLine("{0} - resposta recebida: {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), Encoding.Unicode.GetString(resposta[0]));
     
     Thread.Sleep(2000);
    }
   }
  }
 }
}

Use o trecho de código a seguir para o servidor -- reply.

using System;
using System.Collections.Generic;
using System.Text;
using ZeroMQ;

namespace zeromq.testes.async_reply
{
 class Program
 {
  static void Main(string[] args)
  {
   using (ZmqContext context = ZmqContext.Create())
   using (ZmqSocket router = context.CreateSocket(SocketType.ROUTER))
   {
    router.Bind("tcp://*:5555");

    router.ReceiveReady += (sender, eventArgs) =>
    {
     ZmqMessage request_msg = eventArgs.Socket.ReceiveMessage();
     
     string request_body = Encoding.Unicode.GetString(request_msg[2].Buffer);
     
     string reply_body = request_body.Replace("-", string.Empty).ToUpper();
     
     Console.WriteLine("{0} - resposta enviada: {1}", DateTime.Now, reply_body);
     
     ZmqMessage reply_msg = new ZmqMessage(new List<byte[]> 
     {
      request_msg[0].Buffer, // Id da requisição
      Encoding.Unicode.GetBytes(string.Empty), // truque necessário
      Encoding.Unicode.GetBytes(reply_body), // conteúdo da resposta
     });

     router.SendMessage(reply_msg);
    };

    Poller poller = new Poller(new List<ZmqSocket> { router });

    while (true)
    {
     poller.Poll();
    }
   }
  }
 }
}

Execute os dois projetos ao mesmo tempo e você deverá ver o resultado: os dois consoles exibindo trocas de mensagens.

Truques

Assim como no exemplo anterior, aqui também há truques que se você souber de antemão, sua vida terá mais qualidade ;)

Linha 21 do server: A requisição síncrona é composta por basicamente 3 frames: 1) O Id do componente de requisição (também chamado de endereço) 2) Um frame vazio delimitador e 3) O corpo propriamente dito da mensagem. Dessa forma, nessa linha obtemos o corpo da mensagem.

Linha 27 do servidor (29, 30, e 31 na verdade): Como explicado acima, a requisição vem com um frame indicando o endereço, um delimitador nulo e o corpo da mensagem. Sendo assim, nós temos que devolver a mensagem de resposta da mesma forma. Caso contrário, o componente da requisição vai descartar a mensagem de resposta não a reconhecendo como uma mensagem válida. Acredite, foi um porre descobrir isso... Eu queria ter visto isso nos exemplos, mas acabei tendo que ler a documentação.

Linha 41 do servidor: Como o socket ROUTER é assíncrono, nós temos que usar o Poller para que o evento registrado na linha 17 seja disparado.

Uma outra questão que ainda não mencionei em nenhum outro post sobre o ZeroMQ: o tal do Bind e Connect. Por padrão, o lado "servidor" da relação, geralmente usa Bind, e o lado "cliente" usa Connect. O Bind só pode ser feito em um endereço e o Connect não tem restrição; mais de um Connect pode ser feito em um endereço.

Se você se interessou pelo ZeroMQ, dê uma boa lida em seu guia em http://zguide.zeromq.org/page:all. É longo, mas é um material muito bom e tem exemplos em muitas linguagens (inclusive C#) para a maioria dos padrões explicados.

domingo, 9 de fevereiro de 2014

ZeroMQ: Request Assíncrono/Reply Síncrono

Preparação


Dando continuidade ao post anterior que ilustra o padrão request/reply mais simples com o ZeroMQ, vou demonstrar uma aplicação desse padrão com um cliente (request) assíncrono e o servidor (reply) síncrono.

O primeiro passo é criar um projeto para o cliente -- eu criei um projeto do tipo console, versão 4.5 do .NET e dei o nome de zeromq.testes.request. Agora, inclua o ZeroMQ em seu projeto com o seguinte comando pelo NuGet:

PM> install-package clrzmq -pre

Obs.: No post anterior, eu usei uma versão legada dessa biblioteca. Agora, vamos sempre usar essa última versão.

Pronto, agora faça o mesmo procedimento para termos o projeto para o reply. Dei o nome do projeto do servidor de zeromq.testes.reply.

Vamos ao código


Para o cliente, use o seguinte trecho de código:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using ZeroMQ;

namespace zeromq.testes.request
{
 class Program
 {
  static void Main(string[] args)
  {
   using (ZmqContext context = ZmqContext.Create())
   using (ZmqSocket request = context.CreateSocket(SocketType.DEALER))
   {
    request.Connect("tcp://127.0.0.1:5555");

    request.ReceiveReady += (sender, eventArgs) =>
    {
     ZmqMessage reply_msg = eventArgs.Socket.ReceiveMessage();
     string reply_body = Encoding.Unicode.GetString(reply_msg[1].Buffer);
     Console.WriteLine("{0} - mensagem recebida: {1}", DateTime.Now, reply_body);
    };

    Poller poller = new Poller(new List { request });

    while (true)
    {

     string request_body = Guid.NewGuid().ToString();

     ZmqMessage request_msg = new ZmqMessage(new List 
     {
      Encoding.Unicode.GetBytes(string.Empty),
      Encoding.Unicode.GetBytes(request_body),
     });

     request.SendMessage(request_msg);

     poller.Poll();

     Console.WriteLine("{0} - mensagem enviada: {1}", DateTime.Now, request_body);

     Thread.Sleep(2000);
    }
   }
   
  }
 }
}

Para o servidor, use o seguinte trecho de código:

using System;
using System.Collections.Generic;
using System.Text;
using ZeroMQ;

namespace zeromq.testes.reply
{
 class Program
 {
  static void Main(string[] args)
  {
   using (var context = ZmqContext.Create())
   using (var reply = context.CreateSocket(SocketType.REP))
   {
    reply.Bind("tcp://*:5555");

    while (true)
    {
     ZmqMessage request_msg = reply.ReceiveMessage();

     Console.WriteLine("--------------------------------------");
     Console.WriteLine(DateTime.Now);
     Console.WriteLine("requisição recebida.", DateTime.Now);

     string reply_body = Encoding.Unicode.GetString(request_msg[0].Buffer)
      .Replace("-", string.Empty)
      .ToUpper();
     
     ZmqMessage reply_msg = new ZmqMessage(new List<byte[]> 
     {
      Encoding.Unicode.GetBytes(reply_body)
     });
     
     reply.SendMessage(reply_msg);
     
     Console.WriteLine("resposta enviada.");
    }
   }
  }
 }
}

Agora, execute os dois projetos juntos. Você deve ver as requisições e respostas sendo exibidas nas telas dos dois componentes.

Detalhes Sórdidos ou Truques


No post anterior, eu disse que o ZeroMQ era fácil. Eu menti! Bom, se você conhecer os truques dele, ele é fácil. Se não conhecer, vai achar que ele simplesmente não funciona. Eu descobri esses truques na dor, pois não estão claros nos exemplos da documentação oficial. Entretanto, eles são mencionados na parte textual da documentação -- acabei comprando o livro ZeroMQ: Messaging for Many Applications de Pieter Hintjens, um dos caras que construiu essa biblioteca (merece todo o crédito: http://en.wikipedia.org/wiki/Pieter_Hintjenshttp://hintjens.com/https://twitter.com/hintjens).

Bom, vamos aos truques.

Linha 18 do cliente: Este trecho não é um truque, é bem óbvio o que faz. Aqui, você está delegando um método anônimo para quando uma mensagem estiver pronta para ser recebida como todo bom modelo assíncrono que não trabalha com retornos de métodos.

Linhas de 25 e 40 do cliente: Para os sockets assíncronos, você precisa fazer o polling para que os eventos ReceiveReady e SendReady sejam disparados. Primeiro, você instancia um Poller e inclui todos os sockets de modelo assíncrono para fazer a multiplexação desses canais. Depois, você invoca o método Poll() dessa instância para aguardar uma resposta e invocar o ReceiveReady. (Este trecho foi atualizado para não usar threads. Aprendi que nunca se deve compartilhar um socket entre threads.)

Linha 32 do cliente: Uma mensagem pode ser composta de vários frames. Um socket do tipo Reply (REP), sempre espera uma mensagem com o primeiro frame vazio; se este não estiver vazio, ele simplesmente descarta a mensagem. Por isso, que nessa linha incluímos um frame vazio. Quando você usa o socket síncrono REQ, este frame vazio já é incluído automaticamente. No caso do DEALER, temos que fazê-lo manualmente.

Pronto, se souber sobre esse detalhes antes de brincar de request/reply com os sockets DEALER/REP, sua vida terá mais qualidade. Te garanto!

terça-feira, 4 de fevereiro de 2014

ZeroMQ: Introdução com Request/Reply Síncrono

Um dos frameworks mais legais para auxiliar no desenvolvimento de soluções distribuídas que conheço é o ZeroMQ (http://zeromq.org/). Empresas grandes o utilizam (AT&T, Microsoft, etc.), mas, por alguma razão que desconheço, nunca ouvi ninguém falar dele nas empresas onde trabalhei.

O ZeroMQ é uma biblioteca bem fácil de se trabalhar que abstrai os detalhes sórdidos de se trabalhar com sockets e é voltado a padrões de integração (Pub/Sub, Request/Reply, Push/Pull, etc.).

O intuito deste post é lhe inspirar a pesquisar mais sobre o ZeroMQ demonstrando um exemplo bem comum e legal: fazer uma requisição e obter uma resposta usando dois projetos em C# do tipo console.

Reply

Primeiramente, crie um projeto do tipo console em C# -- eu usei a versão 4.5 do .NET -- e de o nome de zeromq.testes.reply ou qualquer outro que quiser. Agora, vamos importar a referência do ZeroMQ pelo NuGet: install-package clrzmq

Dentro do método Main, cole o seguinte trecho de código:

Console.Title = "Replier";

using (var context = new Context())
using (var socket = context.Socket(SocketType.REP))
{
 socket.Bind("tcp://*:5555");

 while (true)
 {
  var requisição = socket.Recv(Encoding.UTF8);
  Console.WriteLine("respondendo à requisição: '{0}'", requisição);
  var resposta = string.Format("resposta à requisição: '{0}'", requisição);
  socket.Send(resposta, Encoding.UTF8);
 }
}

O trecho de código acima corresponde ao "servidor" que responde à requisições.

Request

Agora, crie um projeto do mesmo tipo do anterior e dê o nome de zeromq.testes.request. Como anteriormente, faça referência ao ZeroMQ via NuGet: install-package clrzmq

O método Main deve conter algo como o trecho a seguir.

Console.Title = "Requestor";

using (var context = new Context())
using (var socket = context.Socket(SocketType.REQ))
{
 socket.Connect("tcp://127.0.0.1:5555");

 while (true)
 {
  var requisição = Guid.NewGuid().ToString();
  socket.Send(requisição, Encoding.UTF8);
  Console.WriteLine("--------------------------------");
  Console.WriteLine("requisição enviada: {0}", requisição);
  var resposta = socket.Recv(Encoding.UTF8);
  Console.WriteLine("resposta: {0}", resposta);
  Thread.Sleep(1000);
 }
}

Testando o Projeto

Agora execute os dois projetos ao mesmo tempo. Se tudo deu certo, duas telinhas de console devem aparecer com algumas mensagens definidas de segundo em segundo. Eu não expliquei os detalhes propositalmente para te desafiar a procurar saber o que está acontecendo. Acredito que aprendemos mais quando somos curiosos e investigativos.

Gostou? Procure saber mais sobre essa biblioteca. E se sua linguagem de programação preferida não for o .NET, existem APIs para o ZeroMQ para todas as linguagens mais conhecidas (e desconhecidas também).

Importante!

Todo o exemplo acima usa uma versão antiga do clrzmq. Quando estiver confortável com o ZeroMQ, você deveria usar a versão mais recente que pode ser obtida usando a chave "-pre" na hora de instalar via NuGet (install-package clrzmq -pre).

domingo, 2 de fevereiro de 2014

Composite Pattern: Um exemplo prático e usável

O padrão de projeto Composto -- conhecido como Composite Pattern -- é um padrão em que um cliente lida com um outro objeto o tratando como apenas um. E este único objeto geralmente possui uma coleção de objetos de um mesmo tipo.

É mais fácil demonstrar um exemplo prático do que explicar a teoria deste padrão. Vamos supor um cenário (que eu mesmo já enfrentei algumas vezes) para lidar com tratamento de erros de um sistema.

Imagine que queremos logar todos os erros de um sistema num arquivo de texto e, dependendo da criticidade do erro, enviar um email ao administrador do sistema. Com este cenário, conseguimos extrair 2 objetos (logar em texto e enviar email) que possuem um denominador em comum:

LidarComUmErro

Com o padrão Composite, conseguimos que os clientes de um componente lidem com uma coleção como se estivessem lidando com apenas um objeto. Dessa forma, poderíamos criar dois tipos de objetos que compartilham uma ideia parecida (LidarComUmErro):


public class LogArquivoTexto
{
 public void LogarErro(object erro)
 {
  // lógica para salvar os detalhes do erro em um arquivo de texto
 }
}


public class NotificadorDeErroCritico
{
 public void NotificarErroCritico(object erro)
 {
  // lógica para enviar a notificação de erro crítico por email
 }
}


Se usarmos a beleza da abstração, conseguiríamos uma interface como a seguir:

public interface GerenciadorDeErros
{
 void GerenciarErro(object erro);
}

Nós abstraimos a ideia geral dos componentes que logam um erro num arquivo e enviam um email sobre um erro, correto?

Com algumas modificações, conseguiríamos que os dois componentes realizassem a interface de GerenciadorDeErros:


public class LogArquivoTexto
 : GerenciadorDeErros
{
 public void GerenciarErro(object erro)
 {
  LogarErro(erro);
 }
 
 public void LogarErro(object erro)
 {
  // lógica para salvar os detalhes do erro em um arquivo de texto
 }
}


public class NotificadorDeErroCritico
 : GerenciadorDeErros
{
 public void GerenciarErro(object erro)
 {
  LogarErro(erro);
 }
 
 public void NotificarErroCritico(object erro)
 {
  // lógica para enviar a notificação de erro crítico por email
 }
}


O que temos até agora  é a base para usarmos o padrão Composite. Precisamos de um cara que nos permita trabalhar com nossos dois componentes como se fossem um só:


public class GerenciadorDeErrosDoMeuSistema
 : GerenciadorDeErros
{
 public void GerenciarErro(object erro)
 {
  // O que fazemos agora?!?
 }
}

Sem mais delongas, a implementação que nos permitiria trabalhar com uma coleção parecendo que é apenas um objeto seria como a seguir:


public class GerenciadorDeErrosDoMeuSistema
 : GerenciadorDeErros
{
 private IList<GerenciadorDeErros> _gerenciadoresDeErros;
 
 public GerenciadorDeErrosDoMeuSistema()
 {
  _gerenciadoresDeErros = new List<GerenciadorDeErros>();
  _gerenciadoresDeErros.Add(new LogArquivoTexto());
  _gerenciadoresDeErros.Add(new NotificadorDeErroCritico());
 }
 
 public void GerenciarErro(object erro)
 {
  foreach(GerenciadorDeErros gerenciadorDeErro in _gerenciadoresDeErros)
  {
   gerenciadorDeErro.GerenciarErro(erro);
  }
 }
}


E este objeto seria usado mais ou menos como a ideia seguinte:

try
{
 ...
}
catch(Exception ex)
{
 GerenciadorDeErros gerenciadorDeErros = AlgumaFormaParaObterOGerenciadorDeErros();//
 gerenciadorDeErros.GerenciarErro(ex);
}

Pronto!

Espero ter passado de forma clara um exemplo prático e usável deste padrão.

quarta-feira, 29 de maio de 2013

HTML, CSS, Javascript - "Baixo Acoplamento e Alta Coesão" (Sim, Por Aqui Também!)

Por causa do Colabre, eu retomei muitas questões de desenvolvimento com HTML, CSS, Javascript -- todos esses assuntos relacionados a front end. E esse post foi inspirado por uma reescrita pesada nas telas. O que gostaria de dizer é:

Evite definir as mesmas classes de elementos HTML que têm propósitos de estilo e Javascript!

Por exemplo, uma classe link-especial de um elemento <a href="/exemplo" class="link-especial">Meu Link Especial</a> não deveria ser usada para deixar esse elemento vermelho, com uma borda preta em baixo e azul em cima e fazer com que elementos dessa classe, sempre que clicados, enviem uma requisição ajax para registrar um log.


.link-especial
{
 border-top: 1px solid blue;
 border-bottom: 1px solid black;
 color: red;
}

(Ok. Eu sei que esse estilo é horrível.)

E

$(".link-especial").click(function(){
 // enviar requisição ajax para registrar log...
});

Talvez para você seja óbvio o motivo de não misturar classes do DOM com propósitos diferentes. Ou talvez nunca tenha tido problema com isso. De toda forma, evite para manter uma boa separação entre estilização e de outros aspectos.

No meu caso, por exemplo, estou mudando todo o Colabre para usar o framework Bootstrap. Se não tivesse separado bem as classes de estilo e as de uso com javascript, teria um trabalho enorme para remover os estilos não mais usados. Você pode usar um prefixo para as classes de uso com javascript -- js-link-logger, por exemplo.

Os princípios de "baixo acoplamento e alta coesão" muito conhecidos na orientação a objetos não são exclusivos ;)