scieee AI-readable full text Open interactive document viewer

Rosetta: catálogo online para bibliotecas compatible con datos estructurados

Moreno López, José Miguel

Abstract

Departamento de Informática (Arquitectura y Tecnología de Computadores, Ciencias de la Computación e Inteligencia Artificial, Lenguajes y Sistemas Informáticos)

Full text

Universidad de Valladolid ESCUELA DE INGENIERÍA INFORMÁTICA DE SEGOVIA Grado en Ingeniería Informática de Servicios y Aplicaciones Rosetta: catálogo online para bibliotecas compatible con datos estructurados Alumno: José Miguel Moreno López Tutor: Juan José Álvarez Sánchez El código es como un chiste, si tienes que explicarlo es porque es malo CORY HOUSE Resumen Las bibliotecas actuales están tecnológicamente estancadas en el siglo pasado. La falta de innovación y de acuerdos entre instituciones ha provocado que los estándares y formatos utilizados para almacenar, representar e intercambiar metadatos apenas hayan cambiado desde la década de los noventa, y las dificultades para encontrar recursos bibliográficos en los buscadores web de estos centros siguen siendo las mismas. Rosetta es un nuevo tipo de software distribuido bajo licencia GPLv3 que plantea una alternativa a estos formatos obsoletos y permite a las instituciones ofrecer un OPAC moderno sin la necesidad de cambiar el sistema informático o el software ILS que estén utilizado. Palabras clave: bibliotecas, rosetta, ILS, OPAC, software Abstract Today’s libraries are technologically stuck in the last century. The lack of innovation and agreements between institutions has meant that the standards and formats used to store, represent and exchange metadata have barely changed since the nineties, and the difficulties in finding bibliographic resources in the web search engines of these centers continue to be the same. Rosetta is a new type of software distributed under the GPLv3 license that proposes an alternative to these obsolete formats and allows institutions to offer a modern OPAC without the need to change their computer systems or ILS software. Keywords: libraries, rosetta, ILS, OPAC, software ÍNDICE DE CONTENIDOS 1. Introducción 1 1.1. Motivación............................... 2 1.1.1. Problemática.......................... 3 1.2. Estadodelarte ............................. 4 1.2.1. Millennium ILS . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2.2. SierraLSP ........................... 5 1.2.3. ExLibrisAlma......................... 6 1.2.4. SirsiDynix Symphony . . . . . . . . . . . . . . . . . . . . . 7 1.2.5. Koha.............................. 8 1.2.6. Comparativa y conclusiones . . . . . . . . . . . . . . . . . 9 1.3. Objetivosyalcance........................... 9 1.4. Metodología .............................. 10 1.4.1. Herramientas y tecnologías utilizadas . . . . . . . . . . . . 11 1.4.2. Ciclo de trabajo . . . . . . . . . . . . . . . . . . . . . . . . 13 1.4.3. Planificación.......................... 14 1.4.4. Presupuesto .......................... 18 1.5. ContenidosdelCD........................... 20 2. Análisis 21 2.1. Características del sistema . . . . . . . . . . . . . . . . . . . . . . . 21 2.1.1. Árbol de características . . . . . . . . . . . . . . . . . . . . 22 2.2. Actores del sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.3. Requisitos de usuario . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.3.1. Diagrama de casos de uso . . . . . . . . . . . . . . . . . . . 24 2.3.2. Especificación de los casos de uso . . . . . . . . . . . . . . 25 2.4. Requisitos de información . . . . . . . . . . . . . . . . . . . . . . . 29 2.4.1. Diagrama entidad-relación . . . . . . . . . . . . . . . . . . 29 2.4.2. Diccionario de datos . . . . . . . . . . . . . . . . . . . . . . 30 2.5. Requisitos no funcionales . . . . . . . . . . . . . . . . . . . . . . . 33 iii ÍNDICE DE CONTENIDOS 3. Diseño e implementación 35 3.1. Entidades................................ 37 3.1.1. Jerarquía de entidades . . . . . . . . . . . . . . . . . . . . . 40 3.1.2. Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . 41 3.1.3. Relaciones entre entidades . . . . . . . . . . . . . . . . . . 43 3.1.4. Caché de entidades . . . . . . . . . . . . . . . . . . . . . . 45 3.2. Motordebúsqueda........................... 51 3.2.1. Consultas de búsqueda . . . . . . . . . . . . . . . . . . . . 53 3.2.2. Proveedores .......................... 57 3.2.3. Agrupación de resultados . . . . . . . . . . . . . . . . . . . 59 3.2.4. Datos estructurados con Wikidata . . . . . . . . . . . . . . 62 3.3. Interfazgráfica............................. 63 3.3.1. Extensión de Twig . . . . . . . . . . . . . . . . . . . . . . . 65 3.3.2. Mapas de ubicación . . . . . . . . . . . . . . . . . . . . . . 66 3.3.3. Internacionalización . . . . . . . . . . . . . . . . . . . . . . 68 4. Pruebas 71 5. Documentación 83 5.1. Manualdeusuario........................... 83 5.1.1. Iniciar una búsqueda . . . . . . . . . . . . . . . . . . . . . 83 5.1.2. Resultados de búsqueda . . . . . . . . . . . . . . . . . . . . 84 5.1.3. Página de detalle . . . . . . . . . . . . . . . . . . . . . . . . 85 5.2. Manual de despliegue . . . . . . . . . . . . . . . . . . . . . . . . . 86 5.2.1. Despliegue automático . . . . . . . . . . . . . . . . . . . . 86 5.2.2. Despliegue avanzado . . . . . . . . . . . . . . . . . . . . . 87 5.2.3. Despliegue manual . . . . . . . . . . . . . . . . . . . . . . 88 5.3. Manual de personalización . . . . . . . . . . . . . . . . . . . . . . 92 5.3.1. Configuración de la aplicación . . . . . . . . . . . . . . . . 92 5.3.2. Personalización de la interfaz web . . . . . . . . . . . . . . 94 5.3.3. Configuración de mapas . . . . . . . . . . . . . . . . . . . 96 6. Conclusiones 99 6.1. Posiblesmejoras ............................ 100 Índice de figuras 101 Índice de tablas 103 Índice de códigos 105 Referencias 107 Rosetta: catálogo público de bibliotecas iv CAPÍTULO 1 INTRODUCCIÓN Desde sus orígenes hace ya más de cuatro mil años, las bibliotecas han tenido como principal objetivo preservar el conocimiento humano y su historia. El afán de las últimas décadas por conseguir que estos centros sean más abiertos y accesibles se está viendo mermado por una creciente centralización de la información que está provocando, a su vez, una privatización de la misma. El resultado de este Trabajo Fin de Grado (el software “Rosetta”) pretende ser una materialización de la primera ley de la bibliotecología de Ranganathan: «los libros están para usarse» [1]. De nada sirve contar con un amplio catálogo de recursos bibliotecarios si estos no puedenserlocalizadosatravés de un buscador. Deigual forma,sielespíritumoderno de las bibliotecas consiste en que los usuarios accedan a todos los medios que estas pueden ofrecer, no tiene sentido que los metadatos (y en última instancia las bases de datos)esténcentralizadosen servidores deempresasprivadasqueposeenycontrolan sistemas informáticos propietarios. Rosetta es un aplicación web formada por un OPAC1y un broker ogestor de contenidos. Este último componente, hasta ahora poco conocido en el ámbito de bibliotecas, es un agente que conecta con múltiples fuentes de datos externas para obtener los resultados de las búsquedas que se realizan desde el OPAC. 1Online Public Access Catalog 1 INTRODUCCIÓN 1.2. ESTADO DEL ARTE Como el resto de competidores, también está sufriendo una transformación de aplicación de escritorio a servicio web (éste último llamado SymphonyWeb, sucesor de BLUEcloud), aunque sin tanto éxito o popularidad como Ex Libris y su plataforma Alma. 1.2.5. Koha Koha es una solución ILS libre y de código abierto surgida en enero delaño 2000 bajo licencia GPL (GNU Public License). Es una solución ILS en formato de aplicación web que, a diferencia del resto de productos basados en la nube, es auto-alojada (self-hosted). Es decir, cada biblioteca instala Koha en un servidor central al que se conectan los bibliotecarios y resto de clientes. Gracias a esta arquitectura (que es la misma utilizada por Rosetta) los datos siguen siendo gestionados por la institución a la que pertenecen y no pasan a ser propiedad de terceros, como en el caso de Ex Libris Alma. Figura 1.6: Página de inicio de administración de Koha Rosetta: catálogo público de bibliotecas 8 INTRODUCCIÓN 1.3. OBJETIVOS Y ALCANCE 1.2.6. Comparativa y conclusiones Millennium Sierra Alma Symphony Koha Desarrollador Innovative Interfaces Innovative Interfaces Ex Libris Group (ProQuest) SirsiDynix Koha Community Licencia Propietario Propietario Propietario Propietario GPL 3.0 Precio Pago único por licencia* Coste fijo anual* Coste por uso* Coste fijo anual* Gratuito Lanzamiento 1997 2013 2011 2006 2000 Tipo ILS ILS/LSP LSP ILS/LSP Self-hosted En desarrollo - ✓ ✓ - ✓ Adquisiciones ✓✓✓✓✓ Catalogación ✓✓✓✓✓ Circulación ✓✓✓✓✓ P. periódicas ✓✓✓✓✓ OPAC ✓✓✓✓✓ GUI moderna - ✓ ✓ - - Fácil de usar - ✓ ✓ - - API préstamos ✓✓✓✓✓ API recursos - ✓ ✓ - ✓ Tabla 1.1: Comparativa de software para bibliotecas El asterisco (*) mencionado en la tabla anterior se refiere a la imposibilidad de conocer el rango de precios del producto al variar con cada contrato y a la existencia de acuerdos de confidencialidad que impiden revelar dicha información. 1.3. Objetivos y alcance El objetivo de este trabajo es el desarrollo de un OPAC para bibliotecas que siente las bases para una nueva generación de sistemas informáticos bibliotecarios. Para lograr tal objetivo, se acota el alcance del proyecto dentro de los siguientes enunciados: Rosetta: catálogo público de bibliotecas 9 INTRODUCCIÓN 1.4. METODOLOGÍA Crear un software retrocompatible con los ILS más populares del mercado y adaptable a cualquier formato existente e imaginable Estructurar la información de forma que se puedan crear relaciones entre los datos (datos estructurados) Ofrecer una interfaz web para buscar información en el catálogo de la aplicación Permitir a los administradores de las plataformas que usen este software poder configurar las fuentes de datos y personalizar la interfaz web fácilmente 1.4. Metodología Para la realización de este proyecto se ha empleado una metodología incremental en la que se ha ido construyendo el producto final progresivamente al añadir funcionalidades en intervalos de tiempo periódicos denominados “incrementos”. Todas estas iteraciones o incrementos constan de cuatro fases (análisis, diseño, implementación y pruebas) y siempre terminan en un entregable del producto para su evaluación (pre-releases onightlies). Gracias al uso de un sistema de integración continua como Travis CI, la fase de pruebas y la creación y despliegue al servidor de staging de las versiones alpha está prácticamente automatizada y apenas requiere de intervención humana, agilizando los procesos. Antes de esta fase principal de incrementos y debido a la naturaleza novedosa del proyecto, se utilizó un modelo de prototipos para determinar la mejor arquitectura para crear el producto final. Al ser un proceso de desarrollo evolutivo, se crearon múltiples programas sencillos, mínimamente funcionales, en muy poco tiempo para probar formas de lograr el objetivo deseado. Gracias a los conocimientos adquiridos durante la fase de prototipado, se pudo diseñar y desarrollar un sistema que cumpliera con los requisitos de nuestro proyecto en la siguiente etapa. Los incrementos realizados durante la fase incremental del proyecto fueron los siguientes: Primer incremento (v0.1.0-alpha): crear código base del proyecto utilizando el framework Symfony. Incluye la configuración de Webpack para compilar los recursos estáticos y de Travis CI para la integración continua, así como Rosetta: catálogo público de bibliotecas 10 INTRODUCCIÓN 1.4. METODOLOGÍA el desarrollo de las entidades mínimas y el proveedor de Z39.50 para poder empezar a probar la aplicación. Segundo incremento (v0.2.0-alpha): añadir compatibilidad con Docker, configurado despliegue automático hacia el servidor de staging, crear resto de proveedores y entidades, añadir algoritmo de agrupación de resultados e implementar las fuentes externas en el motor de búsqueda. Tercer incremento (v0.3.0-alpha): crear métodos de combinación de entidades tras haber implementado el algoritmo de agrupación, añadir soporte para portadas o vistas previas de entidades, y crear interfaces gráficas de inicio y resultados de búsqueda. Cuarto incremento (v0.4.0-alpha): crear normalizador, añadir soporte para Wikidata, mapeado de entidades para Doctrine ORM y servicio de caché. Quinto incremento (v0.5.0-alpha): añadir página de detalles de una entidad con soporte para plantillas jerárquicas, crear iconos de resultados sin vista previa e implementar soporte para internacionalización (principalmente traducciones). Sexto incremento (v1.0.0-beta): último incremento previsto, con duración hasta el final del proyecto, pensado para añadir las últimas funcionalidades y otras no previstas. Incluye crear servicio de mapas. Todos estos incrementos incluyen sus respectivas fases de análisis y diseño en base a los resultados de la fase anterior, y de pruebas con respecto a la fase de implementación de la iteración actual, así como arreglar fallos. 1.4.1. Herramientas y tecnologías utilizadas Dentro de este subapartado podemos distinguir entre herramientas utilizadas para poder lograr el objetivo final del proyecto (como IDEs, VCS8, editores de texto, etc.) y tecnologías que hacen funcionar el producto final (frameworks, lenguajes y librerías). Las herramientas utilizadas son las siguientes: PhpStorm: IDE desarrollado por la compañía JetBrains especializado en proyectos web que utilicen PHP, también compatible con Node.js y otras tecnologías similares. Es software propietario aunque ofrece licencias gratuitas a estudiantes universitarios. 8Version Control Systems Rosetta: catálogo público de bibliotecas 11 INTRODUCCIÓN 1.4. METODOLOGÍA GitHub: plataforma online para el control de versiones de proyectos de software mediante Git. También ofrece otras funcionalidades como gestión de tareas o pizarras Kanban. Travis CI: plataforma de integración continua en la nube compatible con GitHub que permite automatizar procesos de testing y despliegue, entre otros. Es gratuito para proyectos de código abierto y estudiantes universitarios. Codefactor: herramienta online de revisión de código automatizada para controlar la calidad del código de un producto de software y detectar vulnerabilidades y errores, como anti-patrones. Cada vez que hay cambios en el repositorio central del proyecto en GitHub analiza los ficheros y otorga una puntuación que representa la calidad del producto. Dependabot: complemento de GitHub que, al añadirlo a un proyecto y configurarlo, crear pull-requests automáticos cuando existan actualizaciones de dependencias para evitar paquetes obsoletos o fallos de seguridad. Scaleway: proveedor de servicios en la nube utilizado para alojar una instancia virtual de una máquina con Debian 9 que sirva de servidor de staging. Texmaker: editor de texto para documentos de TeX con vista previa integrada y gestión de proyectos distribuidos en múltiples ficheros. Utilizado para escribir la memoria del TFG. Draw.io: editor de diagramas gratuito con versión en la nube y de escritorio que permite exportar las figuras finales a múltiples formatos, entre ellos PDF, facilitando la inserción de imágenes vectoriales en documentos TeX. Las tecnologías empleadas son las siguientes: PHP 7: lenguaje de programación multi-paradigma pensado para el desarrollo de aplicaciones web. JavaScript (ES6): lenguaje de programación interpretado utilizado para el desarrollo web tanto en el lado del cliente como en el lado del servidor. ECMAScript 2016 (ES6) es su versión más reciente. SASS: lenguaje de hoja de estilos basado en CSS que añade nuevas funcionalidades a este último, como variables que resuelve en tiempo de compilación o anidamiento de reglas. Node.js: entorno de ejecución basado en ECMAScript habitualmente utilizado como backend de aplicaciones web. Para este proyecto se emplea para compilar Rosetta usando Webpack. Rosetta: catálogo público de bibliotecas 12 INTRODUCCIÓN 1.4. METODOLOGÍA Symfony 4:framework de PHP para el desarrollo de aplicaciones web basado en un conjunto de componentes reutilizables denominados bundles que buscan obtener el mayor rendimiento posible incluso en máquinas con prestaciones bajas. Webpack 4: Librería para Node.js basada en módulos que permite compilar recursos estáticos de una aplicación y minificarlos (comprimirlos). Bootstrap 4: kit de herramientas (toolkit) para el diseño de interfaces gráficas de aplicaciones web programado en SASS, JavaScript y HTML. Su repositorio oficial dispone de un paquete de Node.js y es fácilmente integrable con Webpack. YAZ: extensión de PHP para la conexión a servidores de Z39.50/SRW/SRU basado en el YAZ toolkit de la empresa Index Data. Utilizado para conectar con fuentes de datos usando el protocolo Z39.50. Docker: plataforma de virtualización de software que permite la creación y despliegue de paquetes denominados “contenedores” facilitando la portabilidad y la creación de entornos de ejecución de aplicaciones. nginx: servidor web recomendado para ejecutar Rosetta y el utilizado por defecto en la imagen de Docker Compose del proyecto. Ofrece un alto rendimiento sin necesidad de un consumo de recursos excesivo. 1.4.2. Ciclo de trabajo La metodología empleada durante el desarrollo del producto de este trabajo se basa en una versión simplificada del gitflow workflow, consistente en crear commits de Git en una rama de desarrollo llamada “develop” y en hacer un merge de esa rama hacia “master” con cada nueva versión o release. Gracias a este flujo, se pueden automatizar procesos con relativa facilidad, como la puesta en pre-producción (staging) o el control de calidad. Figura 1.7: Esquema de ramas de gitflow workflow [5] Rosetta: catálogo público de bibliotecas 13 INTRODUCCIÓN 1.4. METODOLOGÍA En el caso de este trabajo, el control de calidad es realizado por la plataforma Codefactor9, que cada vez que se envían commits a una rama analiza los cambios y otorga al repositorio una puntuación sobre diez que determina la calidad del código (a mayor puntuación, mayor calidad). Esta herramienta se puede configurar para detectar posibles fallos de seguridad, métodos complejos, anti-patrones y code-smells. A fecha de entrega del producto final, la puntuación otorgada por Codefactor es de 9,9 sobre 10 (grado A). En cuanto a las pruebas y el despliegue automático, se ha utilizado Travis CI10 para ejecutar una serie de comandos cada vez que se detectaran cambios en el repositorio central del proyecto. Entre las tareas realizadas por la herramienta se encuentran validación de dependencias, ficheros de configuración, plantillas y mapeos de Doctrine ORM, búsqueda de vulnerabilidades, verificación de traducciones, y ejecución de tests unitarios. Adicionalmente, al detectar cambios en la rama “master” Travis CI realiza un despliegue hacia el servidor de staging. Es relevante mencionar que para poder probar la aplicación con el catálogo de Almena de la Universidad de Valladolid fue necesario establecer un servidor VPN en las instalaciones de la Escuela de Ingeniería Informática de Segovia (previa autorización de Secretaría General), ya que el servidor Z39.50 de Almena autentica mediante dirección IP en vez de par usuario-contraseña. 1.4.3. Planificación El proyecto se inicia en febrero de 2019 y se estima finalice a principios de mayo (misma duración que el cuatrimestre en la Escuela de Ingeniería Informática de Segovia), contando con una sola persona. No se estima una duración superior a cuatro meses pese a lo innovador del proyecto debido a la experiencia adquirida con Bibliozambrano en los años anteriores y al conocimiento de antemano de las librerías y herramientas a usar. Se espera trabajar a jornada completa (8 horas diarias) de lunes a viernes, con posibilidad de recuperar horas o trabajo los fines de semana. El diagrama de Gantt estimado del proyecto es el mostrado en la figura 1.8, en el que se aprecia una fecha inicial del 4 de febrero de 2019 con un final previsto el 6 de mayo de 2019. Las tareas de planificación se dividen, principalmente, en tres grandes grupos: formalización,prototipado yfase incremental. Del último grupo de tareas, es destacable mencionar la sexta iteración que posee una duración estimada de aproximadamente un mes (cuatro semanas), en la que se pretende acomodar nuevas funcionalidades no previstas, afinar determinados detalles del producto final y completar la memoria de este trabajo. 9Véase https://www.codefactor.io 10Véase https://travis-ci.com Rosetta: catálogo público de bibliotecas 14 INTRODUCCIÓN 1.4. METODOLOGÍA Si comparamos la evolución real del proyecto (figura 1.9) con el diagrama de Gantt estimado, se aprecian retrasos en su formalización a la hora de obtener permiso de la Secretaría General de la Universidad de Valladolid para acceder al servidor Z39.50 de Almena y poder probar Rosetta en un entorno pseudo-real. Por suerte, esta incidencia no supone un aumento en la duración total del proyecto. Lo mismo sucede en la fase de prototipado en lo relativo a la obtención de manuales de uso y de desarrollo de Millennium ILS®. Lamentablemente, no es posible obtener acceso a estos últimos pero sí a los primeros, lo que retrasa dicha tarea en más de una semana sin afectar a la duración de la fase. Por último, los dos retrasos que sí provocan un aumento de la duración total del proyecto son el segundo y el cuarto incremento de la fase incremental. En el caso del segundo incremento se debe a la falta de documentación y formación previa para el despliegue de la aplicación como contenedor de Docker. En lo relativo al cuarto incremento, se alarga una semana más de lo previsto debido a la necesidad de implementar un tipo de relación especial para el caché de entidades que Doctrine ORM no proporciona de forma nativa y que no estaba contemplado desde un principio (véase página 48 para más información sobre la implementación final). Estimación de horas Tomando como referencia el diagrama de Gantt estimado, se obtiene un total de 13 semanas que, a razón de 5’5 días/semana, equivalen a 71’5 días o 572 horas, una cifra más que acertada teniendo en cuenta que un Trabajo Fin de Grado se valora en 24 ECTS11, o lo que es lo mismo, 600 horas. Fijándonos en la distribución real del trabajo, se han empleado 15 semanas para terminar el proyecto que, siguiendo los mismos factores de conversión del caso anterior (mismos días por semana a jornada completa) obtenemos un total de 660 horas reales (11 días más de lo previsto). 11European Credit Transfer and Accumulation System, equivale a 25 horas de trabajo Rosetta: catálogo público de bibliotecas 15 INTRODUCCIÓN 1.4. METODOLOGÍA L M X J V S D 25 marzo - 31 marzo L M X J V S D 1 abril - 7 abril L M X J V S D 8 abril - 14 abril L M X J V S D 15 abril - 21 abril L M X J V S D 22 abril - 28 abril L M X J V S D 29 abril - 5 mayo Fecha de inicio Fecha de finNombre de la tarea Completar la ejecución del proyecto 04/02/2019 05/05/2019 1. Formalización y preparación del proyecto 04/03/2019 25/03/2019 1.2. Obtener autorización de Secretaría General 04/03/2019 17/03/2019 1.3. Envío autorización de acceso a biblioteca UVa 18/03/2019 18/03/2019 1.4. Despliegue servidor VPN para acceso Almena 25/03/2019 25/03/2019 1.1. Despliegue servidor de staging 04/03/2019 06/03/2019 2. Fase de prototipado 04/02/2019 03/03/2019 2.2. Estudiar y recabar información de manuales 11/02/2019 03/03/2019 2.3. Diseño de prototipos 18/02/2019 03/03/2019 2.1. Obtención de manuales de Millennium ILS 04/02/2019 17/02/2019 3. Fase incremental 04/03/2019 05/05/2019 3.1. Primer incremento (v0.1.0-alpha) 04/03/2019 10/03/2019 3.2. Segundo incremento (v0.2.0-alpha) 11/03/2019 17/03/2019 3.3. Tercer incremento (v0.3.0-alpha) 18/03/2019 24/03/2019 3.4. Cuarto incremento (v0.4.0-alpha) 25/03/2019 31/03/2019 3.5. Quinto incremento (v0.5.0-alpha) 01/04/2019 07/04/2019 3.6. Sexto incremento (v1.0.0-beta) 08/04/2019 05/05/2019 L M X J V S D 4 febrero - 10 febrero L M X J V S D 11 febrero - 17 febrero L M X J V S D 18 febrero - 24 febrero L M X J V S D 25 febrero - 3 marzo L M X J V S D 4 marzo - 10 marzo L M X J V S D 11 marzo - 17 marzo L M X J V S D 18 marzo - 24 marzo L M X J V S D 6 mayo - 12 mayo L M X J V S D 13 mayo - 19 mayo Tar. Tot. 1. 1.2. 1.3. 1.4. 1.1. 2. 2.2. 2.3. 2.1. 3. 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. Tar. Tot. 1. 1.2. 1.3. 1.4. 1.1. 2. 2.2. 2.3. 2.1. 3. 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. Figura 1.8: Diagrama de Gantt estimado Rosetta: catálogo público de bibliotecas 16 INTRODUCCIÓN 1.4. METODOLOGÍA L M X J V S D 25 marzo - 31 marzo L M X J V S D 1 abril - 7 abril L M X J V S D 8 abril - 14 abril L M X J V S D 15 abril - 21 abril L M X J V S D 22 abril - 28 abril L M X J V S D 29 abril - 5 mayo Fecha de inicio Fecha de finNombre de la tarea Completar la ejecución del proyecto 04/02/2019 19/05/2019 1. Formalización y preparación del proyecto 04/03/2019 05/04/2019 1.2. Obtener autorización de Secretaría General 14/03/2019 28/03/2019 1.3. Envío autorización de acceso a biblioteca UVa 29/03/2019 29/03/2019 1.4. Despliegue servidor VPN para acceso Almena 05/04/2019 05/04/2019 1.1. Despliegue servidor de staging 04/03/2019 06/03/2019 2. Fase de prototipado 04/02/2019 03/03/2019 2.2. Estudiar y recabar información de manuales 11/02/2019 03/03/2019 2.3. Diseño de prototipos 18/02/2019 03/03/2019 2.1. Obtención de manuales de Millennium ILS 04/02/2019 27/02/2019 3. Fase incremental 04/03/2019 19/05/2019 3.1. Primer incremento (v0.1.0-alpha) 04/03/2019 10/03/2019 3.2. Segundo incremento (v0.2.0-alpha) 11/03/2019 24/03/2019 3.3. Tercer incremento (v0.3.0-alpha) 25/03/2019 31/03/2019 3.4. Cuarto incremento (v0.4.0-alpha) 01/04/2019 14/04/2019 3.5. Quinto incremento (v0.5.0-alpha) 15/04/2019 21/04/2019 3.6. Sexto incremento (v1.0.0-beta) 22/04/2019 19/05/2019 L M X J V S D 4 febrero - 10 febrero L M X J V S D 11 febrero - 17 febrero L M X J V S D 18 febrero - 24 febrero L M X J V S D 25 febrero - 3 marzo L M X J V S D 4 marzo - 10 marzo L M X J V S D 11 marzo - 17 marzo L M X J V S D 18 marzo - 24 marzo L M X J V S D 6 mayo - 12 mayo L M X J V S D 13 mayo - 19 mayo Tar. Tot. 1. 1.2. 1.3. 1.4. 1.1. 2. 2.2. 2.3. 2.1. 3. 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. Tar. Tot. 1. 1.2. 1.3. 1.4. 1.1. 2. 2.2. 2.3. 2.1. 3. 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. Figura 1.9: Diagrama de Gantt real Rosetta: catálogo público de bibliotecas 17 ANÁLISIS 2.3. REQUISITOS DE USUARIO 2.3.1. Diagrama de casos de uso Interfaz web (OPAC) «extends» UC-01: Realizar búsqueda UC-02: ver detalles de entidad UC-03: ver ubicación de ejemplar «include» UC-04: ver entidad en página externa Usuario «include» Figura 2.2: Diagrama de casos de uso de usuario Como se ve en la figura 2.2, el usuario del OPAC puede realizar una búsqueda en la plataforma (UC-01). Habitualmente, una vez completada la búsqueda, el usuario elegirá un resultado para ver más información (UC-02) aunque también es posible acceder a la página de detalles de una entidad desde su dirección URL. Desde la página de detalles, el usuario puede hacer click sobre un ejemplar para obtener una vista previa del mapa con su ubicación (UC-03) o pulsar en uno de los enlaces externos para ver la información sobre la entidad en otra fuente (UC-04). Rosetta: catálogo público de bibliotecas 24 ANÁLISIS 2.3. REQUISITOS DE USUARIO 2.3.2. Especificación de los casos de uso CU-01 Realizar búsqueda Versión 1.0 Autor José Miguel Moreno López Requisitos asociados UR-01: un usuario podrá realizar búsquedas desde el OPAC Actor Usuario Descripción Un usuario puede iniciar una nueva búsqueda en la plataforma web a partir de una consulta y de unos filtros, estos últimos opcionales. Precondiciones N/A Flujo normal 1. El usuario accede a la página de inicio o a la página de resultados de una búsqueda. 2. El usuario escribe la consulta de búsqueda en la barra de búsqueda. 3. Opcionalmente, el usuario filtra por base de datos del desplegable de la barra de búsqueda. 4. El usuario hace click en “Buscar” o pulsa INTRO en el teclado. 5. El sistema muestra los resultados de la búsqueda. Postcondiciones N/A Excepciones No hay resultados de búsqueda: se mostrará un mensaje de error animando al usuario a utilizar otros términos de búsqueda. Frecuencia Alta: la interfaz web tiene como principal objetivo permitir al usuario realizar búsquedas en el catálogo Importancia Alta Prioridad Alta Observaciones N/A Tabla 2.1: Caso de uso “realizar búsqueda” (CU-01) Rosetta: catálogo público de bibliotecas 25 ANÁLISIS 2.3. REQUISITOS DE USUARIO CU-02 Ver detalles de entidad Versión 1.0 Autor José Miguel Moreno López Requisitos asociados UR-02: un usuario podrá ver los detalles de una entidad Actor Usuario Descripción El usuario podrá consultar los detalles de una entidad, como sus ejemplares, relaciones con otras entidades, etc. Precondiciones N/A Flujo normal Existen dos posibles flujos para este caso de uso: 1a. El usuario hace click sobre uno de los resultados de una búsqueda. 1b. El usuario abre un enlace ya conocido de una entidad. 2. El sistema muestra la página de detalles de la entidad solicitada. Postcondiciones N/A Excepciones No existe la entidad o ha sido eliminada: se mostrará un mensaje de error animando al usuario a utilizar el buscador. Frecuencia Alta: la consecuencia de una búsqueda suele ser ver la página de detalles Importancia Alta Prioridad Alta Observaciones N/A Tabla 2.2: Caso de uso “ver detalles de entidad” (CU-02) Rosetta: catálogo público de bibliotecas 26 ANÁLISIS 2.3. REQUISITOS DE USUARIO CU-03 Ver ubicación de ejemplar Versión 1.0 Autor José Miguel Moreno López Requisitos asociados UR-03: un usuario podrá ver la ubicación de un ejemplar Actor Usuario Descripción Para aquellos ejemplares cuya ubicación sea conocida, la aplicación mostrará un mapa con su ubicación física. Precondiciones El usuario debe estar en la página de detalles de una entidad. La entidad debe ser prestable o consultable. Flujo normal 1. El usuario hace click sobre el ejemplar del listado de ejemplares que quiere consultar. 2.El sistemamuestraen lacolumnadela derecha de la pantalla el mapa con las estanterías en las que se encuentra el ejemplar resaltadas. Postcondiciones N/A Excepciones No hay ejemplares con ubicación conocida: en este caso no se renderizará ningún mapa. Frecuencia Media: no todas las entidades tiene ejemplares ni todos los ejemplares tienen ubicación conocida Importancia Alta Prioridad Media Observaciones El sistema mostrará un cursor especial (una mano) para dar feedback al usuario sobre cuándo es posible hacer click en un ejemplar para renderizar su mapa. Tabla 2.3: Caso de uso “ver ubicación de ejemplar” (CU-03) Rosetta: catálogo público de bibliotecas 27 ANÁLISIS 2.3. REQUISITOS DE USUARIO CU-04 Ver entidad en página externa Versión 1.0 Autor José Miguel Moreno López Requisitos asociados UR-04: un usuario podrá acceder a los datos de una entidad en otra página externa Actor Usuario Descripción Un usuario puede acceder a las fuentes externas que utilizó la aplicación para generar la página de detalles de una entidad. Precondiciones El usuario debe estar en la página de detalles de una entidad. Flujo normal 1. El usuario hace click sobre el nombre del proveedor externo de la lista de enlaces externos al que quiera acceder. 2. El sistema abre en una nueva pestaña/ventana el enlace externo. Postcondiciones N/A Excepciones No hay enlaces externos: en este caso no se renderizará la sección de enlaces externos. Frecuencia Baja: no es habitual que un usuario quiera ver la misma información desde otra fuente en este contexto Importancia Baja Prioridad Media Observaciones N/A Tabla 2.4: Caso de uso “ver entidad en página externa” (CU-04) Rosetta: catálogo público de bibliotecas 28 ANÁLISIS 2.4. REQUISITOS DE INFORMACIÓN 2.4. Requisitos de información Los requisitos de información son aquellos conjuntos y especificaciones de datos necesarias para que el sistema puedan funcionar o para la correcta finalización del proyecto. IR-01: el sistema almacenará en caché las entidades que sean resultado de las búsquedas IR-02: el sistema permitirá guardar un registro de error y de eventos IR-03: el sistema almacenará la configuración de la aplicación IR-04: el sistema guardará versiones compiladas de las vistas en un directorio de almacenamiento temporal 2.4.1. Diagrama entidad-relación AbstractEntity Holding se relaciona con tieneThingBookPerson type type identifica a Identifier type Organization tiene Figura 2.3: Diagrama entidad-relación Pese a la complejidad de la aplicación, el diagrama E/R de su base de datos es relativamente sencillo. La principal entidad es “AbstractEntity”, de la que parten casi todas las demás (“Organization”, “ Person”, “Book” y “Thing”). Esta entidad tiene un discriminante que determina el tipo de hijo, por tanto, en la práctica solo se utilizará una tabla para almacenar esta información. Tanto “Book” como “Thing” puede tener varios ejemplares (se relacionan con la entidad “Holding”), que a efectos de lógica de aplicación se implementa con un trait. Cualquier hijo de “AbstractEntity” se indexa a través de sus relaciones con la entidad “Identifier” y puede relacionarse con otros hijos de la misma entidad. Rosetta: catálogo público de bibliotecas 29 ANÁLISIS 2.4. REQUISITOS DE INFORMACIÓN 2.4.2. Diccionario de datos El producto final de este trabajo no necesita de un análisis previo de los requisitos de información relativos al diccionario de datos. El motivo es que este tipo de análisis está obsoleto a día de hoy pues solo tiene sentido cuando se trabaja con bases de datos relacionales. Aunque Rosetta utiliza por defecto el motor MariaDB (basado en MySQL) nunca llegar a tocar lógica a tan bajo nivel, sino que existe un agente intermedio (Doctrine ORM) que traduce una jerarquía de clases (orientado a objetos) a sentencias SQL y viceversa. Rosetta es, por tanto, database agnostic: desconoce la base de datos que se encuentra por debajo y le es irrelevante. Aún así, en este apartado se incluye la definición del diccionario de datos que Doctrine ORM genera por defecto al utilizar una base de datos relacional, no sin antes volver a hacer hincapié en que esta puede cambiar en función de la arquitectura final elegida por el administrador del sistema. De las tablas que se muestran a continuación, cada una de ellas se corresponde con una tabla de la base de datos e incluye la siguiente información: Atributo: nombre de la columna en la tabla Tipo: tipo primario de dato en el que está almacenado el valor de la columna Tamaño: longitud reservada para almacenarlos valores de la columna, pueden ser bytes o dígitos y tratarse de una longitud fija o máxima dependiendo del tipo de dato Flags: booleanos que especifican propiedades de la columna •PK: es clave primaria •FK: es clave foránea •NL: es nullable (puede ser nulo) •UQ: es unique (único) •AI: es AUTO_INCREMENT •US: es unsigned (no admite números negativos) Rosetta: catálogo público de bibliotecas 30 ANÁLISIS 2.4. REQUISITOS DE INFORMACIÓN Atributo Tipo Tamaño PK FK NL UQ AI US id int 10 ✓ - - ✓ ✓ ✓ creation_date datetime - - - - - - - modification_date datetime - - - - - - - slug varchar 300 - - - - - - image_url varchar 2083 - - ✓ - - - entity_type varchar 5 - - - - - - title varchar 1024 - - ✓ - - - legal_deposits longtext - - - ✓ - - - pub_year smallint 5 - - ✓ - - ✓ pub_month smallint 5 - - ✓ - - ✓ pub_day smallint 5 - - ✓ - - ✓ languages longtext - - - ✓ - - - num_of_pages smallint 5 - - ✓ - - ✓ num_of_volumes smallint 5 - - ✓ - - ✓ name varchar 255 - - ✓ - - - foundation_date date - - - ✓ - - - website varchar 300 - - ✓ - - - firstname varchar 255 - - ✓ - - - lastname varchar 255 - - ✓ - - - description varchar 256 - - ✓ - - - birth_date date - - - ✓ - - - death_date date - - - ✓ - - - subtitle varchar 3072 - - ✓ - - - signature_url varchar 2083 - - ✓ - - - birth_place varchar 256 - - ✓ - - - death_place varchar 256 - - ✓ - - - Tabla 2.5: Diccionario de datos de “entity” Rosetta: catálogo público de bibliotecas 31 ANÁLISIS 2.4. REQUISITOS DE INFORMACIÓN Atributo Tipo Tamaño PK FK NL UQ AI US id int 10 ✓ - - ✓ ✓ ✓ entity_id int 10 - ✓ ✓ - - ✓ call_number varchar 64 - - ✓ - - - loanable tinyint 1 - - - - - - available tinyint 1 - - - - - - location_name varchar 128 - - ✓ - - - online_url varchar 2083 - - ✓ - - - source_id varchar 16 - - ✓ - - - Tabla 2.6: Diccionario de datos de “holding” Atributo Tipo Tamaño PK FK NL UQ AI US id varchar 54 ✓ - - ✓ - - entity_id int 10 - ✓ ✓ - - ✓ type smallint 5 - - - - - ✓ value varchar 50 - - - - - - Tabla 2.7: Diccionario de datos de “identifier” Atributo Tipo Tamaño PK FK NL UQ AI US type smallint 5 ✓ - - ✓ - ✓ from_id int 10 ✓ - - ✓ - ✓ to_id int 10 ✓ - - ✓ - ✓ Tabla 2.8: Diccionario de datos de “relation” Rosetta: catálogo público de bibliotecas 32 ANÁLISIS 2.5. REQUISITOS NO FUNCIONALES 2.5. Requisitos no funcionales Non-Functional Requirements (NFR) son aquellas restricciones del sistema que afectan a su desarrollo o comportamiento. Dentro de este grupo las más habituales son los atributos de calidad (QA). Usabilidad QA-01: la aplicación web deberá ser fácil de usar QA-02: la aplicación web cumplirá con los principios de responsive design QA-03: la aplicación informará al usuario de errores de forma clara y sin proporcionar información innecesaria QA-04: la aplicación web se localizará a inglés de los Estados Unidos (en-US) y español de España (es-ES) Rendimiento QA-05: las consultas hacia fuentes de datos externas se paralelizarán en la medida de lo posible QA-06: los resultados de una búsqueda se guardarán en caché para reducir el número de llamadas externas Disponibilidad QA-07: el servidor de staging tendrá un SLA de tres nueves (99,9 % de disponibilidad) Seguridad QA-08: el sistema sanetizará todas las entradas proporcionadas por el usuario QA-09: los datos sensibles de configuración se guardarán como variables de entorno QA-10: las consultas a la base de datos se escribirán en DQL (Doctrine Query Language) para evitar ataques de inyección Rosetta: catálogo público de bibliotecas 33 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES Entidad Ejemplares se relaciona con tieneObraPersona[...] Tipo Tipo Figura 3.5: Arquitectura simplificada de Rosetta 3.1.1. Jerarquía de entidades En Rosetta, todas las entidades parten de la clase AbstractEntity que, como indica su nombre, es abstracta y por tanto no puede ser instanciada. Un AbstractEntity se caracteriza por tener un identificador numérico entero (utilizado con fines de caché) un slug6y fechas de creación y última modificación. Las clases que heredan AbstractEntity se caracterizan por ser relacionables e identificables como se verá más adelante en el subapartado 3.1.3. Esta última cualidad juega un papel clave dentro de la aplicación a la hora de buscar información duplicada y relaciones entre entidades. AbstractEntity AbstractWork PersonThing Organization HoldingsTraitBook Figura 3.6: Diagrama de clases de entidades 6Un slug es la parte de una dirección URL que se refiere una página o post específico Rosetta: catálogo público de bibliotecas 40 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES Por debajo de AbstractEntity encontramos las clases finales Organization, Person yThing, así como la clase abstracta AbstractWork. Una instancia de Organization representa a un colectivo o grupo de personas (generalmente en el contexto de bibliotecas hablamos una editorial), mientras que Person sirve para guardar la información de un individuo concreto (un autor, ilustrador, fundador de una organización, etc.). AbstractWork es un tipo más concreto de AbstractEntity utilizado para representar obras, entendiendo por obra a cualquier creación artística que puede ser consultada o prestada. Por defecto, la única entidad final que implementa AbstractWork es Book, aunque otros tipos de obras que extenderían dicha clase abstracta serían películas, álbumes de canciones y sencillos, artículos científicos, prensa y revistas, etc. La clase Thing es un tanto especial ya que comparte algunas cualidades con AbstractWork pese a ser una entidad completamente distinta. Para Rosetta, una “cosa” o Thing es un objeto físico que puede ser consultado o prestado como, por ejemplo, un portátil o una pizarra que se prestan por horas. Tanto los hijos de AbstractWork como la entidad Thing tienen asociados una serie de ejemplares que son los objetos realmente consultables o prestables. Para implementar esta multi-herencia se hace uso del trait HoldingsTrait, que otorga a una entidad la capacidad de contener ejemplares. 3.1.2. Identificadores Tal y como se ha mencionado antes, las clases que implementan AbstractEntity son identificables. Esto, a afectos de implementación, supone que una entidad abstracta se asocia a una serie de identificadores. Estos identificadores se representan en la clase Identifier y almacenan un par tipo-valor: el tipo de identificador y su correspondiente valor. Por ejemplo, para un libro que se identifica por un código ISBN, la entidad tendría asociado un identificador del tipo “ISBN” con el valor correspondiente al número estándar del libro. Habitualmente, una entidad posee un identificador para cada tipo o, en algunos casos, varios (una misma edición de un libro puede tener varios ISBN si ésta se encuentra recogida en varios tomos). Rosetta: catálogo público de bibliotecas 41 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES Para poder trabajar con identificadores, los cuatro métodos principales que proporciona AbstractEntity son: AbstractEntity::getIdentifiers(): devuelve las instancias de identificadores asociados a la entidad AbstractEntity::addIdentifier($identifier): asocia una instancia de identificador a la entidad AbstractEntity::getIdsOfType($type): devuelve los valores de los identificadores de la entidad que sean del tipo indicado AbstractEntity::getFirstIdOfType($type): igual que el método anterior, devolviendo solo el primer valor public function getIdentifiers() { return $this->identifiers; } public function addIdentifier(Identifier $identifier):self { $key =(string) $identifier; $this->identifiers->set($key,$identifier); return $this; } public function getIdsOfType(int $type):array { $res =[]; foreach ($this->identifiers as $identifier) { if ($identifier->getType() == $type) { $res[] =$identifier->getValue(); } } return $res; } public function getFirstIdOfType(int $type): ?string { foreach ($this->identifiers as $identifier) { if ($identifier->getType() == $type) { return $identifier->getValue(); } } return null; } Código 2: Métodos de identificadores de AbstractEntity Rosetta: catálogo público de bibliotecas 42 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES Además de estos métodos, determinadas entidades disponen de atajos que solo tienen sentido en el contexto en el que están siendo usados, como en el caso de la entidad Book desde la que se puede llamar a Book::getIsbns() o Book::addIsbn(string $isbn), entre otros. Los identificadores y otras estructuras de lista se almacenan en Rosetta utilizando colecciones de Doctrine (ArrayCollection) en vez de arrays, pues facilita su serialización e instanciación desde y hacia el caché (véase subapartado 3.1.4). Por defecto, Rosetta provee de los siguientes tipos de identificadores: INTERNAL: para referirse a la numeración privada de un proveedor (véase apartado 3.2.2) ISBN10: código ISBN de 10 dígitos ISBN13: código ISBN de 13 dígitos OCLC: identificador dentro de la base de datos de WorldCat7 GBOOKS: identificador dentro de la base de datos de Google Books WIKIDATA: identificador del elemento dentro la base de datos de Wikidata A nivel de caché, los identificadores sirven de índices para buscar a otras entidades o determinar si una entidad dada ya existe en la base de datos (véase subapartado 3.1.4). 3.1.3. Relaciones entre entidades Todas las entidades son relacionables entre sí. Para cumplir con este enunciado, Rosetta debe ser capaz de representar en su estructura de datos interna frases como “Cervantes es el autor del Quijote” o “el CSIC es la editorial del Archivo Español de Arqueología”. Al abstraernos de la semántica, se puede apreciar que estas frases poseen una estructura del tipo «sujeto» «acción» sobre «objeto», por lo que una relación, almacenada en la clase Relation, deberá tener tres partes: una entidad de origen, una entidad de destino y el tipo de relación. 7WorldCat es un catálogo en línea de recursos bibliotecarios gestionado por el OCLC (Online Computer Library Center) Rosetta: catálogo público de bibliotecas 43 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES AbstractEntity Identifier - type: int - value: string Relation - type: int 0..* 1 2 0..* Figura 3.7: Diagrama de clases de AbstractEntity Los tipos de relaciones existentes en la versión Beta de Rosetta son: IS_AUTHOR_OF: Sujeto es autor de objeto IS_EDITOR_OF: Sujeto es editor de objeto IS_ILLUSTRATOR_OF: Sujeto es ilustrador de objeto IS_PUBLISHER_OF: Sujeto es editorial de objeto IS_FOUNDER_OF: Sujeto es fundador de objeto Para facilitar al desarrollador la búsqueda y recorrido de relaciones de una entidad, la aplicación provee de una serie de métodos que reducen las líneas de código necesarias para lograr tal objetivo, siendo el más relevante Relation::getOther($subject): public function getOther($subject) { return ($subject === $this->getTo()) ? $this->getFrom() : $this->getTo(); } Código 3: Método Relation::getOther($subject) Dado que en una relación intervienen dos partes, hay ocasiones en las que nos interesa conocer la otra parte de la misma cuando encontramos en una de ellas. Gracias a este método se generan otros auxiliares en la clase AbstractEntity: getRelatedOfType ygetFirstRelatedOfType. Igual que con los identificadores, a partir de estos dos métodos se crean otros más específicos en las distintas entidades de Rosetta, como en Book, que ofrece el atajo Book::getPublisher() como sustituto de AbstractEntity::getFirstRelatedOfType(Relation::IS_PUBLISHER_OF), entre otros. Rosetta: catálogo público de bibliotecas 44 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES public function getRelatedOfType(int $type):array { $res =[]; foreach ($this->getRelations() as $relation) { if ($relation->getType() == $type) { $res[] =$relation->getOther($this); } } return $res; } public function getFirstRelatedOfType(int $type) { foreach ($this->getRelations() as $relation) { if ($relation->getType() == $type) { return $relation->getOther($this); } } return null; } Código 4: Métodos de relaciones de AbstractEntity 3.1.4. Caché de entidades Con el objetivo de reducir la transferencia de datos entre Rosetta y los servidores de las bases de datos de los proveedores, la aplicación guarda un caché local de las entidades que han sido buscadas con anterioridad para poder ser recuperado más tarde. Este proceso se realiza mediante la colaboración de dos servicios, que son CacheService (nativo de Rosetta) y EntityManagerInterface (perteneciente al proyecto Doctrine ORM). Mapping de entidades Antes de explorar cómo consigue la clase CacheService crear nuevas entidades en el caché, sobrescribir las existentes y detectar duplicados debemos entender cómo se almacenan las instancias que heredan de AbstractEntity en la base de datos. Rosetta utiliza por defecto una base de datos relacional estructurada por tablas para almacenar estas entidades al considerarse la opción más extendida y fácil de implementar, aunque se puede configurar para emplear cualquier otro sistema (por ejemplo, una base de datos no relacional como MongoDB, u otros motores orientados a columnas como ClickHouse si buscamos un alto rendimiento). Rosetta: catálogo público de bibliotecas 45 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES Entre las instancias de AbstractEntity y la base de datos existe un componente intermedio conocido como Object-Relational Mapper (ORM) que se encarga de traducir entre objetos de PHP y sentencias SQL o el lenguaje que usemos para comunicarnos con el gestor de la base de datos. Más concretamente, el ORM utilizado por Rosetta es Doctrine ORM al ofrecer una muy buena integración con Symfony. Cuando Doctrine ORM recibe una entidad para guardar en la base de datos, serializa los atributos para los que ha sido configurado y generar la sentencia SQL correspondiente, que luego ejecuta, y realiza el proceso inverso para recuperar del caché. La forma en la que el ORM debe guardar los objetos se especifica a través de anotaciones8en las clases de las entidades. Estas anotaciones son comentarios de PHP que empiezan por el carácter «@» y son leídas por Doctrine ORM en tiempo de compilación. 8https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/ index.html Rosetta: catálogo público de bibliotecas 46 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES /** * @ORM\Entity * @ORM\HasLifecycleCallbacks * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="entity_type") * @ORM\DiscriminatorMap({ * "thing": "App\RosettaBundle\Entity\Thing", * "work": "App\RosettaBundle\Entity\Work\AbstractWork", * "book": "App\RosettaBundle\Entity\Work\Book", * "org": "App\RosettaBundle\Entity\Organization", * "pers": "App\RosettaBundle\Entity\Person" * }) */ abstract class AbstractEntity { /** * @ORM\Id * @ORM\Column(type="integer", options={"unsigned":true}) * @ORM\GeneratedValue */ protected $id; /** @ORM\Column(type="datetime") */ protected $creationDate =null; /** @ORM\Column(length=300) */ protected $slug =null; // [...] } Código 5: Anotaciones ORM de AbstractEntity El comentario asociado a la clase del código 5 define a AbstractEntity como una entidad que se guardará en la tabla “entity” junto a todas las propiedades de sus clases hijas, utilizando la columna “entity_type” como un discriminador que indica el tipo de entidad. Los comentarios que se encuentra encima de cada atributo define cómo se guardará el valor de dicho atributo en la base de datos. Una clase de Doctrine ORM también puede tener relaciones con otras clases, que igualmente se configuran utilizando anotaciones: Rosetta: catálogo público de bibliotecas 47 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES abstract class AbstractEntity { // [...] /** * @ORM\OneToMany( * targetEntity="Other\Identifier", * indexBy="id", * mappedBy="entity" * ) */ protected $identifiers; public function __construct() { $this->identifiers =new ArrayCollection(); // [...] } // [...] } Código 6: Ejemplo de relación ORM en AbstractEntity Para el ejemplo anterior, se define una relación uno a muchos (One to many) entre AbstractEntity y la clase Identifier, donde el atributo “id” de AbstractEntity es el inverse side y el atributo “entity” de Identifier se corresponde con el owning side. Del mismo modo, habrá que definir una relación muchos a uno en la clase Identifier para completar la unión. Lamentablemente, los tipos de relaciones permitidos en Doctrine ORM son limitados. Es por ello que la relación entre AbstractEntity yRelation, que es un tanto peculiar (véase apartado 3.1.3), son en realidad dos relaciones pues no se puede distinguir un solo par owning einverse como nos exige Doctrine ORM. Rosetta: catálogo público de bibliotecas 48 DISEÑO E IMPLEMENTACIÓN 3.1. ENTIDADES abstract class AbstractEntity { // [...] /** * @ORM\OneToMany(targetEntity="Relation", * mappedBy="to") */ protected $relationsTo; /** * @ORM\OneToMany(targetEntity="Relation", * mappedBy="from") */ protected $relationsFrom; // [...] public function getRelations() { return new ArrayCollection(array_merge( $this->relationsTo->toArray(), $this->relationsFrom->toArray() )); } public function addRelation(Relation $relation):self { if ($relation->getFrom() === $this) { $this->relationsFrom->add($relation); }else { $this->relationsTo->add($relation); } return $this; } // [...] } Código 7: Implementación de relaciones en AbstractEntity Por último en relación con el mapping de entidades, una clase puede tener escuchadores de eventos, que provee Doctrine ORM. Un ejemplo de uso de esta funcionalidad está en la clase AbstractEntity, que guarda una referencia a sí misma en todos sus identificadores asociados para definir el owning side de esa relación: Rosetta: catálogo público de bibliotecas 49 DISEÑO E IMPLEMENTACIÓN 3.2. MOTOR DE BÚSQUEDA La primera ronda de tokenización devuelve el siguiente array: [ 'author:"%cervantes%"', 'AND', "'la galatea' OR title:%quijote%" ] Código 11: Listado de tokens para consulta avanzada de ejemplo En la segunda ronda, Rosetta procesará los tokens que están semi-extendidos, como el tercer elemento de la lista anterior: [ 'author:"%cervantes%"', 'AND', ['"la galatea"','OR','title:"%quijote%"'] ] Código 12: Listado de tokens extendidos para consulta avanzada de ejemplo Por último, y al igual que el ejemplo anterior, se convierten los tokens a una representación de clases: SearchQuery operand: AND Comparison field: author operand: CONTAINS value: "cervantes" SearchQuery operand: OR Comparison field: any operand: EQUALS value: "la galatea" Comparison field: title operand: CONTAINS value: "quijote" Figura 3.12: Representación de clases para consulta avanzada de ejemplo Rosetta: catálogo público de bibliotecas 56 DISEÑO E IMPLEMENTACIÓN 3.2. MOTOR DE BÚSQUEDA 3.2.2. Proveedores Los proveedores son la pieza fundamental del motor de búsqueda de Rosetta, pues juegan el papel de “intérpretes” o mediadores entre las fuentes de datos y la aplicación. Estos son clases que implementan App\RosettaBundle\Provider\AbstractProvider y obtienen las entidades como resultado de una búsqueda en cinco pasos: 1. Instanciación: se crea un nuevo objeto de un proveedor de un tipo determinado que se utilizará para realizar la búsqueda. 2. AbstractProvider::configure($config, $query): el proveedor recibe la configuración de la base de datos con la que conectar y la consulta de búsqueda (SearchQuery) a ejecutar. 3. AbstractProvider::prepare(): prepara la instancia para ser ejecutada, generalmente utilizado para sincronizar múltiples objetos de un mismo tipo de proveedor a la hora de realizar una búsqueda paralelizada. 4. AbstractProvider::execute(): ejecuta la búsqueda, siendo lo habitual que este método llame a otro método estático del proveedor para ejecutar varias búsquedas a la vez e ignorando las llamadas del resto de instancias de dicho proveedor. 5. AbstractProvider::getResults(): devuelve un array de AbstractEntity con los resultados obtenidos y limpia la memoria o estado interno del proveedor, por lo que una vez llamado a este método el servicio de búsqueda (SearchEngine) guarda una referencia a los resultados para no perderlos. De los métodos mencionados en los pasos anteriores, todos son abstractos (deben ser implementados por los distintos tipos de proveedores) salvo el primero, pues la configuración de AbstractProvider se realiza de forma automática y soporta la detección de presets o configuraciones predeterminadas a través del método AbstractProvider::getPresets(), que puede ser implementado por cada tipo de proveedor. Es importante entender que todos estas etapas o pasos son realizados por el motor de búsqueda en tandas a excepción de AbstractProvider::prepare() que se ejecuta inmediatamente después de configurar la instancia del proveedor. Esto puede apreciarse en el método getResultsFromProviders($query, $providersConfig) de SearchEngine, donde se ve claramente el ciclo de vida de un proveedor (código 13 que aparece a continuación). Rosetta: catálogo público de bibliotecas 57 DISEÑO E IMPLEMENTACIÓN 3.2. MOTOR DE BÚSQUEDA private function getResultsFromProviders($query,$configs) { $providers =[]; foreach ($configs as $config) { $provider =new $config['type']($this->logger); $provider->configure($config,$query); $provider->prepare(); $providers[] =$provider; } foreach ($providers as $provider)$provider->execute(); $results =[]; foreach ($providers as $provider) { $providerResults =$provider->getResults(); $results =array_merge($results,$providerResults); } foreach ($providers as $provider)unset($provider); return $results; } Código 13: Método SearchEngine::getResultsFromProviders() Este proceso es el mismo a seguir tanto para los proveedores internos como para los externos pues, aunque la consulta de búsqueda no es la misma en ambos casos, las entidades finales se tratan igual independientemente de su origen. Por último, mencionar que la configuración de la instancia del proveedor y la consulta de búsquedas pueden ser accedidas en tiempo de ejecución desde los atributos protegidos AbstractProvider::config yAbstractProvider::query, respectivamente. public function configure(array $config, SearchQuery $query) { $presets =$this->getPresets(); foreach ($presets[$config['preset']] as $prop=>$val) { if (empty($config[$prop])) $config[$prop]=$val; } $this->config =$config; $this->query =$query; } Código 14: Método AbstractProvider::configure() simplificado Rosetta: catálogo público de bibliotecas 58 DISEÑO E IMPLEMENTACIÓN 3.2. MOTOR DE BÚSQUEDA Diferencias entre proveedores internos y externos A nivel de instancias de clases que heredan de AbstractProvider no existe ninguna diferencia que modifique su comportamiento en función de si ejerce como proveedor interno o externo (un proveedor se limita a recibir una consulta y a devolver resultados). La discordancia entre estas dos formas de actuar de un proveedor reside en la clase SearchEngine y en sus métodos getLocalResults($query, $databases) ygetExternalResults($entities). Ambos métodos acaban llamando a SearchEngine::getResultsFromProviders($query, $configs) aunque con consultas de búsqueda diferentes. Para el caso de los proveedores internos (locales), se toma la consulta proporcionada por el usuario y se pasa tal cual por referencia a los proveedores que se instancian en ese el mismo método. Para los proveedores externos, la SearchQuery a utilizar se genera a partir de los resultados obtenidos de los proveedores internos, extrayendo los identificadores de dichas entidades y creando una expresión fruto de combinar múltiples comparaciones con operadores Operand::OR. $identifiers =[]; foreach ($entities as $entity) { // [...] } $query =[]; foreach ($identifiers as $expression) { $query[] =$expression; $query[] =Operand::OR; } array_pop($query); $query =SearchQuery::of($query); Código 15: Generación de consulta de búsqueda para proveedores externos 3.2.3. Agrupación de resultados Como ya se ha mencionado al inicio de este apartado en la página 51, después de obtener los resultados de los proveedores internos y externos las entidades resultantes que sean idénticas se agrupan para después combinar esos grupos en una sola entidad siguiendo el algoritmo definido en el bloque de código 9. Rosetta: catálogo público de bibliotecas 59 DISEÑO E IMPLEMENTACIÓN 3.2. MOTOR DE BÚSQUEDA Esta agrupación de elementos es realizada por el servicio de búsqueda en el método SearchEngine::groupResults($entities) y consta de los siguientes pasos: Crear mapa de índices Eliminar elementos individuales Generar nuevo mapa de índices Crear arrayde grupos Figura 3.13: Proceso simplificado de agrupación de resultados En primer lugar, se toma un array de resultados (entidades) de las que se extraen sus respectivos identificadores para acabar creando un mapa que relacione a ambos: $ids =[]; foreach ($entities as $i=>$entity) { foreach ($entity->getIdentifiers() as $identifier) { if ($identifier->getType() !== Identifier::ISBN_13) { $id =(string) $identifier; if (!isset($ids[$id])) $ids[$id]=[]; $ids[$id][] =$i; } } } Código 16: Generación de mapa de índices para agrupación Como se puede ver en el código anterior, guardamos grupos de números enteros, correspondientes a los índices de las entidades en el array $entities, en vez de las referencias a las entidades. Una vez generado este array bidimensional, se eliminan (filtran) los elementos que solo tengan un identificador, pues esos no hace falta agruparlos: $ids =array_filter($ids,function($elem) { return count($elem)> 1; }); $ids =array_values($ids); Código 17: Eliminación de índices duplicados para agrupación Para generar el nuevo mapa (al que llamaremos $parsedIds) se recorren todos los grupos de índices sacando la cabeza de la lista en cada iteración para comparar sus elementos con el segundo grupo del array, de acuerdo al siguiente proceso: Rosetta: catálogo público de bibliotecas 60 DISEÑO E IMPLEMENTACIÓN 3.2. MOTOR DE BÚSQUEDA 1. Sea $n igual a 0 2. Se extrae la cabeza del array $ids, que se almacena en $indexes 3. Se buscan elementos comunes entre $indexes e$ids[$n] a) Si no hay elementos comunes, se marca un flag para inserta el grupo actual ($indexes) al final de la iteración en $parsedIds b) Si hay elementos comunes significa que son la misma entidad, por lo que se añaden los índices de $indexes a$ids[$n] eliminando duplicados y ordenándolos de forma ascendente 4. Se suma una unidad a $n y se vuelve al paso 3 hasta recorrer por completo el array $ids 5. Se vuelve al paso 2 mientras queden elementos en $ids Por último, con el array $parsedIds ya generado que contiene los índices de las entidades que son idénticas agrupados, se reemplazan estos números enteros por la instancia de su correspondiente entidad extraída de la variable $entities. Ejemplo de agrupación Supongamos que partimos de las siguientes entidades numeradas del 1 al 5 que tienen asociadas unos identificadores (A, B, C, D, E, F): 1 A B 2 B E 3 C 4 E D 5 C F Figura 3.14: Entidades de partida para ejemplo de agrupación Para realizar su agrupación en entidades idénticas, en primer lugar tendríamos que crear un mapa de índices (invertir la relación entidades-identificadores): [ "A" => [1], "B" => [1,2], "C" => [3,5], "D" => [4], "E" => [2,4], "F" => [5] ] Código 18: Ejemplo de mapa de índices para agrupación Rosetta: catálogo público de bibliotecas 61 DISEÑO E IMPLEMENTACIÓN 3.2. MOTOR DE BÚSQUEDA De este mapa nos quedaríamos solo con [(1,2) (3,5) (2,4)]. Para generar el mapa de identificadores procesado ($parsedIds), recorremos los tres elementos que tenemos de izquierda a derecha, lo que implica las siguientes transformaciones (a la izquierda del separador vertical $parsedIds, a la derecha $ids): [] |[(1,2) (3,5) (2,4)] [] |[(3,5) (1,2,4)] [(3,5)] |[(1,2,4)] [(3,5) (1,2,4)] |[] Código 19: Ejemplo de construcción de $parsedIds Por último, sustituimos los números del 1 al 5 por su correspondiente entidad y habremos terminado de agrupar los resultados. Si faltasen entidades por nombrar en el array $parsedIds (por ejemplo, por ser ya únicas de por sí) se añadirían en este momento en grupos independientes. 3.2.4. Datos estructurados con Wikidata Tal y como se ha mencionado al comienzo de este apartado en la página 53, antes de cachear y devolver los resultados de una búsqueda se llama al servicio de Wikidata para intentar añadir entidades relacionadas que no ha sido posible obtener de los proveedores internos o externos. Este servicio, localizado en la clase WikidataService, recibe un array de entidades a través del método WikidataService::fillEntities($entities) y modifica esas mismas entidades añadiendo las nuevas que haya sido capaz de localizar. Básicamente, el flujo de trabajo de este servicio se reduce a tres pasos: Obtener identificadores de Wikidata Lo primeroquesedebe hacer paraextraer informaciónutilizandolaAPIdeWikidata es obtener los identificadores externos del tipo Identifier::WIKIDATA con los que se corresponden nuestras entidades. Para ello se preparan una serie de llamadas hacia el endpoint https:// www.wikidata.org/w/api.php?action=wbsearchentities con el nombre de la entidad como término de búsqueda. Estas llamadas HTTP se ejecutan en paralelo gracias a la clase HttpClient que provee el núcleo de Rosetta. Rosetta: catálogo público de bibliotecas 62 DISEÑO E IMPLEMENTACIÓN 3.3. INTERFAZ GRÁFICA Obtener datos de entidades de Wikidata Una vez se tienen los identificadores correspondientes a las entidades encontradas en Wikidata, se realiza una sola petición GET hacia https://www.wikidata.org/ w/api.php?action=wbgetentities para obtener los datos de todas las entidades de Wikidata. Rellenar entidades de Rosetta Por último, se recorre cada una de las entidades obtenidas en el paso anterior y se rellenan los atributos existentes comunes entre la entidad de Wikidata y la de Rosetta, añadiendo nuevas relaciones e información a los resultados de la búsqueda. Para este último paso se llaman a varios métodos privados del estilo WikidataService::fill<tipo>($entity, $data) en función del tipo de entidad. Por ejemplo, para una instancia que represente a una persona se llamaría primero a WikidataService::fillEntity($entity, $data) y después a WikidataService::fillPerson($entity, $data). 3.3. Interfaz gráfica La interfaz gráfica de Rosetta se encuentra estructurada en controladores (que contienen la lógica a ejecutar cuando se visitan determinadas páginas) y plantillas (que definen la respuesta que devuelve el servidor al cliente). Los controlares se ubican en el directorio src/Controller y son clases que extienden de AbstractController, esta última proporcionada por Symfony. La tarea principal de un controlador es definir rutas y devolver las respuestas a las peticiones de un cliente. Estas rutas o direcciones URL visitables por un usuario se configuran utilizando anotaciones escritas antes de la cabecera de un método: class MainController extends AbstractController { /** @Route("/", name="homepage") */ public function homepage() { return $this->render("pages/homepage.html.twig"); } } Código 20: Controlador de la página principal Para el caso de páginas estáticas como la de inicio, el controlador se define en apenas unas líneas como se ve en el bloque de código 20, aunque lo habitual es que los métodos de estos controladores tenga más lógica dedicada a interactuar con otros Rosetta: catálogo público de bibliotecas 63 DISEÑO E IMPLEMENTACIÓN 3.3. INTERFAZ GRÁFICA servicios para recuperar los resultados de una búsqueda, obtener recursos de otras máquinas, etc. Las plantillas se localizan en los directorios templates/pages para las páginas usadas en los controladores y templates/components para los componentes reutilizables entre plantillas, y funcionan con el motor de renderizado «Twig». Todas las páginas heredan del fichero templates/base.html.twig que provee de una definición de documento HTML común para toda la aplicación: <!doctype html> <html lang="{{ app.request.getLocale() }}"> <head> <meta charset="utf-8"> <title>{{ rosetta.opac.app_name }}</title> {% block stylesheets %} {{ encore_entry_link_tags('app')}} {% endblock %} </head> <body> {% block content %}{% endblock %} {% block javascripts %} {{ encore_entry_script_tags('app')}} {% endblock %} </body> </html> Código 21: Extracto de base.html.twig Esta plantilla base contiene unos bloques (entre block yendblock) que pueden ser sustituidos o extendidos por las plantillas que heredan de esta: {% extends 'base.html.twig' %} {% block content %} <div class="search-container"> <div class="search-wrapper"> {% include 'components/logo.html.twig' %} {% include 'components/search-bar.html.twig' %} </div> </div> <div class="version-watermark">v{{ rosetta.version }}</div> {% endblock %} Código 22: Extracto de homepage.html.twig Rosetta: catálogo público de bibliotecas 64 DISEÑO E IMPLEMENTACIÓN 3.3. INTERFAZ GRÁFICA Esta misma arquitectura jerárquica se utiliza para renderizar los atributos y componentes propios de cada una de las entidades de Rosetta a partir de las plantillas del directorio templates/pages/details. 3.3.1. Extensión de Twig Las plantillas de Twig están aisladas del resto del entorno de la aplicación y disponen de un set de funciones muy limitado. Aunque esto está hecho adrede para aislar la lógica de la aplicación de la interfaz gráfica, hay momentos en los que es necesario establecer puentes entre ambas piezas de un software para que puedan interactuar entre sí determinados componentes. La forma en la que Twig permite extender las funciones que pueden ser llamadas desde dentro de una plantilla es a través de las extensiones. Rosetta incluye una extensión llamada AppExtension que añade constantes en forma de variables globales y funciones al motor de renderizado para que las interfaces gráficas puedan obtener información de determinados servicios. Nótese que esta clase está fuera del núcleo de la aplicación (RosettaBundle) al añadir funcionalidad a la interfaz gráfica y no al núcleo en sí. Las constantes proporcionadas por AppExtension se engloban dentro del objeto rosetta y contiene las siguientes propiedades: "rosetta" => [ "version" => ConfigService::getVersion(), "opac" => ConfigService::getOpacSettings(), "databases" => [ {identificador} => {propiedades}, // [...] ], "context" => [ "db" => {identificador}, "logo" => {ruta_hacia_logo}, "leading" => {ruta_hacia_leading} ] ] Código 23: Propiedades de la variable global rosetta La constante rosetta.database es un array asociativo o mapa con todas las bases de datos de la aplicación. El contexto (rosetta.context) está formado por las constantes asociadas a la base de datos actualmente seleccionada, por ejemplo, en una búsqueda. Rosetta: catálogo público de bibliotecas 65 PRUEBAS Aunque todos las pruebas se encuentran en el directorio /tests del proyecto en forma de tests unitarios de PHPUnit y pueden ser ejecutadas en cualquier momento usando el comando ./bin/phpunit, a continuación se muestran unas tablas con la información de estas pruebas y su comportamiento y resultado esperados. UT-01 Tokenización de consultas Clase asociada SearchQueryTest Método asociado testTokenization Componente SearchQuery Versión 1.0 Autor José Miguel Moreno López Objetivo Comprobar la correcta interpretación de strings de consultas en tokens Precondiciones N/A Datos de entrada title:' %la galatea %' AND author: %cervantes % AND publisher:'Project Gutenberg' Secuencia 1. El sistema crea una instancia de SearchQuery partiendo de los datos de entrada 2.El sistema obtienela representación de lainstancia en forma de tokens Datos de salida (<title CONTAINS 'la galatea'>AND <author CONTAINS 'cervantes'>AND <publisher EQUALS 'Project Gutenberg'>) Observaciones N/A Tabla 4.1: Prueba “tokenización de consultas” (UT-01) Rosetta: catálogo público de bibliotecas 72 PRUEBAS UT-02 Conversión de consultas a RPN Clase asociada SearchQueryTest Método asociado testRpn Componente SearchQuery Versión 1.0 Autor José Miguel Moreno López Objetivo Comprobar la correcta traducción de SearchQuery a consultas RPN Precondiciones N/A Datos de entrada title:' %la galatea %' AND author: %cervantes % AND publisher:'Project Gutenberg' Secuencia 1. El sistema crea una instancia de SearchQuery partiendo de los datos de entrada 2.El sistema obtienela representación de lainstancia en forma de consulta RPN Datos de salida @and @attr 1=4 'la galatea' @and @attr 1=1003 'cervantes' @attr 1=1018 'Project Gutenberg' Observaciones N/A Tabla 4.2: Prueba “conversión de consultas a RPN” (UT-02) Rosetta: catálogo público de bibliotecas 73 PRUEBAS UT-03 Conversión de consultas a Innopac Clase asociada SearchQueryTest Método asociado testInnopac Componente SearchQuery Versión 1.0 Autor José Miguel Moreno López Objetivo Comprobar la correcta traducción de SearchQuery a consultas de Innopac Precondiciones N/A Datos de entrada title:' %la galatea %' AND author: %cervantes % AND publisher:'Project Gutenberg' Secuencia 1. El sistema crea una instancia de SearchQuery partiendo de los datos de entrada 2.El sistema obtienela representación de lainstancia en forma de consulta de Innopac Datos de salida t:'la galatea' and a:'cervantes' and 'Project Gutenberg' Observaciones N/A Tabla 4.3: Prueba “conversión de consultas a Innopac” (UT-03) Rosetta: catálogo público de bibliotecas 74 PRUEBAS UT-04 Consultas mal formuladas Clase asociada SearchQueryTest Método asociado testMalformedQueries Componente SearchQuery Versión 1.0 Autor José Miguel Moreno López Objetivo Comprobar el fallback hacia texto literal de consultas mal escritas Precondiciones N/A Datos de entrada this (isn't a valid:query)) Secuencia 1. El sistema crea una instancia de SearchQuery partiendo de los datos de entrada 2.El sistema obtienela representación de lainstancia en forma de tokens Datos de salida (<any CONTAINS 'this (isn\'t a valid:query))'> Observaciones N/A Tabla 4.4: Prueba “consultas mal formuladas” (UT-04) Rosetta: catálogo público de bibliotecas 75 PRUEBAS UT-05 Agrupación de resultados Clase asociada SearchEngineTest Método asociado testGroupResults Componente SearchEngine Versión 1.0 Autor José Miguel Moreno López Objetivo Validar el comportamiento del algoritmo de agrupación de resultados Precondiciones N/A Datos de entrada 7 entidades con los siguientes identificadores de prueba: ['red', 'scarlet'] ['green'] ['blue'] ['scarlet'] ['cyan', 'blue'] ['teal', 'olive'] ['olive', 'green'] Secuencia 1. El sistema obtiene una referencia al servicio de búsqueda 2. El sistema modifica los permisos para llamar al método privado groupResults 3. El sistema pasa los datos de entrada al método Datos de salida Tres grupos de entidades con los siguientes identificadores únicos por grupo: ['red', 'scarlet'] ['blue', 'cyan'] ['green', 'teal', 'olive'] Observaciones N/A Tabla 4.5: Prueba “agrupación de resultados” (UT-05) Rosetta: catálogo público de bibliotecas 76 PRUEBAS UT-06 Página de inicio Clase asociada HomepageTest Método asociado testHomepage Componente OPAC Versión 1.0 Autor José Miguel Moreno López Objetivo Comprobar la correcta renderización de la página de inicio Precondiciones N/A Datos de entrada N/A Secuencia 1. El sistema carga la página de inicio de la aplicación 2. El sistema comprueba que existe el formulario de búsqueda Datos de salida N/A Observaciones N/A Tabla 4.6: Prueba “página de inicio” (UT-06) Rosetta: catálogo público de bibliotecas 77 PRUEBAS UT-07 Formulario de búsqueda de página de inicio Clase asociada HomepageTest Método asociado testSearchForm Componente OPAC Versión 1.0 Autor José Miguel Moreno López Objetivo Validar el comportamiento del formulario de búsqueda Precondiciones N/A Datos de entrada Consulta de búsqueda: “kurose” Secuencia 1. El sistema carga la página de inicio de la aplicación 2. El sistema escribe la consulta de entrada en el formulario de búsqueda 3. El sistema envía el formulario Datos de salida Redirección hacia la página de resultados de búsqueda para la consulta de entrada Observaciones N/A Tabla 4.7: Prueba “formulario de búsqueda de página de inicio” (UT-07) Rosetta: catálogo público de bibliotecas 78 PRUEBAS UT-08 Página de resultados de búsqueda Clase asociada SearchTest Método asociado testSearch Componente OPAC Versión 1.0 Autor José Miguel Moreno López Objetivo Comprobar la correcta renderización de la página de resultados de una búsqueda Precondiciones N/A Datos de entrada Consulta de búsqueda: “kurose” Secuencia 1. El sistema carga la página de resultados de búsqueda para la consulta de entrada 2. El sistema comprueba que existe el leading 3. El sistema comprueba que existe el spinner Datos de salida N/A Observaciones N/A Tabla 4.8: Prueba “página de resultados de búsqueda” (UT-08) Rosetta: catálogo público de bibliotecas 79 PRUEBAS UT-09 Obtención de resultados de búsqueda Clase asociada SearchTest Método asociado testSearchPost Componente OPAC Versión 1.0 Autor José Miguel Moreno López Objetivo Validar los resultados obtenidos al realizar una búsqueda en la aplicación Precondiciones N/A Datos de entrada Consulta de búsqueda: “kurose” Secuencia 1. El sistema solicita mediante una petición POST los resultados para la consulta de entrada 2. El sistema comprueba que existe al menos un resultado que sea un libro Datos de salida N/A Observaciones N/A Tabla 4.9: Prueba “obtención de resultados de búsqueda” (UT-09) Rosetta: catálogo público de bibliotecas 80 PRUEBAS UT-10 Página de detalles Clase asociada DetailsTest Método asociado testDetails Componente OPAC Versión 1.0 Autor José Miguel Moreno López Objetivo Comprobar la correcta renderización de la página de detalles de una entidad Precondiciones N/A Datos de entrada Consulta de búsqueda: “kurose” Secuencia 1. El sistema realiza una búsqueda para la consulta de entrada 2. El sistema hacia click sobre el primer resultado 3. El sistema comprueba que la página de detalles de la entidad tiene una columna de detalles y una principal Datos de salida N/A Observaciones N/A Tabla 4.10: Prueba “página de detalles” (UT-10) Rosetta: catálogo público de bibliotecas 81 DOCUMENTACIÓN 5.2. MANUAL DE DESPLIEGUE Ingress Rosetta (aplicación) Servidor web app-source Servidor de base de datos BBDD Caché Figura 5.5: Arquitectura recomendada de despliegue con Kubernetes LaimagendeRosettaha sido diseñada paraserstateless,porloquepuede serescalada horizontalmente creando múltiples instancias de un pod. Para escalar el servidor de base de datos no podemos replicar pods ya que se corrompería el estado interno de los datos si se ejecutasen múltiples instancias a la vez. En su lugar, debemos crear un clúster replicado de MariaDB formado por un pod maestro y uno o más esclavos. 5.2.3. Despliegue manual Tanto el fichero docker-compose.yml como Dockerfile permiten automatizar la instalación de dependencias y la preparación del entorno de ejecución necesario para ejecutar Rosetta. En caso de querer desplegar la aplicación sin utilizar Docker o Kubernetes, se deberán seguir los mismos pasos que realizan estos dos ficheros de forma manual. Los comandos que aparecen a continuación están especialmente indicados para sistemas operativos basados en Debian GNU/Linux con procesador x86 al ser el entorno de ejecución más popular en servidores web. Rosetta: catálogo público de bibliotecas 88 DOCUMENTACIÓN 5.2. MANUAL DE DESPLIEGUE Instalación de Node.js® Rosetta depende de Node.js® para la compilación de recursos estáticos (imágenes, hojas de estilos y scripts de JavaScript), por lo que deberá estar instalado en nuestra máquina antes de realizar la primera ejecución de la aplicación: curl -sL https://deb.nodesource.com/setup_10.x | bash - apt install nodejs Código 31: Comandos para instalación de Node.js® También necesitaremos el gestor de paquetes Yarn, que por defecto no se instala con Node.js®: curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | \ apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" |\ tee /etc/apt/sources.list.d/yarn.list apt update && apt install yarn Código 32: Comandos para instalación de Yarn Instalación de PHP Una vez instalado Node.js®, se deberá instalar la última versión de PHP disponible (o cualquiera igual o superior a PHP 7.1.3): apt install ca-certificates apt-transport-https wget -q https://packages.sury.org/php/apt.gpg -O- | \ apt-key add - echo "deb https://packages.sury.org/php/ stretch main" |\ tee /etc/apt/sources.list.d/php.list apt update && apt install php-cli php-common php-fpm \ php-curl php-mbstring php-zip php-pdo-mysql php-intl \ php-dev php-pear Código 33: Comandos para instalación de PHP Junto a PHP necesitamos instalar el gestor de dependencias Composer: curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer Código 34: Comandos para instalación de Composer Rosetta: catálogo público de bibliotecas 89 DOCUMENTACIÓN 5.2. MANUAL DE DESPLIEGUE Para los proveedores de Z39.50, es requisito indispensable la extensión YAZ, que hay que compilar desde el código fuente: apt install yaz libyaz-dev pecl install yaz Código 35: Comandos para compilación de YAZ Instalación del servidor de base de datos Rosetta necesita un gestor de base de datos para manejar el caché local, siendo MariaDB un software más que apto para este propósito y muy fácil de configurar: apt install mariadb-server Código 36: Comando para instalación de MariaDB Por defecto, MariaDB en Debian se ejecuta en el puerto 3306 con usuario root y sin contraseña, por lo que es recomendable cambiar estos ajustes antes de pasar a producción. Compilación de Rosetta Ahora que el entorno de ejecución está listo, descargamos la última versión de Rosetta desde el repositorio remoto: git clone https://github.com/josemmo/rosetta.git Código 37: Comando para descargar Rosetta En este momento se deben modificar los ficheros de configuración y, una vez hecho, ya podremos compilar los recursos estáticos: yarn install && yarn build composer install --no-dev --optimize-autoloader chmod -R 777 var php bin/console doctrine:database:create --if-not-exists php bin/console doctrine:schema:update --force Código 38: Comandos para compilar Rosetta Rosetta: catálogo público de bibliotecas 90 DOCUMENTACIÓN 5.2. MANUAL DE DESPLIEGUE Instalación del servidor web Symfony, el framework en el que está basado Rosetta, es compatible con un amplio abanico de servidores web, incluyendo nginx y Apache, dos de los más utilizados. Si optamos por usar Apache, lo más sencillo es modificar el fichero httpd.conf y apuntar la ruta pública hacia el directorio public de Rosetta e instalar el apache-pack de Symfony: composer require symfony/apache-pack Código 39: Comando para instalar apache-pack en Symfony En el caso de nginx (el servidor web recomendado por tener el mejor rendimiento con Rosetta) solo deberemos cambia los ajustes del fichero nginx.conf por los siguientes: server { server_name localhost; root /ruta/hacia/rosetta/public; location /{ try_files $uri /index.php$is_args$args; } location ~^/index\.php(/|$) { fastcgi_pass unix:/var/run/php/php-fpm.sock; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; internal; } location ~\.php$ { return 404; } } Código 40: Configuración de nginx recomendada Rosetta: catálogo público de bibliotecas 91 DOCUMENTACIÓN 5.3. MANUAL DE PERSONALIZACIÓN 5.3. Manual de personalización Rosetta ha sido diseñado para ser un software de “marca blanca” que provee de múltiples archivos de configuración y otros recursos modificables para adaptarse a prácticamente cualquier biblioteca u organización. Los tres recursos principales a modificar son rosetta.yml,_theme.scss y el directorio assets/custom. 5.3.1. Configuración de la aplicación La configuración esencial del núcleo de Rosetta y algunos ajustes de la interfaz gráfica se especifican en el fichero rosetta.yml del directorio config/packages. Este archivo sigue el formato de serialización YAML, aunque también es posible formatearlo en XML o en un array de PHP según se detalla en el apartado de configuración del manual de Symfony4. La estructura de rosetta.yml se divide en los siguientes apartados: opac: configuración de la interfaz web wikidata: ajustes del servicio de Wikidata databases: configuración de los distintos proveedores de datos (fuentes internas) external_providers: configuración de los proveedores de terceros (fuentes externas) El grupo opac puede tener las siguientes propiedades: app_name: nombre de la aplicación que se mostrará desde la interfaz web admin_email: dirección de correo electrónico de contacto del administrador de la plataforma covers_expiration: caducidad de las portadas y otras imágenes de entidades guardadas en caché en formato DateTime string de PHP (por ejemplo, “+90 days”) El grupo wikidata solo puede tener un atributo, language, que define en qué idioma se obtendrán los datos de Wikidata siguiendo el código de dos letras del país especificado en el ISO 639-1. 4https://symfony.com/doc/current/configuration.html Rosetta: catálogo público de bibliotecas 92 DOCUMENTACIÓN 5.3. MANUAL DE PERSONALIZACIÓN El grupo databases contiene las siguientes propiedades: id: Identificador de la base de datos a nivel interno name: Nombre de la base de datos short_name: Nombre corto de la base de datos external_link (opcional): Patrón de URL para enlaces externos provider: Configuración del proveedor La propiedad external_link sirve para redirigir al usuario a la fuente original de los datos y puede contener variables que se reemplazarán en tiempo de ejecución. Por ejemplo, para la Universidad de Valladolid, un posible valor para este campo sería https://almena.uva.es/search/i?search={{isbn13}}, donde Rosetta sustituiría {{isbn13}} por el ISBN de 13 dígitos del recurso. Por último, el grupo external_providers contiene una lista de configuraciones de proveedores. La estructura de una configuración de proveedor, que es la misma tanto para databases/provider como para los elementos de external_providers, es la mostrada en la tabla 5.1. Propiedad Por defecto Z39.50 Google Books type - ✓ ✓ preset - ✓ ✓ url - - ✓ user - - ✓ password - - ✓ group - - ✓ charset - - ✓ syntax - - ✓ oclc_field 935 - ✓ country -✓- key - ✓ - get_holdings - ✓ ✓ covers_url - ✓ - timeout 3 ✓ ✓ max_results 20 ✓ ✓ Tabla 5.1: Propiedades configurables de un provider Rosetta: catálogo público de bibliotecas 93 DOCUMENTACIÓN 5.3. MANUAL DE PERSONALIZACIÓN Esta tabla incluye qué propiedades se pueden usar con qué proveedor, ya que algunas son específicas de determinadas fuentes de datos. Dentro de esta estructura, la propiedad type determina el proveedor a utilizar tomando como valor la ruta completa de la clase. Los dos proveedores que proporciona Rosetta en su instalación son: App\RosettaBundle\Provider\Z3950: para el protocolo Z39.50 App\RosettaBundle\Provider\GoogleBooks: para Google Books Los proveedores de las fuentes internas se utilizan para obtener los resultados de una búsqueda y los ejemplares disponibles dentro de una biblioteca o institución, mientras que las fuentes externas (que son opcionales) reemplazan y añaden información adicional que no se pudo obtener de las fuentes internas. El funcionamiento de los proveedores se detalla en el apartado 3.2.2. 5.3.2. Personalización de la interfaz web Rosetta permite cambiar la paleta de colores del tema de la interfaz web sin tener que hacer grandes cambios en su código a través del fichero assets/scss/_theme.scss. Esta hoja de estilos está pensada para almacenar reglas específicas de un tema y contiene las siguientes variables de SCSS cuyos valores se utilizarán a lo largo de toda la interfaz: $foreground-color: #222; $background-color: #f7f7f7; $accent-color: #d70b23; // Auto-generated variables $muted-color:lighten($foreground-color, 30%); Código 41: Contenido por defecto del fichero _theme.scss Tenga en cuenta que deberá recompilar la aplicación cada vez que haga cambios en las reglas de estilo de Rosetta usando el comando yarn build o, en su defecto, npm run build. Si tomamos la página de inicio como ejemplo, cada variable mostrada en el snippet anterior se corresponde con los mostrados en la figura 5.6, que aparece inmediatamente a continuación. Rosetta: catálogo público de bibliotecas 94 DOCUMENTACIÓN 5.3. MANUAL DE PERSONALIZACIÓN Rosetta Demo Todo el catálogo Buscar EXPLORAR CATÁLOGO ÚLTIMAS ADQUISICIONES BÚSQUEDAS POPULARES $background-color $foreground-color $muted-color $accent-color Figura 5.6: Variables del tema para la página de inicio Además de la paleta, los logotipos y leadings5de las bases de datos también se pueden personalizar. Estos recursos de imagen deben guardarse en la raíz del directorio assets/custom siguiendo el esquema de nombres [dbId]-logo.png o [dbId]-leading.jpg, donde “dbId” es el identificador de la base de datos que figura en el fichero de configuración rosetta.yml. (a) Sin logotipo ni leading (b) Solo logotipo (c) Con logotipo y leading Figura 5.7: Interfaces de las cabeceras de búsqueda 5Dentro de Rosetta se conoce como leading a la imagen de fondo ancha y estrecha que se utiliza en la cabecera de los resultados de una búsqueda Rosetta: catálogo público de bibliotecas 95 DOCUMENTACIÓN 5.3. MANUAL DE PERSONALIZACIÓN Estas imágenes son opcionales y solo se mostrarán si el recurso existe. En caso de no existir, se mostrará en su lugar el nombre de la base de datos y el color de fondo primario ($accent-color) de la aplicación. Si se quisiera aplicar cambios más específicos a los estilos, se tendría que modificar el resto de ficheros del directorio assets/scss. Esta carpeta contiene las reglas correspondientes a los componentes reutilizables en el subdirectorio components y las reglas aplicables a páginas concretas en pages. Una vez más, se tendría que recompilar la aplicación después de hacer cambios sobre estos archivos. 5.3.3. Configuración de mapas Rosetta permite mostrar al usuario a través de una serie de mapas la ubicación de un recurso dentro de las instalaciones de la biblioteca u organización. Para que la aplicación sea capaz de localizar los recursos se deben introducir, a través de ficheros basados en SVG, mapas con los códigos UDC6asociados a su correspondiente estantería o ubicación. Estos mapas se guardan en el directorio assets/custom/maps y pueden tener el nombre que se considere conveniente, siempre y cuando tengan la extensión .svg y sigan el siguiente formato de archivo: <?xml version="1.0" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:rosetta="https://github.com/josemmo/rosetta" viewBox="[...]" rosetta:database="uva" rosetta:locationPattern="(.+)Segovia" rosetta:room="Cubo azul (planta 2)"> <!-- [...] --> <rect rosetta:udc="001, 002" x="1295" y="245" width="27" height="88" /> <rect rosetta:udc="003, 004" x="1295" y="333" width="27" height="88" /> <rect rosetta:udc="004.2, 004.3" x="1295" y="421" width="27" height="88" /> <!-- [...] --> </svg> Código 42: Ejemplo de mapa de Rosetta 6Universal Decimal Classification Rosetta: catálogo público de bibliotecas 96 DOCUMENTACIÓN 5.3. MANUAL DE PERSONALIZACIÓN Como se puede apreciar en el código del ejemplo anterior, los mapas de Rosetta no son más que imágenes SVG que contienen un nombre de espacios de XML adicional (en este caso, definido en xmlns:rosetta). Estos archivos pueden ser creados con un software de edición de imágenes vectoriales como Inkscape o Adobe Illustrator® y después modificados con un editor de texto para añadir las propiedades adicionales que convierten la imagen en un mapa de Rosetta. El atributo rosetta:database contiene el identificador de la base de datos con la que se corresponde el mapa, rosetta:locationPattern es un patrón regex que deberá coincidir con la ubicación del recurso devuelta por el proveedor para que el mapa sea cargado; y, por último, rosetta:room es el nombre que recibirá la sala o ubicación que se mostrará al usuario. Para localizar al recurso, la aplicación extrae la categoría de su signatura (correspondiente al código UDC) y la busca dentro de un mapa. Por tanto, el atributo rosetta:udc que contienen las estanterías es una lista separada por comas de los códigos UDC de los recursos que alojan. Gracias a esta arquitectura, una estantería puede contener múltiples categorías y una categoría puede expandirse por varias estanterías. Para más información sobre cómo Rosetta es capaz de encontrar el mapa correspondiente a cada categoría de forma eficiente, consulte el apartado 3.3.2. Rosetta: catálogo público de bibliotecas 97 ÍNDICE DE CÓDIGOS 1. Ejemplo de inyección de dependencias en un servicio . . . . . . . . 37 2. Métodos de identificadores de AbstractEntity ........... 42 3. Método Relation::getOther($subject) .............. 44 4. Métodos de relaciones de AbstractEntity .............. 45 5. Anotaciones ORM de AbstractEntity ................ 47 6. Ejemplo de relación ORM en AbstractEntity ............ 48 7. Implementación de relaciones en AbstractEntity ......... 49 8. Ejemplo de escuchador ORM en AbstractEntity .......... 50 9. Combinación de resultados de una búsqueda . . . . . . . . . . . . . 52 10. Listado de tokens para consulta de ejemplo . . . . . . . . . . . . . . 55 11. Listado de tokens para consulta avanzada de ejemplo . . . . . . . . . 56 12. Listado de tokens extendidos para consulta avanzada de ejemplo . . 56 13. Método SearchEngine::getResultsFromProviders() . . . . . . 58 14. Método AbstractProvider::configure() simplificado . . . . . . 58 15. Generación de consulta de búsqueda para proveedores externos . . 59 16. Generación de mapa de índices para agrupación . . . . . . . . . . . 60 17. Eliminación de índices duplicados para agrupación . . . . . . . . . 60 18. Ejemplo de mapa de índices para agrupación . . . . . . . . . . . . . 61 19. Ejemplo de construcción de $parsedIds ............... 62 20. Controlador de la página principal . . . . . . . . . . . . . . . . . . 63 21. Extracto de base.html.twig ..................... 64 22. Extracto de homepage.html.twig .................. 64 23. Propiedades de la variable global rosetta .............. 65 24. Obtención del fichero de un mapa desde el índice . . . . . . . . . . 67 25. Ejemplo de plantilla Twig con textos localizables . . . . . . . . . . . 68 26. Extracto del fichero translations/messages.es.json . . . . . . 69 105 ÍNDICE DE CÓDIGOS 27. Extracto de LocaleListener ..................... 69 28. Comandos para despliegue de Rosetta usando Docker . . . . . . . . 86 29. Modificación de docker-compose.yml para cambiar configuración 87 30. Comandos para actualización de Rosetta usando Docker . . . . . . 87 31. Comandos para instalación de Node.js® . . . . . . . . . . . . . . . . 89 32. Comandos para instalación de Yarn . . . . . . . . . . . . . . . . . . 89 33. Comandos para instalación de PHP . . . . . . . . . . . . . . . . . . 89 34. Comandos para instalación de Composer . . . . . . . . . . . . . . . 89 35. Comandos para compilación de YAZ . . . . . . . . . . . . . . . . . 90 36. Comando para instalación de MariaDB . . . . . . . . . . . . . . . . 90 37. Comando para descargar Rosetta . . . . . . . . . . . . . . . . . . . 90 38. Comandos para compilar Rosetta . . . . . . . . . . . . . . . . . . . 90 39. Comando para instalar apache-pack en Symfony . . . . . . . . . . 91 40. Configuración de nginx recomendada . . . . . . . . . . . . . . . . 91 41. Contenido por defecto del fichero _theme.scss ........... 94 42. Ejemplo de mapa de Rosetta . . . . . . . . . . . . . . . . . . . . . . 96 Rosetta: catálogo público de bibliotecas 106 REFERENCIAS [1] Ranganathan, S., Sivaswamy Aiyer, P., & Sayers, W. (2006). Las cinco leyes de ciencia de la bilbiotecología. Nueva Delhi [India]: Ess Ess Publications. [2] MARC standards (2019). Disponible en https://en.wikipedia.org/wiki/ MARC_standards [Consultado en mayo de 2019]. [3] Wood, D., Lanthaler M., & Cyganiak, R. (2014). RDF 1.1 Concepts and Abstract Syntax. Disponible en http://www.w3.org/TR/2014/ REC-rdf11-concepts-20140225/ [Consultado en mayo de 2019]. [4] Schema.org (2019). Disponible en https://en.wikipedia.org/wiki/ Schema.org [Consultado en mayo de 2019]. [5] Gitflow Workflow (2017). Disponible en https://es.atlassian.com/git/ tutorials/comparing-workflows/gitflow-workflow [Consultado en mayo de 2019]. [6] Ex Libris Announces the Cloud-Based Alma Library Management Service (6 de enero de 2011). Disponible en https://www.exlibrisgroup.com/press-release/ ex-libris-announces-the-cloud-based-alma-library-management-service/ [Consultado en mayo de 2019]. [7] Varios autores (2019). Symfony: The Best Practices Book. [ebook] Disponible en https://symfony.com/pdf/Symfony_best_practices_4.2.pdf [Consultado en mayo de 2019]. 107