Conociendo la Money-API, JSR 354 ¿Por qué utilizar una API para el dinero?

Por Otavio Santana Java Champion Groundbreaker Ambassador
Publicado en Julio 2019

Revisado por Francisco Riccio




Según Wikipedia, el dinero es el medio usado en el intercambio de bienes, usado en la compra de bienes, servicios, fuerza de trabajo, divisas extranjeras o en las demás transacciones financieras, emitido y controlado por el gobierno de cada país, que es el único que tiene esa atribución. Considerando eso, muchos sistemas en Java terminan utilizando o representando ese valor monetario de alguna manera, pero ¿Cómo representar el dinero en su sistema correctamente?

Para representar el dinero la primera estrategia es utilizar los tipos nativos de Java, pero el libro Effective Java no recomienda la utilización del uso de double y float cuando resultados precisos son necesarios.

<code>
double val = 1.03 - .42;
System.out.println(val); //0.6100000000000001
</code>



Ese mismo libro recomienda dos estrategias, la primera de ellas es utilizando long e int, para esto, es necesario realizar una conversión del valor a centavos, esta solución es muy recomendada cuando la velocidad y la ocupación de memoria son puntos importantes, sin embargo, es importante preocuparse por el número de lugares decimales, el libro no recomienda representación mayor que nueve lugares decimales.

<code>
public static void main(String[] args) {
    int itemsBought = 0;
    int funds = 100;
    for (int price = 10; funds >= price; price += 10) {
        itemsBought++;
        funds -= price;
    }
    System.out.println(itemsBought + " items bought.");
    System.out.println("Money left over: " + funds + " cents");
}
</code>



El problema de utilizar la representación de dinero con int y long es la dificultad y la no legibilidad de representar valores monetarios de esa forma. Para dar un ejemplo, un producto tiene un precio de doce dólares de valor, como representamos en centavos, colocaremos el valor de mil doscientos centavos.

<code>
public class Producto {
     private String name;
     private int money;
     //getter and setter
}
Producto banana = new Producto("banana", 12_00);
Producto pasta = new Producto("pasta", 4_00);
int sum = banana.getMoney() + pasta.getMoney();
</code>



Pero, ¿Qué sucede si olvidamos convertir ese valor a centavos (es decir, dejar doce en vez de poner doce mil)? aquí el resultado sería erróneo, otro problema sería el control de redondeos de decimales.

Además de int y long el libro Java Efectivo recomienda el uso de BigDecimal, con eso, nuestro producto tendrá una llamada más intuitiva y más común, después de todo, es más natural hablar de un producto que cuesta doce dólares y no mil doscientos centavos.

<code>
public class Producto { private String name; private BigDecimal money; //getter and setter } Producto banana = new Producto("banana", BigDecimal.valueOf(12D)); Producto pasta = new Producto("pasta", BigDecimal.valueOf(4D)); BigDecimal sum = banana.getMoney().add(macarrao.getMoney()); </code>



Otro punto importante es que BigDecimal ya trata el control de redondeo de decimales de manera más fácil.

Al ir más allá con nuestra clase Producto, tenemos un pequeño problema con ella, no hemos representado la moneda! O sea, ésta quedó implícita en todos los casos, si mi sistema soporta apenas una moneda no es un problema, pero imaginemos que mi producto sea vendido en diversos lugares del mundo. Solo el valor doce no significa nada, doce puede ser cualquier cosa (reales, soles, pesos, dólares, etc.).

Para representar el dinero es importante entenderlo. De forma resumida, el dinero está compuesto por dos partes, la parte del valor que es una cantidad numérica, pero solo con ese valor no conseguimos hacer mucho, necesitamos conocer también la moneda. La moneda representa el "sistema de dinero" de uso común especialmente dentro de una nación, siguiendo esa definición el real, nuevo sol, peso, dólar y el euro son tipos de monedas. Por lo tanto, tendremos que adicionar una moneda dentro del producto. Podemos representar la moneda de varias formas:

La primera de ellas es utilizando el tipo String, pero ¿Qué sucede si en vez de escribir dólar escribimos “dolra” con un pequeño problema de escritura? no tenemos ningún control sobre el valor String y por lo tanto, puede recibir un pequeño error de escritura hasta valores ilógicos como “banana”, “pasta”, etc. Estos últimos no serían monedas, pero serían aceptados normalmente por el tipo String.

<code>
public class Producto { private String name; private String currency; private BigDecimal money; //getter and setter } </code>



La segunda estrategia sería utilizar un enum para representar las monedas, de esa forma, las opciones serían constantes. Con esta estrategia resolvemos el problema de String, solo serán posibles los valores que definamos a partir de un enum, sin embargo, nuestro enum necesitará quedar más detallado una vez que tengamos que enfrentar aspectos de internacionalización, dentro de esos la ISO 4217, un estándar para monedas.

<code>
public class Producto {
     private String name;
     private Currency currency;
     private BigDecimal value;
     //getter and setter
}
enum Currency {
    	REAL, DOLLAR, EURO;
}
</code>



Para resolver esto, es posible utilizar una clase ya existente dentro del JDK: la clase java.util.Currency, con esta clase conseguimos resolver los dos problemas:

  • Solamente entrarán valores de tipo Currency en los setter.
  • Esta clase ya trabaja con la ISO 4217.

<code>
public class Producto {
     private String name;
     private Currency currency;
     private BigDecimal value;
     //getter and setter
}
</code>



Con el manejo de dinero de distintos tipos, necesitamos validar si las monedas son las mismas a la hora de realizar la compra o hacer una suma, después de todo, el tipo de cambio de un producto en real debe ser distinto de uno en dólar.

<code>
Producto banana = //instance;
Producto pasta = //instance; 	
if(banana.getCurrency().equals(pasta.getCurrency())) {
  BigDecimal value = celular.getValue().add(notebook.getValue());
 }//exception
</code>



Posiblemente tendremos que realizar esa validación en diversos lugares de nuestro código, así que creamos una clase utilitaria.

<code>
public class ProductoUtils {
public static BigDecimal sum(Producto pA, Producto pB) {
    if(pA.getCurrency().equals(pB.getCurrency())) {
      return pA.getValue().add(pB.getValue());
    }
    throw new IllegalArgumentException("Currency mismatch");
   }
}
BigDecimal sum = ProductoUtils.sum(pasta, banana);
</code>



Listo, con esto resolvemos todos nuestros problemas, ¿Correcto?, Falso!, vamos a nombrar algunos posibles problemas:

  • Para realizar la sumatoria de productos es necesario que la persona se acuerde de realizar la llamada a la clase utilitaria, pero ¿Qué pasa con aquello que tiene que ser recordado? Exacto!, desafortunadamente se olvida.
  • Como hablamos anteriormente, el dinero puede ser usado no solamente con Producto, con diversas cosas también, servicios, fuerza de trabajo, etc., así será necesario duplicar los dos campos, moneda y valor monetário, en diversos lugares de nuestro código.
  • Desde diversas clases utilizando dinero tendremos dos estrategias para realizar la validación, una seria crear clases utilitarias para todo modelo que use dinero, como ServiceUtils, GoodsUtils, etc., o una clase utilitaria que recibe cuatro parámetros (el valor de la moneda de los dos para ser comparado y entonces sumado).

<code>
public class MoneyUtils {
public static BigDecimal sum(Currency currencyA, BigDecimal valueA, Currency currencyB, 
BigDecimal valueB) {
   //...
}
public class ServiceUtils {}
public class WorkerUtils {}
</code>



  • ¿Que pasa si solo definimos un único ítem de dinero, valor o moneda? ¿Tiene sentido decir que el producto vale doce? o que ¿Vale dólar? Definitivamente no, este vale doce dólares y eso necesita ser validado.
  • ¿Es responsabilidad de la clase producto, o cualquier otra que necesite trabajar con dinero, cuidar de la creación y del estado de dinero?
  • Una vez utilizamos clases utilitarias para realizar esa validación, ¿No estamos disminuyendo el encapsulamiento? Después de todo es posible realizar la sumatoria de dos valores ignorando la validación de la moneda generando así un error. Mirando la definición de Wikipedia sobre el encapsulamiento: Permite esconder propiedades y métodos de un objeto para proteger el código de acceso directos y efectos secundarios accidentales.

 

Además de esos problemas, usando como referencia el libro Clean Code, tenemos una óptima definición entre estructura de datos y un objeto, básicamente el objeto esconde los datos para exponer un comportamiento, o sea, no estamos programando orientado a objetos de esa forma.

Una solución para resolver ese problema viene de un artículo de Martin Fowler, en el cual él cita un ejemplo de tipo Dinero como su favorito y con eso resolveremos:

  • Centralización de código, todo el comportamiento de dinero estará en la clase Dinero.
  • Eliminaremos la responsabilidad de las otras clases, no será necesario, por ejemplo, tener el control a la hora de crear valores dentro de la clase Producto citada anteriormente.
  • Adiós a las clases utilitarias, una vez que la validación dentro de la clase Dinero, las clases utilitarias no serán más necesarias, para ya no pensar en el clásico problema de olvidarnos de usarlas.

<code>
public class Money {
   private  Currency currency;
   private  BigDecimal value;
   //behaviour goes here ...
}
Product banana = new Product("banana", new Money(12, dollar));
Product pasta = new Product("pasta", new Money(4, dollar))
Money money = banana.getMoney().add(abacaxi.getMoney());
</code>



Así seguimos buenas prácticas de programación utilizando las técnicas de la creación del nuevo tipo, en nuestro caso el tipo Dinero. Un punto importante es que se utilizó uno de los conceptos del programador pragmático que es el punto de la memoria corta, sin embargo, un elemento quedó fuera: el de no reinventar la rueda. Después de todo, un buen programador no crea algo que ya existe, siempre se apoyará en tecnologías probadas y usadas por la comunidad de software.


El problema de tratar con la manipulación de dinero es algo muy común para los desarrolladores de Java y con este objetivo se creó una especificación que tiene como foco ayudar en la solución de este problema, nació así la JSR 354 o Money-API.

<code>
public class Product {
     private String name;
     private MonetaryAmount money;
}
CurrencyUnit usd = Monetary.getCurrency("USD");
Product banana = new Product("banana", Money.of(12, usd));
Product pasta = new Product("pasta", Money.of(4, dollar))
Money total = banana.getMoney().add(abacaxi.getMoney());
</code>



Así pues, ya conocemos la motivación para la creación de una API de tipo dinero. En la cual, además de evitar problemas, por ejemplo, de olvidarse de validar la moneda, de tener código repetitivo y desencapsulado, se garantiza mayor calidad de código por medio de responsabilidad única, dinero como objeto y no solo como estructura de datos. Traemos el dinero al dominio de nuestra aplicación como una API.




Otavio Santana es un ingeniero de software centrado en la tecnología Java. Tiene experiencia principalmente en aplicaciones de alto rendimiento en finanzas, gobiernos, redes sociales y comercio electrónico. El conocimiento y la experiencia de Otavio se encuentran en Java SE, Java EE, Spring y Google App Engine (GAE). También trabaja con varias bases de datos tanto SQL como NoSQL. Orador, vicepresidente de SouJava, Otavio es miembro de varios grupos de expertos de JSR, miembro ejecutivo de JCP y colaborador de JBoss Weld, Hibernate, Apache Commons. Ha recibido un Premio a la Excelencia JCP, un Premio Dukes Choice y un Premio Java Champion. twitter: @otaviojava

Este artículo ha sido revisado por el equipo de productos Oracle y se encuentra en cumplimiento de las normas y prácticas para el uso de los productos Oracle.