sábado, 16 de agosto de 2014

Desconto por meio de pagamento no PagSeguro

A maneira mais comum de um vendedor oferecer desconto no PagSeguro é através da criação de uma promoção. A promoção é configurada no menu "Preferências -> Parcelamento", e nada mais é que o vendedor assumir algumas parcelas para o comprador ter opção de pagar com parcelamento sem juros (por exemplo, o vendedor assume 5 parcelas, oferecendo assim 5 parcelas sem juros para seus compradores). Na criação da promoção, o vendedor pode optar por oferecer desconto para os pagamentos realizados com meios de pagamentos diferente de cartão de crédito (boleto, débito online, etc). Esse desconto é calculado pelo PagSeguro com base em um fator informado na criação da promoção.

Mas hoje é muito comum o vendedor querer dar desconto por meio de pagamento. Quem nunca viu uma loja oferecer 15% de desconto no pagamento de boleto?. Como fazer também se o vendedor quiser anunciar um produto com desconto, ele teria que passar o valor do carrinho já com desconto para o PagSeguro. Mas não seria muito melhor passar o valor original e deixar que checkout mostrar o valor do desconto?
Existe uma forma de fazer isso em integrações criadas através da API de checkout. Nessa API o vendedor pode configurar parâmetros para os meios de pagamentos, e um desses parâmetros é o percentual de desconto:

XML:


    
      
        credit_card
      
      
        
          discount_percent
          5.00
        
      
    
  

Parâmetros HTTP:
paymentMethodGroup1=credit_card
paymentMethodConfigKey1_1=discount_percent
paymentMethodConfigValue1_1=5.00

Ambas integrações configuram 5% de desconto para meio de pagamento cartão de crédito. É possível passar configurações de desconto diferentes para mais meios de pagamento e o valor de desconto é calculado sobre o valor dos itens de carrinho, excluindo assim o valor do frete.

Vamos ver então como ficaria uma chamada completa da API para um checkout que ofereça 5% de desconto no pagamento de cartão de crédito e 10% de desconto no pagamento de boleto:

XML:
  
    BRL  
      
        
        0001  
        Teste  
        100.00  
        1  
        
      
    
      
        
          boleto
        
        
          
            discount_percent
            10.00
          
        
      
      
        
          credit_card
        
        
          
            discount_percent
            5.00
          
        
      
    
    

Ou, se não estiver usando XML:

curl https://ws.pagseguro.uol.com.br/v2/checkout/ -d\
  "email=EMAIL\
  &token=TOKEN\
  &currency=BRL\ 
  &itemId1=0001\
  &itemDescription1=Teste\
  &itemAmount1=100.00\
  &itemQuantity1=1\
  &paymentMethodGroup1=boleto
  &paymentMethodConfigKey1_1=discount_percent
  &paymentMethodConfigValue1_1=10.00
  &paymentMethodGroup2=credit_card
  &paymentMethodConfigKey2_1=discount_percent
  &paymentMethodConfigValue2_1=5.00"


As opções de meios de pagamentos que podem ser passadas são:
  • Cartão de crédito: credit_card
  • Boleto: boleto
  • Débito online: online_debit
  • Depósito: deposit
  • Saldo PagSeguro: balance


domingo, 5 de maio de 2013

Ativando gzip em aplicação java no heroku

Para quem estiver usando heroku rodando um war com webapp-runner e quer habilitar gzip, altere a versão do webapp-runner para que seja maior que 7.0.34.3 (atualmente essa é a última versão do webapp-runner, quando clonei o projeto no heroku veio com 7.0.34.0, não testei com 7.0.34.2 ou anterior).

pom.xml:

...

  com.github.jsimone
  webapp-runner
  7.0.34.3
  provided

...

  
    
      com.github.jsimone
      webapp-runner
      7.0.34.3
      webapp-runner.jar
    
  

...

Isso irá atualizar a versão do webapp-runner para suportar um novo parâmetro chamado '--enable-compression'.

Depois disso altere o procfile adicionando o parâmetro e teste:
web: java $JAVA_OPTS -Dspring.profiles.active=prod -jar target/dependency/webapp-runner.jar --enable-compression --port $PORT target/*.war

Se quiser otimizar mais um pouco, use html compressor que você pode reduzir mais uns 10% o tamanho da resposta.

terça-feira, 14 de setembro de 2010

Cache, pra que te quero?

Essa semana ocorreu a qcon aqui em São Paulo. O evento foi muito bom, embora algumas palestras recomendo menos teatro e piadas e mais objetividade no assunto.

De qualquer forma procurei ir em todas apresentações que falavam de escalabilidade, e um ponto me chamou atenção, um tema presente em praticamente todas apresentações e que curiosamente já estava preparando um post aqui no blog. É a utilização de cache.

Muitas vezes eu vejo pessoas falando em cache e na maioria das vezes vinculando cache apenas a dados armazenados. Quando envolve banco de dados, os dados armazenados estão em tabelas, então cacheiam as linhas da banco de dados. Bem, não que isso esteja errado, geralmente um banco de dados é remoto a aplicação e se o cache for local, obviamente a aplicação vai ficar mais rápida. Mas eu acho que tem que pensar em cache não para cachear apenas dados armazenados e sim para cachear dados processados.

Logo na primeira apresentação da qcon isso ficou bem claro, nela Nick Kallen descreveu a arquitetura de dados no twitter.

Você deve imaginar que o twitter tem uma base gigantesca de dados, e que não é um banco de dados relacional comum, eles usam muito estratégias de particionamento. Porém mesmo assim a tarefa de mostrar os tweets de uma pessoa é muito difícil. Quando você acessa o twitter do seu colega você vê os últimos tweets dele junto com os últimos tweets dos usuários que ele segue. Essa lista de tweets eles chamam de timeline.

Isso não é muito simples a medida que seu colega segue vários outros usuários e que esses usuários tem vários tweets postados. Imagina se para cada visualização de página de um usuário o twitter tivesse que buscar do banco e calcular o timeline?

Para isso eles usam cache, mas veja bem, não adianta cachear um tweet, ou os tweets de cada usuário, pois dessa forma eles estariam apenas mudando o ponto onde iria buscar o dado, ao invés do disco estariam buscando na memória em um cache. Ok, isso iria melhorar a performance, mas não elimina a necessidade de montar o timeline, o que implicaria em buscar também no cache os últimos tweets de cada usuário que seu colega segue.

O que o twitter faz é cachear o timeline, ou seja, os tweets em uma ordem já definida para cada usuário. Eles não cacheiam apenas os dados da forma como estão armazenados no modelo, ele cacheia dados processados, o timeline já calculado, pois se cacheasse somente tweets, eles teriam o trabalho imenso de processar e montar o timeline para cada usuário a cada request.

Mas como assim? para todos usuários? Isso mesmo, o timeline esta calculado e processado em um cache para todos usuários do twitter. O cache é imenso, mas compensa.

Mas se um usuário postar qualquer coisa, o timeline fica inválido? Em teoria sim, a menos que ao postar algo, o twitter atualize todos timelines dos usuários que seguem o usuário que postou o tweet. Algo como, usuário José e João seguem o usuário Luiz, quando usuário Luiz posta nova mensagem, o timeline em cache do usuário José e João são atualizados. E adivinha? é isso que o twitter faz, porém um usuário pode ter milhares de seguidores, e o processo de atualização do timeline de todos eles pode demorar. Por isso a atualização é feita de forma assíncrona, e por esse motivo que um post no twitter não aparece imediatamente no twitter dos seus seguidores (Luiz posta e em um momento do futuro esse post vai aparecer no timeline de João e José).

Mas e se o cache morrer? Nesse caso o twitter vai penar bastante, afinal buscar os tweets para montar o timeline a partir do base pode ser extremamente custoso, teria que buscar e processar os últimos tweets de todos os usuários que determinado usuário segue. De qualquer forma há uma forma, só é mais complicada e demorada. Ou seja, o twitter usa cache, mas se der qualquer problema, os dados que ali estavam podem ser totalmente reconstruídos através de informações da base de dados.

Enfim, o que realmente me chamou atenção nisso foi o exemplo claro de uma abordagem de cache que não apenas cacheia uma entidade do banco de dados, um registro de tabela, o resultado de uma query, o dado como ele está armazenado. Eles economizam não apenas queries mas também processamento, o processamento que seria necessário para criar o timeline, isso está lá cacheado também e é dessa forma que eu acho que deve ser pensado os caches.

Para não ficar apenas com o twitter, um outro exemplo. Imagine um sistema como paypal ou pagseguro, onde um usuário é redirecionado para realizar o pagamento de uma compra. Nesses sistemas geralmente quando o usuário comprador vai efetuar o pagamento, a página de compra é customizada para a loja do vendedor, o que implica buscar os dados das várias configurações dele, como customizações de tela, meios de pagamentos aceitos, limites, nome, telefone, etc.

Um sistema sem cache iria realizar várias queries direto no banco para buscar todos os dados da loja do usuário que está vendendo.

Um sistema com cache não muito bem pensado iria cachear 'n' registros de 'n' queries no banco, isso iria resultar em 'n' buscas no cache, objetos pequenos que representavam o resultado de cada query e o processamento de iteração entre esses objetos precisaria ser feito sempre.

Finalmente uma abordagem mais inteligente iria cachear um objeto maior, que não existe na base de dados e que iria conter todas informações passiveis de serem cacheadas já processadas e prontas para serem usadas. Então 'n' consultas no banco iriam resultar em uma única busca no cache de um objeto já pronto para ser usado, economizando não apenas em buscas, mas no processamento que iria ser realizado.

Bem é isso, cache é uma das soluções mais eficientes para resolver problemas de escalabilidade, isso ficou bem claro nas palestras que fui. Mas ao mesmo tempo, para se tirar melhor proveito dele, deve se pensar no que está cacheado, nem sempre colocar o cache junto aos DAO's do seu sistema pode ser a melhor alternativa.

domingo, 21 de junho de 2009

Business, Service e Generic Exceptions

Depois de um período de mudanças (sai de Uberlândia e vim morar em São Paulo), volto a postar. Vou falar sobre minhas impressões sobre os famosos BusinessException, ServiceException, GerericException, enfim, as exceções genéricas presentes em muitos sistemas por ai.

Imagine a arquitetura de uma aplicação web simples, onde temos os controladores acessando interfaces de serviço, até ai tudo bem. Agora imagina que uma dessas interfaces possui um método para incluir usuário, esse método poderia ter tal assinatura:

Usuario insereUsuario(String nome, String email, Date dataNascimento) throws ServiceException;

Parece normal, como muitos sistemas por ai, mas tem um grande problema, aquela exceção. Imagine que um esse método lance exceção quando:

  1. Encontre um usuário com mesmo nome já cadastrado.
  2. Encontre um usuário com mesmo email já cadastrado.
  3. O usuário tem menos de 18 anos.

Em cada uma das exceções a implementação lançaria um ServiceException:

throw new ServiceException("Já existe um usuário com esse nome cadastrado")

throw new ServiceException("Já existe um usuário com esse email cadastrado")

throw new ServiceException("O usuário deve ter pelo menos 18 anos")

Parece perfeito, no controlador eu faria:

Servico servico = ...
try {
    servico.insereUsuario(nome, email, dataNascimento);
} catch(ServiceException e) {
    erros.add(e.getMessage());
}

Economia de linha no try catch, sem varias exceções, a mensagem vem do domínio e eu apenas jogo na tela para o usuário ver.

Mas ai vem dois grandes problemas, o primeiro é encapsulamento e o segundo a internacionalização. Eu, como controlador web ou como qualquer outro cliente da interface de serviço, quero direcionar o usuário da minha aplicação web para meu site de adolescentes caso o usuário não possa ser incluído devido a problemas de idade. Como faria isso? duas formas:

A primeira é antes de chamar o método de serviço testar se ele tem mais de 18 anos, o que resultaria em quebra de encapsulamento da minha lógica de serviço, se no futuro meu serviço mudar e eu aceitar usuários a partir de 16 anos, esse controlador (cliente do meu serviço) precisará ser alterado.

A segunda solução seria testar a mensagem de erro que a exceção de serviço (ServiceException) retornou (arrhhgg), caso for a mensagem de idade invalida, eu faço o direcionamento, o que também seria uma quebra de encapsulamento. Notem que não tem como eu cliente realizar essa lógica minha sem conhecer detalhes da implementação, isso porque nossa interface tem aquele ServiceException genérico, ela não nos diz muita coisa sobre as exceções que podem lançar, caso eu não queira simplesmente mostrar a mensagem para o usuário da minha aplicação web e sim fazer tratamentos diferentes para as varias exceções que modelo pode lançar.

Para quem realiza testes unitários de suas classes, nunca notou algo errado quando precisa fazer um teste para testar uma exceção especifica? Como faz, assim?

@Test
public void testaInclusaoDeUsuarioComNomeJaExistente() {
  try {
    service.incluiUsuario("nomeExistente", "email@teste.com", new Date());
  } catch(ServiceException e) {
    assertEquals(e.getMessage(), "Já existe um usuário com esse nome cadastrado")
  }
}

Perceba a baita quebra de encapsulamento, não é assim que se testa exceções, ou vocês acham que aquelas anotações que alguns frameworks de teste tem (como o springtest) @ExpectedException não servem para nada?! Usando exceções genéricas, elas realmente não servem para nada, pois não tem como usar de forma confiável, pois a exceção pode vir indicando que ocorreu X quando na verdade você queria testar a ocorrência da exceção indicando Y.

O outro problema que comentei foi internacionalização. O chefe vem e fala: vamos vender esse sistema para fora do Brasil, teremos que ter um sistema internacionalizavel. Você pensa: "beleza, coloca um buddle com as mensagens em cada idioma, e mudo todas exceções para ao invés de passar a string da mensagem, passar um código, as exceções retornam o código e meu framework web joga na tela a mensagem associada a esse código".

Mais uma vez quebra de encapsulamento, o que era antes isso:

@Test
public void testaInclusaoDeUsuarioComNomeJaExistente() {
  try {
    service.incluiUsuario("nomeExistente", "email@teste.com", new Date());
  } catch(ServiceException e) {
    assertEquals(e.getMessage(), "Já existe um usuário com esse nome cadastrado")
  }
}

Passou a ser isso:

@Test
public void testaInclusaoDeUsuarioComNomeJaExistente() {
  try {
    service.incluiUsuario("nomeExistente", "email@teste.com", new Date());
  } catch(ServiceException e) {
    assertEquals(e.getMessage(), "nome.do.usuario.ja.existe")
  }
}

Pense nisso, seu serviço provavelmente é stateless, você não sabe qual a localidade do usuário, então não tem como mandar uma mensagem bonita, já formatada para o idioma, pois você não sabe o idioma do seu usuário (a menos que o locale seja passado em todos seus métodos de serviço - nunca pense nisso), tem que mandar esse código mesmo, quem faz a tradução é quem está ali próximo do usuário, conhece ele, ou seja, a camada web, de interface com o usuário, mas não fica estranho?! Os códigos de erros definidos em uma camada de domínio e as mensagens definidas na camada web?!

Mas enfim, minha questão é, em meio de tempos de debates sobre VO, DTO e modelos anêmicos, porque as pessoas têm preguiça de criar exceções e gostam tanto de usar uma exceção genérica? Mesmo com esses problemas já mostrados?!

Como eu acho que deve ser feito: a inclusão de usuário recebe 3 parâmetros, nome, email e data de nascimento, o que pode ocorrer? Não quero dois usuários com mesmo nome nem mesmo email e também não quero usuários com idade menor que 18 anos. Isso é da interface, a minha interface de serviço tem que dizer isso, portanto a assinatura do meu método seria essa:

Usuario insereUsuario(String nome, String email, Date dataNascimento) throws UsuarioJaExistente, IdadeInvalidaException;

Podendo haver uma hierarquia de exceções, como por exemplo:

public class RegistroJaExistenteException extends Exception

public class UsuarioJaExistenteException extends RegistroJaExistenteException

public class NomeUsuarioJaExistenteException extends UsuarioJaExistenteException

public class EmailUsuarioJaExistenteException extends UsuarioJaExistenteException

Agora eu como cliente desse serviço posso fazer uns try catch e tratar as exceções de forma normal, minhas "mensagens" de erro agora estão na assinatura do método, se vier um IdadeInvalidaException eu faço alguma coisa especifica, posso redirecionar para outro site ou posso simplesmente chamar a minha mensagem “usuario.com.idade.invalida" na locale do usuário.

Meus testes unitários ficariam bonitos:

@Test
@ExpectedException(NomeUsuarioJaExistenteException.class)
public void testaInclusaoDeUsuarioComNomeJaExistente() {
  service.incluiUsuario("nomeExistente", "email@teste.com", new Date());
}

Como pode ver, dessa forma, você ira criar varias exceções, para gerenciar todas elas, com certeza você também não criara os packages da sua aplicação no estilo:

br.com.empresa.projeto.dao
 -- todos daos
br.com.empresa.projeto.entity
 -- todos entities
br.com.empresa.projeto.service
 -- todos servicos
br.com.empresa.projeto.exception
 -- todas exceções

Vai à sugestão de criar algo no estilo:

br.com.empresa.projeto.usuario
 Usuario
 UsuarioService
 UsuarioRepository
 UsuarioDAO
 UsuarioJaExistenteException
 EmailUsuarioJaExistenteException
 NomeUsuarioJaExistenteException
 IdadeInvalidaException
 etc

Agrupando em packages o que são relativos ao negócio e não agrupando pelo "tipo" de objetos (DAO's, Entities, Service's, etc).

Caso você já esteja no meio do projeto, não é tarde demais, refatoração faz parte de praticas ágeis, mas você não precisa sair por ai alterando tudo de uma só vez. Você pode considerar sua ServiceException como java.lang.Exception, começar a criar umas classes filhas para os novos desenvolvimentos e aos poucos refatorar o que já esta feito, não vai quebrar o código do seu cliente ou seu controlador (quem usa sua classe de serviço), pois vai continuar lançando ServiceException, com a vantagem que suas interfaces vão ficando aos poucos com uma assinatura mais descente.

Bem, eu já criei sistemas com BusinessException, aprendi com o erro, hoje é muito claro para mim que esse modelo não é certo. Utilizar serviços com exceções genéricas é terrível (a interface não te diz nada sobre o que pode ocorrer), fazer internacionalização fica muito chato, será que a criação de uma classe a mais indicando a exceção é tão custosa assim?! Eu acho que não.

segunda-feira, 2 de março de 2009

Como não criar um framework

Muitas pessoas tem a bendita ideia de pegar seus códigos fontes, a maneira como elas desenvolvem, seus padrões e criar um framework, uma arquitetura de referência para impor na empresa e todos outros desenvolvedores usarem. Geralmente esses frameworks são baseados em outros frameworks que irão evoluir e com certeza o framework criado não vai conseguir evoluir junto, causando um puta atraso para a empresa e também para os desenvolvedores que são obrigados a usar a gambiarra.

Por outro lado existem pessoas que realmente fazem frameworks úteis, que não tem o objetivo de resolver todos os problemas do mundo, e sim um problema especifico e que ainda não tenha solução.

De qualquer forma, se você ainda tem a ideia de fazer uma arquitetura de referência para resolver a fome do mundo e definir todas as camadas de todos futuros sistemas da sua empresa, ou então tem uma ótima ideia para um framework realmente útil, vale a pena ler esse comentário feito pelo Rod Johnson, criador e CEO do Spring em entrevista para a revista Java Magazine deste mês:

Houve uma série de decisões de projeto / design que foram importantes para que o Spring prevalecesse, uma delas foi que muitos frameworks são baseados na idéia de restringir os desenvolvedores, acreditando que os desenvolvedores e projetistas do framework sabem mais que os usuários do framework. E por isso um dos objetivos do framework é checar e controlar o que os desenvolvedores fazem.

Tivemos uma abordagem diferente com Spring, acreditando que desenvolvedores são profissionais que merecem ser tratados com respeito, ao invés de usar rótulos e forçá-los a trabalhar da forma que queremos que eles trabalhem.

Confesso que conheço muitos desenvolvedores que merecem ser presos e controlados. Nesse comentário, a frase que mais me chamou atenção foi: desenvolvedores são profissionais que merecem ser tratados com respeito. Esse pensamento já vem a algum tempo martelando minha cabeça. Hoje sou muito a favor de tratar desenvolvedores como profissionais, não precisamos e nem devemos fazer frameworks para controlar as coisas que esses caras fazem no sistema, afinal, ele é um profissional, recebe por isso e deve agir e ser cobrado como tal. Caso ele não tenha atitude nem conhecimento necessário para o desenvolvimento no projeto, na minha opinião ele deve ir para outro projeto, para não ser radical e dizer que ele não é necessário para empresa. As reuniões de retrospective e diária do scrum são ótimas para identificar esses caras ;).

Enfim, se for criar um framework, crie algo no espírito do Spring, tenha em mente como alvo desenvolvedores profissionais. Tendo essa mentalidade, caso sua ideia é fazer uma arquitetura de referência, você provavelmente já desistiu. O segundo apelo é para os responsáveis pela contratação de desenvolvedores, por favor, contratem bons profissionais e ajude as arquiteturas de referência a morrerem de vez.