Hay veces que llegamos a un punto en que intentamos abstraer demasiado las cosas y acabamos teniendo lo que se suele llamar una leaky abstraction. Se trata de casos en los cuales el mecanismo de abstracción no es efectivo, aquello que queremos abstraer acaba traspasando la capa de abstración y lo único que conseguimos es una complicación innecesaria.
Recientemente he padecido un caso claro de esto usando la API de consultas QueryOver
de NHibernate. QueryOver
está diseñado como una implementación del patrón Query Object aprovechando expresiones lambda para hacer static reflection y conseguir así que el API sea type-safe y refactor friendly.
QueryOver
intenta abstraer el acceso a la base datos, pero a la vez trata de mantener toda la potencia del SQL, por lo que en cuanto las consultas se complicando un poco acaba siendo un lío y la ganancia que tenemos por ser type-safe y refactor friendly no compensa la pérdida de legibilidad.
Veamos un ejemplo concreto bastante simpe. Supongamos que tenemos el típico modelo de clientes y pedidos y queremos lanzar una consulta para obtener el número de pedidos y el importe total pedido por cada cliente. La implementación con QueryOver
es algo así:
Customer customer = null; var stats = s.QueryOver<Order>() .JoinAlias(x => x.Customer, () => customer) .SelectList(lst => lst .SelectGroup(() => customer.Id) .SelectGroup(() => customer.Name) .SelectSum(x => x.Total) .SelectCount(x => x.Id)) .List<object[]>();
El resultado es bastante feo. Para poder hacer el equivalente al inner join
de forma type-safe, tenemos que declarar una variable de tipo Customer
que usamos únicamente como alias para usarla luego en las expresiones lambda. Además, para construir la lista de columnas a devolver se usa un builder (lst
) que simplifica un poco las cosas pero no acaba de resultar claro.
Si la misma consulta la lanzamos con el equivalente HQL, el lenguaje de consultas de NHibernate, tenemos:
var stats = s.CreateQuery(@"select c.Id, c.Name, sum(o.Total), count(o.Id) from Order o right join o.Customer c group by c.Id, c.Name") .List<object[]>();
Para cualquiera que esté acostumbrado a manejar SQL, está claro que la consulta resulta mucho más legible. Es cierto que no es type-safe no refactor friendly, pero en mi opinión la legibilidad lo compensa.
Parece que nuestra tendencia natural como desarrolladores es siempre abstraerlo todo, pero a veces hay que tener cuidado porque puede que estemos causando más problemas de los que solucionamos.
Pingback: Generación de Sql – ToleSql | 0 errors, 0 warnings