sábado, 3 de abril de 2010

Data Repositories - low coupling with persistence layer.

Someday I used to think a method is good if it can be very flexible. But thins article tells a diferent story:

http://codebetter.com/blogs/gregyoung/archive/2009/01/16/ddd-the-generic-repository.aspx

quarta-feira, 24 de março de 2010

Concordion - parece algo de qualidade para BDD

Tem tempo que procuro uma ferramenta útil que una testes unitários e escrita de especificação em uma coisa só. Ou seja, uma ferramenta boa de BDD. Recebi este link de um colega de trabalho e gostei. Esta pode ser uma boa ferramenta para se ter um port para php:

http://www.concordion.org/

quinta-feira, 18 de março de 2010

Refactoring e Clareza de Código - Negação da Negação

Este faz parte de uma série de pequenos posts com os tags código limpo e refactoring que vou estar publicando na medida que vou limpando alguns códigos por aqui.

A negação da negação geralmente atrapalha a leitura:
if(!$userAllowed) {
//x
} // aqui estamos negando a negação do if acima...
else {
//y
}
ou seja, se o sistema não tiver acesso, faça x e se ele não não tiver (que é o mesmo que ele ter, mas da um trabalho para ler) faça y. Sei que isso parece simples, principalmente em um código pequeno, mas tudo que diminui a velocidade de leitura deve ser evitado, até mesmo porque em alguns métodos fica difícil não ter um código extenso.

Normalmente quando um if tem um else, é mais claro se não começamos negando. Porque então o else vai ser a negação da negação.

Ou então algo assim:

if(!$accesForbidden) {//a} else {//b}

parece normal, mas da um trabalhinho pra ler. É bem mais fácil ler algo como:

if($hasAccess) {//a} else {//b}

ou no máximo:

if($accesForbidden) {//b} else {//a}
apesar de que este segundo caso só acho aceitável quando a variável accessForbiden já existe e tem esse nome justificado em outro contexto.

quarta-feira, 17 de março de 2010

Desacoplando para Testar - Injeção de dependência, inversão de controle e outras palavras bonitas.

Objetivo: Ensinar uma maneira de se estruturar uma aplicação para ela ser mais fácil de testar. Reduzir o tempo dos testes. Simplificar os testes usando mocks de maneiras não intrusivas.

Uns dos pontos principais para o teste de software é:
  • testar algo coeso, não atrapalhando um teste com funcionalidades de outras bibliotecas
  • ser rápido
Para isso geralmente ouvimos falar muito de mocks e stubs. Neste artigo vamos abordar não só uma maneira de usa-los como também como inserir estas ferramentas de maneira "transparente" no ambiente de testes, deixando o nosso código limpo. Vou começar de uma maneira simples e ir aprofundando no assunto para atender também a quem não está acostumado ao uso de mocks e stubs.

Não vou entrar de jeito nenhum na discussão do que é mock e o que é um stub. Vou melhorar este termo e traduzir. Digamos que podemos usar um "orêia". Por exemplo, se temos alguma parte do software que acessa um serviço externo, provavelmente isso não vai acontecer em menos de 0,1 segundo e isso é MUITO demorado para um teste. Você não acha? Imagine que ao final de um projeto pequeno para médio você terá em torno de 1000 testes.

Bom, então como fazemos para o nosso teste não demorar tanto? Usamos um mock, quer dizer stub, ou melhor: um "orêia". O nosso orêia vai ser usado no lugar do serviço original e fingir que executa o serviço. Ele pode até retornar algo estático ou que esteja em memória para simular o componente que ele está substituindo.

Por exemplo, digamos que no método inserirTexto() usamos o serviço do yahoo de extrair palavras chaves. Para isso usamos uma classe nossa onde encapsulamos a chamada a esse serviço: a classe PalavraChaveExtractorTabajaras. então teremos algo assim:
function inserirTexto($texto) {
$this->palavrasChaves = PalavraChaveExtractorTabajaras::extrair($texto);
$this->gravarNoBanco();
}
Beleza, então esse método extrai as palavras chaves e grava tudo no banco. Hum... e agora como vamos fazer para chamar esse método de um teste? Imaginem que temos o seguinte teste:
function testDeveInserirTextoNoBanco() {
$texto = $this->getTextoFixture();
$conteudo = FabricaDoConteudo::criaUmNovoAiPorFavor();
$conteudo->inserirTexto($texto);
$conteudoNoBanco = RepositorioDoConteudo::lerPeloTexto($texto);
$this->assertTrue((boolean)$conteudoNoBanco,"O conteúdo deve ser gravado no banco.");
}

Mas como vamos fazer no nosso método inserirTexto para poder dizer que queremos usar o ôreia (Mock)?

Temos que ter um jeito de fazer com que o sistema use o "PalavraChaveExtractorTabajaras" em um ambiente de produção e use o mock nos testes. Já sei, que tal...

if($ambiente == 'test') {
$this->palavrasChaves = PalavraChaveExtractorTabajarasMock::extrair($texto);
}
else {
$this->palavrasChaves = PalavraChaveExtractorTabajaras::extrair($texto);
}
Funcionar funciona, mas ainda não é algo muito bonito. Se eu quiser colocar outro ambiente vou ter que adicionar mais um if. Além disso, neste meu método, o ideal é que eu escreva o código de maneira fluente sem ter que me preocupar com testes. Já pensou ter que colocar estes ifs em todo canto. Vai ficar muito sujo.

Bom, uma outra alternativa é criar um builder para o extractor assim:

class PalavraChaveExtractorTabajaras {
static function build() {
// retorna de acordo com o ambiente
}
Eu teria que alterar a declaração do método "extrair", de estática para dinâmica, mas funciona melhorzinho, mas ainda assim eu acrescento código a minha classe que não tem a ver com ela. A minha classe não deve saber em que ambiente eu estou. Isso deve ficar para as classes das camadas mais altas, da aplicação ou do framework que eu estiver usando.

E então?

Então o melhor que já encontrei até agora para poder ter estes serviços variando de acordo com ambiente e não precisar saber de nada disso no código que usa o serviço é passar esta responsabilidade para uma outra classe. Esta classe vai gerenciar esse processo de instanciar e entregar os "serviços" de acordo com o ambiente que estamos. Essa classe geralmente é chamada de serviceContainer e já existem algumas implementações interessantes para ela em php, como por exemplo esta:

http://components.symfony-project.org/dependency-injection/trunk/book/03-Service-Container

Apesar de ser muito boa esta implementação, para um projeto que fizemos agora eu preferi escrever algo mais simples e que funcionou tão bem quanto, inclusive com algum ganho de performance, economizando em torno de 1m a cada requisição.

Porque o nome ServiceContainer ?

Porque a classe é um container que contém serviços. Por serviços podemos entender os objetos que implementam serviços como o de palavras chave do nosso exemplo, ou serviços de envio de email, ou até mesmo um repositório de objetos que se responsabiliza por cuidar da persistência de um determinado objeto.

Assim no nosso código podemos ter algo como:
function inserirTexto($texto) {
$extrator = SimpleServiceContainer::getInstance()->getService('keywords');
$this->palavrasChaves = $extrator->extrair($texto);
$this->gravarNoBanco();
}

O código fica um pouco mais extenso, mas continua claro. Estou pegando o serviço responsável por processar 'keywords'. Não sei qual serviço ele é, mas tudo bem, polimorfismo é isso aí.

Desta maneira apenas o container centraliza a responsabilidade de definir em qual ambiente estamos. Também ele vai instanciar o objeto de acordo com o ambiente. Um outro benefício que ganhamos disso é que se precisarmos trocar serviço que usamos poderemos intanciar ele e configurar também de outra maneira que não seja a que usavamos antes.

O SimpleServiceContainer

Com base na mesma interface do container do symfony, eu criei esse aí mais simplezinho:

class SimpleServiceContainer {

public $parameters;
static $containers = array();
protected $services = array();

protected $env;

/**
* @return SimpleServiceContainer
*/
static function getContainer($env) {
if(!array_key_exists($env,self::$containers)) {
$class = 'SimpleServiceContainer_'.$env;
if(!class_exists($class)) {
$class = 'SimpleServiceContainer_all';
}
self::$containers[$env] = new $class($env);
}
return self::$containers[$env];
}

static function getInstance() {
return self::getContainer(ConfigSystem::get('environment'))
}

function init() {}

protected function __construct($env) {
$this->env = $env;
$this->init();
}

/**
* @return SimpleServiceContainer
*/
function setParameter($name,$value) {
$this->parameters[$name] = $value;
return $this;
}

function getParameter($name) {
return $this->parameters[$name];
}

function getService($serviceName) {
if(!isset($this->services[$serviceName])) {
$this->createService($serviceName);
}
return $this->services[$serviceName];
}

private function createService($serviceName) {
$methodName = 'createService_'.$serviceName;
if(!method_exists($this, $methodName)) {
throw new ServiceBuilderNotConfiguredException(
'this container does not have a builder for the service called "'
.$serviceName."', the method should be called ".$methodName
);
}
$this->services[$serviceName] = $this->$methodName();
}

public function getInstantiatedServiceNames() {
return array_keys($this->services);
}
}

Por padrão ele usa as definições do SimpleServiceContainer_all. Esse eu tenho que criar obrigatóriamente. Depois eu poso extender dele de acordo com o ambiente que eu quiser. Por exemplo, se eu chamo o ambiente de desenvolvimento de test eu posso criar as minhas classe da seguinte maneira:


class SimpleServiceContainer_all extends SimpleServiceContainer {

protected function createService_keywords() {
return PalavraChaveExtractorTabajaras::build();
}

}

class SimpleServiceContainer_test extends SimpleServiceContainer_all {

protected function createService_keywords() {
return PalavraChaveExtractorTabajarasMock::build();
}

}


Assim desta maneira, quando eu crio o meu serviço com:


$extrator = SimpleServiceContainer::getInstance()->getService('keywords');


Eu vou receber o mock para o ambiente de testes e o normal para quando eu não estiver rodando os meus testes. Pode parecer simples, mas faz toda a diferença.

Veja:
  1. No meu código eu não digo como instanciar o serviço, pois o container funciona como uma factory. Assim qualquer outro componente que tenha a mesma interface pode ser substituido por ambiente eu até em um refactoring futuro sem impacto nas camadas que o usam.
  2. Isso diminui o acoplamento das classes que usam o serviço com o serviço em sí.
  3. Quando eu rodo meus testes o sistema automáticamente instancia o serviço de acordo com o ambiente de maneira transparente e me manda um componente que eu posso usar só sabendo a sua interface.
  4. Isso permite eu usar mocks transparentemente nos testes e também em outros ambientes e até substituí-los futuramente por outras classes quando estas forem desenvolvidas.
  5. Eu gosto desta implementação pois também instancio os objetos em puro php, sem a necessidade de um xml ou algo assim.
  6. Os objetos são instanciados apenas quando necessito deles.
  7. Os objetos dos serviços são por padrão instanciados uma vez só e usados em diferentes partes do sistema. Claro que isso pode não ser o comportamento desejado, mas pela simplicidade da implementação é fácil trocar este comportamento.

quinta-feira, 25 de fevereiro de 2010

Diferentes JS em cada ambiente do Symfony.

Quem estiver querendo colocar um site em produção vai provavelmente se deparar com a necessidade de "minificar" os arquivos de javascript. Porém é interessante manter os arquivos não minificados no projeto também para ficar mais fácil de editar e debugar. Porém o view.yml do symfony não permite configurações sensíveis ao ambiente. Assim achei uma solução bem simples em http://shout.setfive.com/2009/06/09/loading-different-javascript-css-files-in-different-environments/ que é a seguinte:

1 - Crie as configurações (um array) para cada ambiente no app.yml com o nome de "javascript_files".

2 - no view.yml faça referência a estas configurações usando:

javascripts: [< ? php echo implode(',',sfConfig::get('app_javascript_files');) ? >]


Depois me disseram que o view.yml está "deprecated", então fiz o seguinte:

Incluí uma partial em todos meus leiautes e nessa parcial usei:


< ?php if(sfConfig::get('sf_debug')): ? >
< ?php use_javascript('x') ?>
< ?php use_javascript('y') ?>
< ?php use_javascript('z') ?>
< ? else: ? >
< ?php use_javascript('xyz.min.js') ?>
< ? endif; ? >

quarta-feira, 24 de fevereiro de 2010

Tratando exceções no ambiente de produção.

* ver o primeiro comentário para ajustar um bug de loop eterno na homepage

Objetivo: Logar qualquer exceção não tratada no código, mandar um email para a equipe, gravar um log e mostrar uma mensagem para o usuário do tipo: "Estamos em manutenção para melhor servir." enquanto a equipe corre pra consertar o erro.

Como Fazer

1 Primeiro conectamos um listener ao evento de exception. Inicialmente pensei em capturar isso com um filtro, mas vi que existem algumas exceções que são capturadas que atrapalham o bom funcionamento do framework, como por exemplo a ReflectionException que vai gerar um 404 mais na frente.

Usando um listener para o evento, capturamos apenas aquilo que o symfony não trata:


class frontendConfiguration extends sfApplicationConfiguration {
public function configure() {
$this->dispatcher->connect(
'application.throw_exception',
array('MyExceptionHandler', 'listenToApplicationException')
);
}
...


2 Depois criamos a classe que ser chamada, contendo o método que vai "ouvir" o evento, conforme determinamos acima:


class MyExceptionHandler extends sfFilter {
static public function listenToApplicationException($event)
{
// pegamos a exception original
$e = $event->getSubject();
// para passar um link na mensagem mostrada ao usuário
sfLoader::loadHelpers(array('Url'));
try {
// manda email e grava um log
self::notify($e);
}
catch(Exception $e) {
// "Engulindo" propositadamente os
// erros quando envia email de log
// para não impedir que a mensagem
// seja mostrada para o usuário
}
if(self::isAjax()) {
// lembre dos requests de ajax...
// tratar de acordo com o framework do freguês.
}
else {
sfContext::getInstance()->getUser()->setFlash(
'feedback',
sprintf(
Messages::UNEXPECTED_ERROR,
url_for('@support')
)
);
sfContext::getInstance()->getController()->redirect('@homepage');
}
// este evento específico chama cada listener até que um responda que pôde tratar o evento. Por isso esse retorno.
return true;
}

static function notify(Exception $e) {
// cria a mensagem...manda email e registra um log.
self::log($mensagem);
}

static function log($message) {
$exceptionLogLocation = sfConfig::get('sf_log_dir') . '/exceptions.log';
$fileHandle = fopen($exceptionLogLocation, "a+");
fwrite($fileHandle, $message."\n");
}

static function isAjax(){
return sfContext::getInstance()->getRequest()->isXmlHttpRequest() ||
sfContext::getInstance()->getRequest()->hasParameter('iframeAjaxUpload');

}
}


3 Feito. Fica aqui mais uma dica de algumas coisas que você pode capturar e colocar na mensagem para facilitar a vida depois:



$username = sfContext::getInstance()->getUser()->getService()->getUsername();
$referer = sfContext::getInstance()->getRequest()->getReferer();
$uri = sfContext::getInstance()->getRequest()->getUri();
$ajax = self::isAjax() ? 'Yes' : 'No';
'time' => date('Y-m-d H:i:s'),
'exception_class' => get_class($e),
'exception_message' => $e->getMessage(),
'trace' => $e->__toString()

quarta-feira, 13 de janeiro de 2010

Método para Gerar Índice para HTML com JS


var htmlMenu = {
generate : function htmlMenu(id) {
var headers = {};
var stack = [];
var lastLevel = 0;
var text,level;
var levels = {'H1':0,'H2':1,'H3':2,'H4':3,'H5':4}
var menu = '';
$('#' + id + ' :header').each(function(a){
actualLevel = levels[this.nodeName];
if(actualLevel < lastLevel) {
for(var r = actualLevel+1; r <= lastLevel; r++) {
delete stack[r];
}
}
text = $(this).html();
if(stack[actualLevel] == undefined) {
stack[actualLevel] = 1;
}
else {
stack[actualLevel]++;
}
var index = '';
var first = true;
for(i in stack) {
index += (first ? '' : '.') + stack[i];
first = false;
}
headers[index] = text;
$(this).append('<a name =" '">');
menu += "<a href="'#">" + index + " - " + text + "</a><br/>";
lastLevel = actualLevel;
})
return menu;
}
};