viernes, 19 de mayo de 2017

Porque el mapping ORM viola los principios de la programación orientada a objetos (POO)

El tema de este artículo - porqué la aplicación del mapping ORM en Java (se habla de cualquier tipo de ORM: JPA, Hibernate, etc) no es un buen ejemplo de la programación orientada a objetos y que podemos hacer para mejorar esta situación.
Sabemos que cuando se habla del mapping ORM, hablamos de varios frameworks JPA que permiten acceder a una base de datos (BD) relacional. Los frameworks JPA nos permiten de ser más productivos en el entorno de Java, donde trabajamos con los objetos.
Como ejemplo, consideramos una típica clase de unidad en Hibernate:


@Entity
@table (name = "Users")
public class User {
   @id @GeneratedValue
   @Column (name = "id")
   Identificación del private int;

   @Column (name = "name")
   private String nombre;

   Usuario público () {}
   public int getId () {
      Identificación del retorno;
   }
   setId pública vacío (int id) {
      this.id = id;
   }
   Public String getName () {
      Nombre del retorno;
   }
   pública setName void (String nombre) {
      esto. name = nombre;
   }
}

Se ve que tenemos una tabla SQL llamada "Users" con 2 columnas y la clase User representa esta tabla en el dominio de Java.
En verdad este código me parece horrible desde punto de vista de la programación orientada a objetos y ahora os explico porque.

Tenemos un problema.

El problema principal es que esta clase no actúa como un objeto en el sentido de la POO, sino simplemente como un contenedor de los datos.
Debajo he puesto un gráfico, se ve que nuestro programa principal llama la clase 'User' y le asigna un nombre de "Jeff". Luego el programa llama una sesión Hibernate, le pasa nuestro usuario para actualizar la base de datos. En este sentido nuestro objeto no hace nada por si mismo, sirve simplemente como un contenedor de los datos.
Hibernate hace lo siguiente: abre una transacción, recupera "usuario" con el antiguo nombre de la BD, cambia su nombre por el nuevo  al final actualiza la base de datos.

La vuelta a la programación por procedimientos?

Como he dicho antes, nuestro objeto “User” no se comporta como un objeto en el sentido de POO, sino simplemente un contenedor.  El paradigma de POO se inventó precisamente para evitar los problemas relacionados con la programación por procedimientos. En la época de los lenguajes como C o Assembler tuvimos una secuencia de comandas que se ejecutaron una detrás de otra y estas comandas manejaban a los datos. Este método ha funcionado bien con los pequeños programas con poco código. Cuando la cantidad de código aumenta, este paradigma deja de ser eficaz: se hace difícil de gestionar el código y mantenerlo.
Por esta razón fue inventada la programación orientada a objetos (POO). Este paradigma nos ha permitido de parar a pensar en las funciones y empezar a pensar en los objetos. Con el encapsulamiento cada objeto debe tener sólo los métodos estrictamente relacionados con este objeto y nada más. Y nuestras clases principales hacen "confianza" a las clases secundarias llamandolas sólo para que estas clases ejecuten sus propios métodos. Y estas clases ya "saben" cual de sus métodos hay que llamar para conseguir el resultado necesario! Lo que no se tiene que hacer en la POO, es empezar a sacar las propriedades de las clases para pasarlas a otras clases y crear un jaleo del código que nadie va poder seguir.

Así que en ese sentido Hibernate nos ofrece la manera antigua y ineficaz a trabajar: con los datos y con un procedimiento que manipula estos datos. La clase "Usuario" no se utiliza como un objeto, de lo contrario como un almacenamiento temporal de datos. Esto contradice los principios de la POO, que nos obliga a tener objetos encapsulados. Un buen objeto según la POO no debería tener ni "getter" o "setter" (que son la primera señal de que este no es un objeto OOP, si no sólo un almacenamiento). Un buen objeto encapsula su lógica y su comportamiento y no expone su lógica fuera.

DAO esta contra la POO

Por desgracia, en el mundo Java existen muchos ejemplos de la mala utilización de la POO: todos estos framework como Spring, Hibernate, Jackson, Hadoop - tratan de utilizar el paradigma procedimental en el mundo POO. Tratan de convertir el lenguaje Java en el lenguaje C, con todos sus trozos enormes del código, donde no llegas ni a saber quien envía los datos y quien los gestiona. Donde cualquier objeto pueden acceder a cualquier objeto y cambiarlo. Donde se violan los principios básicos de la POO que aprendemos en la Uni. El propósito de Java - utilizar las ventajas de la programación orientada a objetos. Este concepto hace el código más fácil a leer, fácil a mantener y fácil a probar.

La solución

La solución es muy sencilla:
Aquí nuestra clase principal delega al objeto Usuario toda la lógica y funcionalidad para cambiar su nombre. Diciendole: "yo me confio en ti, haz tu trabajo y cuando acabes, traeme el resultado". En lugar de hacer "setName()" para tener que recuperarlo con "getName()" para luego pasarlo a otra parte, se utiliza "rename()" y punto. Se delega al objeto Usuario toda la responsabilidad a cambiar su nombre, conectar a la base de datos, actualizarla , etc. 
Hay varias bibliotecas que podemos utilizar para conseguir este comportamiento. El concepto se llama ActiveRecord y en Internet podemos encontrar varias bibliotecas relacionadas a eso.
Abajo ponemos un ejemplo que utiliza JOOQ, una pequeña biblioteca para generar los SQL, pero hay varias.  

Todas las operaciones de actualización de usuarios están encapsulados en el objeto de usuario y no son visibles fuera del objeto:

public class User {
   private int id;

   public User() {}
   public int getId() {
      return id;
   }

   public String retrieveName() {
      return this.x.select("name")
      .from("users")
      .where("id", this.id)
      .fetchOne();
   }
   public void updateName( String name ) {
      this.x.update("user")
      .set("name", name=
      .where("id", this.id)
      .execute();
   }

}

Si uno quiere realizar transacciones, puede crear una clase adicional con un método que dice "transactional". De este modo, cada objeto (User y Order) puede ser llamado  para que se auto-actualiza.

En el ejemplo anterior hemos utilizado JooQ, pero hay otro buen ejemplo implementado el concepto ActiveRecord llamado ActiveJPA, donde dejamos de trabajar con objetos DAO, sino directamente con los modelos.

En otras palabras, en este caso, ya no es necesario de obtener un objeto User para pasarlo a la sesión Hibernate, etc. No hacemos otra cosa que simplemente llamar el objeto User (que hereda de una clase llamada ActiveJPA org.activejpa.entity.Model) y este objeto actualiza a si mismo:


User user = User.findById(id);
Map attributes = new HashMap();   
attributes.put("name", name);
user.updateAttributes(attributes);

En este caso nos damos cuenta, que el principio de encapsulamiento se conserva y el código se ha hecho mucho más lógico y fácil a entender.

No hay comentarios:

Publicar un comentario