begin..rescue

Programming is everything and everything is programming

Rails - Controllers X Lógica De Domíno

Muitas vezes, em um projeto em Rails, nos vemos encorajados a escrever parte da lógica da aplicação em nossos controllers. Talvez isso acontença, principalmente, pelo fato da grande maioria dos exemplos existentes na internet e literatura (de Rails) não mostrar preocupação com a arquitetura da aplicação (até pelo foco nestes casos ser a tecnologia, em si).

Acho que essa falta de precupação com design mais rico se justifica na maioria dos casos, onde a aplicação é pequena, o escopo do projeto é bem limitado ou existe uma necessidade imediata de lançar um produto ao público (como acontece em muitas startups - e nada impede um ‘refactoring’ posterior, mas isto é outra estória…). Existem, porém, outros casos… Aplicações grandes, complexas ou onde a criticidade das informações é elevada, requerem cuidados maiores com o design da solução (ou não… dependendo de quão suicida o time for!).

Após trabalhar em algumas aplicações grandes escritas em Rails (apps com mais de 15.000 linhas de código), muitas coisas que passariam despercebidas em alguns casos, tornam-se evidentes, principalmente quando modificações são necessárias: implementação de nova funcionalidade, refactoring ou correção de bugs…

Abaixo segue um exemplo de extração de objetos e lógica de domínio do controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  class PedidosController < ApplicationController

    def cancelar_pedido
      @pedido  = Pedido.find(params[:id])
      @estoque = Estoque.find_by_loja(params[:loja_id])
      @pedido.data_cancelamento = Date.today
      @pedido.save
      GatewayDePagamento.estornar_pagamento(@pedido.pagamento) if @pedido.pagamento
      @estoque.estornar_estoque_por_pedido(@pedido)
      NotificacaoDeUsuarios.notificar_cancelamento_pedido(@pedido) #classe que herda de ActionMailer::Base
    end

    .
    .
    .
  end

O principal problema existente neste controller é que ele, enquanto camada de parte da camada de visualização, têm conhecimento de como aplicar uma regra de negócio (além de outros problemas de design, como testabilidade, violação SOLID, enfim, este não é o foco no post).

A ‘regra de negócio’, enquanto parte de maior importância no sistema, devia estar isolada, independente de como é implementada sua parte “visual”.

Imagine, neste cenário, que será necessário fornecer esta mesma funcionalidade via WebService SOAP. É fácil copiar e colar este código no “handle” do request via SOAP, não?!? É fácil até que exista um novo requerimento ou que seja necessário uma adição/modificação do que existe. Por exemplo, surge a necessidade de que pedidos cancelados devem notificar o departamento de vendas… 2 lugares para modificar, maiores as chances de que algo seja esquecido no caminho, fora os outros problemas anteriormente descritos…

Então, para evitar estes problemas, podemos seguir a seguinte abordagem:

1) Extraímos a lógica de cancelamento e notificação para um objeto (“Domain Service”, se quisermos definir assim, segundo concepções do DDD):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class CancelamentoDePedidos
  def initialize(pedido, estoque, gateway_pagamento=GatewayDePagamento)
    @pedido = pedido
    @estoque = estoque
    @gateway_pagamento = gateway_pagamento
  end

  def proceder_com_cancelamento
    #os dois métodos abaixo em Pedido serão descritos logo mais
    @pedido.cancelar
    @pedido.estornar_pagamento_usando(@gateway_pagamento)
    @estoque.estornar_estoque_por_pedido(@pedido)
    # A decisão de manter este código no serviço vai depender de 
    # quão importante é para o caso de uso de "Cancelar Pedido"
    # esta notificação. Neste caso, trata-se como parte fundamental 
    # dele
    NotificacaoDeUsuarios.notificar_cancelamento_pedido(@pedido)
  end
end

2) Adicionamos a manipulação dos atributos internos relativos ao cancelamento de pedidos ao próprio pedido, que deveria ter este conhecimento.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Pedido
  .
  .
  .
  def cancelar
    self.data_cancelamento = Date.today
    self.save
  end

  def estornar_pagamento_usando(gateway_pagamento)
    gateway_pagamento.estornar_pagamento(self.pagamento) if esta_pago?
  end

  def esta_pago?
    !pagamento.nil?
  end
end

3) Por último, modificamos nosso controller:

1
2
3
4
5
6
7
8
9
10
11
12
class PedidosController < ApplicationController

  def cancelar_pedido
    @pedido  = Pedido.find(params[:id])
    @estoque = Estoque.find_by_loja(params[:loja_id])
    CancelamentoDePedidos.new(@pedido, @estoque).proceder_com_cancelamento
  end

  .
  .
  .
end

Acho que a idéia é mais ou menos essa… Com esse pequeno (e fácil!) refactoring do controller, explicitamos o caso de uso mencioando e nosso controller passa a ser somente um consumidor de nossa API para cancelamento de pedidos!!! Parece razoável (apesar de ainda ser possível melhorar isso)…

O livro do Eric Evans - “Domain Driven Design - tackling complexity in the heart of software” é, na minha opinião, um livro um pouco difícil de ser lido e “digerido”, porém consegue fornecer um insumo suficiente para desenvolver software com um “mindset” que prima a representação em código do negócio de nossos clientes.

Usando O ‘Assets Pipeline’ + Compressão De Javascript Com AngularJS

Se você, como eu, está recebendo o erro

1
Unknown provider: eProvider <- e

ao fazer deploy de uma aplicação Rails usando Asset Pipeline + AngularJS, então muito provavelmente seus arquivo de configurações referente ao ambiente onde o deploy está sendo realizado contém a linha:

1
config.assets.js_compressor = :uglifier

Este erro acontece porquê a obfuscação que ocorre no processo de obfuscação usando o uglifier impede o AngularJS de realizar a identificação interna de DI (dependency injection).

Para corrigir o problema, é simples. Basta trocar a linha mencionada acima por:

1
config.assets.js_compressor = Sprockets::LazyCompressor.new { Uglifier.new(:mangle => false) }

Isso vai impedir o obfuscador do unglifier de atuar sob nomes de variáveis e irá corrigir o problema.

Sobrevivendo Ao Mundo GIT - 1

Algumas vezes no processo diário de desenvolvimento e versionamento dos nossos fontes, nos vemos em situações nas quais temos que lidar com o GIT para realizar algumas tarefas não tanto usuais.

Neste post e alguns no futuro, pretendo mostrar como procedo em alguns destes casos:

Puts… Errei a mensagem de commit! Como faço para trocá-la?

Tranquilo!

Se o seu commit foi o último realizado no branch local, simplesmente use:

1
git commit --amend -m "nova mensagem"

Se quer trocar a mensagem de outros commits, use: (assumindo que você possui um editor padrão configurado no ~/.gitconfig)

1
git reabase -i SHA_DO_COMMIT_ANTERIOR_AO_QUE_SE_DESEJA_EDITAR_A_MENSAGEM

O “-i” acima representa o modo interativo, onde o GIT permite controle manual sob o processo de rebase (em breve tentarei postar algo mais profundo sobre o rebase).

Bom, após executar o comando, será exibida uma tela como esta, onde será possível, dentre outras coisas, trocar a mensagem do commit desejado:

1
2
3
4
5
6
7
8
9
10
11
pick f318e91 Implementando filtro anual 
pick 582a73e Implementando componente de filtro de intervalo de datas

# Rebase a9fda57..582a73e onto a9fda57
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message

Basta modificar a opção pick por reword ou r para cada commit que se deseja editar a mensagem, salvar o arquivo e fechar o editor.

Feito isso, será aberto um arquivo para cada commit apontado anteriormente com o comando ‘reword’, exibindo a tela para edição no formato abaixo (supondo que selecionei o comando para ‘reword’ do primeiro commit acima):

1
2
3
4
5
6
7
8
Implementando componente de filtro de intervalo de datas

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD^1 <file>..." to unstage)
#

Aí, basta editar a mensagem, salvar novamente e sair e as alterações serão aplicadas em nível de branch local.

Cause Everything Starts From Something…

Since 2006, the year i’ve started working with programming, i’ve being wondering about creating a blog.

The idea is just to write anything (interesting, of course) that i’m studying, learning or working with. That will be a great way to strategically organize ideas inside my head and express them in a straightforward way another human being (i supose) can understand.

Well, soon i’ll start with something that is on my mind…


Só uma atualização… Resolvi largar essa idéia de escrever em inglês… Em um lampejo de pensamento, esqueci-me que o propósito de escrever é meramente uma forma de recordação e registro de idéias e, para isso, nada melhor que o idioma nativo…