Trabalhando com dinheiro no Mundo Java EE [MONEY-API]

jobtrends

As aplicações Java EE estão ficando cada vez mais comuns no mundo do desenvolvimento do software para web utilizando Java. Esse crescimento se deu principalmente em 2009 com o lançamento do Java EE 6 e o nascimento do Contexto de injeção de independência, o CDI. Umas das grandes vantagens do uso do Java EE, é que ela é uma especificação Java, assim não existe o vendor lock-in, em outras palavras, não é necessário ficar preso em um fabricante. Outro ponto interessante é que caso não seja necessário usar todo o container do Java EE, é possível utilizar alguns recursos em um servidor simples, por exemplo, no caso é possível rodar o CDI, JSF, Bean validator, etc. em um container como o Tomcat. Outro ponto importante que vale ressaltar é que cada componente, citado anteriormente, é especificado separadamente, uma JSR, essas JSRs são regidas pelo JCP e todos podem participar, dar feedback, reportar bugs, etc. na concepção até o final dessa especificação Java.

O Java EE é certamente a plataforma mais popular para softwares no mundo corporativo, esses softwares têm diversos objetivos. O que eles normalmente tem em comum é o fato de lidar com dinheiro. O valor monetário é muito comum em diversos softwares, por exemplo, sistemas financeiros, locadoras, livraria, sistemas de recursos humanos, contabilidade etc. Porém não existia um padrão para trabalhar com dinheiro no mundo Java, por anos vários desenvolvedores usavam os tipos primitivos ou tipos oriundos do Java para resolver este problema, por exemplo, Long, Double, Integer, String, etc. Utilizar tipos primitivos traz diversos problemas para um software, dentre eles, falta de encapsulamento, problemas no design da API numa aplicação, quebra do SOLID, dentre outros motivos (Como falar das desvantagens do tipo primitivos para representar dinheiro não é o foco desse artigo, ficaremos apenas com esses exemplos, para mais informações existe o money-api book o link estará abaixo desse artigo). Com o intuito de solucionar esse problema existe a JSR 354, o money-api, essa especificação foi desenvolvida por diversas empresas que lidam diretamente com dinheiro, dentre elas, o Credit Suisse, uns dos maiores e mais famosos banco do mundo, e o Paypal, um dos maiores providers de pagamento do mundo.

Para tornar o artigo mais didático, será imaginado um simples exemplo de uma aplicação Java EE, um sistema de compra de livros. Nesse aplicativo, se falará de cada componente do Java EE separadamente. Se utilizará a versão atual do Java EE, JavaEE 7. O foco desse artigo não será falar sobre a API de dinheiro, apenas sua integração com o Java EE.

JPA

Para o exemplo ficar simples, o modelo livro basicamente terá três atributos:

  • Id, que será um número sequencial
  • Nome, o nome do livro
  • Valor, o valor do livro
@Entity 
public class Book implements Serializable { 

@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Long id; 

@Column 
private String name; 

@Column 
private MonetaryAmount value; 

//getter and setter 
    
}

Um problema comum em lidar com tipos únicos, como o dinheiro, é que normalmente os bancos não tem suporte nesse tipo de informação. Uma solução para esse problema nasceu no JPA 2.1 com os converters. A criação de um converter é bastante simples, basta implementar a interface AttributeConverter.

public class MonetaryAmountConveter implements  AttributeConverter<MonetaryAmount, BigDecimal>{ 

    private static final CurrencyUnit CURRENCY = Monetary.getCurrency("BRL"); 
    
    private static final MonetaryQuery<BigDecimal> EXTRACT_BIG_DECIMAL = (m) -> m.getNumber() 
            .numberValue(BigDecimal.class); 
            
    @Override 
    public BigDecimal convertToDatabaseColumn(MonetaryAmount attribute) { 
       return Optional.ofNullable(attribute).orElse(FastMoney 
               .zero(CURRENCY)) 
               .query(EXTRACT_BIG_DECIMAL); 
    } 

    @Override 
    public MonetaryAmount convertToEntityAttribute(BigDecimal dbData) { 
        return Money.of(dbData, CURRENCY); 
    } 
} 

Converter do JPA que persiste apenas o valor numérico.

E definir no campo como ele será convertido, para isso existe a anotação Convert.

@Column
@Convert(converter = MonetaryAmountConveter.class)
private MonetaryAmount value;

No JPA 2.1 os converters trabalham com um campo com um campo único, assim se pode trabalhar com algumas opções:

  • Persistir apenas o número: em sistemas que trabalham apenas com uma única moeda, é possível, por exemplo, definir a moeda em uma constante, que sempre será a mesma, e assim persistir o valor numérico. Assim é possível realizar operações no próprio banco de dados, como soma, porém caso o projeto cresça e precise internacionalizar haverá um grande problema. Uma solução para contornar esse problema seria ter um tenancy para cada país/moeda.

  • Persistir o número e a moeda como String: Utilizando essa estratégia seria o suficiente para manter o sistema internacionalizável. Se teria as informações suficientes para poder recuperar tanto a moeda quanto o valor numérico, porém, seria mais difícil, por exemplo, usar recursos do banco de dados, por exemplo, realizar somatório, assim um somatório teria que ser serializado para o modelo e assim a operação ser realizada.

  • Persistir o número e a moeda em campos diferentes: Essa opção, infelizmente, não está disponível no JPA, conforme foi discutido anteriormente no JPA 2.1 o converter apenas aceita um único campo como converção. Porém, é possível utilizar o hibernate diretamente para realizar essa operação, algo bem semelhante ao JPA 2.1, porém implementando a interface CompositeUserType. Com isso é possível, realizar operações de soma dentro do banco de dados, porém, existe a possibilidade de somar sem levar em consideração da moeda, ou não levar em consideração o modo de arredondamento desejado, que as implementações do MonetaryAmount podem garantir.

O Jadira é um framework que possui diversas implementações do UserType, interface que define o tipo que será persistido no banco de dados semelhante ao AttributeConverter. O Jadira já possui suporte tanto para as JSR 310, time-api, quanto o JSR 354, money-api.

CDI

Não é interessante que a entidade Livro seja responsável por definir a moeda que o objeto vai utilizar, afinal, não é sua responsabilidade. Outro ponto é que existem diversas estratégias para a criação de uma moeda: Definir a moeda a partir do usuário ou definir a moeda a partir do tenancy, por exemplo, caso o usuário utilize as máquinas do “.com.br” a moeda será real, “.com.us” dollar etc. Em nenhum desses casos deixar essa lógica espalhada no código não é uma boa estratégia, ou deixar o livro responsável por isso, nesse caso haverá quebra do SOLID. Para isso, poderíamos utilizar o CDI. O contexto de injeção de dependência, CDI, funciona basicamente como cola no mundo Java EE.

public class MoneyProducer { 

    @Inject 
    private HttpServletRequest request; 

    @Produces 
    @RequestScoped 
    public CurrencyUnit getCurrency() { 
        return Monetary.getCurrency(request.getLocale()); 
    } 
} 

Criar uma moeda a partir do request

public class MoneyProducer { 

    @Inject 
    @Named("money_from_configurations") 
    private String money; 

    @Produces 
    @RequestScoped 
    public CurrencyUnit getCurrency() { 
        return Monetary.getCurrency(money); 
    } 
}

Pegando a informação da moeda a partir de uma configuração.

Com um produtor de moeda definida pelo CDI, se pode injetar a moeda e “magicamente” ela será instanciada. Para não ter diversos pontos injetando moeda e criando dinheiro com ‘n’ implementações, uma recomendação é ter uma classe especializada na criação do dinheiro, caso seja necessário, criar um qualificadores para definir cada tipo de implementação.

public class MonetaryAmountFactory { 

    @Inject 
    private CurrencyUnit currency; 
    
    
    public MonetaryAmount create(Number number) { 
        return Money.of(number, currency); 
    } 

} 

 

JAX-RS

É muito comum, realizar integração com outros sistemas e para realizá-lo, o modo mais comum, é via uma API rest, dessa forma a integração será transparente pela tecnologia, linguagem, etc. que o outro sistema utilize. No mundo Java EE existe a especificação que lida com isso, o JAX-RS. Assim como na implementação de JPA, não existe um converter nativo para a moeda, a interface CurrencyUnit, e para o dinheiro, a interface MonetaryAmount. Na nova versão do JAX-RS é possível criar os próprios converters, para isso é necessário:

Implementar a interface ParamConverterProvider e anota-la com a anotação @Provider. Essa interface tem apenas um método e como saida espera como saida a interface ParamConverter.

@Provider 
public class MonetaryAmountConverterProvider implements ParamConverterProvider { 


    @Override 
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) { 
        if (MonetaryAmount.class.isInstance(rawType)) { 
            return new ParamConverter<T>() { 
                @Override 
                public T fromString(String value) { 
                    if(value == null || value.isEmpty()) { 
                        return null; 
                    } 
                    return rawType.cast(Money.parse(value)); 
                } 

                @Override 
                public String toString(T value) { 
                    if(value == null) { 
                        return null; 
                    } 
                    return value.toString(); 
                } 
            }; 
        } 
        return null; 
    } 
} 


@Provider 
public class CurrencyUnitConverterProvider implements ParamConverterProvider { 

    private CurrencyUnitParamConverter converter = new CurrencyUnitParamConverter(); 

    @Override 
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) { 
        if (CurrencyUnit.class.isInstance(rawType)) { 
            return new ParamConverter<T>() { 
                @Override 
                public T fromString(String value) { 
                    if(value == null || value.isEmpty()) { 
                        return null; 
                    } 
                    return rawType.cast(Monetary.getCurrency(value)); 
                } 

                @Override 
                public String toString(T value) { 
                    if(value == null) { 
                        return null; 
                    } 
                    return value.toString(); 
                } 
            }; 
        } 
            return null; 
    } 
}

JSF

A interação com o usuário é um ponto muito importante nas aplicações, assim é necessário representar o dinheiro e permitir que o usuário informe a moeda ou dinheiro. Assim como as outras especificações do mundo Java EE, não existe suporte para a money-api, porém existe converters dos componentes do JSF. Para isso basta implementar a interface Converter.

@FacesConverter("money.midas.CurrencyConverter") 
public class CurrencyConverter implements Converter { 

	@Override 
	public Object getAsObject(FacesContext context, UIComponent component, 
			String value) { 
		 
		if (Objects.isNull(value)) { 
			return null; 
		} 
		return Monetary.getCurrency(value); 
	} 

	@Override 
	public String getAsString(FacesContext context, UIComponent component, 
			Object value) { 
		if (Objects.isNull(value)) { 
			return null; 
		} 
		return value.toString(); 
	} 

} 

@FacesConverter("money.midas.MoneyConverter") 
public class MoneyConverter implements Converter { 

	@Override 
	public Object getAsObject(FacesContext context, UIComponent component, 
			String value) { 
		 
		if (Objects.isNull(value)) { 
			return null; 
		} 
		return Money.parse(value); 
	} 

	@Override 
<strong>	public String getAsString(FacesContext context, UIComponent component, 
			Object value) { 
		if (Objects.isNull(value)) { 
			return null;
		} 
		return value.toString(); 
	} 
}

O Projeto Midas

Com o intuito de realizar a integração entre as especificaçõeshttp://jadira.sourceforge.net/ e outros frameworks e o money-api existe o projeto Midas, dentre os projetos que estão no midas dar suporte estão:

  • JSF
  • Bean Validation
  • JPA
  • JAX-RS
  • CDI
  • Google Guice
  • Spring

No midas, todos os converters do JSF, JPA, JAX-RS que foram citatos nesse artigo estão nesse projeto. Além dessas especificações, o midas tem suporte para o bean validation.

  • CurrencyAccepted: Define as moedas que serão aceitas dentro da moeda, CurrencyUnit, e do dinheiro, MonetaryAmount. É possível informar as moedas permitidas ou usando o código da moeda ou de * o Locale de onda a moeda veio.
  • CurrencyRejected: Define as moedas que não serão aceitas, rejeitadas, dentro da moeda, CurrencyUnit, e do dinheiro, MonetaryAmount. É possível informar as moedas permitidas ou usando o * código da moeda ou de o Locale de onda a moeda veio.
  • MonetaryMax: Define o valor máximo para o dinheiro.
  • MonetaryMin: Define um valor mínimo para o dinheiro.
@CurrencyAccepted(currencies = "BRL") 
private CurrencyUnit currencyUnit; 

@CurrencyAccepted(currencies = "BRL") 
@MonetaryMax("10.12") 
private MonetaryAmount money;

Com isso foi apresentado a integração entre o money-api e o Java EE. Ainda existe muito mais integrações para serem desenvolvidas com essa espificação que trabalha com dinheiro e , certamente, toda ajuda será bem-vinda por todos os membros da comunidade.

Referências

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s