/ hybris

hybris ecommerce - converter

Entendiendo la capa de converter en el framework hybris ecommerce / sap commerce

La capa de facades se encargan de encapsular comportamientos complejos, orquestando la implementación mediante el uso de servicios, estrategies y otros componentes, dentro de esta, nos encontramos con tres conceptos:

  • Objetos tipo Data
  • Populators
  • Converters

Básicamente, el Data es un DTO (data transfer object), el cual se encarga de contenter la información necesaria para la capa presentacional

Por ejemplo: CartData contiene distintos atributos como el total de la orden, total de descuentos, entradas en el carrito con sus precios correspondientes y la información del producto por entrada. Posiblmente para alguna vista sea necesario únicamente el total de la orden, aquí es donde introducimos el concepto de Populator

populator

El populator se encargará de llenar la información necesaria para la capa de presentación, un error muy común es tener un populator muy grande, en el cuál llenemos absolutamente TODA la información del DTO, por ejemplo

public CartPopulator implements Populator<CartModel, CartData>
{
  public void populate(final CartModel model, final CartData dto)
  {
    dto.setTotalPrice(model.getTotalPrice());
    dto.setEntries(getEntries(model));
    dto.setDiscounts(getDiscounts(model));
    // populate all the elements
  }
}

Lo mejor sería desacoplar el llenado de información por contexto, por ejemplo, imaginemos un populator para los totales globales, precios y descuentos, otro populator para la información de los entries, otro populator para el producto dentro del entry y así sucesivamente

public CartPricePopulator implements Populator<CartModel, CartData>
{
  public void populate(final CartModel model, final CartData dto)
  {
    dto.setTotalPrice(model.getTotalPrice());
    dto.setTotalDiscounts(model.getTotalDiscounts());
  }
}

public CartEntriesPopulator implements Populator<CartModel, CartData>
{
  public void populate(final CartModel model, final CartData dto)
  {
    dto.setTotalPrice(getEntries(model));
  }
}

Esto permite dividir la información a transferir mediante el DTO hacia la capa superior, y es precisamente el momento en el que entran los Converter

converter

Un converter es una configuración de una respuesta para cierto tipo, mediante el uso de populators, por ejemplo:

<bean id="defaultNopalCartConverter" parent="abstractPopulatingConverter">
  <property name="targetClass" value="de.hybris.platform.commercefacades.order.data.CartData"/>
  <property name="populators">
    <list>
      <ref bean="cartEntriesPopulator"/>
      <ref bean="cartPricePopulator"/>
    </list>
  </property>
</bean>

Hay un par de partes importantes aquí:

  • targetClass - Indica la clase (con el package completo) al cuál vamos a convertir, esto es porque un populator / converter funciona para cualquier tipo de datos en realidad, no es un requerimiento que sean sólo de Model a DTO
  • populators - Lista de populators que van a ser ejecutados para llenar la información del target class

Volviendo al ejemplo del carrito, si estamos en la página del carrito, es posible que el converter que mostramos sea el necesario, ya que es aquí donde se muestra TODA la información, pero tomemos de ejemplo el minicart, en el cuál posiblemente sólo necesitemos los precios totales, si utilizamos el mismo converter, estaríamos procesando más información de lo que requerimos, generando pequeños delays en respuestas que a lo largo de la ejecución podrían representar ya un problema significativo en performance, para este caso configuraríamos otro converter

<bean id="defaultNopalPriceCartConverter" parent="abstractPopulatingConverter">
  <property name="targetClass" value="de.hybris.platform.commercefacades.order.data.CartData"/>
  <property name="populators">
    <list>
      <ref bean="cartEntriesPopulator"/>
    </list>
  </property>
</bean>

Como se puede apreciar, el converter ahora es específico para el caso de la respuesta que vamos a necesitar en la capa presentacional

Internamente, el abstractPopulatingConverter utiliza el atributo targetClass para instanciar el elemento que se va a poblar mediante reflection, esto forza a que la clase tenga un constructor vacío, posteriormente, recorre la lista de populators y se van pasando la instancia por referencia para llenar la información que se configuró a nivel de spring

agregar elementos a un converter existente

Existen distintas formas para modificar una lista de populators en un converter, en este caso, para agregar elementos podemos utilizar cualquiera de los siguientes:

modifyPopulatorList

<bean parent="modifyPopulatorList">
  <property name="list" ref="elConverterQueVamosAModificar"/>
  <property name="add" ref="elPopulatorQueVamosAAgregar"/>
</bean>

sobreescritura del bean

<!-- override -->
<alias name="nopalCartConverter" alias="cartConverter"/>

<!-- definición del bean -->
<bean id="nopalCartConverter" parent="defaultCartConverter">
  <property name="targetClass" value="de.hybris.platform.commercefacades.order.data.CartData"/>
  <property name="populators">
    <list merge="true">
      <ref bean="cartEntriesPopulator"/>
    </list>
  </property>
</bean>

En este tipo de modificación de la lista de populators, en realidad, estamos extendiendo el converter existente a nivel de spring, y re-definiendo la lista de populators, en este caso, estamos utilizando el parámetro merge en el nodo de list, el cuál permite indicarle que agregue elementos, en caso de no indicarlo, se sobreescribirán por completo. Finalmente, la notación de alias, permite a nuestro converter sobreescribir el converter "default" al que habíamos extendido. NOTA: el parent NO es el mismo alias, hacemos el parent del ID con el cuál se definió el bean originalmente, y sobreescribimos en el alias al alias que se le haya definido, de lo contrario puede generar referencias circulares y no será posible sobreescribirlo de esta forma

conclusión

El converter es un componente sumamente importante ya que ayuda a confeccionar las respuestas a la capa superior mediante la configuración de distintos populators, estos populators normalmente deberían ser contextuamlente separados para permitir esta flexibilidad, de lo contrario es difícil escalar