Matías Navarro Carter

Backend Web Developer @ Santiago, CL

Desmitificando las ORM

Hace poco en el sitio de Programadores Chile, un usuario hizo una pregunta muy interesante, que al menos todos nos hemos hechos alguna vez:

Hola, quiero hacerles una consulta… ¿para ustedes es mejor SQL nativo o un ORM y por qué ?

Un montón de respuestas y comentarios, algunos de ellos bastante increíbles como “solo usar SQL con procedimientos almacenados” o “ORM en desarrollo y SQL nativo en producción” intentaron proveer al usuario de alguna guía al respecto de su buena pregunta.

"ORM en desarrollo y SQL nativo en producción"

Inmediatamente me picaron las manos por escribir esta pieza. Ya había escrito algo antes sobre esto, pero ahora planeo darle un poco más de extensión. Esto, porque creo que como desarrolladores, necesitamos corregir algunas malas prácticas relativas al uso de herramientas de persistencia, sobretodo cuando estamos desarrollando una aplicación de escala empresarial bajo el paradigma orientado a objetos.

Disclaimer: Este articulo solamente contiene recomendaciones para aquellos que usan bases de datos relacionales para persistir el estado de una aplicación web, en otras palabras, desarrolladores web comunes y corrientes. Si tú eres un DBA o un Data Scientist, lo que escribo aquí no aplica ni se relaciona a tu campo.

“¿Qué es mejor?”

Lo primero que tenemos que entrar a definir es qué es lo que la palabra “mejor” significa en la pregunta del usuario. Mejor es un término relativo y comparativo. Tenemos que aclarar. “¿Mejor que qué?”, “¿Mejor en qué?”, “¿Mejor para qué?” son todas preguntas pertinentes.

Estamos llenos de estas preguntas sin contexto en el mundo del desarrollo de software. Tenemos nuestros “Qué es mejor” bien definidos… SQL vs NOSQL, PHP vs Node, Rust vs Go, Laravel vs Symfony, you name it. Estas son preguntas sin alma, sin contexto y por lo tanto, sin respuesta. Por ello nos perdemos en debates interminables en grupos de Facebook: porque no sabemos preguntar.

Si yo preguntara “¿qué es mejor, un martillo o un destornillador?” sería evidente ver que estoy haciendo una pregunta absurda. No es tan evidente en el caso de SQL vs ORM, porque pensamos que las herramientas de las cuales estamos hablando son comparables. Pero no lo son. SQL es un lenguaje de consulta de bases de datos relacionales; ORMs son librerías que mapean tablas de bases de datos relacionales a objetos. Hay un mundo de diferencia. Y esto nos lleva a nuestra a desarmar nuestro primer mito.

“Las ORM sirven para hacer queries más fácilmente”

Existe la creencia común de que las ORM son simplemente herramientas para hacer queries más fácilmente. Esto constituye una caricaturización extrema de lo que es una ORM, y no hace justicia a su propósito original.

Si sólo quisiera una herramienta para hacer queries más fácilmente, usaría librerías como fluent PDO o similares. Pero las ORM son mucho más que herramientas para realizar queries. Como su nombre lo dice, son Object Relational Mappers o Mapeadores Relacionales de Objetos. Es decir, son capaces de abstraernos del hecho de que estamos usando un mecanismo de storage relacional, y en vez de eso, nos devuelven objetos con los cuales podemos trabajar.

Todo esto parte de la premisa de que estamos modelando nuestro dominio a partir de objetos. Por dar el ejemplo antonomásico de un blog, tenemos una clase Author con métodos como login, changePassword, resetPassword, publish, unpublish, y una clase Article que puede ser publicada por un usuario. Me interesa persistir los Author y los Article, pero aún quiero tener la flexibilidad de trabajar con objetos.

Las ORM hacen este proceso de forma automática y sencilla. Ese es su principal propósito, mapear SQL a objetos y viceversa.

“Las ORM son lentas”

Por supuesto, todo este proceso de mapeo es costoso, especialmente por el uso de funcionalidades como Reflection. Pero no debería ser un impacto tremendo en tu aplicación si usas la ORM para lo que corresponde.

Muchas personas intentan hacer reportería usando la ORM. Por favor, no hagas esto. La ORM se usa para tu capa de dominio/negocio, y no para reportería. Reportería nunca constituye lógica de negocio en el sentido estricto del término.

Volviendo al ejemplo del blog, imaginemos una conversación ficticia con un cliente. El cliente viene y dice “Quiero un blog que permita a múltiples autores publicar artículos de diversa índole”. Esa es la lógica de dominio, digna de trabajar en una ORM. Pero, cuando el cliente viene después y nos dice: “Necesito saber cuál es la tasa de publicación por autor quebrada por mes, o cuántas son las palabras promedio por artículo y por autor”, entonces esto ya no es lógica de negocio. Es un derivado de la lógica y las acciones del negocio, pero no lógica de negocio en sí.

Cuando usamos ORM para iterar sobre muchos resultados para sacar promedios, contar ocurrencias o cualquier otra operación similar, vamos a sentir el costo de las operaciones de hidratación de objetos, además del ridículo uso de memoria. No necesitamos hidratar objetos para sacar promedios, contar o hacer cálculos sobre datos.

“¡Ah! ¡Las ORM son extremadamente lentas e ineficientes! ¡Además no sirven para reportería!” No. Tú las estás usando para algo para lo cual no fueron diseñadas. Usa SQL normal para tus consultas de reportería y que esa funcionalidad no tenga nada que ver con tu capa de negocio. PDO tiene poderosas herramientas de buffering para este tipo de cosas.

Y ante todo, aprende la lección principal: conóce tu herramienta y sus limitaciones. No hay silver bullets en programación. Allí donde sea que hay beneficios, también hay compensaciones.

NOTA: Si realmente necesitas iterar sobre un montón de objetos para hacer reportería (cosa que no recomiendo para nada), puedes crear un hidratador personalizado en doctrine. Puedes usar esta técnica para implementarlo y será mucho más eficiente.

“Las ORM no logran abstraer SQL”

Una crítica más técnica contra las ORM es que conforman lo que se llama un leaky abstraction. Esto quiere decir que, debido a que su propósito es abstraernos de SQL y de que estamos usando una base de datos relacional, en realidad no logran hacerlo ya que sus abstracciones contienen jerga SQL. Hablamos del QueryBuilder y sus métodos de joins, selects e incluso, en el caso de Doctrine, de DQL mismo.

Pero, en realidad, no hay problema cuando una librería es leaky. De hecho, uno podría argumentar que un Cliente HTTP es extremadamente leaky, dada toda la jerga relacionada al protoloco que está involucrada, y de igual forma los usamos. Pero no hay problema en usar una abstracción leaky si la implementas debidamente, es decir, detrás de tus propias interfaces.

Escribiré un artículo sobre cómo abstraerse de las ORM próximamente, pero por ahora, basta decir que, en el caso de un blog, este contrato puede implementarse fácilemente y ser usado por toda tu aplicación, usando las abstracciones (tanto buenas o malas) de la ORM detrás de toda esta API:

interface AuthorRepository
{
    public function all(): AuthorCollection;

    public function add(Author $author): void;

    public function remove(Author $author): void;

    public function ofId(string $id): ?Author;
}

interface AuthorCollection implements Countable, IteratorAggregate
{
    public function count(): int;

    public function slice(int $offset, int $size): AuthorCollection;

    public function whereNameContains(string $contains): AuthorCollection;

    public function registeredAfter(DateTimeInterface $time): AuthorCollection;
}

Tu aplicación dependerá de este contrato, que efectivamente logra abstraer todo el SQL definitivamente de tu lógica de negocio.

Puedes implementar todo este contrato por medio de persistir en el filesystem si quisieras, o creando una implementación en memoria, y aún así sería transparente para toda tu aplicación.

La lección es sencilla: no hay abstracciones perfectas, sobretodo de persistencia. Pero si proteges la implementación de estas abstracciones detrás de tu propio contrato, jamás tendrás problema alguno.

Newer >>