Equation Chapter 1 Section 1
Proyecto Fin de Carrera
Ingeniería de Telecomunicación
Desarrollo de aplicación IoMT sobre Raspberry Pi
para monitorización de datos biomédicos
Autor: Alfredo Enrique Sáez Pérez de la Lastra
Tutor: Antonio Jesús Sierra Collado
Departamento de Ingeniería Telemática
Escuela Técnica Superior de Ingeniería
Universidad de Sevilla
Sevilla, 2016
Proyecto Fin de Carrera
Ingeniería de Telecomunicación
Desarrollo de aplicación IoMT sobre Raspberry Pi
para monitorización de datos biomédicos
Autor:
Alfredo Enrique Sáez Pérez de la Lastra
Tutor:
Antonio Jesús Sierra Collado
Profesor titular
Departamento de Ingeniería Telemática
Escuela Técnica Superior de Ingeniería
Universidad de Sevilla
Sevilla, 2016
ÍNDICE 1 Objetivos ............................................................................................................................................. 11
2 Introducción ........................................................................................................................................ 13 Recolección de datos ................................................................................................................................. 13 Almacenamiento e intercambio de la información procedente de dispositivos clínicos ...................... 14 Visualización de las constantes biomédicas del paciente ....................................................................... 14 Diagrama de componentes implicados .................................................................................................... 14
3 Soluciones IoMT alternativas ............................................................................................................... 17 Capsule ....................................................................................................................................................... 17 Picis Hawkeye ............................................................................................................................................. 19 OpenICE ...................................................................................................................................................... 19 Resumen de las soluciones IoMT alternativas ......................................................................................... 21
4 Análisis de tecnologías ......................................................................................................................... 23 Hardware de integración .......................................................................................................................... 23
4.1.1 Descripción ......................................................................................................................................... 23 4.1.2 Análisis de alternativas ...................................................................................................................... 23 Sistema Operativo del Hardware de integración .................................................................................... 25
4.2.1 Descripción ......................................................................................................................................... 25 4.2.2 Análisis de alternativas ...................................................................................................................... 25
4.2.2.1 SO Linux ...................................................................................................................................... 26 4.2.2.2 SO Windows ............................................................................................................................... 26 4.2.2.3 Otros SO ..................................................................................................................................... 26
Clúster de servidores de mensajes ............................................................................................................ 26 4.3.1 Descripción ......................................................................................................................................... 26 4.3.2 Análisis de alternativas ...................................................................................................................... 27
4.3.2.1 Apache Kafka .............................................................................................................................. 27 4.3.2.2 RabbitMQ ................................................................................................................................... 29
Base de datos ............................................................................................................................................. 30 4.4.1 Descripción ......................................................................................................................................... 30 4.4.2 Análisis de alternativas ...................................................................................................................... 30
4.4.2.1 Oracle .......................................................................................................................................... 31 4.4.2.2 MySQL......................................................................................................................................... 32 4.4.2.3 SQL Server .................................................................................................................................. 32
Entorno de desarrollo para BBDD............................................................................................................. 32 4.5.1 Descripción ......................................................................................................................................... 32 4.5.2 Análisis de alternativas ...................................................................................................................... 33
4.5.2.1 Oracle SQL Developer ................................................................................................................ 33 4.5.2.2 TOAD ........................................................................................................................................... 34
Lenguaje de programación ....................................................................................................................... 34 4.6.1 Descripción ......................................................................................................................................... 34 4.6.2 Análisis de alternativas ...................................................................................................................... 34
4.6.2.1 Java ............................................................................................................................................. 34 4.6.2.2 Python......................................................................................................................................... 35
Capa de persistencia .................................................................................................................................. 36 4.7.1 Descripción ......................................................................................................................................... 36 4.7.2 Análisis de alternativas ...................................................................................................................... 36
4.7.2.1 Bajo nivel de abstracción .......................................................................................................... 36 4.7.2.2 ORM: Object-Relational mapping............................................................................................. 36 4.7.2.3 Soluciones mixtas ...................................................................................................................... 38
Gestor de dependencias y construcción del proyecto ............................................................................. 38
4.8.1 Descripción ........................................................................................................................................ 38 4.8.2 Análisis de alternativas...................................................................................................................... 39
4.8.2.1 Ant .............................................................................................................................................. 39 4.8.2.2 Maven ........................................................................................................................................ 39 4.8.2.3 Gradle ......................................................................................................................................... 39
Entorno de desarrollo software ................................................................................................................ 40 4.9.1 Descripción ........................................................................................................................................ 40 4.9.2 Análisis de alternativas...................................................................................................................... 40
4.9.2.1 Eclipse ......................................................................................................................................... 41 4.9.2.2 IntelliJ IDEA ................................................................................................................................ 44 4.9.2.3 NetBeans .................................................................................................................................... 45 4.9.2.4 JDeveloper ................................................................................................................................. 45 Resumen del análisis de tecnologías empleadas en el proyecto............................................................ 46
5 Descripción de la propuesta ................................................................................................................. 47 Arquitectura ............................................................................................................................................... 47
5.1.1 Diseño funcional ................................................................................................................................ 47 5.1.2 Prototipo inicial de la arquitectura del proyecto ............................................................................ 48 5.1.3 Modelo definitivo .............................................................................................................................. 50 Hardware de integración con biodispositivos ......................................................................................... 53
5.2.1 El estándar HL7 .................................................................................................................................. 53 5.2.2 Protocolos propietarios .................................................................................................................... 54 5.2.3 Adaptación entre puertos estándares ............................................................................................. 56 Base de datos ............................................................................................................................................. 58
5.3.1 Tablespace ......................................................................................................................................... 58 5.3.2 Usuarios / Esquemas ......................................................................................................................... 59 5.3.3 Tablas ................................................................................................................................................. 61
5.3.3.1 Información común a todas las tablas ..................................................................................... 62 5.3.3.2 Esquema HEALTH ...................................................................................................................... 63 5.3.3.3 Esquema DATA_ACQ ................................................................................................................. 66
5.3.4 Secuencias .......................................................................................................................................... 71 5.3.5 Índices ................................................................................................................................................ 73 5.3.6 Permisos ............................................................................................................................................. 74 Sistema de intercambio de datos: Kafka ................................................................................................. 76
5.4.1 Nodos ................................................................................................................................................. 77 5.4.2 Topics ................................................................................................................................................. 78 5.4.3 DTOs ................................................................................................................................................... 80 5.4.4 Modelo ............................................................................................................................................... 81 5.4.5 Producers ........................................................................................................................................... 82 5.4.6 Consumers ......................................................................................................................................... 85 Componentes software ............................................................................................................................. 88
5.5.1 Estructura del software del proyecto .............................................................................................. 88 5.5.2 MEDIPI Suite ...................................................................................................................................... 92 5.5.3 MEDIPi-DOM ..................................................................................................................................... 94
5.5.3.1 CORE-DOM ................................................................................................................................. 96 5.5.3.2 HEALTH-DOM .......................................................................................................................... 103 5.5.3.3 DATACQ-DOM ......................................................................................................................... 107
5.5.4 MEDIPI-CLUSTER ............................................................................................................................. 108 5.5.4.1 CORE-CLUSTER ......................................................................................................................... 109
5.5.5 MEDIPI-APP ...................................................................................................................................... 113 5.5.5.1 CORE-APP ................................................................................................................................. 116 5.5.5.2 DDA ........................................................................................................................................... 120 5.5.5.3 SAVER ....................................................................................................................................... 135 5.5.5.4 CHART ....................................................................................................................................... 139
Resumen de la descripción de la propuesta ........................................................................................... 145
6 Validación ........................................................................................................................................... 147 Funcionamiento de la solución ............................................................................................................... 147 Resumen de la validación ........................................................................................................................ 150
7 Planificación........................................................................................................................................ 151 Fases de la planificación .......................................................................................................................... 151 Resumen de la planificación.................................................................................................................... 153
8 Conclusiones y líneas futuras .............................................................................................................. 155 Conclusiones ............................................................................................................................................. 155 Líneas futuras ........................................................................................................................................... 156
Referencias ................................................................................................................................................ 157
ÍNDICE DE FIGURAS FIGURA 2-1. PRIMER ESQUEMA GENERAL DEL PROYECTO.................................................................................................... 14 FIGURA 3-1. ESQUEMA DE SMARTLINX, DE CAPSULE TECH, INC ....................................................................................... 17 FIGURA 3-2. MODELOS DE LA SERIE SMARTLINX AXON ...................................................................................................... 18 FIGURA 3-3. SMARTLINX IQ ............................................................................................................................................ 18 FIGURA 3-4. LOGO DE HAWKEYE, DE LA COMPAÑÍA PICIS CLINICAL SOLUTIONS .................................................................. 19 FIGURA 3-5. INTERFAZ DE USUARIO DE HAWKEYE CON LAS CONSTANTES DE UN PACIENTE. .................................................. 19 FIGURA 3-6. ASTM F-2761 VS IMPLEMENTACIÓN DE OPENICE ......................................................................................... 20 FIGURA 3-7. INTERFAZ SUPERVISOR DE OPENICE .............................................................................................................. 20 FIGURA 3-8. INTERFAZ DISPOSITIVO OPENICE .................................................................................................................. 21 FIGURA 4-1. CATÁLOGO DE SISTEMAS OPERATIVOS PARA LA RASPBERRY PI 2 ....................................................................... 25 FIGURA 4-2. LOGO DE APACHE KAFKA .............................................................................................................................. 27 FIGURA 4-3. ARQUITECTURA BÁSICA JAVA ........................................................................................................................ 27 FIGURA 4-4. CONSUMICIÓN DE MENSAJES DEL CLÚSTER ..................................................................................................... 28 FIGURA 4-5. TOPICS Y PARTICIONES EN KAFKA ................................................................................................................... 28 FIGURA 4-6. LOGO RABBITMQ ....................................................................................................................................... 29 FIGURA 4-7. RANKING DE BASES DE DATOS DE DB-ENGINES. .............................................................................................. 30 FIGURA 4-8. LOGO ORACLE DATABASE ............................................................................................................................. 31 FIGURA 4-9. LOGO MYSQL ............................................................................................................................................ 32 FIGURA 4-10. LOGO SQL SERVER .................................................................................................................................... 32 FIGURA 4-11. LOGO SQL DEVELOPER .............................................................................................................................. 33 FIGURA 4-12. INTERFAZ GRÁFICA SQL DEVELOPER ............................................................................................................ 33 FIGURA 4-13. LOGO DE TOAD ........................................................................................................................................ 34 FIGURA 4-14. LOGO DE JAVA .......................................................................................................................................... 34 FIGURA 4-15. PLATAFORMAS JAVA .................................................................................................................................. 35 FIGURA 4-16. LOGO DE PYTHON ..................................................................................................................................... 35 FIGURA 4-17. LOGO DE JDBC ......................................................................................................................................... 36 FIGURA 4-18. LOGOS DE LAS PRINCIPALES IMPLEMENTACIONES JPA: HIBERNATE, ECLIPSELINK Y OPENJPA ............................ 37 FIGURA 4-19. LOGO DE QUERYDSL ................................................................................................................................. 37 FIGURA 4-20. LOGO DE IBATIS ........................................................................................................................................ 38 FIGURA 4-21. LOGO DE ANT ........................................................................................................................................... 39 FIGURA 4-22. LOGO DE MAVEN ...................................................................................................................................... 39 FIGURA 4-23. LOGO DE GRADLE ...................................................................................................................................... 39 FIGURA 4-24. COMPARATIVA ENTRE LOS IDES JAVA MÁS USADOS ...................................................................................... 41 FIGURA 4-25. ARQUITECTURA DE ECLIPSE. ........................................................................................................................ 41 FIGURA 4-26. INTERFAZ DE ECLIPSE IDE. .......................................................................................................................... 43 FIGURA 4-27. PERSPECTIVAS. .......................................................................................................................................... 43 FIGURA 4-28. VISTA DE GESTIÓN DE PROYECTOS. .............................................................................................................. 43 FIGURA 5-1. TIPOS DE CONSTANTES MÉDICAS, CONTINUAS Y DISCRETAS. .............................................................................. 48 FIGURA 5-2. PRIMER PROTOTIPO DE MEDIPI ................................................................................................................... 48 FIGURA 5-3. LEYENDA PRIMER PROTOTIPO DE MEDIPI ...................................................................................................... 49 FIGURA 5-4. ESQUEMA DEL SISTEMA DE FICHEROS DISTRIBUIDOS HDFS EMPLEADO POR HADOOP. ........................................ 50 FIGURA 5-5. ESQUEMA GENERAL DE LA ARQUITECTURA ..................................................................................................... 51 FIGURA 5-6. LEYENDA ESQUEMA GENERAL DE LA ARQUITECTURA ........................................................................................ 51 FIGURA 5-7. A LA IZQUIERDA, UN SENSOR DE PULSO CARDÍACO. A LA DERECHA, UN VENTILADOR DE GENERAL ELECTRICS ......... 53 FIGURA 5-8. LA RASPBERRY PI 2 B OFRECE 4 PUERTOS USB A BAJO COSTE ........................................................................... 55 FIGURA 5-9. ESQUEMA DE UNA ADAPTACIÓN RS232 (DB9) A USB .................................................................................... 57 FIGURA 5-10. ADAPTADOR EMPLEADO EN EL PROYECTO .................................................................................................... 57 FIGURA 5-11. TABLESPACE INICIALES DE UNA BASE DE DATOS ORACLE XE 11G R2 ................................................................ 58 FIGURA 5-12. PARÁMETROS DE CONEXIÓN POR DEFECTO DE LA BASE DE DATOS ................................................................... 59 FIGURA 5-13. MODELO DE DATOS ................................................................................................................................... 61
FIGURA 5-14. MODELO DE DATOS SOBRE BIODISPOSITIVOS ................................................................................................ 66 FIGURA 5-15. ARQUITECTURA BÁSICA DE KAFKA ............................................................................................................... 76 FIGURA 5-16. MODELO DEL CLÚSTER ............................................................................................................................... 81 FIGURA 5-17. ESTRUCTURA DE PROYECTOS DOM (DOCUMENT OBJECT MODEL) (BASE DE DATOS) ...................................... 89 FIGURA 5-18. ESTRUCTURA DE PROYECTOS CLUSTER (KAFKA) ............................................................................................ 89 FIGURA 5-19. ESTRUCTURA DE PROYECTOS APP (ELEMENTOS COMUNES DE APLICACIONES MEDIPI) .................................... 89 FIGURA 5-20. ESTRUCTURA DE PROYECTOS MEDIPI. REPRESENTACIÓN GRÁFICA................................................................. 90 FIGURA 5-21. ESTRUCTURA DE PROYECTOS MEDIPI. REPRESENTACIÓN EN CARPETAS .......................................................... 91 FIGURA 5-22. PROYECTO CORE-DOM............................................................................................................................... 96 FIGURA 5-23. PATRÓN DAO EN MEDIPI ...................................................................................................................... 101 FIGURA 5-24. PROYECTO HEALTH-DOM ......................................................................................................................... 104 FIGURA 5-25. PROYECTO DATACQ-DOM ........................................................................................................................ 107 FIGURA 5-26. PROYECTO CORE-CLUSTER ....................................................................................................................... 110 FIGURA 5-27. PROYECTO CORE-APP .............................................................................................................................. 117 FIGURA 5-28. ESTRUCTURA DE UN PROYECTO DURANTE EL DESARROLLO VS COMPILADO ..................................................... 118 FIGURA 5-29. PROYECTO DDA ..................................................................................................................................... 120 FIGURA 5-30. ESQUEMA DE DDA ................................................................................................................................. 123 FIGURA 5-31. PROTOCOLREADER.................................................................................................................................. 130 FIGURA 5-32. COMMANDLISTENER ............................................................................................................................... 133 FIGURA 5-33. DATASENDERMANAGER .......................................................................................................................... 134 FIGURA 5-34. PROYECTO SAVER .................................................................................................................................. 136 FIGURA 5-35. ESQUEMA DE SAVER .............................................................................................................................. 137 FIGURA 5-36. PROYECTO CHART ................................................................................................................................. 140 FIGURA 5-37. ESQUEMA DE CHART ............................................................................................................................. 141 FIGURA 6-1. RASPBERRY PI 2 MODELO B CONECTADA A UN BIODISPOSITIVO MEDIANTE UN ADAPTADOR USB – RS232 ........ 147 FIGURA 6-2. MÓDULO DDA DESPLEGADO EN LA RASPBERRY ........................................................................................... 147 FIGURA 6-3. ENTORNO DE DESARROLLO SQL CONECTADO CORRECTAMENTE A LA BASE DE DATOS........................................ 148 FIGURA 6-4. NODOS ZOOKEEPER, BROKER 1 Y BROKER 2 ARRANCADOS SOBRE UNA MISMA MÁQUINA WINDOWS ................ 148 FIGURA 6-5. LOG DE EJECUCIÓN, EN MODO DEBUG, DE DDA EN LA RASPBERRY ................................................................. 148 FIGURA 6-6. LOG DE UN BROKER KAFKA ........................................................................................................................ 149 FIGURA 6-7. LOG DE SAVER ........................................................................................................................................ 149 FIGURA 6-8. TABLA CONSTANT_INFO CON DATOS INSERTADOS POR SAVER ................................................................. 149 FIGURA 6-9. EJECUCIÓN DE CHART, EN LA QUE PODEMOS VER DATOS (SIMULADOS) A TIEMPO REAL ..................................... 150 FIGURA 7-1. PLANIFICACIÓN PLANTEAMIENTO ................................................................................................................ 151 FIGURA 7-2. PLANIFICACIÓN FASE DE ANÁLISIS ................................................................................................................ 151 FIGURA 7-3. PLANIFICACIÓN PRUEBA DE CONCEPTO ........................................................................................................ 152 FIGURA 7-4. PLANIFICACIÓN DESARROLLO I .................................................................................................................... 152 FIGURA 7-5. PLANIFICACIÓN DESARROLLO II ................................................................................................................... 153 FIGURA 7-6. PLANIFICACIÓN POSTDESARROLLO .............................................................................................................. 153
11
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
11
1 OBJETIVOS
n la actualidad, la práctica totalidad de los centros sanitarios de países desarrollados o en vías de desarrollo
cuentan con aplicaciones informáticas destinadas a la historia clínica del paciente. Son sistemas enfocados
a sustituir la gestión que se realizaba hasta hace pocos años de forma manual, generalmente mediante el
mantenimiento de archivos físicos en los que ubicar los informes del paciente o las notas de los facultativos. Sin
embargo, estas aplicaciones no son capaces de ir un paso más allá de la mera gestión clínica. En los últimos años
han aparecido nuevos enfoques que apuestan por la integración de los dispositivos cotidianos en Internet y la
explotación de las posibilidades que esto genera. A este concepto se la ha comido como Internet de las cosas,
IoT por sus siglas en inglés (Internet of Things). La aplicación de esta visión al ámbito clínico es poco habitual
en el mercado, de forma que apenas se han explorado sus previsibles beneficios.
Para realizar un proyecto que permita llevar el IoT al ámbito médico se debe partir de que los principales
dispositivos electrónicos vinculados al diagnóstico y tratamiento sanitario son aquellos encargados de medir la
información biomédica de los pacientes. Por tanto, cualquier apuesta por el desarrollo e implantación del IoMT
(Internet of Medical Things) debe sustentarse sobre la conexión de dichos dispositivos a la red gracias a
determinadas soluciones hardware y/o software que permitan su comunicación con otros programas o
aplicaciones.
Partiendo de todo lo anterior, parece razonable aventurar que toda apuesta por el IoMT debe marcarse como
labor fundamental abordar, en mayor o menor grado, los siguientes requisitos:
- El enfoque principal debe girar en torno a los biodispositivos, pues son ellos los que nos permiten hablar
de Internet de las cosas. Para ello se ha de diseñar una solución hardware y/o software que permita la
comunicación entre los dispositivos ya existentes en el mercado y el mayor número posible de
aplicaciones software a través de la red.
- Dichos dispositivos se han de comportar grosso modo como fuente de información. Este
comportamiento es análogo al ya existente en la actualidad, en el que los biodispositivos sirven como
fuente de información a los facultativos. Por tanto, la conexión de dichos dispositivos al Internet de las
cosas pasa por diseñar una herramienta capaz de ofrecer la información biomédica del paciente. Para
realizar tal labor se torna imprescindible conocer los protocolos empleados en la exportación de datos
clínicos.
- Una vez se ofrezca a las aplicaciones de Internet acceder a la información medida por los biodispositivos
se deberá estudiar la posibilidad de establecer una comunicación bidireccional, de forma de que dichas
aplicaciones puedan influir en el comportamiento de los biodispositivos.
- Gracias a la IoMT, la información biomédica puede estar centralizada y ser fácilmente accesible desde
cualquier dispositivo con acceso a Internet. Será necesario abordar algún desarrollo en este sentido, con
el fin de ejemplificar los beneficios más inmediatos de la tecnología empelada.
- Trabajar con biodispositivos va a estar ligado inherentemente a la gestión de grandes cantidades de
datos, dada la alta frecuencia a la que dichos componentes miden las variables clínicas de los pacientes.
Esto abre la puerta al big data, concepto que hace referencia al almacenamiento y procesado de los
datos recuperados en busca de patrones. La posibilidad de añadir big data a todo proyecto IoMT debe
ser tenida en cuenta desde el primer momento, incluso condicionando las decisiones de diseño si es
necesario.
Por tanto, el presente proyecto plantea una solución concreta a la necesidad de implantar el concepto IoT en el
ámbito sanitario, apostando por tomar como elemento central la recolección y centralización de la información
biomédica proporcionada por los múltiples biodispositivos empleados en el tratamiento sanitario de los
pacientes, permitiendo además que toda la información recuperada esté disponible por otras aplicaciones a través
de Internet. Por tanto, el proyecto abarca la intercomunicación con los biodispositivos y la articulación de un
método adecuado para que otras aplicaciones puedan acceder a los datos. Además, se ha fijado como objetivo
proporcionar alguna funcionalidad adicional que nos permita mostrar las utilidades que la IoMT (Internet of
E
Objetivos
12
12
Medical Things) puede llegar a ofrecer a los usuarios. Para ello se ha diseñado una herramienta de visualización
que permita a los facultativos observar en tiempo real todas las constantes de cualquier paciente. De lo expuesto
hasta ahora surgen claramente tres sectores, tres funcionalidades diferentes que conformarán el proyecto
desarrollado:
- Recuperación de la información biomédica
- Almacenamiento e intercambio de dichos datos con cualquier aplicación
- Visualización a tiempo real de la misma.
Como se ha visto en estos objetivos, la introducción del Internet de las cosas en el ámbito médico nos obliga a
alcanzar una serie de hitos, enfocados fundamentalmente a la comunicación con los biodispositivos. A lo largo
de esta memoria se detallará la solución concreta ideada para satisfacerlos, exponiendo en todo momento los
motivos de las decisiones adoptadas.
13
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
13
2 INTRODUCCIÓN
l presente proyecto plantea una solución concreta a la necesidad de implantar el concepto IoT (Internet de
las cosas) en el ámbito sanitario, apostando por tomar como elemento central la recolección y
centralización de la información biomédica proporcionada por los múltiples biodispositivos empleados en
el tratamiento sanitario de los pacientes, permitiendo además que toda la información recuperada esté disponible
por otras aplicaciones a través de Internet. Por tanto, el proyecto abarca la intercomunicación con los
biodispositivos y la articulación de un método adecuado para que otras aplicaciones puedan acceder a los datos.
Además, se ha fijado como objetivo proporcionar alguna funcionalidad adicional que nos permita mostrar las
utilidades que la IoMT (Internet of Medical Things) puede llegar a ofrecer a los usuarios. Para ello se ha diseñado
una herramienta de visualización que permita a los facultativos observar en tiempo real todas las constantes de
cualquier paciente. De lo expuesto hasta ahora surgen claramente tres sectores, tres funcionalidades diferentes
que conformarán el proyecto desarrollado:
- Recuperación de la información biomédica
- Almacenamiento e intercambio de dichos datos con cualquier aplicación
- Visualización a tiempo real de la misma.
Para mostrar esto de la manera más clara posible, tanto en el proyecto en sí como a lo largo de la presente
memoria se hará alusión constante a esta clasificación.
Recolección de datos
El primer aspecto a considerar es la recuperación de las constantes vitales de los pacientes, núcleo central del
proyecto. La primera tarea es conocer las capacidades de exportación de datos de los dispositivos médicos
existentes actualmente en el mercado. El principal problema a afrontar en esta sección es la amplia diversidad:
existen numerosísimos productos en el sector (desde empresas especializadas hasta gigantes del mercado como
Philips, General Electric, Siemens, etc.) y hay una evidente falta de estandarización tanto a nivel hardware como
software en el terreno de la exportación de la información.
Esto ha ido cambiando con el tiempo, y en la actualidad es cada vez más habitual encontrarse con biodispositivos
del mercado que ofrecen la posibilidad de exportar los datos mediante el protocolo HL7 (Health Level Seven)
[1]. Sin embargo, y a pesar de este y otros esfuerzos realizados por iniciativas como la Integrating the Healthcare
Enterprise [2], HL7 todavía no es un estándar ampliamente usado por las empresas del sector en el ámbito de la
exportación de datos e interoperación con los biodispositivos, y solo los productos más modernos lo soportan.
Esto último no es baladí; dado el alto coste de estos dispositivos su periodo de renovación es amplio, lo que
dificulta su implantación.
Este proyecto constituye la base de un sistema de gestión de información biomédica real, por lo que no se debe
ignorar la realidad del mercado: aunque el estándar HL7 es realmente interesante, es prioritario ofrecer una
solución de recolección de datos que satisfaga las necesidades actuales de los centros sanitarios, obligando a
tratar con los diferentes protocolos propietarios de cada empresa. Además, algunos protocolos propietarios
permiten modificar el funcionamiento del biodispositivo, funcionalidad para la que HL7 no está diseñado.
La inmensa mayoría de los protocolos propietarios empleados por los biodispositivos del mercado para permitir
la exportación de la información clínica medida emplean una comunicación cliente – servidor (a menudo
entendida como maestro – esclavo) a través de interfaces series (RS232 o USB) o paralelo. Por tanto, el escenario
que se ha de abordar es un conjunto de dispositivos diferentes, que necesitan cada uno un cliente específico con
el que establecer una comunicación directa. Para ello, se ha llevado a cabo la tarea de elaborar una solución que
incluye un componente hardware de integración. Por tanto, se necesita un hardware especial para cada
dispositivo (mejor dicho, para un conjunto de ellos) sobre el que correrá un software capaz de transformar la
información a un estándar interno de la suite. Cada hardware contará con una versión del mismo software, que
será totalmente parametrizable, pudiendo indicársele con qué conjunto de dispositivos debe comunicarse. Para
E
Introducción
14
14
cubrir esta necesidad, se ha realizado el diseño de la arquitectura del software, estableciendo un conjunto de
elementos comunes y patrones de diseño enfocados a facilitar el trabajo con protocolos diferentes.
Almacenamiento e intercambio de la información procedente de dispositivos clínicos
La información recuperada deberá almacenarse en algún sistema apropiado que permita a otras aplicaciones
acceder a la mima de forma estructurada. Es por este motivo por el que se ha diseñado una base de datos SQL
sobre la que gestionar no solo las variables clínicas de los pacientes, sino también información administrativa de
los pacientes necesaria para dotar de sentido a los biodatos, parametrización de las aplicaciones, etc. Sin
embargo, esto no cubre uno de los objetivos centrales del proyecto: permitir a otras aplicaciones de Internet
acceder a los datos de la manera más cómoda pasible. Con esto en mente, se ha optado por emplear un clúster
de servidores de mensajes que facilite el intercambio de información entre todos los puntos. Además, este
sistema es más adecuado para soportar el gran volumen de datos procesables y la necesidad de trabajar con
información a tiempo real.
Visualización de las constantes biomédicas del paciente
La última parte del proyecto es una aplicación que permita una visualización sencilla de la información
biomédica de un paciente concreto a tiempo real, ofreciendo así una interfaz de acceso para todos los públicos a
las posibilidades del conjunto de aplicaciones desarrolladas.
Diagrama de componentes implicados
Con todo lo anterior se ha introducido la función básica del proyecto: recuperación, almacenamiento/intercambio
y visualización de la información biomédica de un paciente. La solución ideada se compone de elementos
hardware y software, por lo que es necesario entender cuáles conforman conformar el proyecto y cómo se
relacionan entre sí. Para ello, se ha propone implementar el siguiente esquema esquema.
Figura 2-1. Primer esquema general del proyecto
Como se ha comentado ya, en MEDIPi existirán tres sectores diferentes, cada uno encargado de implementar
una funcionalidad determinada: recolección, almacenamiento/intercambio y visualización. Cada uno de ellos se
corresponde con una aplicación software diferente, que se deberá ejecutar, o no, sobre un hardware concreto:
15
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
15
- Software de recolección: se ejecutará sobre un hardware de integración con biodispositivos
específico, en concreto, una Raspberry Pi 2 Modelo B configurada para actuar como conector del
biodispositivo a otras aplicaciones; es decir, al Internet de las cosas.
- Software de almacenamiento: se podrá ejecutar sobre cualquier servidor u ordenador personal. Deberá
tener acceso a la base de datos y al clúster para poder realizar su funcionalidad.
- Software de visualización: correrá sobre cualquier ordenador o dispositivo compatible. Recuperará la
información de algún sistema de almacenamiento.
En resumen, MEDIPi se compondrá de tres aplicaciones software, un hardware de integración con
biodispositivos, una base de datos y un sistema de intercambio de mensajes formado por un clúster de servidores.
Todos estos elementos permiten cubrir las necesidades detectadas a la hora de abordar un proyecto que lleva el
IoT al ámbito sanitario, algo prácticamente inexistente en la inmensa mayoría de centros sanitarios. Aun así, sí
que existen ya en el mercado productos que apuestan por la recuperación y explotación de la información
procedente de biodispositivos, enfocando su funcionalidad desde una perspectiva similar a la aquí comentada:
recuperación de datos, almacenamiento/intercambio y visualización, lo que permite comprobar la solidez de los
puntos de partida del proyecto.
Todo lo descrito hasta ahora se detallará a lo largo de esta memoria: el próximo capítulo se dedicará a analizar
las soluciones existentes en el mercado de los productos IoMT, mientras que el siguiente realizará un estudio de
las tecnologías empleadas en el desarrollo, incluyendo una comparativa con el resto de alternativas. A
continuación, llegará el turno de llevar a cabo una descripción detallada del proyecto realizado, comentando
todas las partes implicadas y exponiendo ejemplos concretos de la solución implementada. Tras esto, se validará
el correcto funcionamiento del proyecto y se dedicará un capítulo a la planificación temporal que se ha seguido
durante el desarrollo del mismo. Para terminar, la memoria se cierra con un apartado enfocado a realizar las
conclusiones del proyecto, exponiendo los aspectos más importantes, analizando si se han cumplido o no los
objetivos marcados de partida y poniendo sobre la mesa las posibilidades futuras del proyecto.
Introducción
16
16
17
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
17
3 SOLUCIONES IOMT ALTERNATIVAS
l conjunto de herramientas y aplicaciones diseñadas nos permite ofrecer una serie de funcionalidades que
giran en torno a la información biomédica de un paciente. El proyecto no busca solo recuperar la
información biomédica, también gestiona su almacenamiento e intercambio y visualización. Encontrar en
el mercado soluciones con este enfoque no es tarea sencilla, pues nos encontramos ante una funcionalidad poco
extendida, pero es posible reconocer algunos productos comparables con la solución MEDIPi aquí diseñada, de
los cuales destacan tres:
- Capsule SmartLinx
- Picis Hawkeye
- OpenICE
Capsule
Figura 3-1. Esquema de SMARTLINX, de Capsule Tech, Inc
La empresa líder en soluciones de centralización e integración de datos procedentes de biodispositivos médicos
es, sin lugar a dudas, Capsule. A través de la marca SmartLinx [3], Capsule ofrece un conjunto variado y sólido
de hardware de integración con los dispositivos médicos compatible con software especializado en el tratamiento
de la información recopilada y con aplicaciones de gestión de historia clínica. La estructura empleada por
Capsule se sustenta en una red propia a la que se conectan todos los dispositivos compatibles. Cuando se trabaje
con protocolos propietarios se deberá emplear un módulo intermedio, que traduzca del protocolo propietario al
interno empleado en la red. Para ello cuenta con varios dispositivos, siendo el más básico las distintas revisiones
de la serie SmartLinx Axon [4]. Para el usuario el dispositivo Axon se comporta como una caja negra con
entradas y salidas. Como interfaces de entrada se ofrecen un conjunto de puertos serie a los que conectar los
biodispositivos, mientras que la salida es una mera interfaz ethernet o incluso WiFi que se debe conectar a la red
de Capsule. El componente Axon será el encargado de recuperar la información y adaptarla al protocolo interno
de la compañía.
E
Soluciones IoMT alternativas
18
18
Figura 3-2. Modelos de la serie SmartLinx Axon
Junto a los productos de la serie Axon Capsule ofrece otras soluciones de integración similares, que suelen
aportar funcionalidades extras a las ya comentadas. Por ejemplo, la serie SmartLinx Vitals Stream [5] permite
visualizar en su pantalla integrada la información recuperada, además de traducirla y enviarla a la red. En la
citada red de dispositivos que necesita la solución SmartLinx se encuentra al menos un servidor HL7 (Health
Level Seven) que procese la información procedente de aquellos biodispositivos que empleen el estándar de
comunicación médica.
Además de la mera recuperación de las variables clínicas de los pacientes, Capsule cuenta con el denominado
SmartLinx Client [6], un software que se comunica con el servidor central de la red para gestionar la selección,
filtrado, transformación y adición de más información a los datos recopilados. Con esta utilidad se pueden
mejorar la calidad de los biodatos de los pacientes, supliendo deficiencias o eliminando aquella información
errónea o incoherente. Sin embargo, estas funcionalidades solo se entienden como pasos previos a la utilización
de las constantes clínicas del paciente junto al resto de su historia médica, permitiendo así que sean útiles en el
diagnóstico de la situación del enfermo. Capsule centra sus esfuerzos en la integración con biodispositivos, más
que en presentar una solución de gestión de historia médica electrónica del paciente (Electronic Medical Record,
EMR). Su modelo de negocio consiste en ofrecer acuerdos con otros proveedores de aplicaciones EMR, que
recibirán los biodatos ya adaptados en formato HL7 o en otro formato acordado. Aun así, Capsule sí ha realizado
algunos movimientos en el ámbito de las interfaces gráficas para usuarios: SmartLinx IQ [7] es una aplicación
de escritorio que provee a los usuarios mecanismos para realizar análisis y generar estadísticas a partir de los
datos de los pacientes.
Figura 3-3. SmartLinx IQ
19
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
19
Picis Hawkeye
Figura 3-4. Logo de HAWKEYE, de la compañía Picis Clinical Solutions
La solución Hawkeye [8], de Picis, dice integrar datos procedentes de una gran variedad de dispositivos,
soportando varias interfaces de comunicación distintas, gracias a una librería con más de 350 drivers. La
compañía afirma soportar comunicaciones con biodispositivos a través de cables serie (entendiendo por ello
RS232), USB o a través de una red TCP/IP (Ethernet o Wifi), por lo que parece contar con un sistema de
integración propio para establecer las comunicaciones necesarias con protocolos propietarios, aunque luego se
sustente en el estándar HL7. Hawkeye usa interfaces HL7 estándar tanto para recibir información del paciente
(clínica y administrativa) como para enviar información interna en dicho formato, lo que permite conectar el
sistema con otras soluciones del mercado. Además, la información recuperada es centralizada en su sistema de
historia clínica, de forma que desde un solo ordenador se pueda acceder a las constantes clínicas de todos los
pacientes actuales del hospital, complementando esta funcionalidad con notificaciones, elementos de soporte a
la decisión, etc.
Figura 3-5. Interfaz de usuario de HAWKEYE con las constantes de un paciente.
OpenICE
OpenICE [9] es un software libre desarrollado en Java y soportado por el Medical Device Plug-and-Play (MD
PnP) Interoperability Program [10]. Esta iniciativa pretende fomentar el uso de la información procedente de los
biodispositivos mediante la estandarización del mayor número posible de los elementos implicados en la
recolección y procesamiento de la información clínica de los pacientes. Por ello, no solo apuestan por el empleo
de estándares relativamente consolidados como HL7, sino también por la creación y consolidación de otros
como el ASTM F-2761 [11], que define un modelo funcional de categorización y establecimiento de relaciones
entre los diferentes elementos claves que componen un entorno de gestión clínica centralizada enfocada en el
paciente. Es decir, este estándar va mucho más allá de HL7, busca definir un modelo concreto de integración
con biodispositivos clínicos: estructuración en capas, interfaces determinadas, etc.
Soluciones IoMT alternativas
20
20
Figura 3-6. ASTM F-2761 vs implementación de OpenICE
OpenICE se ha diseñado para servir como una posible implementación libre de este estándar, centrándose no
solo en la arquitectura definida por el mismo, sino también desarrollando librerías capaces de entender algunos
de los protocolos propietarios más empleados en el mercado. Llegado a este punto es fácil comprender las
enormes diferencias entre OpenICE y el resto de alternativas comentadas en el siguiente capítulo: hasta ahora se
han tratado soluciones comerciales, que buscaban ofrecer a los centros sanitarios un producto capaz de recuperar
información del mayor número posible de biodispositivos para poder ser incluidos en una aplicación EMR
determinada o ser analizados por determinadas soluciones. Sin embargo, OpenICE solamente pretende servir
como punto de apoyo a la expansión de un determinado patrón de trabajo, no estando enfocado a su implantación
directa en centros sanitarios. Aun así, OpenICE soporta algunas características comparables con el resto de
competidores.
Figura 3-7. Interfaz Supervisor de OpenICE
La ventana principal de OpenICE es la supervisora, que coincide con el elemento ICE Supervisor del estándar
comentado. En ella se debe tener acceso a la información recuperada por cada dispositivo existente en la red,
además de a un conjunto de funcionalidades extra (aquellas ubicadas en la zona izquierda de la ventana). El
estándar ASTM F-2761 implica que este supervisor será el enlace con todos los dispositivos, que deberán enviar
la información que recuperen a través de la red. Para ello, se necesita un adaptador en cada dispositivo que
21
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
21
conozca el protocolo propietario y lo transmita en el protocolo interno de la red. Para solucionar esto, OpenICE
propone arrancar su software en modo “consola” y seleccionar el dispositivo, de forma que tras establecer la
comunicación enviará los biodatos a la red mediante un sistema de datos distribuido conocido como DDS [12].
Figura 3-8. Interfaz Dispositivo OpenICE
Desde el supervisor bastará con seleccionar el dispositivo para visualizar sus datos a tiempo real, una
funcionalidad similar a la implementada en el presente proyecto.
Resumen de las soluciones IoMT alternativas
Las tres soluciones detalladas a lo largo del capítulo parten de una misma idea: existe la necesidad de recuperar
y poner a disposición de usuarios y terceras aplicaciones la enorme cantidad de información clínica que
proporcionan los dispositivos médicos implicados en el tratamiento de los pacientes. Sin embargo, a pesar de
que todas tienen un objetivo común se pueden observar fácilmente enormes diferencias. No solo tenemos que
establecer una separación entre soluciones propietarias (Capsule y Picis) y open-source (OpenICE), sino que es
imprescindible comprender las diferentes visiones implicadas: OpenICE está orientado a construir una red de
dispositivos con entidad propia mientras que SmartLinx y Hawkeye piensan claramente en integrar sus
soluciones en aplicaciones de historia clínica. Esta visión es menos innovadora, pero facilita la integración de
las soluciones con las aplicaciones informáticas existentes actualmente en centros sanitarios, algo que sin duda
habrá sido tenido en cuenta por las compañías desarrolladoras. Parece evidente que OpenICE está mucho más
cerca que sus competidores de alcanzar los requisitos que se han marcado en este proyecto, ya que comparte un
enfoque parecido. Al ser software libre, es posible realizar una comparativa tecnológica entre esta solución y el
proyecto realizado. En general existen elementos comunes, ya que se han empleado tecnologías para el lenguaje
de programación Java, aunque OpenICE carece de base de datos y apuesta por un sistema de intercambio de
datos mucho menos potente que el empleado en MEDIPi. En cualquier caso, ninguna de las aplicaciones aquí
comentadas abarca al completo el enfoque IoMT (Internet of Medical Things) sobre el que se ha construido el
proyecto.
Soluciones IoMT alternativas
22
22
23
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
23
4 ANÁLISIS DE TECNOLOGÍAS
ntes de plantearse abordar en detalle la descripción del proyecto realizado es imprescindible dedicar un
capítulo a realizar un estudio de las diferentes tecnologías que serán necesarias para llevarlo a cabo. Para
cada una de ellas se procederá a elaborar un análisis comparativo entre las diferentes opciones existentes
en el mercado, estableciendo sus puntos fuertes y débiles, como paso previo indispensable para elaborar la
justificación de la opción empleada en la suite desarrollada.
El proyecto abarca tres funcionalidades claramente diferenciadas: recuperación de datos, intercambio y
visualización de los mimos. Parece evidente que lo más sencillo es clasificar las tecnologías empleadas de
acuerdo a la función que pretenden satisfacer. Sin embargo, algunas de ellas pueden emplearse en más de un
ámbito, algo que ocurre con facilidad en todo proyecto software. Para aclarar todo este se parte de la siguiente
categorización:
- Tecnologías exclusivas de comunicación con biodispositivos: hardware de integración y sistema
operativo del Hardware de Integración
- Tecnologías exclusivas de intercambio de la información biomédica: clúster de servidores de mensajes,
base de datos y entorno de desarrollo para base de datos
- Tecnologías exclusivas de visualización: no se considera necesario al emplear las librerías de
visualización del lenguaje de programación común.
- Tecnologías comunes a todos los sectores: lenguaje de programación Software, capa de persistencia,
gestor de dependencias y construcción del proyecto y entorno de desarrollo software.
Hardware de integración
4.1.1 Descripción
Para abordar la integración con los biodispositivos se ha optado por un enfoque que nos permita soportar aquellos
dispositivos con protocolos propietarios, al ser la mayoría de los existentes en el mercado. Con este objetivo en
mente, es necesario un hardware de integración al que conectar físicamente los dispositivos mediante interfaces
serie RS232, USB, paralelo, etc. En el presente apartado se realizará un estudio de los principales ordenadores
de placa reducida (SBC, por sus siglas en inglés), que nos permitirán manejar los datos recibidos de cada
dispositivo diferente, basándonos en los criterios justificados en el análisis: versatilidad, escalabilidad, presencia
de interfaces estándar, coste y rendimiento
4.1.2 Análisis de alternativas
En la actualidad, el mercado de los SBCs consta de una gran salud debido a la explosión de popularidad, en
términos de ventas y de soporte por la comunidad, que primero Arduino y después las diferentes versiones de la
Raspberry Pi han cosechado. El número de opciones disponibles ha aumentado considerablemente, sobretodo
siguiendo el modelo de Raspberry, que ofrece un sistema más “completo” que Arduino, no solo enfocado al
desarrollo electrónico. En nuestro caso, nos encontramos ante un proyecto desarrollado en Java, que necesita de
la JVM corriendo sobre el hardware elegido. Conseguir esto sobre Arduino, una placa hardware enfocada a
desarrolladores de microcontroladores, es mucho más costoso que emplear un SBC, que pretende ofrecer la
misma funcionalidad que ordenadores personales o servidores con un bajo tamaño y coste. Es por este motivo
por el que se ha centrado el análisis en las SBCs existentes en el mercado descartando otro tipo de dispositivos
hardware.
A
Análisis de tecnologías
24
24
SBC Procesador Almacenamiento RAM Conectividad Precio
Raspberry Pi 2
Model B [13]
ARM Cortex
A7 900 MHz
quad-core
SD 1 GB
4 USB 2.0,
HDMI 1.4 y
Ethernet
45$
Jaguar One [14] Intel Atom
Z3735G 16 GB 1 GB DDR3
3 USB 2.0,
HDMI 1.4 y
Ethernet
70$
Orange Pi [15] ARM A7 4x1.2
Ghz
SD
512 MB
1 USB 2.0,
HDMI y
Ethernet
10$
ODROID-C1+
[16]
ARMv7 1.5
Ghz quad core SD 1 GB DDR3
4 USB 2.0,
HDMI 2.0 y
Ethernet
40$
Hummingboard
[17]
ARM A9 hasta
Dual Core SD 2 GB
2 USB 2.0,
HDMI 2.0 y
Ethernet
70$
Beaglebone
Black [18]
ARM A8 1 Ghz
(TI Sitara
AM3358)
4 GB 512 MB
DDR3
1 USB 2.0,
HDMI 2.0 y
Ethernet
45€
Cubieboard5
[19]
ARM A7 8
núcleos 8 GB + SATA 2 GB DDR3
HDMI, USB,
Displayport,
audio digital,
Wifi, BT 4.2
99$
pcDuino4 [20]
ARM A7 4
núcleos
(Allwinner H3)
8 GB 1 GB DDR3
2 USB 2.0,
HDMI 2.0 y
Ethernet
49$
En la tabla superior se puede observar una comparativa de las principales características de los SBCs con mayor
presencia en el mercado. El primer punto a tener en cuenta es que se va a conectar el hardware escogido con los
biodispositivos del paciente mediante interfaces serie o paralelo. Para ello, se partirá de puertos USB (los más
habituales) empleando adaptadores cuando sea necesario. De cara a reducir los costes del proyecto se ha de
optimizar el número de placas de integración, pasando de una primera lógica 1 dispositivo – 1 SBC a la de 1
puerto – 1 dispositivo. Para ello no solo es necesario un dispositivo hardware con el mayor número de puertos
USB posibles, sino también con la capacidad suficiente como para ejecutar la solución de recolección de datos
con múltiples lectores de protocolos a la vez. La condición anterior nos lleva a reducir las opciones a dos: la
Raspberry Pi 2 Model B y el ODroid-C1+, quienes cuentan con unas características similares: 4 puertos USB,
ARMv7 y 1GB DDR3. ODroid gana en procesador, ofreciendo mayor velocidad de cálculo, mientras que se
produce un empate técnico en el precio. Las características técnicas de ambos SBCs proporcionan la capacidad
suficiente como para ejecutar un sistema operativo ligero (un Linux sin entorno gráfico) y la aplicación de
recolección de datos diseñada con los 4 lectores de protocolos funcionando en paralelo. Por tanto, la decisión
está reñida, pero el gran soporte que la comunidad ofrece a Raspberry decanta la balanza a su favor: los sistemas
operativos diseñados para Raspberry cuentan con una amplia utilización en el mercado, estando
convenientemente pulidos y garantizando una gran disponibilidad. Además, el rendimiento de estas placas
hardware durante largos periodos de tiempo es más que solvente, como han podido comprobar miles de usuarios
en los últimos meses.
25
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
25
Sistema Operativo del Hardware de integración
4.2.1 Descripción
Una vez se ha elegido a la Raspberry Pi 2 Modelo B como la placa que conformará el hardware de integración
con biodispositivos el siguiente paso lógico es seleccionar el sistema operativo de la SBC, que deberá poder
ejecutar el software de recolección de datos diseñado en el presente proyecto. Como se verá un poco más
adelante, el único requisito que se deriva del software diseñado es la necesidad de soportar la Java Virtual
Machine, pues todos los componentes de la suite MEDIPi se han desarrollado bajo el lenguaje de programación
de Oracle.
4.2.2 Análisis de alternativas
Para ejecutar la aplicación de recuperación de biodatos del paciente solo necesitaremos un sistema operativo
capaz de correr la máquina virtual de Java con soltura suficiente, garantizándonos el rendimiento y fiabilidad
que requiere una tarea tan crítica como la que se va a realizar en la Raspberry.
Figura 4-1. Catálogo de Sistemas Operativos para la Raspberry Pi 2
La imagen superior corresponde con una captura del apartado dedicado a sistemas operativos de la página web
de Raspberry. En la línea superior, encuadrados en un rectángulo negro, se encuentran los sistemas operativos
con soporte oficial, dejando las filas inferiores para los sistemas operativos de terceras compañías.
El primer paso va a ser eliminar de la lista de candidatos a los sistemas operativos que no sean de propósito
general ni estén enfocados a desarrolladores.
- Noobs: sistema operativo de primer contacto. Más que un sistema operativo, puede entenderse como
un conjunto de utilidades básicas que nos permitirán seleccionar el sistema operativo que queremos
instalar de forma sencilla.
Análisis de tecnologías
26
26
- OSMC / LibreElec: sistemas operativos enfocados a convertir la Raspberry Pi en centros multimedia.
- PiNet: sistema operativo enfocado a la utilización de la Raspberry en ámbitos educativos, ofreciendo
utilidades para la gestión de múltiples Raspberrys en una misma red escolar.
- Weather Station: versión de Raspbian para llevar a cabo acciones de estación climática.
Una vez descartados los SOs específicos, clasificaremos los restantes en tres grupos:
4.2.2.1 SO Linux
Los Sistemas Operativos Linux dominan las preferencias de los poseedores de los distintos modelos de
Raspberry Pi, gracias a las amplias posibilidades que ofrecen tanto a desarrolladores como a usuarios finales.
Tanto Raspbian [21] como Ubuntu Mate [22] proporcionan un Sistema Operativo Linux completo (incluso con
interfaz gráfica), adaptando respectivamente Debian y Ubuntu Mate a la arquitectura ARM v7. Al hablar de
implementaciones código libre de reputados sistemas operativos no solo podemos contar con la tranquilidad de
encontrarnos frente a SOs sólidos, sino que también tenemos a nuestra disposición una enorme gama de
utilidades, librerías, paquetes, etc. Por ejemplo, la imagen de Raspbian trae por defecto las últimas versiones de
Python y Java, buscando facilitar lo máximo posible las tareas al desarrollado. Esto último, unido al soporte
oficial de la Raspberry Foundation, hacen de Raspberry la opción más lógica para servidor de base a la aplicación
de recolección de biodatos del proyecto.
Ya solo queda una opción por comentar: Snappy Ubuntu Core [23] es el producto de la familia Ubuntu enfocado
al IoT (Internet de las cosas). Se trata de un sistema operativo muy joven que parte de un enfoque totalmente
diferente, hasta el punto de romper con algunos puntos habituales hasta ahora en las distribuciones de Canonical.
Se trata sin duda de una opción interesante, pero no está todavía suficientemente madura, por lo que cualquier
problema podría necesitar de un tiempo de resolución superior al de otras distribuciones Linux.
4.2.2.2 SO Windows
Windows 10 IoT Core [24] es una versión del núcleo de Windows 10 adaptada para el Internet de las Cosas,
pudiendo funcionar tanto en dispositivos con pantalla o sin ella, cargando tan solo el núcleo del sistema y
funcionando con pocos recursos. Este SO está claramente enfocado al mundo de IoT, y ni siquiera cuenta con
soporte para Java, lo que lo hace inviable para nuestro Proyecto.
4.2.2.3 Otros SO
En esta categoría clasificamos a RISC OS [25], un sistema operativo con núcleo o kernel propio, distinto a los
kernels habituales como Linux y Windows. Fue desarrollado por Acorn Computers, una compañía británica que
dejó de existir en noviembre del año 2000, y cuyo código fuente es mantenido por RISC OS Ltd. con una licencia
Open Source.1 Se diseñó pensando en ordenadores basados en chips ARM.2. Ofrece un rendimiento menor al
de los SO Linux, además de contar con un soporte mucho más limitado, motivos por los que se ha descartado
su uso.
Clúster de servidores de mensajes
4.3.1 Descripción
Uno de los aspectos centrales del proyecto es la creación de un clúster que nos permita un intercambio adecuado
de la información biomédica de los pacientes entre todos los componentes del proyecto: almacenador de
constantes vitales en base de datos, visualización y otros posibles módulos a construir sobre las bases existentes,
así como aplicaciones de terceros. Para satisfacer esta necesidad, el proyecto abarca la configuración de un
conjunto de nodos y servidores diseñados que conformarán un clúster empleado como un bus de información,
así como la implementación y parametrización de consumidores y productores para dicha estructura de red. Las
características que debe soportar el clúster del proyecto no escapan de las requeridas habitualmente en esta
tecnología: alta disponibilidad, redundancia, seguridad, escalabilidad y facilidad para la integración con el
software diseñado.
27
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
27
4.3.2 Análisis de alternativas
4.3.2.1 Apache Kafka
Figura 4-2. Logo de Apache Kafka
Apache Kafka [26] es un sistema de procesado de mensajes. Los mensajes se publican por los productores
(producers) en colas denominadas topics y se consumen de la cola por los consumidores (consumers) suscritos
a dicha cola. Es una solución al problema de los productores-consumidores concurrentes. En este problema
existen uno o más actores (productores) que generan los mensajes que son procesados por uno o más actores
(consumidores) de manera concurrente.
El sistema de encolado y procesado de mensajes es distribuido y particionado en diferentes instancias
denominadas brokers que conforman el clúster de Kafka. Dentro del clúster, en cada broker, cada topic o cola
está dividida en particiones en las que los mensajes se almacenan de manera secuencial. Las particiones de un
topic permiten:
- Distribuir la carga de trabajo entre diferentes brokers y consumidores
- Tener tolerancia a errores de los brokers, al poder tener replicadas la misma partición en brokers
distintos (aunque solo una es la activa, pueden tomar el sitio de la activa si ésta cae)
Figura 4-3. Arquitectura básica Java
El productor es quien encola los mensajes en cada partición, que mantiene estos mensajes de manera secuencial
y ordenada. Es el propio productor quien decide en qué partición específica del topic se almacena el mensaje.
Análisis de tecnologías
28
28
De esta manera el balanceado de los mensajes en las diferentes particiones queda en manos del productor, no
del clúster, que puede basarse en criterios de balanceado de carga o de lógica de negocio.
El modo en que se distribuyen los mensajes a los consumidores es también bastante flexible. Los consumidores
se agrupan en grupos de consumidores. El clúster distribuye los mensajes de cada partición a un único
consumidor del grupo, pero si hubiese más de un grupo de consumidores lo haría para cada grupo. De esta
manera nos encontramos en una cola clásica con la carga balanceada entre varios consumidores si solo hay un
grupo, y en un modelo de suscripción si tuviésemos más de un grupo.
Figura 4-4. Consumición de mensajes del clúster
El único modo de garantizar que los mensajes se consumen en el orden en que se crean es teniendo una única
partición por topic y un solo consumidor por grupo. El único modo de garantizar que los mensajes se consumen
una sola vez es teniendo un único grupo de consumidores.
El consumidor de cada partición es el que decide el orden en que se procesan los mensajes y los mensajes que
se procesan. El consumidor tiene libertad para moverse en la partición accediendo a los mensajes de manera
indexada (offset desde comienzo) y en sentido y con el criterio que quiera, y es quién lleva constancia de si han
sido consumidos o no. Esto permite que la política o lógica de procesado de mensajes sea mucho más flexible
que la cola tradicional, permitiendo el reprocesado de mensajes, el procesado en cualquier orden o el consumo
de mensajes en la misma partición por diferentes consumidores (si están en distintos grupos).
Los mensajes se mantienen en la partición, con independencia de si han sido consumidos o no, durante el tiempo
indicado por la política de retención de mensajes. No hay borrado de los mensajes. Esto se debe a una decisión
de diseño muy eficiente, puesto que el acceso al disco secundario es siempre secuencial y no aleatorio, con las
ventajas de rendimiento que supone.
Figura 4-5. Topics y particiones en Kafka
29
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
29
En Kafka podemos combinar el modo de almacenaje clásico de los mensajes en las particiones con un modo
mapa, en el que los mensajes cuentan con clave y valor. Como todos los mensajes con la misma clave se
almacenarán siempre en la misma partición de un topic podemos activar la opción log.cleaner.enable para
mantener en la partición solo el último valor de una clave. A esta operación se le denomina “log compaction”
[27] y permite gestionar los topics como si fuesen mapas.
Solo queda comentar un punto de Kafka: pueden existir uno o varios nodos Zookeeper en un clúster de Kafka.
Apache ZooKeeper es un servicio de datos en estructura de árbol. El servicio es centralizado, replicado, de alta
disponibilidad y escalable; y garantiza la integridad en accesos concurrentes y el orden de modificación de los
datos de manera secuencial. El sistema está formado por un conjunto distribuido de instancias denominado
quorum que sirven la misma información, en el caso de Kafka, los brokers.
El servicio es centralizado porque solo una de las instancias en ejecución hace las veces de instancia maestra, es
la única que puede modificar los datos y que los replica al resto de las instancias. Esto garantiza el orden de los
cambios, la atomicidad transaccional y la integridad concurrente de la información. Cuando la instancia maestra
cae, el algoritmo de elección de líder, decide qué instancia es la nueva instancia maestra. El acceso de lectura,
sin embargo, puede hacerse a cualquiera de las instancias levantadas del sistema. Esto por un lado garantiza la
disponibilidad, al poder cualquier nodo convertirse en líder, y el rendimiento y la escalabilidad, al permitir poder
añadir todas las instancias que queramos al quorum para repartir la carga. Este diseño lo hace más adecuado para
situaciones en que la lectura de datos es mucho más habitual que la escritura (al menos 1:10 según la
documentación).
En resumen, Kafka es un solución tremendamente sólida, muy versátil y sencilla para consumir o transformar
información, ofreciendo una fiabilidad, escalabilidad y velocidad sin competencia en el mercado, motivos por
los que se ha elegido como sistema de intercambio de mensajes del proyecto.
4.3.2.2 RabbitMQ
Figura 4-6. Logo RabbitMQ
RabbitMQ [28] es un software de negociación de mensajes de código abierto, y entra dentro de la categoría de
middleware de mensajería. Implementa el estándar Advanced Message Queuing Protocol (AMQP). El servidor
RabbitMQ está escrito en Erlang, un lenguaje enfocado a programación concurrente y distribuida, y utiliza el
framework Open Telecom Platform (OTP) para construir sus capacidades de ejecución distribuida y
conmutación ante errores. Este MOM (Message oriented middleware), implementa un tipo de componentes
denominados exchanges, los cuales permiten enlazar colas, topics y otros exchanges entre sí. Esta opción facilita
mucho la labor de diseñar una jerarquía de colas más compleja y dinámica. El proyecto RabbitMQ consta de
diferentes partes:
- El servidor de intercambio RabbitMQ en sí mismo
- Pasarelas para los protocolos HTTP, XMPP y STOMP.
- Bibliotecas de clientes para Java y el framework .NET. (Bibliotecas similares para otros lenguajes se
encuentran disponibles por parte de otros proveedores).
- El plugin Shovel que se encarga de replicar mensajes desde un corredor de mensajes a otros
Se trata de un producto sin duda más maduro que Kafka, pero también menos optimizado, contando con
rendimiento notablemente inferior. Además, Kafka es una tecnología que se encuentra actualmente en plena
fase final de desarrollo por Apache, lo que nos garantiza una empresa fuerte detrás preparada para dar soporte y
mejorar el producto, además de poner en valor el gran número de usuarios que ya ha conseguido antes de lanzar
su primera versión estable.
Análisis de tecnologías
30
30
Base de datos
4.4.1 Descripción
Un elemento clásico en las aplicaciones informáticas es la base de datos, lugar donde se persiste la información
manejada. El presente proyecto define una suite de aplicaciones, desarrollando para tal efecto una arquitectura
sólida que permita la adición de nuevos módulos y funcionalidades. Por tanto, el diseño de una base de datos
era inherente al proyecto desde el principio, incluso si no se hubiera detectado su necesidad durante el análisis
de las aplicaciones que abarca esta memoria.
4.4.2 Análisis de alternativas
Para el proyecto se requerirá una base de datos relacional, por lo que en este capítulo nos centraremos en
comparar los diferentes motores de base de datos relacionales existentes, dividiéndolos en dos categorías:
- Libres: MySQL, PostgreSQL, Firebird, SQLite. las dos primeras son las más conocidas y, sin duda, son
válidas para un proyecto, por complicado que sea.
- Propietarias: Dejando de lado las menos “potentes”, dBASE (un clásico, pero muy superada),
FileMaker, Interbase, Access… tenemos las 2 más conocidas, Oracle y SQL Server de Microsoft,
además de IBM DB2, Informix, Progress…
Con lo anterior, y con el objetivo de ofrecer un análisis sólido que sirva para elegir la base de datos del proyecto
se va a partir de un estudio del mercado de motores de bases de datos. La investigación más completa es la
realizada por DB-Engine [29], quienes miden la popularidad de los distintos motores de base de datos a partir
de las consultas en buscadores de ámbito general (Google, Bing, etc.), menciones en webs especializados
(Stackoverflow. DBA Stack Exchange, etc), número de ofertas de trabajo en portales como Indeed y encuestas
de uso entre profesionales del sector. Según los datos de dicho estudio, existe un trio de soluciones que llevan
una amplia ventaja sobre el resto en número de usuarios y popularidad. Esta es una característica fundamental,
no solo índica la posible robustez de una base de datos, sino que implicará un mayor soporte e información
técnica sobre ellas por parte de la comunidad. Por tanto, en el presente análisis solo contemplaremos esas tres
bases de datos: Oracle, MySQL y SQL Server.
Figura 4-7. Ranking de bases de datos de DB-Engines.
31
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
31
4.4.2.1 Oracle
Figura 4-8. Logo Oracle Database
La base de datos de Oracle, conocida como Oracle Database [30], es uno de los motores de bases de datos más
robustos del mercado, destacando por su baja tasa de errores y su alta fiabilidad. Su principal punto negativo
radica en su condición de código propietario, permitiendo solo su distribución bajo licencia comercial. Por el
contrario, cuenta con una amplia gama de puntos positivos. En primer lugar, cuenta con algunas características
no habituales en la competencia, como recuperación de transacciones erróneas, Grid Computing, Gateways,
FlashBack Table, etc. El otro gran punto a favor de Oracle es el lenguaje PL/SQL. PL/SQL es un lenguaje de
programación propio de Oracle que permite a los desarrolladores escribir procedimientos almacenados, triggers
e incluso funciones en Java. Gracias al compilador Java y la JVM incluida en las bases de datos, es posible
compilar programas Java directamente en la base de datos o leer una clase utilizando utilidades proporcionadas
por la base de datos. A pesar de tratarse de software propietario, la comunidad entorno a Oracle Database es
especialmente extensa, lo que nos permitirá encontrar ayuda de otros usuarios ante prácticamente cualquier
problema que nos pueda surgir. Todas estas características han hecho a Oracle Database dominar el mercado y
ser escogido como motor de base de datos de este proyecto.
Actualmente Oracle mantiene cinco ediciones de su base de datos, intentando presentar una gama de productos
que se adecue a las necesidades específicas de cada cliente.
- Oracle Database Standard Edition One: Ofrece facilidad de uso, potencia y rendimiento para grupos de
trabajo, a nivel de departamentos y aplicaciones Web. Desde los entornos de un solo servidor para
pequeñas empresas a los entornos de sucursales altamente distribuidos.
- Oracle Database Standard Edition: Oracle Database Standard Edition ofrece las funcionalidades de la
edición Standard Edition One, con el apoyo de máquinas más grandes y la agrupación de los servicios
con Oracle Real Application Clusters (Oracle RAC).
- Oracle Database Enterprise Edition: Ofrece el rendimiento, la disponibilidad, la escalabilidad y la
seguridad necesaria para las aplicaciones de misión crítica, tales como el procesamiento de grandes
volúmenes de transacciones en línea (OLTP), almacenes de datos en consultas intensivas y exigentes
aplicaciones de Internet.
- Oracle Database Express Edition: Es una edición básica de la base de datos de Oracle. Es rápida de
descargar, fácil de instalar y administrar, y es libre de desarrollar, implementar y distribuir. Es fácil de
actualizar a las otras ediciones de Oracle sin migraciones costosas y complejas. Oracle Database XE se
puede instalar en cualquier máquina tamaño con cualquier número de CPUs, almacena hasta 11 GB de
datos de usuario, con un máximo de 1 GB de memoria, y con una sola CPU en la máquina host. Existe
un foro en línea, para dar soporte. Como parece evidente, es la opción ideal para nuestro proyecto.
Actualmente la última versión estable de Oracle Database es la 12, pero no cuenta con una versión
exprés, por lo que emplearemos la última disponible: la 11g xe.
- Oracle Database Personal Edition: Soporta los entornos de desarrollo de un solo usuario y el despliegue
que requieren la plena compatibilidad con Oracle Database Standard Edition One, Oracle Database
Standard Edition y Oracle Database Enterprise Edition. Le diferencia la excepción de la no opción de
Oracle Real Application Clusters. Personal Edition sólo está disponible en los sistemas operativos
Windows y Linux. Tampoco incluye los módulos de administración.
Análisis de tecnologías
32
32
4.4.2.2 MySQL
Figura 4-9. Logo MySQL
MySQL [31] es la base de datos de código abierto más popular del mundo, tal y como anuncia orgullosamente
Oracle en su web. MySQL fue inicialmente desarrollado por MySQL AB, que fue adquirida por Sun
Microsystems en 2008, y ésta a su vez fue comprada por Oracle Corporation en 2010, la cual ya era dueña desde
2005 de Innobase Oy, empresa finlandesa desarrolladora del motor InnoDB para MySQL. MySQL es popular
por su velocidad de procesamiento y por emplear una licencia GLP (General Public Licence). Gracias a su
extensión, tiene conectores para la práctica totalidad de lenguajes importantes (algunos como Ruby o C++ no
cuentan con conector para Oracle Database). En cuento funcionalidad, destaca sobre la mayoría de rivales,
aunque quede un poco por debajo en una comparación con Oracle Database.
4.4.2.3 SQL Server
Figura 4-10. Logo SQL Server
SQL Server [32] es el sistema de gestión de bases de datos relacionales Microsoft que está diseñado para el
entorno empresarial. SQL Server se ejecuta en T-SQL (Transact-SQL), un conjunto de extensiones de
programación de Sybase y Microsoft que añaden varias características a SQL estándar, incluyendo control de
transacciones, excepción y manejo de errores, procesamiento fila, así como variables declaradas. SQL Server
fue lanzado en noviembre de 2005 y Microsoft presume de que su entrada en el mercado proporcionó una mayor
flexibilidad, escalabilidad, confiabilidad y seguridad a las aplicaciones de base de datos, y permitió que fueran
más fáciles de crear y desplegar, lo que reduce la complejidad en la gestión de bases de datos. Para este proyecto
SQL Server es una solución demasiado cerrada y engorrosa, frente a la facilidad de instalación y manejo que
ofrece la Express Edition de Oracle. Además, se encuentra en clara inferioridad frente a las otras bases de datos
analizadas en este apartado.
Entorno de desarrollo para BBDD
4.5.1 Descripción
Siempre que se trabaje con una base de datos es necesario un entorno de desarrollo para la misma, una aplicación
que nos permita ejecutar instrucciones en lenguaje SQL y administrar las funcionalidades de la base de datos.
La elección del IDE para base de datos dependerá enormemente del motor de base de datos escogido en el punto
anterior, pues existen muchas soluciones que solo soportan determinadas bases de datos, aunque exista una
tendencia cada vez mayor a que esto cambie. Para las bases de datos Oracle existen dos entornos de desarrollo
que destacan sobre los demás, por presencia en el mercado y funcionalidad: Oracle SQL Developer y TOAD.
33
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
33
4.5.2 Análisis de alternativas
4.5.2.1 Oracle SQL Developer
Figura 4-11. Logo SQL Developer
SQL Developer [33] es el entorno de desarrollo para bases de datos de Oracle. Desarrollado en Java, actualmente
ofrece soporte para otras bases de datos distintas a la proa del Oracle, como MySQL o SQL Server. Al ejecutarse
sobre la Java Virtual Machine el programa se encuentra disponible para todos los sistemas operativos soportados
por Java, lo que en la práctica abarca cualquier opción que pueda hacer el usuario. Es una aplicación gratuita de
código propietario. Cuenta con una amplia gama de funcionalidades, cubriendo todas las elementales y contando
con alguna función avanzada. Se construye en torno a un interfaz principal que permite navegar por un árbol
jerárquico de objetos contenidos en bases de datos y realizar operaciones sencillas sobre ellos. Junto al
explorador se encuentra un área para ejecutar sentencias SQL y PL/SQL. A través de esta interfaz gráfica no
solo es posible ejecutar sentencias, sino también modificar gráficamente distintos objetos de la base de datos
(tablas, campos, índices, etc.) facilitando la tarea a los desarrolladores inexpertos. Otras funciones interesantes
son el modelado E/R (que genera un diagrama de relación entre clases) y la importación / exportación de datos
en múltiples formatos. Además, cuenta con un Sistema de plugins de forma que los usuarios puedan desarrollar
sus propias funcionalidades.
Figura 4-12. Interfaz gráfica SQL Developer
En resumen, SQL Developer es un entorno de desarrollo para base de datos tremendamente intuitivo y fácil de
usar, que no requiere de grandes conocimientos para su utilización. Además, cubre de sobra las funcionalidades
básicas, proveyendo a los usuarios expertos de otras capacidades más avanzadas. En nuestro caso, su condición
de gratuito y su facilidad de uso han sido los puntos clave que han provocado su elección en el presente proyecto.
Análisis de tecnologías
34
34
4.5.2.2 TOAD
Figura 4-13. Logo de TOAD
TOAD [34] es una herramienta gráfica para desarrollo de base de datos creada por Quest Software, actualmente
dentro de la corporación Dell, Inc. Originalmente se trataba de una aplicación diseñada exclusivamente para
bases de datos de Oracle (de hecho, TOAD son las siglas de Tool for Oracle Application Developers) aunque
actualmente soporta la práctica totalidad de bases de datos incluyendo las dominadoras del mercado (Oracle,
MySQL, PostgreSQL, Microsoft SQL Server, etc.). Se trata de una aplicación propietaria que se distribuye bajo
dos versiones: una Freeware limitada y una versión de pago completa. Cuenta con una enorme cantidad de
funcionalidades, centradas especialmente en los DBA (Data Base Administrator). Por ejemplo, realizar copias
de una base de datos (exports e import) es tremendamente sencillo desde la interfaz gráfica de TOAD, contando
con un simple asistente que te ayuda a través de todo el proceso. Es, sin duda, la opción más completa del
mercado para los desarrolladores de una base de datos de Oracle. Sin embargo, la versión gratuita se queda un
poco corta, ofreciendo prácticamente las mismas funciones que el SQL Developer, lo que no compensa una
interfaz menos intuitiva.
Lenguaje de programación
4.6.1 Descripción
Llega el momento de comentar el lenguaje de programación empleado para todo el proyecto, comparándolo con
otras opciones posibles. En este caso la elección está fundamentada en los conocimientos del desarrollador, pues
no es tan fácil cambiar de lenguaje de programación como optar entre dos entornos de desarrollo. Aun así, se
comentarán las ventajas e inconvenientes de la opción elegida frente a otros posibles lenguajes que presentan
características interesantes para el proyecto realizado.
4.6.2 Análisis de alternativas
4.6.2.1 Java
Figura 4-14. Logo de Java
Java [35] es un lenguaje de programación de propósito general, concurrente, orientado a objetos que fue
diseñado específicamente para tener tan pocas dependencias de implementación como fuera posible. Su
intención es permitir que los desarrolladores de aplicaciones escriban el programa una vez y lo ejecuten en
cualquier dispositivo, por lo que las aplicaciones de Java son generalmente compiladas a bytecode (clase Java)
que puede ejecutarse en cualquier máquina virtual Java (JVM) sin importar la arquitectura de la computadora
subyacente. Fue originalmente desarrollado por James Gosling de Sun Microsystems (la cual fue adquirida por
la compañía Oracle) y publicado en 1995 como un componente fundamental de la plataforma Java de Sun
Microsystems.
35
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
35
Se definen tres plataformas Java, en un intento por cubrir distintos entornos de aplicación. Así, las APIs
pertenecen a una plataforma u otra en función de su funcionalidad.
- Java ME (Java Platform, Micro Edition): orientada a entornos de recursos limitados, como teléfonos
móviles.
- Java SE (Java Platform, Standard Edition): para entornos de gama media y estaciones de trabajo. Aquí
se sitúa al usuario medio en un PC de escritorio.
- Java EE (Java Platform, Enterprise Edition): orientada a entornos distribuidos empresariales o de
Internet.
Figura 4-15. Plataformas Java
La plataforma Java EE define un estándar para el desarrollo de aplicaciones empresariales multicapa basadas en
componentes. La lógica de la aplicación se divide en componentes de acuerdo a su función, y cada componente
puede ser instalado en una máquina diferente dependiendo de la capa a la que pertenezca. Cada uno de los
componentes de Java EE se ejecuta en un contenedor apropiado. Un contenedor es el entorno de ejecución para
un componente, que le proporciona acceso a una serie de servicios. Además de los componentes ya comentados
destaca el Enterprise Bean, encargado de la lógica de negocio. Sirven para automatizar la persistencia, dar
soporte a la implementación de fachadas del modelo (objetos de sesión, aplicación, etc.), declarar operaciones
transaccionales y aspectos de seguridad, etc.
La tendencia actual del mercado es relegar a Java al desarrollo back-end (capa de negocio y tratamiento de datos,
empleando otras opciones más potentes para la capa de presentación (como las diferentes implementaciones de
Javascript). En el caso que nos ocupa, realizaremos fundamentalmente desarrollo back-end, por lo que Java
parece una opción tremendamente sólida, teniendo en cuenta la robustez del lenguaje y su amplísimo uso.
Cualquier funcionalidad requerida externa requerida durante el desarrollo estará ya implementada por un tercero,
siendo tremendamente sencillo anexarla a nuestro proyecto. Además, es, sin duda, el lenguaje de programación
que mejor abarca múltiples escenarios, lo cual no es una característica baladí para una suite de productos que
pretende abarcar muchas aplicaciones diferentes.
4.6.2.2 Python
Figura 4-16. Logo de Python
Python [36] es un lenguaje de script desarrollado por Guido van Rossum en el que es posible codificar
empleando programación lineal, estructurada y orientada a objetos (siendo esta última la que ámbito de la
programación en la actualidad). Existen intérpretes de Python en múltiples plataformas: Windows, Linux, Mac
etc. Empresas como Google, Yahoo, Nasa etc. utilizan este lenguaje para sus desarrollos (actualmente el creador
Análisis de tecnologías
36
36
de Python Guido Van Rossum trabaja para Google.)
Se puede ejecutar instrucciones de Python desde la línea de comando o creando archivos con extensión *.py. Al
ser un lenguaje interpretado, la línea de comandos nos permite conocer el resultado de las líneas codificadas
inmediatamente, favoreciendo mucho la programación. Python se emplea en múltiples ámbitos: aplicaciones
que se ejecutan en un servidor web (equivalentes a lo que se puede hacer con PHP, ASP.Net, JSP, Ruby),
aplicaciones de escritorio con interfaces visuales accediendo a componentes escritos en .Net (Microsoft), Qt,
GTK, MFC, Swing (Java) etc., programas no visuales para cubrir funcionalidades back-end…
Es, por tanto, un lenguaje interesante que está creciendo considerablemente en la actualidad. Ya existen soportes
para la práctica totalidad de bases de datos (como por ejemplo Oracle Database) y domina el segmento de
programación enfocado a domótica, sobretodo sobre SBCs como Raspberry o placas Arduino. Para la
comunicación con los biodispositivos, núcleo del presente proyecto, Python sería una elección más que
adecuada, pero nos obligaría a limitar las funcionalidades en otras aplicaciones posibles o incluso en el diseño
de la suite. Python es una opción de futuro, que poco a poco comienza a consolidarse y a abordar múltiples
escenarios, pero se considera más adecuado diseñar una suite que se espera sirva de base para múltiples
desarrollos en un lenguaje con el mayor número de librerías y utilidades externas posibles.
Capa de persistencia
4.7.1 Descripción
El acceso desde las aplicaciones Java a la base de datos se va a estudiar en dos niveles. En el primero se
comentará la filosofía de trabajo con la base de datos, la API escogida, mientras que en el segundo se abarcará
que implementación concreta de esa filosofía se ha utilizado en el proyecto.
4.7.2 Análisis de alternativas
4.7.2.1 Bajo nivel de abstracción
Figura 4-17. Logo de JDBC
Java provee una API básica para establecer las comunicaciones con la base de datos denominada Java Database
Connectivity (JDBC). A través de algunas sus clases, organizadas en el paquete java.sql, podemos establecer la
comunicación con la base de datos y ejecutar sentencias SQL, almacenando los resultados de una consulta. Por
tanto, directamente sobre el JDBC podemos realizar las comunicaciones que queramos con la base de datos, sin
necesidad de establecer mayores niveles de abstracción. Esta política tiene claras ventajas: es muy sencilla, solo
requiere conocimiento SQL para trabajar con la base de datos y es la menos pesada y, generalmente, la que
ofrece mayor rendimiento. Por otra parte, implica tener que definir manualmente la consulta / sentencia SQL
que se quiera realizar cada vez, de forma que un desarrollador debe conocer SQL, no independizando los
lenguajes. Además, un cambio en base de datos implica buscar manualmente las posibles sentencias afectadas.
Está claro que estamos ante una política muy simple, lo cual implica algunas ventajas, pero se puede volver
demasiado engorrosa (poco escalable, actualizable, propensa a errores) en un Proyecto como este, en el que
varias aplicaciones diferentes van a querer trabajar con la base de datos. Parece interesante analizar otras
opciones que busquen un mayor nivel de abstracción, independizándonos definitivamente del lenguaje de base
de datos.
4.7.2.2 ORM: Object-Relational mapping
El mapeo objeto-relacional (Object-Relational mapping, O/RM, ORM, o O/R mapping) es una técnica de
programación para mapear datos entre el sistema de tipos utilizado en un lenguaje de programación orientado a
37
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
37
objetos y el utilizado en una base de datos relacional, utilizando un motor de persistencia. En la práctica, esto
implica crear una base de datos orientada a objetos “virtual”, que el motor ORM es capaz de transformar en la
base de datos relacional. Esto no solo posibilita el uso de las características propias de la orientación a objetos
(fundamentalmente herencia y polimorfismo), sino que abstrae al desarrollador del conocimiento de SQL al ser
el motor ORM el encargado de transformar las acciones realizadas sobre los objetos o clases de mapeo en
sentencias sobre las tablas / campos reales en la base de datos. El desarrollador solo tiene que crear el mapeo
entre el objeto Java y el elemento de la base de datos, pudiendo operar con los objetos en su desarrollo y
olvidándose prácticamente del resto del trabajo. Este sistema es, evidentemente, más complejo que el anterior
pero también mucho más robusto. Por ejemplo, si tenemos una clase Java que mapea los campos de una tabla
modificar el nombre de un campo solo implica modificar el nombre de una variable, pudiéndonos servir del
compilador para detectar los errores que esto pueda derivar. Además, ORM abre la puerta a funcionalidades más
avanzadas, como ya se ha comentado anteriormente.
El uso de ORM se ha vuelto cada vez más popular en los últimos años, sobre todo para proyectos de tamaño
medio-grande, facilitando el tratamiento con la base de datos. Todos estos son motivos más que suficientes para
optar por JPA (Java Persistence API), el estándar ORM de Java.
JPA es la API diseñada por Java para cubrir la metodología ORM. Es, no obstante, una mera interfaz que define
un flujo de trabajo y una metodología determinada, sirviendo de estándar para los diferentes motores ORM del
mercado. Es decir, JPA nos permite una capa más de abstracción: un mapeo objeto-relacional realizado en JPA
podrá valer para varios motores ORM diferentes, pudiendo cambiarlos si el desarrollador así lo desea.
4.7.2.2.1 Motor ORM
Figura 4-18. Logos de las principales implementaciones JPA: Hibernate, EclipseLink y OpenJPA
Hibernate [37] es, con mucha distancia, el principal motor ORM empleado en desarrollo Java. De hecho, aunque
Hibernate pueda entenderse como una implementación de JPA (cosa afirmativa) se trata de un framework
mucho más amplio y antiguo. JPA está basado en Hibernate, y es un intento de estandarizar la capa de
persistencia frente al boom de los frameworks ORM que provocó la entrada de Hibernate en el mercado. Por
tanto, Hibernate puede usarse tanto bajo JPA como capa de persistencia propia. En esta modalidad Hibernate
soporta la capacidad para trabajar con bases de datos NoSQL, algo que JPA no cubre, cachés multinivel, etc.
Más allá de Hibernate existen otras múltiples implementaciones de JPA, como EclipseLink [38]
(implementación seleccionada por Oracle como referencia desde JPA 2.0) y OpenJPA [39], de Apache. Aunque
son motores ORM sólidos se encuentran a demasiada distancia de Hibernate en cuanto a rendimiento,
funcionalidad y posición en el mercado.
En nuestro proyecto se ha intentado optar por la estandarización, ya que JPA cubre de sobra nuestras
necesidades, colocando a Hibernate como motor JPA dada su robustez y solvencia.
4.7.2.2.2 Lenguaje de consultas
Figura 4-19. Logo de QueryDSL
Parece fácil comprender que JPA nos proveerá de un conjunto de utilidades para realizar inserciones,
actualizaciones y eliminaciones de objetos (correspondientes a un registro en una tabla de base de datos), pero
no es tan sencillo imaginar cómo se ejecutan consultas SQL complejas a través de JPA. Para ello, se ha definido
Análisis de tecnologías
38
38
el Java Persistence Query Language (JPQL), un lenguaje de consulta orientado a objetos independiente de la
plataforma, usado para hacer consultas contra las entidades JPA. Es decir, JPQL está inspirado en gran medida
por SQL, y sus consultas se asemejan a las consultas SQL en la sintaxis, pero opera con objetos entidad de JPA
en lugar de hacerlo directamente con las tablas de la base de datos. JPQL define un conjunto de métodos para
facilitar la construcción de consultas, pero necesita recibir el grueso de la lógica en una cadena de texto, en vez
de utilizar los atributos del objeto empleado, algo muy poco mantenible. Es por esto que han surgido utilidades
para desarrollar consultas empleando directamente atributos Java, facilitando la programación y la gestión del
código, entre otras funcionalidades. JPA 2.0 introduzco Criteria, una API para consultas type-safe, es decir, a
prueba de errores de tipos gracias a trabajar completamente con instancias y métodos Java, evitando las cadenas
de texto de JPQL. A Criteria le han surgido algunas alternativas, entre las que destaca especialmente QueryDSL
[40], un framework de código libre que cuenta con soporte para una inmensa cantidad de frameworks de
persistencia, desde JPA hasta bases de datos no relacionales como MongoDB. QueryDSL es mucho más
intuitivo y fácil de manejar que Criteria y es tan potente que puede emplearse hasta para realizar consultas sobre
colecciones Java, algo que Oracle se ha trasladado al propio lenguaje en Java 8. Para poder ofrecer esta
funcionalidad necesita generar unas clases denominadas QClass, quienes contarán con los métodos y atributos
necesarios para facilitar la construcción de consultas. Estas clases se autogeneran a partir de las entidades JPA
definidas, gracias a un plugin integrable con Maven y otros gestores de dependencias y construcción del
proyecto.
4.7.2.3 Soluciones mixtas
Figura 4-20. Logo de iBatis
Existen algunos frameworks de persistencia que ofrecen soluciones a medio camino entre la ejecución directa
de sentencias SQL y los ORM. El caso más conocido es sin duda iBatis, frecuentemente categorizado dentro de
los ORM. iBatis [41] es una opción intermedia entre las dos políticas definidas previamente. Esta tecnología se
sustenta en definir no solo el mapeo entre los elementos de base de datos y las clases Java, sino también las
sentencias SQL que se utilizarán. Desde el desarrollo Java se indicará que operación (consulta, actualización,
inserción, etc.) se desea realizar. El hecho de que las operaciones se definan en SQL prácticamente puro y que
haya que describir todas y cada una de ellas ofrece una solución menos automática a los desarrolladores, aunque
abre la puerta a optimizar las sentencias SQL, un aspecto siempre criticado de los motores ORM.
Gestor de dependencias y construcción del proyecto
4.8.1 Descripción
En proyectos Java complejos la gestión de dependencias es una labor fundamental. Al igual que en la mayoría
de lenguajes, una correcta gestión de nuestro proyecto implicará conocer qué librerías necesitamos y con qué
versión exacta. Además, estas librerías contarán a su vez con otras dependencias, pudiendo darse el caso de
acabar incorporando a nuestro proyecto librerías duplicadas que reducen el rendimiento y favorecen la aparición
de errores. Para optimizar esta labor surgieron los gestores de dependencias, que nos permiten definir en un
fichero de configuración determinado las dependencias de nuestra aplicación, bastando incluso con indicar una
referencia completa a la librería para que el gestor se encargue de buscarla, descargarla e importarla a nuestro
proyecto automáticamente. Pronto esta funcionalidad se fusionó con la evolución de los programas encargados
de gestionar la vida del proyecto (siendo el más conocido make de C), quienes iban abarcando cada vez más
capacidades. De esta manera, es posible parametrizar la compilación, el empaquetamiento, el tratamiento de
ficheros externos, la ejecución de pruebas, etc. Toda esta amplia gama de funcionalidades provocó que los
gestores de dependencias y construcción crecieran en popularidad, hasta el punto de que hoy en día sea extraño
encontrar un proyecto de medio tamaño que no cuente con los servicios de ninguna de las distintas soluciones
existentes en el mercado.
39
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
39
4.8.2 Análisis de alternativas
4.8.2.1 Ant
Figura 4-21. Logo de Ant
Ant [42] fue la primera herramienta moderna para la construcción de proyectos Java. Lanzado en el año 2000,
pretendía ser el equivalente Java de Make, convirtiéndose en una de las utilidades más populares del lenguaje
en un corto periodo de tiempo. Su facilidad de uso, y la capacidad de extender sus funcionalidades mediante
plugins, fueron sus principales puntos fuertes. Por otra parte, la configuración realizada sobre XML se volvía
demasiado engorrosa, dificultando en algunas ocasiones la legibilidad del código. Aunque sigue bajo desarrollo
en la actualidad, ha sido suplantado en la mayoría de los proyectos por Maven, otra solución de Apache que
apareció pocos años después, partiendo de los conocimientos y las experiencias adquiridas durante el desarrollo
de Ant.
4.8.2.2 Maven
Figura 4-22. Logo de Maven
Maven [43] apareció en 2004, como herramienta sucesora de Ant (ambas desarrolladas por Apache). Continuaba
utilizando XML como formato de configuración, pero replanteando completamente su estructura y lógica
interna. Mientras que Ant exigía a los desarrolladores codificar todos los pasos que quisieran ejecutar durante la
construcción de su proyecto, Maven definió un ciclo de vida (validate, compile, test, package, verify, install y
deploy). Cada fase se compone a su vez de goals u objetivos, de forma que el ciclo de vida siempre se ejecuta,
siendo tarea del programador vincular acciones con determinados goals. Quizás la principal característica de
Maven, aquella que ha convertido a esta utilidad de Apache en un must have de todo proyecto Java, es su gestión
de dependencias. Basta con identificar adecuadamente una librería en el fichero de configuración para que
Maven la descargue y la importe al proyecto. Además, cuenta con una amplia gama de plugins que permiten
abarcar prácticamente cualquier acción imaginable durante la construcción del proyecto. Maven permite filtrado
de ficheros, gestión de ficheros externos (resources), estructuración de proyectos en varios niveles, ejecución de
tests, etc. Sin embargo, no todo son ventajas: el enfoque centrado en el ciclo de vida reduce la flexibilidad en el
desarrollo. Además, usar XML como formato de codificación de Maven es poco versátil frente a otras opciones
que han ido surgiendo en el mercado.
Maven es, hoy en día, el gestor de construcción de Java más potente y útil. Cuenta con una enorme solvencia
fruto de su amplio uso por los desarrolladores. Poco a poco, otras opciones como Gradle han reducido su
hegemonía, pero sigue siendo la opción más adecuada para este proyecto al combinar una pequeña curva de
aprendizaje, una potente integración con el IDE escogido y una gran documentación disponible.
4.8.2.3 Gradle
Figura 4-23. Logo de Gradle
Análisis de tecnologías
40
40
Gradle [44] pretende combinar lo mejor de las dos herramientas vistas hasta ahora. Ofrece mayor flexibilidad al
programador, aunque mantiene la estructura de ciclo de vida introducida por Maven. Desde su lanzamiento en
2012 ha sufrido un crecimiento espectacular, llegando a ser escogida por Google como la build tool por defecto
en Android. Inicialmente, Gradle empleaba el sistema de dependencias Ivy, de Apache, aunque hace poco ha
migrado hacia su propio gestor de dependencias. La principal novedad de Gradle es el empleo de un lenguaje
específico del dominio (DSL) propio, diseñado específicamente para la construcción de software. Como
consecuencia de esto, los scripts de configuración de proyecto de Gradle son generalmente más cortos y claros
que los de sus competidores, facilitando no solo la programación sino también el mantenimiento del proyecto.
Entorno de desarrollo software
4.9.1 Descripción
Para el desarrollo de la aplicación será imprescindible contar con un entorno de desarrollo integrado (IDE). Se
trata de una herramienta software diseñada para facilitar la programación de aplicaciones, permitiendo la edición,
ejecución y depuración del código fuente (Java en este caso). Típicamente, un entorno de desarrollo contiene un
editor de código fuente, un compilador/intérprete/montador y un depurador complementados por una serie de
funciones adicionales. Es decir, además de dar soporte a la fase programación también lo hace a la fase de
pruebas. Por ejemplo, el editor no es un simple editor de texto, sino que reconoce y maneja elementos sintácticos
del lenguaje usado, avisa de errores en la programación, provee de funciones de autocompletado inteligente de
código, etc. El depurador, por su parte, suele permitir al desarrollador comprobar el efecto de cada línea de
código en la ejecución del programa, en vez de presentar la información en términos de lenguaje máquina. En
resumen, la principal utilidad de un entorno de desarrollo es la integración de todas las herramientas útiles
durante el desarrollo de software en una única interfaz, promoviendo un marco de trabajo amigable al
programador.
Podemos encontrarnos con entornos de desarrollo integrados con distintas filosofías: existen IDEs específicos
para un lenguaje determinado, frente a los entornos multilenguaje. A pesar de lo que pueda parecer, la diferencia
va más allá de la cantidad de lenguajes soportados. Los entornos de desarrollo integrados centrados en un único
lenguaje están fuertemente integrados, de forma que se presentan como una única herramienta compacta al
programador. Esto puede provocar una falta de flexibilidad, pero les dota de mayor accesibilidad. Por su parte,
los entornos de desarrollo multilenguaje están construidos como un conjunto de módulos que interoperan entre
sí. Se les conoce como toolkit, pues se presentan como un conjunto de herramientas para la programación. Como
es evidente, este enfoque provoca una integración más débil, mayor complejidad para el usuario, pero también
mayor versatilidad. La principal utilidad de estos IDEs es la capacidad de ver extendida su funcionalidad a través
de módulos o plugins. Gracias a ella, los programadores pueden llegar combinar las más variadas tecnologías
para el desarrollo de su proyecto a través de un mismo programa, añadiendo los plugins necesarios al IDE por
defecto e incurriendo en una alta personalización del entorno de trabajo.
Además de todo lo anterior, es interesante comentar que los entornos de desarrollo suelen contar con una serie
de funcionalidades habituales como son herramientas de programación visual, gestión de repositorios para el
trabajo en equipo o editores de estructura.
4.9.2 Análisis de alternativas
En los próximos subapartados se analizarán las opciones más empleadas entre los programadores del ámbito
correspondiente al de este proyecto: aplicaciones web en Java. La fuente más fiable para obtener un estudio
acerca de los IDEs más usados en el sector. Como nota curiosa, dicha empresa es la responsable de JRebel, un
plugin para IDEs que elimina las fases de build y redespliegue en proyectos Java.
41
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
41
Figura 4-24. Comparativa entre los IDEs Java más usados
Como se puede comprobar en el estudio [45], el mercado se encuentra dominado por tres principales alternativas
estudiadas en el presente capítulo: Eclipse, Intellij IDEA y NetBeans. Además de ellas se echará un vistazo al
JDeveloper de Oracle, por los motivos que se argumentarán más adelante.
4.9.2.1 Eclipse
Eclipse [46] es un IDE multiplataforma de código abierto, iniciado originalmente por IBM y desarrollado por la
Fundación Eclipse desde 2001. Puede clasificarse entre los entornos de desarrollo de tipo toolkit, ya que está
compuesto por una estructura base sobre la que pueden instalarse distintos plugins que permiten agregar soporte
para múltiples lenguajes de programación (java, c, php, etc.), tecnologías (maven, gradle, spring, etc.) o
personalización del entorno.
Figura 4-25. Arquitectura de eclipse.
Su arquitectura se compone de los siguientes elementos:
Análisis de tecnologías
42
42
Plataforma
Es el núcleo del Eclipse IDE. Se encarga de descubrir al inicio qué complementos están instalados, creando un
registro de información sobre ellos. Para reducir el tiempo de inicio y el uso de los recursos, no carga ningún
complemento hasta que realmente se lo necesita. Se trata del único componente que no se implementa como un
complemento.
Espacio de trabajo
El espacio de trabajo es el complemento responsable de la administración de los recursos del usuario. Incluye
todos los proyectos que crea el usuario, los archivos de esos proyectos y los cambios en los archivos y otros
recursos. También es responsable de notificar a otros complementos interesados sobre los cambios en los
recursos, como archivos creados, eliminados o modificados.
Entorno de trabajo o workbench
El entorno de trabajo proporciona a Eclipse una interfaz de usuario. Se crea con la utilización de un Kit de
Herramientas Estándar (SWT) [47] — una alternativa al Swing/AWT GUI API de Java — y la API JFace [48],
quien se emplea sobre el citado SWT para proporcionar componentes de la interfaz gráfica.
Equipo
El componente de equipo es responsable de proporcionar soporte para el control de versiones y la gestión de los
repositorios
Ayuda
Componente de ayuda.
Plugins
Como se ha comentado anteriormente, todo en Eclipse son componentes o plugins. Además de los componentes
básicos comentados en los apartados anteriores, existen otros plugins imprescindibles para conformar un entorno
de desarrollo adecuado para el programador. Probablemente el plugin más importante de Eclipse sea JDT [49],
encargado de dar soporte a la programación en Java. Es responsable de gestionar las vistas de esquemas de
clases, además de varias funciones de soporte a la programación como el coloreado de código (realizando para
ello el reconocimiento sintáctico de todas aquellas palabras que son reservadas en el lenguaje) o el
autocompletado de código (con sugerencias dependientes del contexto, lo cual permite escribir código más
rápidamente). A todo esto, hay que sumar que permite configurar el formateo de código, la forma de escribir los
comentarios, generación de esqueletos de clase automáticamente, generación de métodos getters y setters de
manera automática, y un largo etcétera de funcionalidades útiles para el desarrollo. Este componente también
controla la depuración de todo código Java a través del JDT Debug.
Por último, existen una inmensa variedad de plugins que no son imprescindibles para todos los desarrollados,
pero que el programador podrá incluir para soportar las tecnologías que necesite para su proyecto concreto,
extendiendo así la funcionalidad del IDE.
Para terminar, conviene hacer un breve repaso de la interfaz gráfica del IDE, comentando los elementos más
básicos que se han usado durante el desarrollo de la aplicación.
43
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
43
Figura 4-26. Interfaz de Eclipse IDE.
En Eclipse el concepto de trabajo está basado en las perspectivas, que no es otra cosa que una preconfiguración
de ventanas y editores, relacionadas entre sí, y que nos permiten trabajar en un determinado entorno de trabajo
de forma óptima. Por ejemplo, en la imagen superior podemos contemplar la perspectiva Java EE (desarrollo de
aplicaciones web Java) vacía. Abajo, con más detalle, las perspectivas abiertas
Figura 4-27. Perspectivas.
Por otra parte, el desarrollo sobre Eclipse se basa en los proyectos, que son el conjunto de recursos relacionados
entre sí, como puede ser el código fuente, documentación, ficheros configuración, árbol de directorios…
Figura 4-28. Vista de gestión de proyectos.
El IDE proporcionará asistentes y ayudas para la creación de proyectos. Por ejemplo, cuando creamos uno, se
abre la perspectiva adecuada al tipo de proyecto que estemos creando, con la colección de vistas, editores y
ventanas preconfigurada por defecto.
En el anexo de este documento se detalla la configuración y pasos básicos para llevar a cabo el desarrollo de la
aplicación.
Análisis de tecnologías
44
44
En resumen, Eclipse es un entorno de desarrollo integrado altamente versátil, capaz de dar soporte a
prácticamente cualquier tecnología o lenguaje gracias a su amplia moduralización. Irónicamente, su mayor
ventaja también es un principal inconveniente: la extensión a través de plugins permite que un mismo
programador que vaya a realizar varios proyectos convierta a Eclipse en su único entorno de desarrollo,
simplemente añadiendo o quitando los componentes necesarios para su siguiente desarrollo. Además, Eclipse
cuenta con la comunidad de usuarios más extensa, por lo que existen miles de plugins realizados por terceros
que llegan allí donde el Proyecto Eclipse no alcance. Sin embargo, su filosofía tan descentralizada convierte a
Eclipse en un IDE más propenso a bugs, fallos, cuelgues, etc. de lo que debería. Aun así, su comportamiento
sigue siendo, en condiciones normales y con los componentes habituales, lo suficientemente robusto como para
no eclipsar la enorme ventaja existente en su alta capacidad de ampliar funcionalidades. Todo esto lo ha llevado
a ser, claramente, la opción principal del mercado (sobre todo en Java) y la escogida para el desarrollo de la
aplicación expuesta en este documento.
4.9.2.2 IntelliJ IDEA
IntelliJ IDEA [50] es un entorno de desarrollo para programación sobre lenguaje Java creado por la compañía
JetBrains (inicialmente denominada IntelliJ). Se trata de un producto multiplataforma, disponible en Windows,
Mac, Linux y otras plataformas como Solarios, OpenBSD o FreeBSD. Están disponible dos versiones del
mismo:
- Community Edition: es la versión software libre, licenciada bajo licencia Apache.
- Ultimate Edition: versión propietaria y de pago.
Al igual que el resto de IDEs comentados en el presente apartado, está desarrollado en Java, aunque presenta
ciertas características que lo convierten en un producto diferenciado del resto. Se trata de un IDE diseñado para
soportar solamente lenguaje Java o relacionados (JavaScript, Groovy, etc.), por lo que puede clasificarse como
un entorno de desarrollo centrado en un lenguaje. A pesar de esto, IntelliJ IDEA también puede extender sus
capacidades mediante algunos plugins. En ambas versiones está habilitada esta funcionalidad, aunque solo la
versión de pago contiene alguno de los plugins oficiales más interesantes como Php o node.js. Es importante
destacar que, a pesar de contar con la posibilidad de añadir extensiones, la filosofía de este IDE es
diametralmente distinta a la de Eclipse o NetBeans. Aquí no existe una estructura modular donde la práctica
totalidad de las funciones la ejecutan componentes separables del núcleo, sino que se cuenta con una base sólida,
la cual implementa las funciones básicas para la programación, ejecución, depuración, etc., de tal forma que los
plugins solo dan soporte a ciertas tecnologías asociadas a Java o JavaScript. Existe la posibilidad de que las
nuevas versiones del producto incluyan nuevas funciones o características en el núcleo, como ocurrió con la
implementación de soporte para Python o Ruby en la edición Ultimate, los cuales no fueron extendidos como
un plugin independiente. Sin embargo, IntelliJ IDEA permite instalar módulos de terceros, permitiendo a la
comunidad suplir algunas carencias.
IntelliJ IDEA es un entorno de desarrollo muy interesante, tal y como han acreditado varios estudios [51], donde
suele destacar en el soporte a la programación Java, ofreciendo mejoras en funciones como el autocompletado
o la refactorización de código sobre sus competidores. Además, es el IDE más cohesionado de los estudiados,
pero carece del ecosistema de extensiones que tienen NetBeans y, fundamentalmente, Eclipse. Sin duda es un
serio candidato para todo desarrollo con Java, lo que le ha permitido alcanzar mayores cuotas de mercado con
el paso del tiempo hasta el punto de ser escogido por Google como base para el IDE oficial de Android, Android
Studio. Además, es una opción ganadora para comenzar a desarrollador, ya que es más accesible que sus
competidores. A la hora de escoger entre este IDE y Eclipse conviene tener en cuenta que se trata de dos
aplicaciones con filosofías diferentes, por lo que no hay una opción mejor que la otra. En esta ocasión ha pesado
más la posibilidad de conocer una plataforma tan versátil como Eclipse, altamente implantada en el mercado
laboral y en la comunidad, que optar por un entorno de desarrollo más sólido. Probablemente, de trabajar en un
proyecto más complejo hubiera sido más interesante contar con la solidez de IntelliJ IDEA.
45
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
45
4.9.2.3 NetBeans
NetBeans IDE [52] es un IDE multiplataforma de código libre fundado por Sun Microsystems a finales del año
2000. Actualmente Oracle Corporation (comprador de Sun) sigue siendo la principal impulsora del proyecto.
Al igual que Eclipse puede catalogarse como un entorno de desarrollo de tipo toolkit, ya que está diseñado para
dotar a la comunidad de libertad para extender su capacidad gracias a los Módulos o Add-ons Packs. Como
puede suponerse, ya que comparten filosofías, su arquitectura recuerda a la de Eclipse: la práctica totalidad de
las funcionalidades la realizan módulos, de tal forma que el usuario podría quitar y añadir estos plugins a su
antojo para personalizar su herramienta de trabajo. Esto se lleva hasta el extremo, por lo que hasta la
funcionalidad Java es realizada por un módulo. Soporta otros lenguajes tan dispares como C/C++ o PHP, aunque
se utiliza principalmente para Java.
Es interesante destacar que está desarrollado sobre la plataforma NetBeans, un framework diseñado para facilitar
la creación de aplicaciones de escritorio para la API Swing (Java) de Oracle. No es extraño encontrar confusiones
entre la plataforma y el IDE.
Tiene una interesante presencia en el sector empresarial, además de contar con una potente comunidad de
usuarios. Cuenta con el inconveniente de que su ecosistema de extensiones no es comparable al de Eclipse, lo
que le deja por detrás de dicha alternativa en una categoría demasiado similar. Sin embargo, no son pocos los
usuarios que señalan a NetBeans como su IDE favorito, destacando que es más sólido que su competidor. Al
igual que ocurría con IntelliJ IDEA, al no estar desarrollando un proyecto de demasiada envergadura no es
necesario sacrificar las ventajas de Eclipse.
4.9.2.4 JDeveloper
JDeveloper [53] es otro IDE multiplataforma desarrollado por Oracle Corporation desde 1998. Es el único de
los analizados que no dispone de ninguna versión bajo licenciada de código libre, aunque sí es gratuito.
Es un entorno de desarrollo centrado en Java y lenguajes adyacentes (JavaScript, PHP, etc.), por lo que puede
clasificarse entre los entornos centrados en un lenguaje, como era el caso de IntelliJ IDEA. Eso no le impide, al
igual que al programa de JetBrains, contar con la posibilidad de extender sus funciones con plugins o módulos.
En este caso nos encontramos con la menor gama de extensiones de todos los IDEs reseñados. Se debe a que
Oracle JDeveloper está diseñado para optimizar su interconexión con otras aplicaciones, frameworks o librerías
de Oracle, lo cual le dota de tremenda utilidad para determinados desarrollos, pero provoca que la comunidad
no busque extender sus características más allá de las imprescindibles. En el caso concreto del proyecto
presentado en el presente documento, el uso de una base de datos Oracle no es condición suficiente para justificar
la elección de este IDE, pues no aporta ninguna ventaja sobre sus competidores. A pesar de eso se ha considerado
necesario tener una breve introducción a este producto, más aún si se tiene en cuenta que es el entorno en el que
está programado el SQL Developer de Oracle, el IDE de Base de Datos empleado en la aplicación.
Análisis de tecnologías
46
46
Resumen del análisis de tecnologías empleadas en el proyecto
En las líneas superiores se ha dedicado el espacio suficiente para analizar todas y cada una de las tecnologías
empleadas en el proyecto, realizando una adecuada comparativa con el resto de opciones existentes en el
mercado y justificando las elecciones realizadas. En general, se han empleado una serie de elementos recurrentes
en las argumentaciones: se ha intentado apostar, en la medida de lo posible, por tecnologías de código abierto,
con una amplia comunidad detrás y con las mayores garantías posibles en fidelidad. Junto a estos puntos se ha
valorado el futuro de todos los elementos implicados, pues se está diseñando un proyecto en un ámbito
claramente innovador, en el que emplear una tecnología con pronta fecha de caducidad reduciría el impacto que
nuestro trabajo pudiera tener en el mercado o en la comunidad.
La extensión de este capítulo y la enorme cantidad de tecnologías comparadas dan cuenta de la complejidad del
proyecto diseñado, en el que se han abordado numerosos frentes: desde la creación de varios módulos o
componentes software independientes hasta el diseño de una base de datos, desde la parametrización de un
clúster de servidores de colas de mensajes distribuidas hasta la elección de componentes hardware para la
integración física con biodispositivos.
Además, tal y como se comentaba al inicio, la labor realizada a lo largo de este capítulo se considera
imprescindible, pues constituye un elemento básico sobre el que construir la descripción detallada del proyecto.
O, en otras palabras, comentar y justificar las tecnologías empleadas en el desarrollo de la aplicación IoMT es
un requisito irrenunciable que se debe satisfacer antes de abordar los pormenores de la solución realizada.
47
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
47
5 DESCRIPCIÓN DE LA PROPUESTA
n este apartado se va a entrar en detalle de la solución adoptada, explicando paso a paso las decisiones
tomadas en función de las tecnologías escogidas y para cada uno de los tres sectores en los que podemos
dividir el conjunto de aplicaciones y herramientas que componen este proyecto: integración con
biodispositivos para recuperar la información biomédica de los pacientes, almacenamiento e intercambio de
dichos datos y visualización de los mismos.
A lo largo del capítulo se deberá abordar con profundidad la arquitectura diseñada para dar cabida a todos los
componentes del proyecto. Una vez comentada la arquitectura, que establece las relaciones entre todos los
elementos, se entrará en detalle de cada uno de estos componentes, empleando la siguiente lógica:
- Primero se abordarán los aspectos exclusivos de la integración con los biodispositivos.
- En segundo lugar, se tratará la parte de la solución dedicada al almacenamiento e intercambio de la
información.
- Por último, se abordará el software específico creado para el proyecto. Aunque se ha diseñado un
módulo para cubrir cada una de las funcionalidades, todos cuentan con una serie de elementos comunes
agrupados en pequeños proyectos reutilizables. Dentro de este apartado se ha optado por encarar
primero los módulos comunes antes de analizar los módulos principales de integración,
almacenamiento y visualización, por este orden.
Arquitectura
5.1.1 Diseño funcional
Para comprender las decisiones tomadas en el diseño del presente proyecto conviene comenzar citando los
requisitos de partida que se han creído imprescindibles:
- Se realizará un software de recolección de datos para recuperar la información procedente de los
biodispositivos gracias al hardware de integración. Dicho software contará con un diseño versátil, que
permita la elección de los diferentes protocolos que, en cada caso, se emplearan en la comunicación con
los biodispositivos.
- Formará parte del presente proyecto una herramienta básica de visualización de la información extraída
a través del software de recuperación de datos.
- Será imprescindible almacenar la información recuperada en una base de datos.
- El diseño debe ser lo suficientemente robusto como para permitir la ampliación de funcionalidades en
el futuro. Por ejemplo, parece interesante diseñar el sistema de forma que se facilite la creación de un
módulo de explotación avanzado de la información, que saque estadísticas o genere informes; en la
línea de lo expuesto en la presentación del proyecto.
- Se va a trabajar con una información de especial importancia, para la que habrá que contar con un
sistema que garantice que no se pierda ni se corrompan los datos.
A todo lo anterior hay que sumar un nuevo requisito: el caudal de información a tratar es considerable, aunque
no pueda parecerlo a simple vista. Para ello, debemos distinguir entre dos tipos de constantes médicas con las
que trataremos en el proyecto:
- Datos discretos: medidas concretas realizadas cada cierto tiempo, a baja frecuencia. Por ejemplo, es
habitual expresar la frecuencia cardíaca en pulsaciones por minuto, de forma que, por definición, no
tiene sentido que recibamos esta constante con otra frecuencia. En definitiva, este tipo de datos suelen
E
Descripción de la propuesta
48
48
representar datos medios o estadísticos. Para hacer una explicación gráfica accesible, este tipo de
mediciones se corresponden con los valores numéricos de los monitores de pacientes que estamos
acostumbrados a ver tanto en los centros de atención sanitaria como en la ficción.
- Datos continuos: aquí hablamos de información médica que se mide constantemente, a alta frecuencia.
Se diferencian de las anteriores en que no representan valores medios ni agrupaciones, sino la evolución
temporal de una determinada característica del paciente. Por ejemplo, un caso conocido es el
electrocardiograma (ECG), que registra la actividad eléctrica del corazón. Se trata de una gráfica con
una característica forma periódica, con picos de actividad durante los latidos del paciente. Sin embargo,
en ese caso no interesa solo la medición del número de latidos sino observar continuamente la actividad
del corazón para poder analizar si existe alguna anomalía en su funcionamiento. Por tanto, aquí nos
encontramos con una señal analógica que debe pasar por un sistema de muestre para ser convertida en
una señal digital explotable.
Esta información genera un gran caudal de datos, pues dependiendo de la precisión podemos
encontrarnos con ondas con una frecuencia de muestreo de hasta 500 valores por segundo. Por tanto,
no solo el hardware elegido deberá tener capacidad suficiente para procesar varias variables biomédicas
a tiempo real, sino que toda esta información también debe ser tratada por varias aplicaciones, lo que
condicionará el diseño técnico del software.
Figura 5-1. Tipos de constantes médicas, continuas y discretas.
5.1.2 Prototipo inicial de la arquitectura del proyecto
Con todo lo anterior podemos montar un prototipo del proyecto que exprese las relaciones entre todas las partes
y ayude a comprender lo comentado hasta el momento:
Figura 5-2. Primer prototipo de MEDIPi
49
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
49
Aplicación Software Icono Descripción
Device Data Acquisition
DDA
Componente software encargado
de establecer la comunicación con
los biodispositivos y recuperar la
información biomédica.
CHART
Aplicación de visualización de las
constantes biomédicas de los
pacientes en tiempo real.
Figura 5-3. Leyenda primer prototipo de MEDIPi
En la figura superior tenemos una versión del primer esquema del proyecto. De un vistazo observamos no solo
las relaciones entre los diferentes componentes del software, sino también podemos contemplar qué módulo del
software correrá sobre qué componente hardware.
Con todos estos componentes interrelacionados cubriríamos los requisitos funcionales que hemos tomado como
base de la construcción de la solución MEDIPi: el recolector de información, corriendo sobre un determinado
hardware de integración que elegiremos más adelante y a través de los puertos, físicos o no, necesarios según
cado protocolo, almacenará las constantes biomédicas proporcionadas por los biodispositivos en la base de datos.
Por último, el visor de datos leerá de base de datos para representar adecuadamente las gráficas de las constantes
recopiladas.
Sin embargo, este prototipo cuenta con varias limitaciones importantes en dos de los puntos marcados en el
análisis funcional. En primer lugar, hemos considerado fundamental la creación de una estructura lo
suficientemente sólida como para permitir fácilmente la expansión de las funcionalidades existentes y/o la
adición de nuevos módulos como podrían ser el tratamiento masivo de datos, aplicaciones de gestión de historia
clínica, etc. No en vano estamos hablando de la suite MEDIPi, enfocando desde el inicio el proyecto como un
conjunto de soluciones dedicadas a los diferentes aspectos relacionados con la información biomédica de los
usuarios de un sistema sanitario. Este prototipo propone solucionar este problema simplemente construyendo un
sistema de almacenamiento de datos lo suficientemente modularizado como para que los nuevos módulos
software se cimienten en él, leyendo la información allí almacenada y realizando con ellas sus labores
específicas. No es una solución incorrecta, pero sí demasiado básica dadas las características del contexto en el
que nos ubicamos. Hemos hablado antes de múltiples constantes recuperadas para cada usuario, algunas con
cientos de muestras por segundo. El software y el hardware de integración tiene capacidad suficiente para tratar
con ellas, pero para que puedan ser explotadas por terceros deberían almacenarse en base de datos en su totalidad,
no solo las de un paciente, si no las de todos. He aquí la principal diferencia: mientras que el hardware y el
software de recolección de datos solo tratan información recuperada para cada paciente, la base de datos
almacena la de todos. Por tanto, este modelo exige una base de datos inmensa. De partida esto provoca la
necesidad de invertir en un servidor potente, con enorme capacidad de almacenamiento, no solo para la base de
datos, sino para cada lugar en el que se vaya a ejecutar alguno de los módulos que necesiten acceder a la
información biomédica del paciente. Es decir, si uno de los posibles añadidos a los elementos existentes
actualmente a la suite es una aplicación web esta debería ejecutarse sobre un servidor con capacidad de
procesamiento potente. Pero el problema no es solamente ese: supongamos que queremos abordar un módulo
de explotación de datos (big data). Las soluciones existentes, como Hadoop [54], son conscientes de las
limitaciones de procesamiento derivadas de emplear como sistema de entrada una base de datos relacional
clásica. De hecho, en los últimos tiempos estamos observando el ascenso de las bases de datos NoSQL en este
tipo de trabajos. Aun así, incluso utilizando bases de datos relacionales deben realizarse modificaciones sobre la
estructura clásica: es inabordable emplear un servidor centralizado donde se ubiquen todos los datos, optando
siempre por sistemas de ficheros distribuidos como soporte a esas bases de datos.
Descripción de la propuesta
50
50
Figura 5-4. Esquema del sistema de ficheros distribuidos HDFS empleado por Hadoop.
Por tanto, con este prototipo inicial estamos construyendo una arquitectura que, efectivamente, va a permitir la
adición de módulos con nuevas funcionalidades, pero conocemos que estamos generando limitaciones de partida
para una de las funcionalidades que, a primera vista, parecen más interesantes de añadir a la suite.
La primera alternativa a este prototipo es evidente: si el problema radica en la base de datos podemos optar por
cambiarla y emplear un sistema de ficheros distribuidos sobre el que ubicar nuestra base de datos relacional o
NoSQL. Sin embargo, esto implica dificultar enormemente la creación de la misma, el modelado de datos y su
manteamiento, solamente para evitar limitaciones en una característica que ni siquiera se plantea abordar de
momento.
5.1.3 Modelo definitivo
Hasta ahora hemos llegado a una conclusión: el alto volumen de datos que se deberán manejar nos obligan a
replantear la arquitectura del primer prototipo, consiguiendo armonizar la existencia de una base de datos SQL
estándar que no sea excesivamente difícil de manejar por los módulos básicos de la suite con una herramienta
que permita a la vez abordar de forma escalable y sólida la explotación de la información recolectada. Para
abordar este conflicto debemos replantear la arquitectura, quitando el foco en el punto débil de la misma: la base
de datos.
En el modelo definitivo, a diferencia del prototipo comentado en el apartado previo, el dispositivo encargado de
realizar el intercambio de información entre las distintas aplicaciones se desplaza de la base de datos a una
herramienta que permita la comunicación de grandes cantidades de datos de forma escalable y asegurando la
integridad de la información. Por tanto, en vez de que la aplicación de recolección de datos envíe la información
a la base de datos y el resto de módulos lean de ella se empleará un sistema de intercambio alternativo de
información en el que “escribirá” el módulo de recuperación de información y del que “leerán” los módulos que
así lo deseen. En resumen, gracias a este cambio, la base de datos es solo uno de los componentes que “leen” de
este sistema de intercambio de información, permitiendo que muchos otros también lo hagan. Hemos construido
una arquitectura que cumple con la condición de escalabilidad y evita limitaciones en la explotación de la
información que puedan realizar otros módulos. Como toda decisión, esta también tiene sus puntos débiles, sus
inconvenientes: hemos añadido más complejidad a la estructura. Será tarea fundamental del diseño de dicho
sistema intermedio escoger una herramienta y una configuración de la misma que reduzcan este problema.
51
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
51
Surgen varios aspectos a tratar tras este cambio de arquitectura, que implicarán replantear y remodelar algunas
de las deducciones que se planteaban en el primer prototipo.
Lo primero es partir de que, si ahora vamos a tener un “nodo” de intercambio, una herramienta para la
comunicación de la información, deberá existir por fuerza un programa, un módulo de la suite MEDIPi que se
encarga de leer de dicha herramienta y de persistir la información en base de datos. Aplicando estos cambios
obtenemos un esquema general de la arquitectura definitiva planteada.
Figura 5-5. Esquema general de la arquitectura
Aplicación Software Icono Descripción
Device Data Acquisition
DDA
Componente software encargado
de establecer la comunicación con
los biodispositivos y recuperar la
información biomédica.
SAVER
Módulo que persistirá la
información recopilada por DDA
en base de datos
CHART
Aplicación de visualización de las
constantes biomédicas de los
pacientes en tiempo real.
Figura 5-6. Leyenda esquema general de la arquitectura
A simple vista ya se pueden sacar algunas conclusiones: existe un destacable aumento de complejidad con
respecto al primer prototipo y se ha redefinido el papel de la base de datos en la suite. Más adelante
comentaremos el primer punto, pero de momento centrémonos en cómo queda finalmente la arquitectura del
proyecto:
Descripción de la propuesta
52
52
Componentes de MEDIPi: Modelo definitivo
Sector Hardware Software
Integración con biodispositivos Raspberry Pi 2 DDA
Almacenamiento y transmisión Base de datos Clúster SAVER
Visualización PC Cliente CHART
En el prototipo DDA lee y escribe en la base de datos: lee la información de configuración del programa
(incluyendo los datos de los biodispositivos con los que debe integrarse), así como otros parámetros; y escribe
las constantes biomédicas directamente. Ahora el software de recolección lee de base de datos igual que antes,
pero escribe toda la información recuperada en el sistema de intercambio de datos, representado por la nube en
el esquema general.
Como consecuencia de lo comentado anteriormente, necesitamos un nuevo módulo software que ejecute la tarea
de recuperar datos de dicho sistema y escribirlos en base de datos. Esa es la labor de MEDIPi Saver,
representado en el esquema en la esquina inferior derecha. Ahora podemos considerar la base de datos como
“una copia de seguridad” de la información intercambiada por el sistema, un lugar en el que almacenamos la
información antigua. Esto nos permite realizar algunos cambios en el planteamiento: muchas aplicaciones de la
suite usarán la base de datos, pero esta ya no es el punto central del intercambio de los datos biomédicos. En el
apartado correspondiente al hardware de integración se realizó un análisis acerca de dichos datos, estableciendo
una clasificación básica entre datos continuos y discretos. Junto a lo anterior, en párrafos previos hemos hecho
hincapié en los problemas que nos producen las señales continuas, de gran peso, comentando la necesidad que
existe en sistema de tratamiento masivo de datos de emplear bases de datos no relacionales (por definición más
escalables) e incluso fragmentar el sistema de ficheros sobre el que corre la base de datos. Esta nueva arquitectura
nos permite solucionar todo esto: ahora la base de datos es, desde el punto de las constantes biomédicas, un
elemento de almacenamiento de la información antigua, por lo que puede llevarse a cabo un filtrado de dichos
datos. Al fin y al cabo, ahora existe otra manera de acceder a toda la información. Saver será el encargado de
aplicar ese filtrado, que partirá de no almacenar las constantes continuas, reduciendo considerablemente el
tamaño de la base de datos. Además, podrán diseñarse otros filtros que complementen al anterior, dando
empaque a este módulo del software. Al igual que DDA, Saver necesitará acceder a ciertos parámetros de
configuración ubicados en la base de datos.
Al igual que el resto de módulos software de la suite MEDIPi, Chart también sufre algunos cambios como
consecuencia de la nueva arquitectura. Ahora no necesita leer de base de datos la información biomédica, que
llegará a través del sistema de intercambio, reduciendo el acceso a la base de datos a la información general:
configuración, datos del paciente, etc.
En general encontramos “dos puntos de acceso” para los módulos software presentes o futuros: el sistema de
intercambio (clúster Kafka de colas de mensajes) y la base de datos, pudiendo escoger uno u otros según necesite.
Por ejemplo, una aplicación de gestión de historia clínica es probable que no necesite los datos a tiempo real de
los pacientes y le baste con lo existente en base de datos, mientras que un sistema de big data probablemente
recupere los datos mediante el sistema de intercambio, siendo probable incluso que emplee una base de datos
propia más adecuada para sus labores que se pueble a partir de dichos datos. Finalmente hemos concluido una
arquitectura sólida y escalable, en la que conviven la base de datos y un sistema de intercambio de datos masivo,
siendo ambos imprescindibles: mientras que el sistema de intercambio de datos masivo permite cumplir
adecuadamente los requisitos funcionales y la existencia de la base de datos es insustituible, pues en algún lugar
debe estar persistida la configuración de las aplicaciones y la información básica de los usuarios del sistema:
datos del paciente, fechas, constantes, etc.
53
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
53
Hardware de integración con biodispositivos
El primer paso en el planteamiento del proyecto es sin duda realizar el análisis de cómo debe ser la integración
física con los biodispositivos médicos. Empezaremos con una clasificación básica de los elementos con los que
deberá integrarse la solución desarrollada: podremos diferenciar entre mecanismos de soporte médico existentes
en el mercado (como monitores cardíacos, de presión, ventiladores automáticos, bombas de infusión de
medicamentos, etc.) y dispositivos no comerciales, como sensores o soluciones libres empleados en ámbitos
académicos o divulgativos. El presente proyecto pretende caminar en ambos frentes, ofreciendo una herramienta
lo suficientemente sólida como para dar soporte a biodispositivos empresariales usados en las unidades de
atención médica de hospitales y a dispositivos más básicos y accesibles.
Figura 5-7. A la izquierda, un sensor de pulso cardíaco. A la derecha, un ventilador de General Electrics
5.2.1 El estándar HL7
El protocolo HL7, (Health Level Seven) de capa de aplicación en el modelo OSI, está diseñado para intercambiar
todo tipo de información médica como admisiones, traslados, informes, constantes del paciente, etc. Se ubicará
normalmente sobre un protocolo propio de sesión MLLP (Minimum Lower Layer Protocol) [55], aunque es
posible encontrarlo sobre una gran variedad e capas de sesión como ebXML (Electronic Business using
eXtensible Markup Language), SOAP (Simple Object Access Protocol), etc. Sea cual sea el protocolo de sesión,
es poco habitual que no esté construido sobre TCP/IP, de forma que los productos que usan HL7 transmiten la
información finalmente a través de un puerto Ethernet o incluso a través una interfaz WiFi.
Tras este protocolo se encuentra la organización HL7 International que, desde 1987, apuesta por dotar a la
comunidad médica de estándares que permitan la interoperabilidad de datos mundial. Para ello lleva a cabo una
estrategia en múltiples frentes:
- Desarrollar el estándar HL7 buscando una sintaxis coherente y extensible que estructurar información
en salud y apoyar los procesos de atención al paciente, para ser intercambiada entre aplicaciones de
software, conservando al mismo tiempo la semántica de la información.
- Colaborar con otras organizaciones desarrolladoras de estándares y organismos nacionales e
internacionales de estandarización (por ejemplo, ANSI e ISO), para desarrollar infraestructuras de
dominios de información en salud para promover el uso de estándares compatibles.
HL7 define simplemente una estructura, un conjunto de segmentos y de campos que se deben cumplimentar, así
como el contenido de cada uno. Sin embargo, no se indica qué formato exacto debe tener cada campo, por lo
que dos dispositivos que utilicen HL7 tendrán una estructura similar, pero la información no será totalmente
intercambiable. Por ejemplo, un dispositivo puede informar de una constante vital indicando el nombre de la
variable biomédica, mientras que otro puede emplear un código de identificación; uno puede indicar las unidades
en el sistema internacional, mientras otro use el estándar ISO/IEEE 11073. Incluso es posible encontrar el mismo
dato en diferentes campos según se haya interpretado la especificación. Por tanto, HL7 nos ofrece una gran base
para la estandarización de la exportación de datos, pero aun así será necesario conocer los detalles concretos de
la implementación que ha hecho cada proveedor.
Para terminar este breve análisis del protocolo HL7, se va a detallar algunas características básicas de su
Descripción de la propuesta
54
54
representación. Los mensajes HL7 se suelen informar de dos maneras totalmente diferentes: XML o ER7. Este
último no es más que una representación del valor de cada campo separado por el carácter |, tal y como se puede
ver en el siguiente ejemplo.
MSH|^~\&|CERNER||PriorityHealth||||ORU^R01|Q479004375T431430612|P|2.3|
PID|||001677980||SMITH^CURTIS||19680219|M||||||||||929645156318|123456789|
PD1||||1234567890^LAST^FIRST^M^^^^^NPI|
OBR|1|341856649^HNAM_ORDERID|000002006326002362|648088^Basic Metabolic
Panel|||20061122151600|||||||||1620^Hooker^Robert^L||||||20061122154733|||F|||||
||||||20061122140000|
OBX|1|NM|GLU^Glucose Lvl|59|mg/dL|65-99^65^99|L|||F|||20061122154733|
HL7 tiene evidentes aspectos positivos: toda apuesta por estandarizar la exportación de biodatos actualmente
pasa sí o sí por este estándar, no existiendo competidores reales en el mercado. Además, como se ha comentado
hace unas líneas, HL7 apuesta por abordar la práctica totalidad de información clínica de los pacientes, no
quedando reducido al campo de la integración con biodispositivos. Esto es realmente interesante, aunque pocos
centros sanitarios lo emplean para las constantes médicas de los enfermos sí es mucho más habitual que sirva
como base en el intercambio de información administrativa (altas, traslados, informes, etc.) Sin embargo, los
aspectos negativos que rodean al protocolo no son menores: solo los biodispositivos de los últimos años están
apostando decididamente por implementarlo como mecanismo de exportación de datos, lo que, unido a la
elevada vida media de dicha maquinaria, provoca que sea realmente difícil encontrar un centro sanitario en el
que una cantidad representativa de sus biodispositivos soporte HL7. Un rápido vistazo al mercado actual
corrobora la afirmación anterior: los productos alternativos a este proyecto no se centran en implementar un
servidor de HL7, sino que siempre trabajan con protocolos propietarios específicos, pues es el único modo de
dar soporte a un número razonable de dispositivos. A lo anterior hay que sumar dos cuestiones menores: se
pretende que el actual proyecto sea extensible a sensores no comerciales (que no suelen trabajar con HL7) e
incluso que se estudie la posibilidad de influir en el comportamiento del dispositivo, algo que queda totalmente
fuera del planteamiento del estándar.
En un futuro, sería interesante abordar una herramienta que permita recibir y procesar información HL7. Como
ya se ha comentado, HL7 no es solamente un estándar para la exportación de datos biomédicos de los pacientes,
sino que también permite el intercambio de otros mensajes administrativos y clínicos. Por tanto, abordar este
modo de integración con biodispositivos implica diseñar una arquitectura compatible con los otros usos del
estándar, algo totalmente fuera del alcance de este proyecto. Eso sí, parece evidente que estamos ante un gran
cambio de enfoque: basta con un servidor HL7 colocado en la red para recibir los mensajes y procesarlos, sin
necesidad de recurrir a componentes hardware específicos como en el enfoque anterior.
5.2.2 Protocolos propietarios
Una vez comentado por qué se ha descartado una integración con biodispositivos basada en HL7, se pasa a
analizar el enfoque multiprotocolo. Este enfoque consiste en diseñar una aplicación capaz de alternar entre un
conjunto variado de protocolos, generalmente propietarios, cada uno de los cuales se comunicará mediante un
puerto diferente (puertos paralelos, RS232, USB, etc.). Para abordar adecuadamente el diseño de una tarea tan
ambiciosa debemos contar con un hardware de integración para conectar uno o varios dispositivos, de forma
que el software que corra sobre él traslade la información recibida a un formato interno estándar y la envíe al
sistema de almacenamiento / intercambio de información adecuado. Bajo este prisma es inmediato obtener la
primera característica sobre la que se cimentará el hardware de integración del proyecto: la versatilidad. Por
tanto, se descarta diseñar un componente electrónico propio. Un desarrollo específico obligaría a cambiar el
enfoque del proyecto, que no es otro que la integración del mayor número de soluciones tecnológicas posibles
de forma que los usuarios del mismo, profesionales sanitarios u otros, puedan explotar la información biomédica
procesada por los mismos. Dicho enfoque es irrenunciable, ningún cliente optará por implementar una solución
para centralizar información si los elementos soportados son demasiado limitados.
Otra posibilidad sería desarrollar un dispositivo electrónico que disponga de los conectores básicos del mercado,
por ejemplo, una placa con varios USB, un puerto Ethernet y pines para sensores, pero, evidentemente, ya
55
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
55
encontramos placas que realizan perfectamente esta función a precios populares.
Junto a la necesidad de un componente hardware lo suficientemente versátil como para cubrir la comunicación
con diversos productos va unida la escalabilidad del mismo; es decir, la cantidad de dispositivos / protocolos
soportados y su facilidad para que esto aumente en el futuro. Con el enfoque elegido, nos encontraremos en un
escenario en el que nuestro hardware de integración raramente abandonará la dinámica 1 puerto – 1 dispositivo.
Es aquí donde se plantea el asunto de la escalabilidad, intrínsecamente relacionada al punto anterior. Previamente
se ha justificado la preferencia por un dispositivo hardware ya existente en el mercado, dado la poca
especificidad de las conexiones (casi siempre estándares) y la amplia versatilidad necesaria. Ahora a esto
debemos sumarle el mayor número posible de conexiones, teniendo en cuenta que cada conexión podría ser
totalmente diferente (puerto serie, paralelo, USB, etc.). Bajo esta concepción parece convincente plantearse la
necesidad de dotar a nuestro hardware de la mayor cantidad de puertos, del tipo más “estándar” posible. Dicho
puerto debería completarse con un adaptador a otro tipo de conexión cuando sea necesario. Esto facilita aumenta
exponencialmente la versatilidad y escalabilidad del software, aunque no cierra totalmente el coste de
implantación: si en el futuro se pretende dar soporte a un nuevo dispositivo será necesario incorporar al hardware
el adaptador adecuado, con el consiguiente desembolso. Dado el pequeño precio de los adaptadores existentes
en el mercado entre puertos estándares, se entiende que nos encontramos ante la solución óptima, encontrando
un punto intermedio adecuado entre los factores que estamos considerando.
Figura 5-8. La Raspberry Pi 2 B ofrece 4 puertos USB a bajo coste
Quedan por cerrar un par de asuntos importantes, generalmente relacionados entre sí: coste y rendimiento.
Conviene recordar que pretendemos correr sobre el hardware un programa encargado de comunicarse con varios
dispositivos a la vez, recuperando las constantes biomédicas del paciente, procesándolas y enviándolas a una
base de datos para su almacenamiento. En principio, no parece una tarea excesivamente exigente, que requiera
de una velocidad de procesamiento de datos elevada, aunque para analizar adecuadamente este punto debemos
observar con qué velocidad envía información los dispositivos médicos. Dado que planteamos una integración
versátil que permite la adaptación del mayor número posible de dispositivos, la pregunta debe ser reformulada
para pasar de ¿con qué velocidad recibiremos datos? a ¿cuál es la tasa máxima de datos que deberemos soportar?
Antes de acercarnos a las conclusiones de este apartado del análisis, conviene tener claro la función del proyecto:
la idea es recopilar toda la información posible del paciente para almacenarla y ser explotada posteriormente. Es
decir, deberemos estar preparados para recuperar toda la información disponible, de tal forma que corresponde
a otra solución, fuera del alcance de este proyecto, decidir cuánta cantidad de información es realmente necesaria.
Aclaremos a qué nos referimos con la frase anterior: es habitual que en los centros sanitarios los profesionales
de enfermería comprueben las constantes de los pacientes con una periodicidad elevada, cercana a la hora,
excepto en situaciones de crisis. La presente solución no debe entrar en estas cuestiones, solo procesando datos
de acuerdo a determinados patrones médicos, sino que debe almacenar toda información recibida, siendo tarea
posterior filtrar o explotar como se desee los datos recolectados.
Llegado a este punto es fácil comprender que la capacidad de procesamiento dependerá de dos variables:
cantidad de constantes y frecuencia con la que se esperan recibir datos de cada una. Evidentemente, la capacidad
de procesamiento de la información está íntimamente relacionada con la cantidad de constantes a recibir. Incluso
si contemplamos solo de las denominadas variables discretas, no es lo mismo procesar cinco variables por
minuto que cien por segundo. Por su parte, la frecuencia con la que recibiremos los datos no es problemática si
hablamos de constantes discretas, pues es raro trabajar con resoluciones inferiores al segundo, mientras que en
Descripción de la propuesta
56
56
el caso de las señales continuas la tasa de muestreo suele situarse en las 500-1000 muestras por segundo.
Partiendo de lo anterior, parece adecuado concluir que el principal dato para realizar la correcta caracterización
de nuestro sistema es la tasa de muestreo de las señales continuas que debemos procesar a tiempo real. No se
trata de una frecuencia especialmente elevada, por lo que muchas de las placas de bajo coste existentes en el
mercado (Arduino, Raspberry, etc.) deben tener capacidad suficiente para trabajar con ellas siempre que no
hablemos de una cantidad desmesurada de variables continuas. Hasta ahora, esas variables eran solamente las
mostradas en las pantallas de los dispositivos médicos (fundamentalmente monitores, aunque también otros
elementos auxiliares como ventiladores); por lo que nos moveremos sobre la docena de variables en el más
exigente de los casos.
Con cuatro puertos USB, la Raspberry Pi 2 debe ser capaz de soportar las bioseñales de cuatro dispositivos a la
vez. Esta placa hardware cuenta con un procesador cuatro núcleos de 900 MHz, lo que la capacitar de sobre para
actuar como hardware de integración en nuestro proyecto.
Antes de terminar este punto es necesario abordar el único objetivo del proyecto relacionado con la integración
que no se ha comentado hasta el momento: la posibilidad de dotar a las aplicaciones propias, o a programas de
terceros a través de internet, de la capacidad de influir en el comportamiento de los biodispositivos soportados.
La idea inicial pretendía conocer las posibilidades de interacción bidireccional con estos dispositivos,
permitiendo a las aplicaciones setear parámetros o cambiar su configuración, con el objetivo de abrir la puerta a
desarrollar productos más inteligentes. Si todo lo anterior es realizable, es posible imaginar un escenario en el
que determinada información biomédica del paciente pueda provocar un cambio automático en el
comportamiento de los dispositivos, o incluso permitir a los facultativos un control remoto de los mismos.
Durante el apartado anterior se ha comentado que esta funcionalidad escapa totalmente del ámbito del estándar
HL7, siendo solamente posible bajo un enfoque de comunicación basado en protocolos propietarios, Esto es
cierto, existen protocolos propietarios de comunicación con biodispositivos que permiten a los ordenadores
modificar su comportamiento, en la línea de lo comentado en este párrafo. Sin embargo, no existe un patrón
común, un conjunto de funcionalidades habituales soportadas por los protocolos; es más, el número de
protocolos que ofrecen algo más que la mera exportación de datos es insignificante. Por tanto, se concluye que
los dispositivos actuales del mercado no están enfocados a esta tarea, por lo que no se puede abordar en el
presente proyecto.
5.2.3 Adaptación entre puertos estándares
Tal y como se ha comentado en el punto anterior, será necesario complementar nuestro hardware de integración
con los adaptadores necesarios para otros puertos de entrada estándares. Como el caso más habitual será la
utilización de un adaptador USB – RS232 se ha considerado adecuado realizar un breve comentario sobre esta
parte del proyecto.
57
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
57
Figura 5-9. Esquema de una adaptación RS232 (DB9) a USB
RS232 es una interfaz que designa una norma para el intercambio de una serie de datos binarios entre dos
extremos (1 a 1), mientras que USB es un bus, de tal forma que permite a más de un dispositivo conectarse a un
puerto USB, provocando que el estándar no solo describa las propiedades físicas de la interfaz (capa física) sino
también los protocolos necesarios para transmitir adecuadamente la información. Esto no se da en la
comunicación RS232, donde existe una gran libertad para configurar muchos parámetros de la comunicación.
Con frecuencia es habitual que las aplicaciones controlen la tasa de bits, número de bits de parada, número de
bits de datos y otros parámetros, algo impensable en el estándar USB. Por todo esto, la adaptación entre ambos
puertos no es inmediata, como se puede comprobar en la imagen ubicada encima del presente párrafo.
En lo referente al coste, es habitual encontrar adaptadores DB9 a USB por un precio ubicado entre los 5 y 10
euros.
Figura 5-10. Adaptador empleado en el proyecto
Descripción de la propuesta
58
58
Base de datos
Antes de abordar el software de recuperación de datos es conveniente analizar el modelo de datos empleado,
pues este condiciona numerosos aspectos del programa.
En los capítulos anteriores se ha comentado las distintas tecnologías que se pueden usar para implementar una
base de datos, realizando una comparativa entre las mismas y justificando la elección de una base de datos Oracle
11g. En dicho punto se realiza una adecuada explicación de los conceptos más básicos necesarios para operar
con una base de datos como la seleccionada. A continuación, se procederá a comentar la solución escogida,
detallando el modelo de datos de acuerdo a las características previamente documentadas.
5.3.1 Tablespace
Como comentamos anteriormente, las bases de datos Oracle tienen una estructura lógica que comienza con el
Tablespace. Se define un Tablespace para controlar la localización de los ficheros de datos, especificación de
máximas cuotas de consumo de disco, disponibilidad de los datos (en línea o fuera de línea), backup de datos,
etc. Como se está montando una base de datos pequeña y destinada, de momento, solamente a nuestro proyecto,
no tiene sentido establecer una configuración no única para estos parámetros. En general, es poco habitual la
existencia de varios Tablespace en bases de datos de tamaño medio, pues los gestores de la base de datos suelen
renegar de combinar políticas diferentes siempre que no sea estrictamente necesario. En nuestro caso,
planteamos un modelo de datos de una decena de tablas, por lo que se turna absurdo llegar a tal nivel de
complejidad como para combinar varios Tablespace.
Conviene tener en cuenta que las bases de datos Oracle ya cuentan con varios Tablespace de fábrica, necesarios
para su correcto funcionamiento. En la imagen siguiente, obtenida a través de la aplicación web que ofrece
nuestra base de datos Oracle (http://127.0.0.1:8080/apex/), podemos conocerlos y comprobar sus datos básicos.
Figura 5-11. Tablespace iniciales de una base de datos Oracle XE 11g R2
Se procede a comentar de manera muy genérica la utilidad de cada uno de estos tablespace [56, 57]
- SYSTEM: todas las bases de datos Oracle contienen un tablespace SYSTEM, creado automáticamente
con la base de datos. Este tablespace está siempre online cuando la base de datos está abierta. Se trata
de un diccionario de datos, es decir, un repositorio de metadatos que permite almacenar formatos,
significados, relaciones, uso, etc. de cada elemento de la base de datos.
- SYSAUX: es un tablespace auxiliar del tablespace SYSTEM. Algunos componentes de las bases de
datos usan este tablespace como su ubicación por defecto para almacenar información.
- UNDO: al igual que los anteriores, toda instalación de una base de datos Oracle cuenta con un
tablespace UNDO. Como su propio nombre indica se emplean para almacenar información necesaria
para realizar acciones de deshacer. En este tablespace no se pueden crear tablas.
- TEMP: tablespace para el almacenamiento de información temporal.
- USERS: tablespace empleado para almacenar información básica de usuarios.
59
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
59
Comenzaremos a parametrizar la base de datos del proyecto conectándonos con los parámetros establecidos por
defecto para la base de datos seleccionada.
Figura 5-12. Parámetros de conexión por defecto de la base de datos
En este apartado solo tendremos que crear el tablespace, para lo cual ejecutaremos desde alguno de los entornos
de desarrollo de base de datos del proyecto (SQL Plus o SQL Developer) la instrucción:
CREATE TABLESPACE TS_MEDIPI DATAFILE
'C:/oraclexe/app/oracle/oradata/XE/MEDIPI.dbf' SIZE 500M AUTOEXTEND OFF
LOGGING
ONLINE;
En la que se indican los siguientes parámetros:
- Datafile: se comentó en el capítulo de tecnologías empleadas la estructura física de una base de datos
Oracle XE 11g R2. En resumen, un datafile es el fichero (o ficheros) físicos en los que se encontrará la
información del tablespace. En este caso se emplea una base de datos sobre una máquina con Windows,
por lo que se crea el fichero bajo la estructura generada en la instalación de la base de datos.
- Tamaño: en este caso se ha optado por un tamaño base de 500MB. Por motivos de seguridad, se
deshabilita la capacidad de aumento dinámico del tamaño del tablespace. Si nos quedamos sin espacio
deberemos redimensionar el tablespace manualmente.
- Logging: con esta opción quedan registradas las operaciones de DDL e inserciones de datos en tablas
en el sistema de log predeterminado de Oracle.
- Online: es la opción predeterminada, pues un tablespace debe estar online para poder trabajar
normalmente con él. Se opta por poner offline un tablespace cuando se requieren hacer algunas
operaciones especiales como copias de seguridad más rápidas, labores de restauración de datos, mover
un datafile sin cerrar la base de datos, etc.
5.3.2 Usuarios / Esquemas
Conviene recordar algunos aspectos de la relación entre usuarios y esquemas en el modelo de información que
manejan las bases de datos Oracle. Resumiendo, un esquema es una colección de objetos de base de datos,
Descripción de la propuesta
60
60
incluyendo estructuras lógicas, tales como tablas, vistas, secuencias, procedimientos almacenados, sinónimos,
índices, clústeres, y la base de datos de enlaces. Un usuario posee un esquema, que se crea automáticamente al
crear el usuario. En Oracle, un usuario solo puede tener un esquema, a diferencia de lo que ocurre en otras bases
de datos. De esta forma, la relación entre usuario y esquema puede simplificarse hasta indicar que son sinónimos.
Realmente esto no es así, un usuario es el propietario de los objetos y el esquema la agrupación lógica de los
mismos, pero puede considerarse aceptable la simplificación al haber una relación 1 a 1. Además, un usuario
puede acceder a los objetos de esquemas diferentes del propio, si tienen permiso para hacerlo.
En el presente proyecto se ha optado por establecer dos esquemas diferentes para escenificar las dos grandes
categorías que componen el modelo de datos. Funcionalmente, podemos dividir el modelo de datos en dos partes
claramente diferenciadas:
- Información general médica: entran en esta categoría todo objeto relacionado exclusivamente con
información médica. Por ejemplo, parece evidente que necesitaremos almacenar información del
paciente como nombre, apellidos, DNI, etc.
- Información de la integración: en una categoría totalmente distinta podemos encontrar la información
puramente enfocada a la integración. Por ejemplo, habrá que crear una tabla con los dispositivos
conocidos, sus parámetros, su asignación a cada raspberry en caso de que tengamos más de una
funcionando a la vez, etc.
Como se observa, estamos hablando de una clasificación meramente funcional, no técnica. Sin embargo,
recordemos que en las bases de datos Oracle los usuarios tienen asociado un esquema. Si creamos dos esquemas
diferentes, podemos tener dos usuarios distintos para manejar información funcionalmente diferente. Por
ejemplo, podríamos darle a un profesional sanitario acceso a la base de datos con el usuario del esquema de
información médica, de forma que, ya sea directamente o a través de una aplicación concreta, pueda modificar
la información allí almacenada para añadir pacientes, modificar sus datos, etc. Sin embargo, no podría acceder
a la información de la integración con los dispositivos si configuramos adecuadamente la base de datos. Además,
un correcto modelado de la base de datos debe tener en cuenta la posible expansión del proyecto. Con esta
clasificación permitimos que se pueda extender el esquema de información médica para, por ejemplo,
compartirlo con una aplicación encargada de gestionar la historia clínica de un paciente.
Por tanto, vamos a crear dos usuarios: HEALTH y DATA_ACQ.
CREATE USER HEALTH
IDENTIFIED BY "HEALTH"
DEFAULT TABLESPACE TS_MEDIPI
TEMPORARY TABLESPACE TEMP
PROFILE DEFAULT
ACCOUNT UNLOCK;
GRANT CONNECT TO HEALTH;
GRANT RESOURCE TO HEALTH;
GRANT CREATE DATABASE LINK TO HEALTH;
ALTER USER HEALTH DEFAULT ROLE ALL;
CREATE USER DATA_ACQ
IDENTIFIED BY "DATA_ACQ"
DEFAULT TABLESPACE TS_MEDIPI
TEMPORARY TABLESPACE TEMP
PROFILE DEFAULT
ACCOUNT UNLOCK;
GRANT CONNECT TO DATA_ACQ;
GRANT RESOURCE TO DATA_ACQ;
GRANT CREATE DATABASE LINK TO DATA_ACQ;
ALTER USER DATA_ACQ DEFAULT ROLE ALL;
61
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
61
En la creación de los usuarios le asignamos el tablespace creado anteriormente y le damos los permisos y
características necesarias para acceder a la base de datos independientemente. La propiedad del usuario
“ACCOUNT UNLOCK” indica que se permite el login del usuario en la base de datos mediante la contraseña
indicada en la instrucción “IDENTIFIED BY”. A continuación, le indicamos que emplee el perfil por defecto.
Los perfiles [57] se emplean para restringir el uso que un usuario puede hacer de la base de datos, en términos
de cantidad de espacio disponible, tiempo de conexión, etc. En este caso, al tratarse de un modelo de datos
pequeño, no hay necesidad de definir una política de perfiles demasiado compleja, con lo que se opta por emplear
el perfil por defecto, que no limita las características del usuario. Para terminar, es necesario configurar los roles
del usuario: Todos los usuarios que quieran acceder a la BD deben tener el rol CONNECT; aquellos que necesiten
crear segmentos (procedures, triggers, Jobs, etc.) necesitaran el rol RESOURCE. Por último, el permiso “CREATE
DATABASE LINK” [58] añade la posibilidad de crear un enlace privado a la actual base de datos desde una
segunda base de datos de Oracle, lo que permitiría lanzar consultas o realizar modificaciones desde la segunda
base de datos. La última sentencia indica los roles por defecto, es decir, los que tendrá el usuario
automáticamente al logarse en la base de datos.
5.3.3 Tablas
Es el turno de comentar la parte central del modelo de datos: las tablas y los campos que las componen. Hemos
visto anteriormente que nuestra solución va a contar con dos esquemas: HEALTH y DATA_ACQ, los cuales
se detallan a continuación.
Figura 5-13. Modelo de datos
En el gráfico anterior se ha empleado la siguiente notación:
- Las elipses representan las tablas del modelo de datos
- Las flechas representan las relaciones entre tablas. Cuando una tabla apunta a otra se está
representando que en la tabla origen existe una referencia a la tabla apuntada mediante una clave externa
Como se observa, las tablas se reparten entre los dos esquemas creados. Dentro de HEALTH ubicamos las
entidades que recopilan información general, como por ejemplo información del paciente; mientras que en
DATA_ACQ se encuentra todo el modelo de datos de la recolección de información.
Se puede observar que el número de relaciones entre tablas de distintos esquemas es lo más reducido posible y
Descripción de la propuesta
62
62
siempre van de tablas de DATA_ACQ a entidades del esquema HEALTH. Esto no es casualidad, si se desea
implementar otro sistema sobre esta base de datos, como por ejemplo una aplicación que represente la
información del paciente, el esquema HEALTH debería ser completamente independiente, de forma que no sea
necesaria realizar ninguna acción sobre el esquema DATA_ACQ, hasta el punto de poder borrar este esquema.
Emplear esta política de separación de entidades permite una gran modularidad, de forma que nuevas
aplicaciones puedan crear esquemas propios sobre el núcleo HEALTH, ignorando el resto de esquemas.
Además de lo anterior, se observa que el modelo de base de datos construido cumple con el estándar de
normalización 3FN.
5.3.3.1 Información común a todas las tablas
Antes de comenzar, conviene comentar los tipos básicos de datos de Oracle, para comprender la terminología
usada posteriormente en la definición de las tablas:
- NUMBER(X, Y): Tipo numérico. El primer valor representa la precisión (número total de dígitos) y el
segundo la escala (número de dígitos decimales, es decir, a la derecha del punto decimal). El valor por
defecto es (38,0)
- DATE / TIMESTAMP(X): Tipo fecha. El tipo TIMESTAMP es similar a DATE (el tipo fecha más
habitual) pero permite introducir fracciones de segundos, indicando entre paréntesis el número de
dígitos de precisión en las fracciones de segundo. El valor por defecto es 6.
- VARCHAR2(X BYTE): Tipo texto, indicando el tamaño máximo en bytes (50 por defecto).
A lo largo del apartado se empleará la clasificación estándar de tablas en función de la información almacenada:
- Maestras: Información prácticamente fija, que cambia raramente, y generalmente relacionada con un
conjunto de datos maestros del mundo real. Por ejemplo, un catálogo de países del mundo se almacenará
en una tabla maestra, pues esta información no debe cambiar prácticamente nunca. En nuestro caso, la
tabla CONSTANT será maestra, pues es poco habitual añadir o eliminar variables clínicas como la
frecuencia cardiaca, presión arterial, etc. Si se pretende optimizar el uso de una tabla maestra se
centrarán los esfuerzos en agilizar las consultas, penalizando la inserción de nuevos registros. Las tablas
maestras no deben hacer referencia (mediante una clave externa) a ninguna otra tabla que no sea también
maestra.
- Paramétricas: Contienen información variable que no suelen cambiar habitualmente. Este tipo de tablas
se suelen emplear para datos del sistema, aunque puede emplearse para información del mundo real de
variación mayor a las maestras. Como se puede deducir, la tabla PARAMETER de nuestro modelo de
datos se emplea para almacenar determinados parámetros del sistema, como podría ser una constante
que indique el número de intentos de reconexión que deben realizarse tras una pérdida de conexión con
la red.
- Datos: Las tablas de tipo Datos contienen información no fija, que varía constantemente, como las
constantes de un paciente. En este tipo de tablas prima la velocidad de inserción / modificación de
registros. Será muy habitual que las tablas de datos hagan referencia a las tablas maestras, pero nunca
al revés.
Para homogenizar el modelo de datos y facilitar su uso, se seguirán un conjunto de reglas comunes:
- Clave primaria: La clave primaria de toda tabla se denominará TABLA_ID. Cuando un campo haga
referencia a la clave primaria de otra tabla mantendrá el nombre original.
- Fechas: los campos que almacenen una fecha serán de tipo TIMESTAMP.
Además de todo lo anterior, las tablas creadas para el modelo de datos de MEDIPi comparten un núcleo, un
conjunto de columnas comunes que representan información básica. Estas son:
63
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
63
CAMPO TIPO DEFAULT COMENTARIO
DELETED NUMBER(1,0) 0 Indicador de borrado lógico. 1 borrado / 0 no borrado
INSERT_DATE TIMESTAMP(6) SYSDATE Fecha de creación del registro
UPDATE_DATE TIMESTAMP(6) Fecha de última modificación del registro
VERSION NUMBER(38,0) 0 Versión del registro
5.3.3.2 Esquema HEALTH
5.3.3.2.1 PATIENT
Es la entidad base de todo modelo de base de datos para el ámbito sanitario. Esta tabla de datos debe incluir la
información básica del paciente, como el nombre, DNI, etc.
CAMPO TIPO DEFAULT COMENTARIO
PATIENT_ID NUMBER(38,0) Clave primaria de la tabla
PID VARCHAR2(32 BYTE) Identificador del paciente en el servicio sanitario. Debe
estar siempre informado.
DNI VARCHAR2(32 BYTE) DNI
NAME VARCHAR2(32 BYTE) Nombre
SURNAME1 VARCHAR2(32 BYTE) Primer apellido
SURNAME2 VARCHAR2(32 BYTE) Segundo apellido
SEX NUMBER(1,0) 0 0: Desconocido / 1: Hombre / 2: Mujer / 3: Indeterminado
5.3.3.2.2 EPISODE
Cada vez que un paciente visite un centro sanitario se crea un nuevo episodio, con fecha de ingreso y de fin. En
esta tabla debe haber una clave externa a la tabla PATIENT, para poder relacionar la información del episodio
con el paciente al que pertenece. Al igual que PATIENT, se trata de una tabla de datos, que contendrá una gran
cantidad de información que irá aumentando constantemente.
CAMPO TIPO DEFAULT COMENTARIO
EPISODE_ID NUMBER(38,0) Clave primaria de la tabla
PATIENT_ID NUMBER(38,0) Referencia al paciente. Este campo no puede ser nulo.
EID VARCHAR2(32 BYTE) Identificador del episodio en el servicio sanitario. Debe
estar siempre informado.
START_DATE TIMESTAMP(6) Fecha de ingreso
Descripción de la propuesta
64
64
END_DATE TIMESTAMP(6) Fecha de alta
5.3.3.2.3 CONSTANT
Si se pretende recopilar constantes clínicas del paciente es evidente que necesitamos una tabla maestra de
variables biomédicas, un catálogo de variables que podremos obtener automáticamente de la integración con los
dispositivos.
CAMPO TIPO DEFAULT COMENTARIO
CONSTANT_ID NUMBER(38,0) Clave primaria de la tabla
TYPE NUMBER(1,0) 0 0: Numérica / 1: Texto
DESCRIPTION VARCHAR2(128 BYTE) Descripción de la variable clínica
MAX_VALUE NUMBER(38,0) En caso de tratarse de una variable de tipo numérico (las
más habituales) se indica aquí el valor máximo posible de
la misma.
MIN_VALUE NUMBER(38,0) Valor mínimo posible de una constante numérica
Hemos optado por establecer una clasificación de variables en función de si esperamos recuperar valores
numéricos o alfanuméricos, ya que el tratamiento de la información variará en función de esto.
Otra información importante que puede pasar desapercibida a primera vista es la almacenada en las columnas
MAX_VALUE y MIN_VALUE. La idea es emplear estos campos para conocer el umbral de valores posibles
de una variable clínica. En numerosas ocasiones será habitual recibir información incorrecta desde los
dispositivos médicos, no solo datos imprecisos, si no directamente valores fuera de rango. Estos datos, que en el
ámbito de la instrumentación biomédica se conocen como “artefactos”, no suelen ser filtrados por los propios
dispositivos pues la política habitual es mostrar toda la información posible dejando su interpretación a cargo de
los profesionales médicos. En nuestro caso, una de las funcionalidades que MEDIPi aportará a los usuarios es
un tratamiento básico de limpieza de datos, rechazando datos claramente erróneos, que se salgan del umbral de
valores compatibles con la vida. Como es razonable, solo podremos realizar este tratamiento para aquellas
variables clínicas de tipo numérico.
5.3.3.2.4 CONSTANT_INFO
En los puntos anteriores se ha definido el conjunto de tablas básicas del modelo de datos general: pacientes,
episodios y variables biomédicas, con lo que ya se tiene información suficiente para definir la entidad más
importante de MEDIPi: la tabla de valores de constantes clínicas, donde se volcará toda la información
recuperada desde los dispositivos.
Como es fácil deducir, necesitaremos al menos una referencia al paciente y otra a la maestra de variables. Lo
primero lo haremos con una clave externa a la tabla EPISODE, quedando así asociada la constante a una estancia
concreta del paciente. En general, se debe tender a establecer relaciones lo más específicas posibles. Por ejemplo,
si se hubiera asociado el resultado de una medición (p.e. Temperatura) a un paciente en vez de a un episodio
sería más difícil en un futuro sacar estadísticas o patrones de la información recuperada. Siguiendo con el
ejemplo, si un paciente ingresa dos veces, la segunda de ellas con fiebre, es evidente que mantener claramente
los resultados asociados a dos episodios diferentes facilita la explotación de la información. En caso contrario,
al observar que los valores pasan de normales a altos habría que revisar las fechas, pudiendo dar lugar a alguna
confusión.
En la definición de la tabla CONSTANT se definió una categorización muy básica de constantes, una
65
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
65
clasificación entre variables numéricas (la gran mayoría) y de tipo texto. Por tanto, debemos preparar nuestro
modelo de datos para almacenar variables cuyo valor sea un texto. Un caso fácil de comprender puede ser el
modo de funcionamiento del biodispositivo, que se suele tratar como un texto. Por tanto, a pesar de guardar
fundamentalmente valores numéricos, es necesario definir el campo valor como alfanumérico. Existen otras
alternativas a esta metodología: podría haberse optado por crear una tabla de valores posibles identificados por
una clave numérica. De esta forma, bastaría con informar en el campo valor la clave de esta tabla, pudiendo
mantener la columna VALUE como numérica. Sin embargo, este modelo presenta otras deficiencias: no es
posible introducir cualquier valor, solo los presentes en esta tabla. A no ser que se opte por ir introduciendo todos
los valores nuevos en esa tabla, perdiéndose las ventajas sobre el texto libre. Por todo esto, y esperando diseñar
un modelo de datos lo menos restrictivo posible para evitar futuras modificaciones, se ha optado por un campo
VALUE de tipo texto.
CAMPO TIPO DEFAULT COMENTARIO
CONSTANT_INFO_ID NUMBER(38,0) Clave primaria de la tabla
CONSTANT_ID NUMBER(38,0) Clave externa que hace referencia a la constante informada
EPISODE_ID NUMBER(38,0) Clave externa que hace referencia al episodio
correspondiente del paciente para el que se recopila la
variable.
CONSTANT_DATE TIMESTAMP(6) SYSDATE Fecha de recuperación de la variable
VALUE VARCHAR2(32 BYTE) Valor de la variable
MANUAL NUMBER(1,0) 0 0: Valor recuperado automáticamente / 1: Datos
introducido manualmente.
Es interesante realizar una observación acerca de la tabla que nos ocupa: a pesar de tratarse de la entidad diseñada
para almacenar información no solo no pertenece al esquema de adquisición de datos (DATA_ACQ), sino que
ni siquiera tiene relación con alguna tabla de dicho esquema. A primera vista resulta chocante, pero es fácilmente
comprensible desde la política escogida para el diseño del modelo de datos: cada esquema corresponde a una
funcionalidad concreta y diferenciada, de tal forma que las tablas que componen el esquema DATA_ACQ son
aquellas necesarias en la creación de una aplicación para la adquisición de datos, mientras que bajo HEALTH
agrupamos las tablas que mapean información médica de carácter general. El conjunto de constantes de un
paciente es una información médica que no debe ser exclusiva del software de adquisición automática de datos,
hasta el punto de que se podría diseñar una solución que permitiera a los profesionales médicos introducir “a
mano” valores para la temperatura del paciente, la frecuencia cardiaca, etc. La solución imaginada en la frase
anterior debería conocer las tablas en las que se recopile la información del paciente, su estancia actual, la
maestra de variables clínicas, etc., pero nunca debería ser imprescindible tener en cuenta el conjunto de
protocolos soportados por la recolección automática de información, por ejemplo. Por tanto, parece lógico
diseñar nuestro modelo de datos “sacando” la entidad de constantes médicas CONSTANT_INFO al esquema
general. Como toda decisión, este diseño nos genera también inconvenientes: si mantuviéramos
CONSTANT_INFO dentro del esquema DATA_ACQ probablemente se hubiera optado por añadir una
referencia al menos al dispositivo del que proviene la información, permitiendo una gran explotación de la
información a futuro. Bajo nuestra implementación, necesitaríamos combinar información de varias tablas para
poder mostrar esa información (necesitamos saber, a través del episodio, la asignación de dispositivos que tenía
el paciente en aquella fecha; pudiendo incluso no obtener un resultado concluyente si había más de un dispositivo
asignado en ese instante con capacidad de recuperar esa variable clínica). En resumen, hemos ganado en
modularización, pero hemos dificultado la explotación posterior de la información.
Descripción de la propuesta
66
66
5.3.3.3 Esquema DATA_ACQ
5.3.3.3.1 MEDIPI_INSTANCE
Nos encontramos ante la tabla central del esquema DATA_ACQ, sobre la que se construye el modelo de datos
de nuestra solución.
En ella, cada ejecución del software MEDIPi (por ejemplo, cada Raspberry que corra el software del presente
proyecto) estará representada por un registro, en el que será informados algunos de los parámetros necesarios
para especificar el funcionamiento de dicha ejecución. Es fácil imaginar un escenario como el siguiente: un
centro sanitario en el que se desea implantar MEDIPi en una sala con cuatro camas. Para dar cobertura a todos
los pacientes que vayan ocupando las camas, será necesario imaginar el escenario más desfavorable: que las
cuatro localizaciones estén ocupadas a la vez. Parece evidente que son necesarias cuatro Raspberrys, cada una
de ellas ejecutando MEDIPi, lo que se corresponde con cuatro registros en la tabla MEDIPI_INSTANCE en
base de datos. En resumen, con esta tabla podremos representar la parametrización básica de cada instancia.
CAMPO TIPO DEFAULT COMENTARIO
MEDIPI_INSTANCE_ID NUMBER(38,0) Clave primaria de la tabla
INSTANCE_NAME VARCHAR2(64 BYTE) Identificador único de la instancia de MEDIPi. Será único
y no nullable.
IP VARCHAR2(32 BYTE) Dirección IP de la máquina que está ejecutando el software
Se podría haber empleado el campo INSTANCE_NAME como clave primaria, ya que cumple con las dos
condiciones básicas de toda clave primaria: valores únicos y no nullables. Como se comentará en el apartado
correspondiente a la descripción del software de MEDIPi, al arrancar la aplicación se encargará de obtener su
INSTANCE_NAME, con el que se puede identificar el registro correspondiente de la tabla
MEDIPI_INSTANCE. Veremos más adelante que se ha optado por emplear el nombre del host sobre el que
corre la aplicación como INSTANCE_NAME. Conocido lo anterior, no hay motivos inmediatos para descartar
la utilización de este campo como clave primaria de la tabla. Sin embargo, la utilización de una clave numérica
nos proporciona más ventajas, como veremos en el punto siguiente (Secuencias). Además, al independizar la
clave de la instancia disponemos de mayor versatilidad a futuro. Si surge la necesidad de ejecutar dos instancias
en la misma máquina solo habrá que hacer una pequeña revisión en el código no siendo necesario modificar la
estructura de la base de datos. Esto último es fundamental, siempre debe diseñarse el modelo de una base de
datos de forma lo suficientemente robusta como para evitar sufrir modificaciones por pequeños cambios en el
alcance.
5.3.3.3.2 PROTOCOL
Con la descripción de la tabla PROTOCOL comenzamos a desgranar el conjunto de tablas que componen el
modelo de datos destinado a caracterizar los distintos dispositivos de los que el software se integrará para
recuperar información clínica de un paciente. Antes de continuar, recordemos el modelo diseñado:
Figura 5-14. Modelo de datos sobre biodispositivos
67
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
67
Se observa que manejamos una estructura relativamente compleja, formada nada menos que por cuatro tablas
destinadas solamente a informar y categorizar los distintos dispositivos. Esta estructura ha sido necesaria para
agrupar adecuadamente los dispositivos desde 2 puntos de vista:
- Protocolos: es imprescindible realizar una asociación entre el dispositivo con el que nos integraremos y
el protocolo que este implementa, pues es perfectamente posible que dos dispositivos diferentes, de
modelos y fabricantes diferentes, empleen el mismo protocolo.
- Tipos: Para una correcta explotación posterior de los datos es necesario conocer de qué tipo es cada
dispositivo (Monitor, ventilador, etc.). Esto puede ser fundamental, ya que es habitual que varios
dispositivos puedan recuperar la misma constante, siendo necesario priorizar los valores de aquel tipo
de dispositivo que sea más preciso en la medición de dicha variable clínica, ignorando los del otro.
Conociendo ya las condiciones iniciales, que serán fundamentales en la estructura de esta sección de la base de
datos, abordamos la definición de la tabla que nos ocupa.
CAMPO TIPO DEFAULT COMENTARIO
PROTOCOL_ID NUMBER(38,0) Clave primaria de la tabla
CODE VARCHAR2(8 BYTE) Código alfanumérico para identificar unívocamente un
protocolo. No puede ser nulo.
DESCRIPTION VARCHAR2(256 BYTE) Descripción del protocolo.
INFO
VARCHAR2(256 BYTE) Información extra, como podría ser datos de
parametrización.
Nos encontramos ante una tabla muy sencilla, una simple maestra donde listaremos todos los protocolos
conocidos por MEDIPi. Cuando informemos al software acerca del dispositivo asociado, la aplicación deberá
acceder hasta esta tabla, para poder identificar el protocolo empleado y operar en consecuencia. En esta ocasión
solo se evita emplear CODE como clave primaria por homogeneidad con el resto del modelo de datos y para
disfrutar de las ventajas que se comentarán en el próximo apartado.
5.3.3.3.3 DEVICE_TYPE
Esta tabla lista los distintos tipos de dispositivos disponibles para poder realizar acciones posteriores relacionadas
con la explotación de la información.
CAMPO TIPO DEFAULT COMENTARIO
DEVICE_TYPE_ID NUMBER(38,0) Clave primaria de la tabla
CODE VARCHAR2(8 BYTE) Código alfanumérico para identificar unívocamente un tipo
de dispositivo. No puede ser nulo.
DESCRIPTION VARCHAR2(128 BYTE) Descripción del tipo de dispositivo
Como se observa, nos basta con una clave primaria y un campo de descripción.
Descripción de la propuesta
68
68
5.3.3.3.4 DEVICE_MODEL
Esta es, seguramente, la tabla central del conjunto que componen el modelo de datos de dispositivos, con una
importancia superior a la de la tabla DEVICE, pues será el modelo del dispositivo quien nos permitirá reconocer
tanto el tipo del mismo como, sobre todo, el protocolo implementado.
CAMPO TIPO DEFAULT COMENTARIO
DEVICE_MODEL_ID NUMBER(38,0) Clave primaria de la tabla
DESCRIPTION VARCHAR2(256 BYTE) Descripción del modelo de dispositivo. No puede ser nulo.
INFO
VARCHAR2(256 BYTE) Información extra, como podría ser datos de
parametrización.
DEVICE_TYPE_ID NUMBER(38,0) Tipo de los dispositivos del presente modelo. Debe estar
siempre informado.
PROTOCOL_ID NUMBER(38,0) Protocolo de comunicación empleado por los dispositivos
de este modelo. Debe estar siempre informado.
5.3.3.3.5 DEVICE
En la tabla dispositivos incluiremos todos los diferentes dispositivos de los que dispongamos, indicando a qué
modelo pertenecen. Es perfectamente posible disponer de una amplia cantidad de dispositivos del mismo
modelo, teniendo todos ellos características iguales, por lo que se ha optado por crear la ya comentada tabla
DEVICE_MODEL donde centralizar toda esta información. Por tanto, dejamos DEVICE como un mero
catálogo de dispositivos empleados ahora o en el pasado.
CAMPO TIPO DEFAULT COMENTARIO
DEVICE_ID NUMBER(38,0) Clave primaria de la tabla
SERIAL_NUMBER VARCHAR2(256 BYTE) Número de serie del dispositivo. Puede estar nulo, pues en
algunos casos no será fácil obtenerlo.
DESCRIPTION VARCHAR2(256 BYTE) Descripción del dispositivo. No nullable
DEVICE_MODEL_ID NUMBER(38,0) Modelo del dispositivo. Debe estar siempre informado.
5.3.3.3.6 ALLOCATION
Ya se han definido las tablas encargadas de modelar los dispositivos y las diferentes Raspberrys que ejecutan el
software, pero no se ha comentado todavía como se establece la relación entre las mismas. Alejándonos
momentáneamente del modelado de base de datos, es evidente que será necesario informar de alguna manera al
software de, al menos, qué protocolos deberá implementar para establecer la comunicación con el dispositivo
de cara a recuperar la información de constantes clínicas del paciente. En la mayoría de los casos, bastará con
indicar el protocolo, actuando el software en consecuencia. Sin embargo, el estudio de los distintos protocolos
y soluciones de recuperación automática de datos nos ha permitido observar que existen casos en los que varios
modelos de un mismo fabricante pueden emplear el mismo protocolo, pero bajo alguna especificación que el
software debe tener en cuenta de cara a poder realizar una comunicación adecuada. El caso más habitual será el
de los biodispositivos conectados mediante una interfaz serie, donde algunos parámetros físicos de la
comunicación como la existencia de bit de paridad, los baudios, etc. pueden variar dependiendo del modelo de
dispositivo. MEDIPi usará una lógica común, pues el protocolo es el mismo, pero deberá conocer de alguna
69
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
69
manera estas especificidades. Este es el motivo por el que se añadió una columna INFO a la tabla
DEVICE_MODEL y también la razón por la cual se considera imprescindible que la asignación entre
dispositivos y Raspberrys se realice al menos a nivel de modelo de dispositivo y no de protocolo.
Partiendo de lo anterior, se ha optado por realizar una asociación a nivel de dispositivo, lo que genera ventajas e
inconvenientes. La principal ventaja es que permite una gran explotación posterior de la información: a partir
del modelo de datos construido se puede recuperar qué dispositivo fue el responsable de cada constante
almacenada. No es una posibilidad despreciable, si descubrimos que un dispositivo ha estado funcionando
incorrectamente podríamos eliminar la información recuperada por él, o al menos identificarla para ponerla en
cuarentena. Por otra parte, es necesario indicar qué dispositivo concreto es el que está conectado a la raspberry,
lo que requiere identificar de forma unívoca cada dispositivo y mantener correctamente actualizado el catálogo.
Además de todo lo anterior, falta una asociación que realizar. Con el modelo de datos diseñado hasta el momento
el software no es capaz de llevar a cabo un mapeo entre el dispositivo asignado a una instancia concreta y el
episodio del paciente para el que se están recuperando las constantes biomédicas vía integración con dispositivos.
Para cumplir este requisito se introduce una clave externa a la tabla episodio desde ALLOCATION, lo que
implica una asociación a nivel paciente – dispositivo – instancia, aportando una mayor granularidad que si se
optara por una relación paciente – instancia. En otras palabras, una misma instancia podrá tener asignados varios
dispositivos (evidentemente, de modelos, tipo y protocolos diferentes) y cada uno de ellos recuperando
información de un paciente distinto.
CAMPO TIPO DEFAULT COMENTARIO
ALLOCATION_ID NUMBER(38,0) Clave primaria de la tabla
MEDIPI_INSTANCE_ID NUMBER(38,0) Referencia a la instancia del software con la que asociamos
el dispositivo. Debe estar informado
DEVICE_ID NUMBER(38,0) Referencia al dispositivo. Debe estar informado
EPISODE_ID NUMBER(38,0) Referencia al episodio. Debe estar informado.
PORT VARCHAR2(32 BYTE) Puerto de la máquina (Raspberry) a la que se conecta el
dispositivo. Puede ser nulo.
START_DATE TIMESTAMP(6) SYSDATE Fecha de inicio de la asignación
END_DATE TIMESTAMP(6) Fecha de fin de la asignación. Valdrá nulo cuando todavía
esté asignado
Junto a los campos mínimos necesarios para realizar la asignación encontramos tres campos no tan inmediatos.
El primero el puerto a través del cual se realiza la conexión, pues MEDIPi necesitará conocer a qué puerto estará
conectado el dispositivo para poder comunicarse con él. Con este campo cubrimos varias posibilidades:
podemos escribir en él el puerto físico (USB, Serie, etc.) si se trata de una comunicación serie o paralelo, o el
puerto que deberá escuchar el software pare recibir información a través de una red IP. Es bastante habitual que
dispositivos del mismo protocolo o modelo tiendan a emplear el mismo puerto para la comunicación, pudiendo
informarse de ello en el campo de parametrización extra que tienen las tablas correspondientes. Se ha indicado
arriba, en la descripción de los campos de la tabla, que el campo puerto puede no estar informado. En ese caso
se entiende que el puerto está informado en alguno de los campos de información comentados. Si no se diera el
caso el software no tendría la configuración mínima para iniciar la comunicación.
Además de todo lo comentado anteriormente se observa que existen dos campos extra: fecha de inicio de la
asignación y fecha de fin. Con ellos es posible mantener un histórico de asignaciones, indicando para cada
registro cuando comenzó y cuando terminó la asignación. Esto último es fundamental para poder recuperar a
posteriori qué dispositivo estaba asignado a qué Raspberry en todo momento.
Antes de pasar a la definición de la siguiente tabla se realizará un comentario sobre las decisiones meramente
Descripción de la propuesta
70
70
técnicas que se han tomado durante el análisis y realización del modelado de datos. Se ha considerado lo más
correcto emplear una clave primaria propia para la tabla, en vez de definir una clave compuesta. Dicha clave
compuesta debería estar formada por MEDIPI_INSTANCE_ID, DEVICE_ID y START_DATE. Incluso sería
necesario incluir el campo común DELETED, pues hablamos de una tabla de asignaciones, que es previsible se
rellene desde una aplicación, pudiéndose producir errores que justifiquen la utilización del borrado lógica. Nos
encontraríamos con una clave tan compleja que prácticamente abarcaría la tabla en su totalidad, dificultando las
consultas que se lancen sobre la tabla. Es por este motivo por el que se ha considerado más adecuado crear una
clave primaria simple.
5.3.3.3.7 PROTOCOL_CONSTANT_MAP
Los distintos protocolos emplean valores propios para identificar a las distintas constantes que manejan, desde
cadenas de texto hasta números identificativos. El software de MEDIPi necesitará conocer en todo momento
qué significa cada uno de ellos de cara a poder realizar el mapeo entre este identificador interno del protocolo y
uno común a todos y conocido por la aplicación. Es evidente por tanto que PROTOCOL_CONSTANT_MAP
será una tabla encargada de establecer las relaciones entre estos valores y la maestra de variables clínicas
conocidas por el sistema CONSTANT. Parece lógico supone que se tratará de una tabla maestra, que se rellenará
durante el desarrollo de la implementación del protocolo, y que no volverá a tocarse salvo para revisiones
puntales.
CAMPO TIPO DEFAULT COMENTARIO
PROTOCOL_ID NUMBER(38,0) Referencia al protocolo del que pertenece la variable.
Siempre debe estar informado.
CONSTANT_NAME VARCHAR2(128 BYTE) Identificador de la constante biomédica interno empleado
por el protocolo. Debe estar siempre informado y será
único dentro de un protocolo.
CONSTANT_ID NUMBER(38,0) Referencia al registro de la maestra de constantes para la
que se está realizando el mapeo. Debe estar informado
Estamos ante la única tabla del modelo de datos que no cuenta con una clave primaria simple, es decir, no
dispone de su PROTOCOL_CONSTANT_MAP_ID. El motivo es sencillo, estamos ante una tabla de mapeo
para la que, por cada protocolo nos bastará el identificador interno (CONSTANT_NAME) para conocer la
asignación. Por tanto, se empleará una clave primaria compuesta por PROTOCOL_ID y CONSTANT_NAME.
5.3.3.3.8 PARAMETER
La última de las tablas del modelo de datos es la más sencilla: se trata de una mera tabla paramétrica destinada
a almacenar información de configuración común a todas las ejecuciones del software MEDIPi. El software
consultará la base de datos para conocer el valor de estos parámetros y actuar según su valor. La parametrización
del software mediante una tabla de base de datos nos permite actualizar dinámicamente sus valores, sin
necesidad de andar parando y arrancando la solución, como veremos más adelante.
CAMPO TIPO DEFAULT COMENTARIO
PARAMETER_ID NUMBER(38,0) Clave primaria de la tabla.
PARAMETER VARCHAR2(64 BYTE) Identificador del parámetro. Debe ser único y estar siempre
informado.
71
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
71
VALUE VARCHAR2(128 BYTE) Valor del parámetro.
DESCRIPTION VARCHAR2(256 BYTE) Descripción o comentarios del parámetro.
Para terminar el apartado destinado al modelado de tablas se muestran un par de ejemplos de las sentencias SQL
necesarias para crear nuestras tablas en base de datos:
CREATE TABLE DATA_ACQ.ALLOCATION
(
ALLOCATION_ID NUMBER(38,0) PRIMARY KEY,
MEDIPI_INSTANCE_ID NUMBER(38,0) REFERENCES DATA_ACQ.MEDIPI_INSTANCE .
(MEDIPI_INSTANCE_ID) NOT NULL,
DEVICE_ID NUMBER(38,0) REFERENCES DATA_ACQ.DEVICE (DEVICE_ID) .
NOT NULL,
PORT VARCHAR2(32 BYTE),
START_DATE TIMESTAMP(6) DEFAULT SYSDATE NOT NULL,
END_DATE TIMESTAMP(6) ,
DELETED NUMBER(1,0) DEFAULT 0 NOT NULL,
INSERT_DATE TIMESTAMP(6) DEFAULT SYSDATE NOT NULL,
UPDATE_DATE TIMESTAMP(6),
VERSION NUMBER(38,0) DEFAULT 0 NOT NULL
);
CREATE TABLE DATA_ACQ.PROTOCOL_CONSTANT_MAP
(
PROTOCOL_ID NUMBER(38,0),
CONSTANT_NAME VARCHAR2(128 BYTE),
CONSTANT_ID NUMBER(38,0) REFERENCES HEALTH.CONSTANT (CONSTANT_ID) .
NOT NULL,
DELETED NUMBER(1,0) DEFAULT 0 NOT NULL,
INSERT_DATE TIMESTAMP(6) DEFAULT SYSDATE NOT NULL,
UPDATE_DATE TIMESTAMP(6),
VERSION NUMBER(38,0) DEFAULT 0 NOT NULL,
PRIMARY KEY(PROTOCOL_ID, CONSTANT_NAME)
);
A través de la sentencia CREATE TABLE se especifican las características de la tabla a crear:
- Se indica ESQUEMA.TABLA
- Cada definición de un campo sigue la estructura: NOMBRE TIPO OTRA_CONF
- Para indicar una clave primaria se puede optar por añadir la instrucción PRIMARY KEY al final del
campo o colocar una línea final PRIMARY KEY(campos).
- Se define una clave externa mediante la instrucción REFERENCES
ESQUEMA.TABLA_REFERENCIADA (CAMPO_REFERENCIADO).
5.3.4 Secuencias
Mediante las secuencias [59], Oracle puede proporcionar una lista consecutiva de números unívocos que sirve
para simplificar las tareas de programación. La primera vez que una consulta llama a una secuencia, se devuelve
un valor predeterminado. En las sucesivas consultas se obtendrá un valor incrementado según el tipo de
incremento especificado. Es decir, una secuencia (sequence) se emplea para generar valores enteros secuenciales
únicos y asignárselos a campos numéricos; siendo tremendamente útiles para las claves primarias de las tablas.
Mediante el empleo de secuencias garantizaremos que las claves primarías sean únicas y secuenciales, pudiendo
Descripción de la propuesta
72
72
identificar fácilmente el orden en el que se han introducido los registros además de asegurar la integridad de la
información.
Veamos más detalladamente como funciona una secuencia: Todos los parámetros fundamentales de una
secuencia se indican en su creación mediante la sentencia CREATE SEQUENCE.
CREATE SEQUENCE NOMBRESECUENCIA
START WITH VALORENTERO
INCREMENT BY VALORENTERO
MAXVALUE VALORENTERO
MINVALUE VALORENTERO
CYCLE | NOCYCLE
CACHE VALORCACHE;
Sobre la instrucción podemos comentar:
- La cláusula START WITH indica el valor desde el cual comenzará la generación de números
secuenciales. Si no se especifica, se inicia con el valor que indique MINVALUE.
- La cláusula INCREMENT BY especifica el incremento, es decir, la diferencia entre los números de la
secuencia; debe ser un valor numérico entero positivo o negativo diferente de 0. Si no se indica, por
defecto es 1.
- MAXVALUE define el valor máximo para la secuencia. Si se omite, por defecto es
99999999999999999999999999.
- MINVALUE establece el valor mínimo de la secuencia. Si se omite será 1.
- La cláusula CYCLE indica que, cuando la secuencia llegue a máximo valor (valor de MAXVALUE)
se reinicie, comenzando con el mínimo valor (MINVALUE) nuevamente, es decir, la secuencia vuelve
a utilizar los números. Si se omite, por defecto la secuencia se crea NOCYCLE.
- Con la condición CACHE se define el número de valores que se almacenarán en la caché de Oracle,
aumentando la velocidad en la consulta de la secuencia (reduciendo las lecturas y escrituras que realiza
Oracle en disco). Si se produce algún fallo que provoque el cierre o reinicio de la base de datos los
números cacheados se perderán, de tal forma que en el próximo arranque la secuencia comenzará en el
número siguiente al último cacheado.
Si no se especifica ninguna cláusula, excepto el nombre de la secuencia, por defecto, comenzará en 1, se
incrementará en 1, el mínimo valor será 1, el máximo será 999999999999999999999999999, NOCYCLE y no
estará cacheada.
Para emplear las secuencias se emplean dos instrucciones especiales:
NOMBRESECUENCIA.NEXTVAL;
NOMBRESECUENCIA.CURRVAL;
Con la primera instrucción obtenemos el próximo valor de la secuencia, mientras que la segunda se emplea para
recuperar el valor actual de la misma.
Por tanto, aunque las secuencias sean un objeto de base de datos totalmente independientes de las claves
primarias, en el presente proyecto se utilizarán solamente para facilitar el trabajo con las claves primarias simples
de las tablas.
Veamos un ejemplo de la definición habitual de una secuencia en nuestro modelo de datos:
CREATE SEQUENCE HEALTH.PATIENT_ID_SEQ
73
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
73
START WITH 1
INCREMENT BY 1
MAXVALUE 999999999999999999999999999
MINVALUE 1
NOCYCLE
CACHE 20;
5.3.5 Índices
El índice [60] de una base de datos es una estructura de datos que mejora la velocidad de las operaciones,
permitiendo un rápido acceso a los registros de una tabla. Al aumentar drásticamente la velocidad de acceso, se
suelen usar sobre aquellos campos sobre los cuales se vayan a realizar búsquedas frecuentes.
El índice tiene un funcionamiento similar al índice de un libro, guardando parejas de elementos: el elemento que
se desea indexar y su posición en la base de datos. Para buscar un elemento que esté indexado, sólo hay que
buscar en el índice de dicho elemento para, una vez encontrado, devolver el registro que se encuentre en la
posición marcada por el índice. Los índices son construidos sobre árboles, funciones de cálculo u otros métodos.
Los índices pueden ser creados usando una o más columnas, preparando la base de datos tanto para búsquedas
rápidas al azar como para ordenaciones eficientes de los registros.
El espacio en disco requerido para almacenar el índice es típicamente menor que el espacio de almacenamiento
de la tabla (puesto que los índices generalmente contienen solamente los campos clave de acuerdo con los que
la tabla será ordenada, y excluyen el resto de los detalles de la tabla), lo que da la posibilidad de almacenar en
memoria los índices de tablas que no cabrían en ella. En una base de datos relacional un índice es una copia de
parte de una tabla
Automáticamente Oracle crea índices únicos tanto para las claves primarias de las tablas como para los campos
diseñados como únicos y no nulos, pero será necesario informar de otros índices extra que se consideren
necesarios para agilizar las consultas sobre la base de datos.
Eso sí, es importante tener en cuenta que los índices producen una leve ralentización en la inserción de registros,
pues es ahí donde se informa al índice de los nuevos registros. Esto nos lleva a una fácil deducción: las tablas
maestras o paramétricas deberán contener tantos índices como sea necesario, pues apenas se realizarán
inserciones y son tablas constantemente consultadas por los programas. Por otra parte, se debe ser más cauteloso
con los índices en las tablas de datos. En general, el empleo de índices es una política altamente beneficiosa para
el rendimiento de las aplicaciones que emplean bases de datos Oracle. Incluso en tablas de datos que reciben
nuevos registros constantemente es necesario contar con los índices adecuados para que realizar consultas sobre
la misma no lastre la velocidad del software. Por ejemplo, en un centro sanitario las tablas PATIENT o
EPISODE pueden contener decenas de miles de registros pasados unos meses, por lo que realizar una búsqueda
por DNI de un paciente puede tardar segundos si no existe un índice para ese campo. Aunque dicho índice
provoque un retraso prácticamente despreciable en la inserción.
De todo lo anterior sacamos que debemos crear índices para los campos o conjuntos de cambios más
frecuentemente consultados. En el modelo de base de datos de MEDIPI se han implementado los siguientes
índices “extra”, además de los que Oracle define por defecto:
- PATIENT: SEX. Si se pretende realizar una explotación posterior de los datos será habitual intentar
obtener patrones en las constantes biomédicas en función del sexo del paciente.
- EPISODE: PATIENT_ID. Es habitual que las claves externas tengan un índice que permita agilizar las
consultas cuando se crecen información de varias tablas. En general, Oracle recomienda seguir esta
regla [61].
- EPISODE: BED_ID.
- EPISODE: START_DATE y END_DATE. Un índice común para los dos campos, ya que lo habitual
será realizar consultas en las que se informen de las condiciones para ambas columnas. Por ejemplo, se
usará para cuando sea necesario consultar los episodios activos en un momento determinado.
Descripción de la propuesta
74
74
- ALLOCATION: MEDIPI_INSTANCE_ID. Para facilitar las consultas que cada instancia de software
realizará para conocer sus dispositivos asociados.
- ALLOCATION: DEVICE_ID
- ALLOCATION: START_DATE y END_DATE. Un índice común para los dos campos, de forma
similar a los índices de la tabla EPISODE. Se supone que será habitual realizar consultas para conocer
la asignación de dispositivos en un momento concreto.
- DEVICE: DEVICE_MODEL_ID
- DEVICE_MODEL: DEVICE_TYPE_ID.
- DEVICE_MODEL: PROTOCOL_ID
- MEDIPI_INSTANCE: BED_ID
- PROTOCOL_CONSTANT_MAP: CONSTANT_ID
La sentencia que se debe emplear para crear un índice tiene una estructura sencilla, similar a la explicada para
crear claves externas.
CREATE INDEX PAT_SEX_IX ON HEALTH.PATIENT (SEX);
5.3.6 Permisos
Cuando se crea un objeto (como por ejemplo una tabla o una secuencia) se indica el esquema al que pertenece,
como en el siguiente ejemplo:
CREATE TABLE HEALTH.PATIENT (…
El nombre del objeto debe venir precedido por el esquema y un punto. Si no se especifica el esquema se creará
el objeto en el esquema del usuario con el que se está ejecutando la sentencia.
Esta política de usuarios / esquemas tiene un inconveniente: por defecto un usuario tiene permisos para modificar
/ crear objetos en su esquema, pero no para alterar o crear objetos en esquemas ajenos. Cuando el software se
arranque debe iniciar su comunicación con la base de datos, autenticándose contra esta con un usuario. En
nuestro caso usaremos el usuario DATA_ACQ, el más indicado para el software de integración con
biodispositivos. Sin embargo, este usuario no va a tener acceso a los objetos del esquema HEALTH, en el que
crearemos algunas tablas básicas. Esto provocará que, al crear cada objeto, debamos dar permisos al resto de
usuarios si necesitamos que accedan o modifiquen esta información.
Para dar permisos a uno o varios usuarios se emplea la instrucción GRANT [62], que ya se empleó en el apartado
correspondiente a la definición de los usuarios / esquemas de la base de datos. Esto se debe a que la instrucción
GRANT se puede emplear para dos fines:
- Dar permisos del sistema: anteriormente vimos que la sentencia GRANT CONNECT TO DATA_ACQ
daba permiso al usuario DATA_ASQ para conectarse a la base de datos.
- Establecer permisos sobre objetos: de manera análoga a la instrucción del punto anterior, podemos
establecer una sentencia que de determinados privilegios a un usuario o un rol.
En la línea siguiente vemos las dos sentencias que se han empleado en la construcción de nuestra base de datos
para dar los permisos adecuados al usuario DATA_ASQ.
75
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
75
GRANT SELECT ON HEALTH.PATIENT_ID_SEQ TO DATA_ACQ;
GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES ON HEALTH.PATIENT TO DATA_ACQ;
La primera es el GRANT típico de una secuencia, dando permisos de consulta, mientras que la segunda es el
GRANT que se debe ejecutar para permitir a un usuario realizar las acciones básicas sobre una tabla (consultar,
insertar, actualizar, borrar registros y crear una clave externa en un objeto que apunte a dicha tabla)
Es bastante habitual encontrarnos con una sentencia como mostradas arribas pero que den privilegios al rol
PUBLIC en vez de especificar un usuario concreto. PUBLIC es un rol especial que poseen todos los usuarios,
de tal forma que basta con darle permiso a ese rol para que todos los usuarios lo tengan. En nuestro caso se
podría haber optado por darle los permisos a este rol especial, pero se cree más correcto llevar a cabo una política
de permisos restrictiva, dando permisos cuando sea necesario y no a todos los usuarios por defecto.
Descripción de la propuesta
76
76
Sistema de intercambio de datos: Kafka
La arquitectura software de MEDIPi se sustenta en un sistema de intercambio masivo de información biomédica
entre los diferentes módulos que la componen. Para que cumpla adecuadamente su función, es necesario que el
diseño cumpla una serie de características fundamentales:
- Alta disponibilidad: debe estar siempre operativo. Una caída del mismo supone pérdida irrecuperable
de información.
- Integridad: el sistema debe ser suficientemente robusto como para evitar no solo pérdidas de
información, si no también que esta se corrompa.
- Velocidad: vamos a trabajar con grandes cantidades de información, por lo que el retraso con el que se
mueva la información puede ser un punto crítico.
- Escalabilidad: añadir más capacidad de procesamiento al sistema debe ser lo más sencillo posible,
permitiendo incluso una adaptación dinámica, quitando y poniendo elementos según sea necesario.
Basándonos en todo lo anterior se ha realizado un estudio acerca de las diferentes herramientas que cumplen con
los requisitos citados. En el citado estudio (ubicado en el capítulo dedico a las tecnologías empleadas) se ha
justificado la elección de Kafka, además de describir sus componentes y funcionamiento básico. Partiendo de
ese conocimiento, abordaremos aquí la parametrización y configuración de Kafka dentro del presente proyecto.
Figura 5-15. Arquitectura básica de Kafka
Resumiendo lo visto anteriormente en detalle, Apache Kafka es un sistema de almacenamiento
publicador/subscriptor distribuido, particionado y replicado. Estas características, añadidas a que es muy rápido
en lecturas y escrituras lo convierten en una herramienta excelente para comunicar streams de información que
se generan a gran velocidad y que deben ser gestionados por uno o varias aplicaciones. Con respecto a la
estructura, Kafka se basa en un conjunto de servidores o nodos fundamentales, denominados Broker que, junto
a un nodo especial que ejecuta el software Apache Zookeeper componen el clúster. Serán los brokers quienes
manejen los datos, manteniendo la integridad de los datos. La información se estructura en canales o Topics, por
lo que se intercambian mensajes. La arquitectura se completa con los Producers (mandan mensajes al clúster) y
los Consumer (leen mensajes del clúster).
Para parametrizar Kafka debemos crear por tanto nuestro propio clúster, debiendo diferenciar claramente entre
el componente hardware y el software. Es decir, por una parte estarán los servidores físicos y por otra los
componentes software de Kafka: por ejemplo, es posible que corran varios brokers sobre un mismo servidor.
Nosotros nos centraremos en el componente software, pues se considera que no entra dentro del alcance del
77
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
77
presente proyecto elegir los servidores sobre los que deben correr el sistema de intercambio o las aplicaciones
de la suite. Aun así, conviene recalcar que Kafka es una herramienta muy optimizada que no exige un alto coste
de procesamiento, por lo que no será necesario ningún procesador de especial capacidad. Gracias a lo anterior
podemos seguir manteniendo la coherencia con el argumentario establecido en la elección del hardware de
integración, apostando siempre por reducir el coste de instalación de MEDIPi al máximo posible.
Para la configuración partiremos de los binarios de ejecución existentes en la propia página web de Kafka [63],
modificando los ficheros de configuración adecuadamente.
5.4.1 Nodos
El punto de partida es la creación de un clúster que maneje la información biomédica. Hemos comentado que
existen dos tipos de nodos: Zookeeper y Broker.
- Zookeeper: pueden existir uno o varios nodos Zookeeper en un clúster de Kafka. Realizan las tareas de
coordinador de los diferentes brokers que compondrán el clúster llevando a cabo labores de
sincronización, configuración, registro, agrupación de nodos, elección de nodo/s líder/es, etc. Sus
opciones parametrización se modifican en un fichero zookeeper.properties. Al igual que ocurre en el
resto de elementos a comentar del presente apartado, las opciones de configuración de Zookeeper son
numerosas, por lo que solo nos centraremos en las que se han considerado necesarias para parametrizar
el clúster de MEDIPi. En el caso actual, se ha seguido la configuración básica recomendada por Apache
[64]:
Parámetro Valor Descripción
clientPort 2181 (valor por defecto) Puerto para la comunicación con los brokers
u otras instancias de ZK
dataDir /tmp/Zookeeper Directorio en el que ZK almacenará la
información temporal, logs, etc.
tickTime 2000
Tiempo en milisegundos de un tick, unidad
mínima de comunicación empleada por ZK.
Empleada para regular timeouts, keep-
alives, etc.
maxClientCnxns 0 Límite de conexiones para una misma IP
Para terminar, solo queda arrancar el nodo e indicar la ubicación del fichero de parametrización:
../bin/zookeeper-server-start.sh ../config/zookeeper.properties
- Broker: serán los nodos fundamentales del clúster, encargados de almacenar adecuadamente la
información y responder a las peticiones de los consumers que le correspondan a cada uno. Como ya se
describió con detalle el funcionamiento de los brokers de Kafka en el apartado correspondiente a las
tecnologías empleadas abordaremos aquí solo la configuración de los nodos, partiendo de los mismos
criterios expuestos para el Zookeeper y basándonos en las posibilidades de parametrización de la
versión de Kafka empleada de acuerdo a la documentación existente [65]:
Descripción de la propuesta
78
78
Parámetro Valor Descripción
broker.id 1 Clave única para la identificación de un bróker en el
clúster
listeners PLAINTEXT://:9093
Se trata de una lista separada por comas en las que
se informa de los puertos y protocolos que los
brokers ponen a disposición de producers y
consumers para el envío de información.
num.partitions 2
Número de particiones por defecto para cada topic.
Este valor influye, como ya hemos visto en la teoría
de Kafka, en la capacidad del clúster para balancear
la carga entre nodos
log.retention.hours 8
Tiempo mínimo de vida de un fichero de log antes
de ser marcado como eliminable. Se opta por un
valor poco elevado, para evitar que la gran cantidad
de ocupación movida por las constantes del
paciente pueda provocar un elevado consumo de
espacio en las máquinas donde se ubiquen los
nodos del clúster de MEDIPi
zookeeper.connect localhost:2181 IP del nodo Zookeeper al que se conectará
Al igual que ocurre con los nodos Zookeeper, iniciamos un servidor con la siguiente instrucción
../bin/kafka-server-start.sh ../config/server_0.properties
5.4.2 Topics
En la tecnología Kafka denominamos Topic a una categoría, canal u agrupación de mensajes para los que se
emplea una parametrización común. Es decir, un topic no es más que una manera de etiquetar mensajes,
agrupándolos en un mismo “canal”. Gracias a esta estructuración, un consumidor puede suscribirse a un topic y
acceder solo a los mensajes o paquetes de información ubicados bajo una determinada etiqueta. Sin embargo,
esta separación no tiene efectos solamente en el ámbito lógico o funcional, sino también en el técnico, pues cada
topic puede parametrizarse de manera independiente.
Abordemos en primer lugar los distintos topics que manejaremos en el presente proyecto:
Topic Descripción
LFConstantInfoTopic Constantes discretas de los diferentes usuarios de cualquier instancia del
software DDA.
HFConstantInfoTopic Constantes continuas de los pacientes recuperadas por DDA.
Esta división es fundamental, pues separamos dos tipos de datos clínicos que tienen características diferentes.
El gran caudal de datos que generan las señales “continuas” puede provocar un tratamiento diferenciado de las
señales discretas, tal y como hemos comentado ya en varias ocasiones. En nuestro caso, por ejemplo, se ha
implementado un modelo de base de datos enfocado solo a las variables biomédicas del primer tipo, pues se ha
mostrado como escasamente relevante en un primer nivel clínico las formas de ondas pasadas de los pacientes.
Sin embargo, habrá muchos otros ámbitos, entre ellos el diagnóstico médico en situaciones específicas o posibles
79
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
79
estudios y análisis complejos en los que serán de gran interés. Por tanto, al establecer esta separación permitimos
que las aplicaciones de MEDIPi se nutran de uno, otro o ambos según sea necesario para el desarrollo de su
labor concreta. A lo anterior debemos sumar las ventajas en el tratamiento de datos que esta separación conlleva:
la cantidad de información que se mueve en el topic dedicado a las “formas de onda”, “variables continuas” o a
“tiempo real” (términos poco precisos como mínimo) es muy superior a la de las constantes discretas, por lo que
si ambos tipos de información convivieran en un mismo canal la explotación de las constantes discretas debería
pasar siempre por un proceso de filtrado, ralentizando el proceso que se pretenda llevar a cabo.
Además de definir funcionalmente qué tipo de información viaja agrupada bajo cada Topic, se ha de detallar la
parametrización básica de cada uno. Se ha comentado al principio del presente apartado que cada topic permite
aplicar una configuración específica y en este caso la existencia de dos tipos de información con algunas
características importantes diferenciadoras nos obliga a establecer algunos matices entre sus parametrizaciones.
En este momento describiremos solamente los parámetros a abordar, dejando los valores concretos para
apartados ulteriores.
Parámetro Descripción
partitions
El número de particiones especifica en cuántas partes de dividirá un topic. Es
importante entender que cada partición se ubicará completamente en un solo servidor,
de forma que la cantidad de particiones impactará en la capacidad de paralelismo. Por
tanto, podemos dividir un topic en más particiones que nodos existentes, pero no podrá
repartirse la carga en más nodos que números de particiones.
replication-factor
Controla cuantos servidores (nodos brokers) replicarán cada mensaje escrito. Salvo
casos muy básicos es imprescindible que haya al menos 2 servidores, no solo para que
exista redundancia y tolerancia a fallos, si no para poder manejar transparentemente
los servidores implicados. Es decir, si solo tenemos un nodo cada vez que se necesite
realizar una labor de mantenimiento en este se deberá parar, aunque sea
momentáneamente todo flujo de datos, mientras que la existencia de varios nodos
permite ir moviendo uno a uno los servidores de un nodo a otro de forma totalmente
transparente y sin interrumpir la comunicación con consumers y producers.
La configuración puede volverse mucho más compleja, permitiendo modificar un amplio abanico de parámetros,
tal y como se especifican en la documentación oficial [66].
Antes de finalizar comentemos las instrucciones empleadas para el manejo de los topics:
../bin/kafka-topics.sh --create --Zookeeper 127.0.0.1:2181 --replication-
factor 2 --partitions 1 --topic LFConstantInfoTopic
../bin/kafka-topics.sh --alter --Zookeeper 127.0.0.1:2181 --partitions 2
--topic LFConstantInfoTopic
../bin/kafka-topics.sh --delete --Zookeeper 127.0.0.1:2181 --topic
LFConstantInfoTopic
Como observamos, aquí no existe ningún fichero de configuración como ocurría en los Zookeepers o en los
Brokers, si no que los distintos parámetros y sus valores se comunican en la creación / edición del topic
pasándose como argumentos de entrada al binario de Kafka encargado de tales labores. Se observa que la
fórmula empleada es la habitual en el trabajo con cualquier programa mediante línea de comandos: se especifica
el parámetro precedido por un doble guion y, a continuación, el valor que se quiere indicar.
Descripción de la propuesta
80
80
5.4.3 DTOs
Ya prácticamente tenemos definida la totalidad de elementos básicos que compondrán nuestro clúster, a la espera
de tratar un poco más adelante la configuración de producers y consumers. Solo nos queda comentar qué
información enviaremos y cuál será el método que emplearemos para codificarla.
Con respecto al primer punto, se han definido dos objetos de transferencia de datos (DTO), uno para cada topic,
ya que no se intercambia exactamente la misma información por ambos canales.
LFConstantInfo HFConstantInfo
Tipo Atributo Tipo Atributo
Long constantId Long constantId
Long episodeId Long episodeId
Date constantDate Date startDate
String value String[] value
Float period
Boolean manual Boolean manual
Como se observa, hay pocas diferencias entre ambos objetos, más allá del campo periodo para indicar el tiempo
en milisegundos que separan a cada muestra del array de valores. Se ha optado por emplear en el intercambio
de información un objeto específico, pudiendo haberse empleado una instancia de CONSTANT_INFO para el
topic de información a baja frecuencia y una versión adaptada del mismo para el HFConstantInfoTopic. La
opción tomada permite enviar solo la cantidad de información realmente necesaria, de forma que el resto de
datos que deben conocerse para poder formar un registro de ConstantInfo pueden deducirse u obtenerse
mediante una consulta a base de datos. Con esta medida se reduce la cantidad de información que se intercambia
por el clúster, permite a un nodo almacenar más mensajes con el mismo espacio y, sobretodo, permite a
cualquiera acceder a la información sin necesidad de conocer el modelo de datos de MEDIPi. Este último punto
es muy interesante en nuestra búsqueda de gran modularidad: un nuevo módulo podría, aunque sería poco
probable, no necesitar conectarse bajo ninguna circunstancia a la base de datos, por lo que le bastaría con la
información consumida de Kafka para trabajar. Por otro lado, si enviáramos un objeto correspondiente
totalmente a un registro de CONSTANT_INFO facilitaríamos la tarea del módulo Saver, que solo tendría que
realizar un insert o update. Tomándose en consideración ambos puntos de vista se ha optado por optimizar la
información transmitida, aunque eso implique que Saver deba realizar algunas acciones más antes de almacenar
una constante en base de datos. Es preferible apostar por un modelo flexible que tomar decisiones que solo
beneficien a una aplicación de la suite.
Una vez conocido el DTO es necesario analizar qué alternativas existen a la hora de transmitirlo o codificarlo
para su emisión por el clúster. Gran parte de esta labor se ha realizado cuando se ha comparado el formato de
texto JSON con otras posibilidades en el capítulo destinado a las tecnologías empleadas en el proyecto, pero aun
así conviene comentar aquí algunos datos importantes:
{"constantName":" Frecuencia cardiaca
","constantId":1,"episodeId":1,"constantDate":"May 26, 2016 1:27:24
PM","value":"90.89457584984491","manual":false}
El párrafo anterior supone una muestra concreta de un valor para la constante vital de un paciente de prueba. Si
81
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
81
observamos con atención es fácil deducir que se trata de una representación determinada del DTO
LFConstantInfo definido unas líneas más arriba. El objeto Java definido con los atributos ya comentados se
transforma en un texto que sigue el esquema
“nombreAtributoUno”:”valorUno”,”nombreAtributoDos”:”ValorDos”, de acuerdo al formato JSON. Gracias a
él, o mejor dicho a una determinada librería que implementa ese formato en Java) podemos transmitir un texto
plano que apenas llegará a pesar 1KB, una cantidad irrisoria para lo que un clúster de nodos Kafka es capaz de
manejar.
5.4.4 Modelo
Una vez detallados los tipos de nodos, su funcionalidad y su parametrización básica ya podemos intuir algunas
características básicas del clúster de MEDIPi. A lo deducido anteriormente debemos añadir una reflexión
fundamental: la dimensión que deberá tener el clúster, algo que nos indicará el número de nodos y servidores de
un tipo u otro que deberemos manejar. Aquí nos encontramos ante un evidente dilema: no podemos seguir
realizando un estudio “general” como hasta ahora, en el que indicábamos las características de la solución
diseñada ignorando casi por completo el número de usuarios implicados, concurrencia, cantidad de datos a
manejar, etc. En resumen, por cada implantación posible de la solución habrá que realizar un pequeño estudio
acerca de la cantidad de información que se va a procesar, importancia de los datos, necesidad de redundancia
y control de errores, etc.; no pudiendo establecerse un modelo genérico para todos los casos. Aun así, con objeto
de clarificar cómo debe ser un clúster de MEDIPi se aborda un modelo “mínimo”, de acuerdo a unas nociones
concretas adquiridas tanto por la práctica como por el análisis inicial de los actores implicados.
Como se comentó adecuadamente en el apartado dedicado a la teoría de la tecnología empleada, un clúster Kafka
mantiene un log particionado por cada topic, de forma que estas1 particiones son distribuidas a través de los
diferentes brokers. Número de particiones y parametrización de los mimos están, como ya hemos visto en el
punto anterior, íntimamente relacionados con la capacidad de paralelismo del clúster.
Figura 5-16. Modelo del clúster
Descripción de la propuesta
82
82
Con la figura superior se pretende escenificar la arquitectura del modelo mínimo propuesto en el presente
proyecto:
- Servidores: 1 nodo Zookeeper y 2 nodos brokers, servidores para el balance de carga
- Topics: los dos topics indicados, ambos con factor de replicación 2 (existen 2 servidores). Para el topic
de las constantes discretas se ha optado por dos particiones, mientras que se amplía a una tercera en el
caso del topic para las constantes biomédicas “continuas” de los pacientes.
En el esquema se pretende escenificar el control que los brokers realizan de las particiones mediante un resaltado
en negrita. Este aspecto no pertenece al ámbito del usuario, si no que forma parte de las tareas de balanceo de la
tecnología Kafka elegir qué nodo maneja qué partición partiendo de la configuración proporcionada y el
algoritmo del protocolo. Para conocer cuál es el balanceo real de un topic podemos ejecutar la siguiente
instrucción de Kafka:
../bin/kafka-topics.sh --describe --Zookeeper localhost:2181 --topic
LFConstantInfoTopic
Topic:LFConstantInfoTopic PartitionCount: 2 ReplicationFactor:2 Configs:
Topic: LFConstantInfoTopic Partition: 0 Leader: 1 Replicas: 1,0 Isr:
1,0
Topic: LFConstantInfoTopic Partition: 1 Leader: 0 Replicas: 0,1 Isr:
0,1
Donde observamos, para cada partición del topic:
- Quién es el “líder” de cada partición, responsable de las lecturas / escrituras de dicha partición.
- Lista de nodos que replicarán la partición, independientemente de que sean el líder o no.
- Nodos actualmente disponibles de la lista de nodos replicas.
5.4.5 Producers
Sabemos que un Producer es cada uno de los “escritores” de un Topic, es decir, son los actores que representan
la entrada de información en un topic determinado. Si volvemos al esquema del modelo de arquitectura que se
ha definido para la suite software de MEDIPi observamos que solo se ha definido un elemento que realice este
rol: el módulo de adquisición de datos de biodispositivos DDA.
A partir de aquí vamos a comenzar una nueva parte del sistema de intercambio de información, saliéndonos del
clúster en sí mismo, por lo que dejamos de trabajar con la línea de comandos arrancando servidores, indicando
ficheros de configuración, pasando parámetros al ejecutable, etc. Como producers (y también consumers) se
manejarán desde el código de los módulos emplearemos la implementación Java de Kafka para tales labores.
En este apartado vamos a detallar el proceso que seguimos en el código para la correcta creación y configuración
de un producer, así como las instrucciones empleadas en el envío de constantes biomédicas al clúster. Más
adelante, cuando detallemos el software de los módulos implicados, veremos cómo determinados aspectos de
diseño y programación del código se relacionan con el producer (clase Java que lo llama, estructura dentro de la
aplicación, ubicación de los valores, etc.)
Siguiendo la misma estructura que en puntos anteriores, comenzamos abordando las distintas opciones de
parametrización posibles y justificando los valores empleados:
83
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
83
Parámetro Valor Descripción
bootstrap.servers Server0Ip:port,
Server1Ip:port,…
Lista de ips:puertos empleada para la
comunicación inicial con el clúster Kafka.
Mediante estos datos se iniciará el primer
intercambio de información con algunos de
los brokers indicados. Una vez realizada la
comunicación inicial se accede a los nodos
reales del clúster (que pueden modificarse
dinámicamente) y esta información deja de
usarse.
key.serializer org.apache.kafka.common.
serialization.LongSerializer
Clase serializadora para la clave. En
nuestro caso la clave será un numérico de
tipo long.
value.serializer org.apache.kafka.common.
serialization.StringSerializer
Clase serializadora para el valor. En nuestro
caso intercambiamos texto plano.
acks 1
Número de acks que necesita recibir el
producer antes de enviar los datos al
clúster:
0: no se espera ningún ack, el mensaje se
envía inmediatamente por el socket y se
considera enviado
1: se espera a que al menos el nodo bróker
líder la partición implicada devuelva ack
All: se espera a que todos los nodos que
trabajen con la partición (líderes y
seguidores) envíen su consentimiento.
buffer.memory 33554432
Número de bytes que el producer
mantendrá en su buffer a la espera de ser
enviados al servidor.
retries 1 Número de reintentos
linger.ms 1000
El producer agrupará en un batch los
mensajes o records antes de enviarlos al
servidor. Este parámetro especifica el
tiempo a esperar entre cada envío,
agrupando durante ese periodo todos los
records recibidos.
batch.size 102400
Tamaño máximo del batch. Se producirá un
envío siempre que se alcance este valor
máximo para el batch, independientemente
del retraso indicado en el linger.ms. Se
combina con el parámetro anterior para
definir una política que oscile entre valores
que permitan una escritura/lectura casi a
tiempo real y la optimización del número
de peticiones/respuestas intercambiadas
con los servidores.
Descripción de la propuesta
84
84
metadata.fetch.timeout.ms 500 Timeout de la operación realizada en el
intercambio del primer mensaje al topic
timeout.ms 1000 Timeout del ack del servidor
El código que debe tratar el arranque de un Producer de la tecnología Kafka comenzará siempre por la gestión
de la parametrización; en nuestro caso, informando al producer de la configuración arriba detallada:
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9093, localhost:9094");
La fórmula diseñada por Kafka para dotar de la adecuada configuración a sus Producers Java consiste en la
instanciación de un objeto Properties (java.util) al que seteamos, como si de un simple mapa se tratase, una
pareja nombre -valor por cada parámetro que deseamos indicar. De esta manera podemos arrancar varios
Producers con igual configuración a partir del mismo objeto, tal y como observamos a continuación. Además
de informar a la nueva instancia de Producer creado de su parametrización indicamos los tipos de clave
(primer tipo) y valor (segundo tipo) que deberá manejar.
Producer<Long, String> producer = new KafkaProducer<Long, String>(props);
Gson gson = new Gson();
String msg = gson.toJson(constantInfo);
ProducerRecord<Long, String> producerRecord = new ProducerRecord<Long,
String>(AppConstants.KAFKA_TOPIC_CONSTANTINFO, msg);
producer.send(producerRecord);
En este segundo bloque se realiza la conversión al formato de intercambio. La librería Gson empleada nos
permite crear rápidamente un texto que represente, de acuerdo al estándar JSON, un objeto Java recibido. En
este caso le pasamos una instancia de tipo ConstantInfo (la cual no es más que el mapeo de la entidad
correspondiente de Base de Datos) y nos construye un String con los atributos del objeto y sus valores. Gson
también puede ser configurada, pues puede no existir una representación única de un valor baja determinadas
circunstancias. Por ejemplo, podemos tomar la decisión de representar los valores sin informar (null) como una
cadena de texto ‘null’, o una cadena vacía.
Una vez terminada la conversión, creamos un “mensaje”, lo que en Kafka se conoce como un Record o
ProducerRecord, indicando simplemente topic, clave y mensaje. Parémonos un momento a analizar las
posibilidades de instanciar una clase ProducerRecord. El constructor de la clase ProducerRecord puede recibir
los siguientes parámetros:
A) Topic - Value B) Topic – Partition_Id - Value
C) Topic – Key - Value D) Topic – Partition_Id – Key - Value
Topic y value (el mensaje a transmitir) son los únicos parámetros constantes. El resto de posibilidades no son
más que la combinación de la posibilidad de incluir la clave del mensaje y un identificador de la partición a la
que queremos enviar el mensaje. En el capítulo de teoría de Kafka del presente proyecto se ha documentado la
importancia de las claves y las particiones. Como bien se ha explicado, debatir la necesidad de informar de una
clave anexa al mensaje implica necesariamente debatir qué modo queremos emplear para el envío de
información. Para ello realizaremos una doble clasificación:
85
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
85
Clasificación 1: Modos
- Modo clásico: los brokers manejarán particiones compuestas por los distintos records. Formarán una
cola de mensajes.
- Modo compacto: se emplea una determinada configuración de los brokers implicados que nos permite
almacenar solo el último valor asociado a una clave. Podría entenderse como un mapa clave-valor a
gran escala.
Clasificación 2: Orden
- Sin orden: Kafka no realizará acciones especiales para intentar mantener el orden correcto en los
mensajes de un topic. El almacenamiento en el log es FIFO, los datos más antiguos serán los primeros
en ser eliminados del log. Sin embargo, la distribución de records entre particiones, así como otros
factores menores pueden provocar un desordenamiento de la información. Solo la información
gestionada por una misma partición se mantiene ordenada.
- Con orden: todos los mensajes pertenecientes a un determinado grupo o categoría se manejarán en orden
de llegada. Es importante entender que no se puede hablar de un estricto orden temporal: somos
conscientes de la existencia de los ya comentados “batch” en los producers, que agrupan records antes
de ser enviados al bróker. Por tanto, pueden producirse alteraciones del orde1n en situaciones de
múltiples producers.
De lo anterior se deduce que enviar mensajes controlando la partición destino (opciones B y D) nos permite
garantizar en gran medida el ordenamiento temporal de los records en el log. De igual forma, los mensajes que
comparten clave son gestionados por la misma partición, ya sea mediante el modo clásico o eliminando las
duplicidades para crear un log compacto. Con todo esto sobre la mesa nos preguntamos qué categorías deben
aplicar en nuestra situación: está claro que no necesitamos emular ningún mapa, la información enviada por
alguno de los topics deberá ser leída en orden de entrada, sin ningún tratamiento específico. Además, nos interesa
mantener un orden temporal, pero no es necesario que este sea totalmente estricto, pues las aplicaciones que leen
del clúster directamente emplean la fecha (presente en el value) para realizar el tratamiento que deseen a los
datos recibidos. Este asunto puede provocar algún malentendido: Kafka es una aplicación diseñada para manejar
gran cantidad de datos a tiempo real, por lo que siempre pone el foco en mantener un correcto ordenamiento
temporal de los mensajes intercambiados. El uso de técnicas o parámetros que intentan reducir al mínimo la
posibilidad de desórdenes es interesante pero desproporcionada para el proyecto actual, en tanto que implican
limitar otras capacidades de la tecnología, como por ejemplo la disminución del balanceo entre brokers que se
produce al forzar manualmente una misma partición. Por tanto, lo más conveniente es optar por el constructor
A, que emparejamos con el modo clásico sin orden temporal estricto.
5.4.6 Consumers
Un consumer es un lector de la información “almacenada” en el clúster. En el caso de la suite software de
MEDIPi observamos que tenemos dos aplicaciones que deberán ser consumidoras del clúster: Saver y Chart.
Una vez más trabajaremos aquí con la implementación java de Kafka.
Al tratar con dos consumers claramente diferenciados vamos a abordar la parametrización para cada uno de
ellos, aunque la mayor parte de la configuración sea común.
Parámetro Saver Chart Descripción
group.id
saverGroup
(Se producirá
balanceo de
mensajes entre
[No informado]
(Cada ejecución
de Chart recibirá
los mismos datos)
Identificador del grupo de consumers. La
pertenencia a un grupo de consumers afecta
a la recepción de datos.
Un record solo se envía a un consumer de
Descripción de la propuesta
86
86
las instancias de
saver)
un grupo, permitiendo de esta manera
balancear la carga entre los consumers.
bootstrap.servers Server0Ip:port,Server1Ip:port,…
Lista de ips:puertos empleada para la
comunicación inicial con el clúster Kafka.
Mediante estos datos se iniciará el primer
intercambio de información con algunos de
los brokers indicados. Una vez realizada la
comunicación inicial se accede a los nodos
reales del clúster (que pueden modificarse
dinámicamente) y esta información deja de
usarse.
key.deserializer org.apache.kafka.common.
serialization.LongDeserializer
Clase deserializadora para la clave. En
nuestro caso la clave será un numérico de
tipo long.
value.deserializer org.apache.kafka.common.
serialization.StringDeserializer
Clase deserializadora para el valor. En
nuestro caso intercambiamos texto plano.
auto.offset.reset earliest latest
Política a emplear en la inicialización del
offset en la primera petición o cuando haga
referencia a un valor incorrecto:
- Earliest: primer mensaje del log
- Latest: último record en el log.
Módulo LFConstantInfoTopic RealTimeLFConstantInfoTopic
Saver SÍ NO
Chart SÍ SÍ
La configuración de los dos consumers de Chart (uno para cada topic) es similar.
La parametrización común no requiere más detalles de los ya comentados, pero si es conveniente analizar con
mayor finura las decisiones detrás de las diferencias de configuración entre nuestros dos módulos, pues radican
en las distintas funcionalidades de los módulos y nos permiten comprender algunos aspectos más de Kafka.
Como ya sabemos de sobra, Saver es el módulo encargado de consumir del clúster los datos biomédicos para,
tras un filtrado básico, almacenarnos en una base de datos. Por su parte, Chart recibe los valores actuales de
parámetros vitales de un paciente y nos muestra una gráfica a tiempo real de los mismos. Las características de
ambos sistemas son diferentes, pero podemos simplificar sus diferencias en una sola frase: Saver necesita todos
las constantes médicas, presentes y pasadas, para poder almacenarlas adecuadamente; mientras que Chart solo
empleará las últimas. Esta es la idea que está detrás de la heterogénea configuración.
Para Saver emplearemos el group.id, una técnica de Kafka que permite balancear los records que serán
procesados entre los distintos consumers de un grupo. De esta manera, podemos arrancar varias instancias de
Saver en distintas máquinas que se encarguen, en paralelo, del almacenaje en base de datos de la formación.
Además, no queremos dejar ningún dato sin procesar, por lo que cuando se arranque una instancia de Saver se
buscará el primer mensaje del log, procesando el resto a partir de él. Se debe tener cuidado con esta
configuración, si el log abarca mucha información se reprocesarán una gran cantidad de mensajes. En nuestro
caso hemos puesto un periodo de “backup” o de retención en los logs bastante corto y contamos con que
podemos arrancar varias instancias en paralelo que agilicen el procesado de todos los datos desde el primero.
87
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
87
Además, estamos hablando del topic ConstantInfo, el que menos volumen de bytes mueve. Eso sí, si alguna de
las condiciones comentadas en este párrafo cambiara debería replantearse esta parametrización.
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9093, localhost:9094");
...
KafkaConsumer<Long, String> constantInfoConsumer = new
KafkaConsumer<>(props);
constantInfoConsumer.subscribe(Arrays.asList(AppConstants.KAFKA_TOPIC_CONSTAN
TINFO));
ConsumerRecords<Long, String> records =
constantInfoConsumer.poll(SaverConstants.KAFKA_POLL_TS);
for (ConsumerRecord<Long, String> record : records) {
Gson gson = new Gson();
DataWriterManager.writeConstantInfoToAll(gson.fromJson(record.value(),
ConstantInfo.class));
}
En las líneas superiores tenemos las instrucciones que se emplean, grosso modo, en ambos módulos para
instanciar un consumer a través de la API Java de Kafka. Al igual que ocurría con los producers, comenzamos
indicando los parámetros y sus valores, e instanciando un KafkaConsumer y pasándolo el objeto Properties que
contiene la configuración. Tras suscribirse al topic del que deseamos recibir mensajes se realiza un poll. Con un
poll estamos realizando una petición al clúster para recibir un array de records, indicando como parámetro de la
operación el tiempo que se debe esperar mientras no hay datos disponibles. Si el clúster dispone de un nuevo
record antes de que se cumpla ese plazo se termina la acción devolviéndoselo; mientras que si se cumple el
timeout sin tener ningún mensaje disponible se termine la espera devolviendo un array vacío de records. En
nuestro caso lo hemos establecido en 1000 milisegundos. Ya podemos recorrer el array de records recibidos
(ConsumerRecords) y traducir el mensaje enviado mediante JSON de cada ConsumerRecord a un objeto Java
del tipo que corresponda según el topic del que proviene.
Set<TopicPartition> topics = constantInfoConsumer.assignment();
topics.forEach(partition -> constantInfoConsumer. seekToBeginning (partition, record.offset()));
Las líneas de código superiores no se corresponden directamente con ninguna parte de los desarrollos, pero nos
ayudan a explicar algunos conceptos con los que hemos trabajado. Así vemos como podemos recuperar las
particiones de un topic mediante la primera instrucción y trabajar con ellas. Podemos indicar mediante el método
seek el offset a partir del cual queremos leer. En el parámetro auto.offset.reset indicamos por donde comenzar a
leer tras arrancar un consumer, mientras que estas instrucciones permiten muchas más opciones, pues podemos
invocar un seek a un punto determinado del log en varias partes del código. Por ejemplo, podríamos tener interés
en reprocesar determinados mensajes bajo algunas circunstancias de manera dinámica gracias a estas
instrucciones.
Con toda esta información ya conocemos qué sistema de intercambio de información vamos a implementar, su
modelo y arquitectura concreta, así como el conjunto de instrucciones que deberemos emplear para iniciar y
configurar de acuerdo a nuestras necesidades cada uno de los elementos que lo componen. Con esta información
es el momento de abordar el resto de elementos software del presente proyecto.
Descripción de la propuesta
88
88
Componentes software
Nos embarcamos ahora en la parte central del proyecto. A lo largo del actual capítulo ya hemos comentado que
la parte software del proyecto estará compuesta por tres aplicaciones independientes enfocadas a tareas distintas.
Nombre del módulo Icono Descripción
Device Data
Acquisition
DDA
Componente software encargado de establecer la
comunicación con los biodispositivos y recuperar la
información biomédica.
SAVER
Módulo que persistirá la información recopilada por
DDA en base de datos
CHART
Aplicación de visualización de las constantes
biomédicas de los pacientes en tiempo real.
5.5.1 Estructura del software del proyecto
Las tres aplicaciones se van a comportar como módulos independientes, de forma que cada uno puede ejecutarse
en una máquina completa, aunque habrá muchos elementos en común. Por tanto, es lógico pensar que tendremos
tres proyectos diferentes que se sustenten en un cuarto proyecto común que aglutine las utilidades o
dependencias empleadas por todos los módulos. Además, en el capítulo anterior hemos realizado una
comparativa entre las distintas opciones existentes para manejar las comunicaciones con la base de datos,
eligiendo finalmente emplear JPA (Java Persistence Api) con Hibernate como implementación. Ya que el
estándar JPA implica definir clases java que mapeen las tablas del modelo de base de datos parece evidente que
todas las clases formen parte de ese “proyecto” común que los tres módulos emplearán.
Durante el apartado dedicado a la base de datos se ha establecido un modelo de datos compuesto por dos
esquemas: uno específico de la adquisición de datos DATA_ACQ y otro genérico de cualquier software del
ámbito sanitario denominado HEALTH. Durante la explicación del diseño se ha hecho especial hincapié en que
el esquema HEALTH sea independientemente, de forma que si se construye un módulo que no trabaje con la
información almacenada en las tablas del esquema de adquisición de datos no sea necesario, en este caso,
importar las clases java que construyan su mapeo de datos. Por tanto, sería ahora incoherente no crear dos
librerías diferenciadas. En esta línea vamos a ir un paso más allá, llevando a una tercera librería toda la lógica
común de nuestro uso de JPA (clases abstractas, métodos útiles empleados por todas las aplicaciones que quieren
acceder a base de datos, patrones de acceso que deberán emplearse, etc.). Conforme avancemos y vayamos
tratando el contenido de cada una de estas librerías se irá comentando con mayor detalle porqué es más correcta,
funcional y técnicamente, la estructura escogida frente a otras.
89
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
89
Figura 5-17. Estructura de Proyectos DOM (Document Object Model) (Base de datos)
En el párrafo anterior hemos tratado las librerías que van a proporcionar acceso a la base de datos a todos los
módulos o aplicaciones que así lo deseen. Parece lógico realizar una tarea similar con el acceso al sistema de
intercambio de información, al clúster Kafka. Por tanto, contaremos con otra librería que agrupe la lógica común
de acceso a los consumers y producers de Kafka empleados, el modelo de datos de los objetos empleados, las
librerías Java específicas para tales labores, etc. Las aplicaciones que deseen trabajar con Kafka (actualmente
las tres diseñadas) deberán importar esta librería. Gracias a esto se evita duplicar código: no es necesario que en
2 aplicaciones se haga la misma acción (instanciar un consumer de Kafka, subscribirse al topic, arrancar un hilo
para leer datos, etc.) si no que toda esta información está centralizada en una única librería evitando duplicidades.
Sin embargo, es fácil imaginar un módulo de MEDIPi que no necesite acceso al clúster. Por ejemplo, se puede
crear una aplicación de gestión de la historia clínica de un paciente que tenga más que suficiente con la
información biomédica almacenada en la base de datos. En ese caso no será necesario importar esta librería,
evitando cargar con clases inútiles.
Figura 5-18. Estructura de Proyectos Cluster (Kafka)
A pesar de todo lo descrito hasta el momento, todavía es posible encontrarnos con elementos duplicados y lógica
repetida entre las tres aplicaciones descritas (DDA, Saver y Chart). Se trata de la gestión de Logs, properties,
constantes comunes, métodos Java implementados que son útiles para el tratamiento de la información
biomédica en más de un ámbito, etc. Para evitar esto, concluimos la modularización de la suite definiendo una
nueva librería que agrupa lógica común para varias de las aplicaciones o módulos de MEDIPi.
Figura 5-19. Estructura de Proyectos App (Elementos comunes de aplicaciones MEDIPi)
Una vez conocidos los tres grupos funcionales en los que se han divido las librerías diseñadas para la suite
MEDIPi es el momento de plantear como será la relación entre los tres módulos (DDA, SAVER y CHART) y
cada una de ellas.
Descripción de la propuesta
90
90
Figura 5-20. Estructura de Proyectos MEDIPi. Representación gráfica.
Con el objetivo de clarificar la arquitectura software diseñada se han empleado un conjunto de normas y códigos
de colores que se procede a explicar a continuación:
- Los rectángulos con bordes discontínuos representan elementos que son exclusivamente proyectos
Maven; es decir, indican que un determinado proyecto actua sólamente como padre de un conjunto de
hijos, sin tener código Java asociado. Por tanto, las líneas que van de un proyecto de este tipo a un
componente buscan indicar una relación padre-hijo.
- Los rectángulos de línea contínua representan proyectos Maven y Java. Serán hijos de proyectos padres
exclusivamente Maven. Además, se representarán las depenencias con otros proyectos de la suite
mediante una línea contínua.
- Las librerías MEDIPi-DOM se representan en naranja. Serán todas aquellas que contengan clases,
métodos u objetos relacionados con el acceso a la base de datos.
- Las librerías MEDIPi-Cluster se representan en verde. Bajo esta categoría clasificamos toda librería que
agrupe funcionalidad relacionada con el acceso y gestión del Cluster Kafka.
91
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
91
- Las librerías MEDIPi-App se representa en azul. Todo proyecto que contenga bien un módulo de
MEDIPi, bien una librería de utilidades para las aplicaciones se incluirá en este grupo.
- Toda aplicación o librería de la suite se sustenta en una serie de convenciones o elementos básicos. Para
el acceso a base de datos estos se encuentran en core_dom, mientras que para la comunicación con el
clúster se ubican en core_cluster. Por tanto, las librerías cuya denominación empieza por core- se
consideran fundamentales: todo proyecto o librería de una categoría se debe sustentar sobre la clase
core correspondiente de su sector.
No conviene olvidar la clasificación realizada, pues no solo se trata de una categorización funcional, sino que
también tiene implicaciones técnicas, tal y como veremos un poco más adelante.
Para poder trabajar con tantas librerías y proyectos relacionados entre sí se emplea la herramienta de Apache
conocida como Maven, adecuadamente documentada en secciones previas de esta memoria. La especialidad de
maven es la gestión de dependencias, un auténtico quebradero de cabeza en una suite de productos como la aquí
diseñada Es evidente que no solo debemos lidiar con que muchos proyectos compartan dependencias (sobre
todo los que pertenecen a una misma categoría) sino también con la existencia de dependencias internas dentro
de la suite. A todo lo anterior Apache suma varias utilidades ya comentadas: gestión de javadoc, compilación
del proyecto en el jar correspondiente, filtrado de recursos, etc. Por tanto, conforme se avance en la descripción
de los distintos proyectos, junto al detalle del código empleado se irá comentando la configuración de Maven
escogida.
Figura 5-21. Estructura de Proyectos MEDIPi. Representación en carpetas
Descripción de la propuesta
92
92
5.5.2 MEDIPI Suite
En la imagen con la que se cerraba el punto anterior se puede observar la estructura de carpetas del código actual
de la suite MEDIPi. Los diferentes proyectos se agrupan bajo un directorio con el nombre de la categoría
correspondiente, incluyendo un archivo de Maven pom.xml para gestionar la parametrización común. En el
primer nivel se observa que, además de los directorios de las distintas categorías, existe un fichero pom.xml. Su
contenido es el siguiente.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>medipi</groupId>
<artifactId>medipi-suite</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>medipi-dom</module>
<module>medipi-Cluster</module>
<module>medipi-app</module>
</modules>
<properties>
<JAVA_1_8_HOME>C:\Program Files\Java\jdk1.8.0_65</JAVA_1_8_HOME>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<executable>${JAVA_1_8_HOME}/bin/javac</executable>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
93
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
93
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- LOG -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
</dependencies>
</project>
Gracias a este pom se está definiendo un proyecto padre o proyecto general, del que colgarán todos y cada uno
de los proyectos de MEDIPi. Dicha definición se encuentra al principio del fichero: se trata de un proyecto de
nombre “medipi-suite”, de la organización o grupo “medipi”, con versión “1.0-RELEASE” y empaquetamiento
“pom”. Esto último indica que no se trata de un proyecto java, en cuyo caso se indicaría empaquetamiento jar,
war o el que correspondiera, sino de una agrupación de proyectos de maven. La utilidad de esto es que podemos
definir una serie de características comunes a todos los hijos de este proyecto sin tener que duplicar el código en
cada uno de ellos.
En la siguiente región se observan los módulos o hijos del proyecto: se tratan de las categorías comentadas, algo
que coincide perfectamente con el árbol de directorios mostrado más arriba. Parece lógico entender que se va a
realizar una acción similar con cada una de ellas: cada categoría tendrá otro pom padre, que gestionará los
recursos o parametrizaciones Maven comunes a todas las librerías que pertenezcan al grupo.
La sección properties indica una serie de propiedades que heredaran todos los hijos de este proyecto, tanto los
de primer nivel como los de niveles inferiores. En este caso, solo se especifica la ubicación del JDK empleado
en la compilación y la codificación a emplear en los ficheros. Por tanto, gracias a este pom padre los
desarrolladores se ahorran duplicar esta información en el pom de cada proyecto. Además, en ningún momento
se pierde independencia, pues cada proyecto puede compilarse independientemente siempre que se pueda
acceder a los pom superiores.
La etiqueta build agrupa un conjunto de acciones que se realizan durante la compilación del proyecto. En este
caso, se especifica que todo proyecto de medipi deberá compilarse con la versión 8 de java y los ficheros se
trataran con la codificación UTF-8. Con el primer plugin se indica la generación del jar clásico, mientras que
con el tercero se obliga a crear otro jar que incluya los ficheros fuente. En el capítulo dedicado a la tecnología
Maven se comentó con más detalle la utilidad de los plugins y el ciclo de trabajo de maven, pero conviene
Descripción de la propuesta
94
94
repasar un par de cuestiones básicas en este punto. Los plugin de Maven llevan a cabo una funcionalidad
específica. Para emplearlos se indica su versión, configuración y se asocian a una determinada meta. Maven
tiene un ciclo de funcionamiento organizado por fases (preparación, compilación, empaquetado, etc.), de forma
que se ejecutan de forma secuencial. Además, cada fase se subdivide en metas o goals. Todo plugin se asocia a
una fase concreta, algunos de forma explícita (cuando se indica mediante los atributos execution) y otros
implícita (se asignan a la fase y meta por defecto).
La última de las secciones es la más importante, aquella que supone el centro de Maven: la gestión de
dependencias del presente proyecto. En nuestro caso se está indicando una serie de librerías de logs que todo
proyecto de MEDIPi incorporará, debiendo usarla para informar de avances, problemas, errores, etc. Se ha
comentado anteriormente que la configuración aquí indicada se replica para todos y cada uno de los proyectos
hijos. Por tanto, health-dom incorpora a librería de log4j especificada, así como el proyecto core-dom, que a su
vez también usa la citada librería. Es decir, se está produciendo una duplicidad, de forma que bajo el ahondamos
en el jar de health-dom deberíamos ser capaces de encontrar dos log4j-1.2.17.jar. Sin embargo, esto no ocurre:
Maven conoce que ambos proyectos tienen un padre común, por lo que automáticamente importa solo una
librería log4j, ahorrando espacio y previniendo futuros errores. Además, cuando especificamos una librería
Maven en el pom es posible realizar exclusiones, filtrando aquellas de sus librerías hijas que no deseamos. Toda
esta funcionalidad es la que hace de Maven una herramienta tremendamente útil para proyectos tan
modularizados como el nuestro.
5.5.3 MEDIPi-DOM
Se trata de un proyecto maven que pretende agrupar los elementos comunes en la gestión y construcción de
todos los proyectos DOM de la suite MEDIPi, incluyendo dependencias compartidas y repositorios comunes.
Se observa que cada una de las tres categorías en las que se ha dividido funcionalmente los proyectos de MEDIPi
se corresponde con un proyecto maven que centraliza los parámetros de construcción compartidos por todos los
módulos de dicha categoría.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-suite</artifactId>
<version>1.0</version>
</parent>
<artifactId>medipi-dom</artifactId>
<packaging>pom</packaging>
<modules>
<module>core-dom</module>
<module>health-dom</module>
<module>datacq-dom</module>
</modules>
<repositories>
<!-- OJDBC -->
<repository>
<id>novamens</id>
<url>http://maven.novamens.com</url>
</repository>
</repositories>
95
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
95
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Base de datos -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4.0</version>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.1.0.Final</version>
</dependency>
<!-- EHCache -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.1.0.Final</version>
</dependency>
<!-- Query DSL -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>4.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Descripción de la propuesta
96
96
En las líneas superiores se encuentra el fichero pom.xml. Un primer vistazo nos indica que se trata de un proyecto
hijo de medipi-suite, pero a la vez padre de los proyectos dom (core-dom, health-dom y datacq-dom). Por tanto,
estos tres módulos heredarán tanto la configuración aquí indicada como la especificada en el pom superior.
Como hemos visto anteriormente, podemos indicar en un pom.xml las dependencias de nuestro proyecto
especificando grupo, id y versión. Con esta información Maven buscará primero en su repositorio local. Si no
encuentra el archivo buscado irá al repositorio central [67] para descargar el jar correspondiente al repositorio
local. Si, por cualquier razón, necesitamos utilizar librerías que no se encuentran en el repositorio central de
maven es posible especificar otros repositorios auxiliares, tal y como se ha hecho en el presente pom.
En el presente proyecto se trabaja con JPA, usando Hibernate como implementación de dicho estándar. Junto a
este se ha optado por EhCache como herramienta de gestión de una caché de consultas integrable con
JPA/Hibernate. Además, se empleará QueryDSL para la consulta a base de datos, dada su gran versatilidad.
Todo lo anterior será empleado por las aplicaciones Java correspondientes para acceder a la base de datos Oracle
empleada. Observamos que las herramientas comentadas coinciden con las dependencias de todos los proyectos
dom, quedando todavía solo un punto por explicar: la presencia del plugin apt-maven-plugin. Para entender su
utilidad conviene refrescar la memoria: en el capítulo dedicado a las tecnologías se comentó que QueryDSL se
sustenta en unos archivos denominados QClass. Una QClass (Query Class) es una clase Java que se sustenta
sobre una entidad de JPA, ofreciendo al framework QueryDSL una serie de funcionalidades para manejar las
consultas que se deseen realizan sobre dicha tabla / entidad. El plugin ANT de maven, configurado tal como se
indica arriba, permite generar automáticamente las QClass de todas las entidades de nuestro modelo de datos.
Durante el proceso de compilación / construcción, Maven buscará las clases del proyecto que contienen etiquetas
JPA y generará una QClass para cada una de ellas en el directorio destino correspondiente. Es importante
entender que todos los proyectos que contengan clases JPA deberán tener dicho plugin si queremos que se
generen automáticamente las QClass. Ubicar el plugin en este pom padre nos permite automatizar aún más esta
tarea, evitando tener que copiar y pegar esta configuración en todo proyecto dom de la suite.
5.5.3.1 CORE-DOM
5.5.3.1.1 Funcionalidad de la libreria
El proyecto core-dom presenta la arquitectura básica de comunicación con la base de datos común de la suite de
MEDIPi. Todo proyecto JPA requiere de la definición de las entidades (que, grosso modo, mapean las tablas de
la base de datos), la configuración/creación de la conexión a la base de datos y la gestión de las transacciones.
A todo esto, le sumaremos la gestión de las diferentes cachés y la utilización del framework QueryDSL. A la
hora de configurar todos estos parámetros, se han tomado una serie de decisiones de diseño que generan una
metodología de trabajo propia: todas las aplicaciones deberán emplearla, de forma que se unifiquen criterios y
no se produzcan diferencias en las capas básicas sobre las que se construyen los módulos. Para escenificar esto
se ha desarrollado la librería core-dom, donde se agrupa toda la funcionalidad comentada.
Figura 5-22. Proyecto core-dom
97
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
97
5.5.3.1.2 Configuracio n de proyecto Maven
Como todo proyecto de MEDIPi, emplearemos Maven para la gestión de la compilación y construcción del
proyecto. En este caso se trata de uno de los proyectos más sencillos, pues no cuenta con ninguna especificidad
sobre el proyecto padre medipi-dom, por lo que nos bastará con especificar la dependencia con el pom padre e
indicar los parámetros básicos de definición del proyecto.
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-dom</artifactId>
<version>1.0</version>
</parent>
<artifactId>core-dom</artifactId>
<packaging>jar</packaging>
<name>core-db</name>
</project>
5.5.3.1.3 Co digo Java
Vamos a estructurar el código desarrollado en core-dom en cuatro grandes bloques según su funcionalidad:
modelo de entidades JPA, gestión de la conexión con base de datos, comunicación con la misma y utilidades.
5.5.3.1.3.1 DOM Base
Ya se ha comentado en varias ocasiones que usar JPA (Java Persistence Api) implica un enfoque orientado a
objetos, de forma que las tablas de base de datos se mapean en objetos Java denominados entidades. Esto ofrece
una versatilidad mayor de la planteada en un primer punto: podemos utilizar muchos de los elementos de diseño
de la programación orientada a objetos en estas clases: abstracción, herencia, polimorfismo, etc. En nuestro caos
hemos definido un modelo de datos en el que todas las tablas tienen un conjunto de columnas o atributos
comunes.
CAMPO TIPO DEFAULT COMENTARIO
DELETED NUMBER(1,0) 0 Indicador de borrado lógico. 1 borrado / 0 no borrado
INSERT_DATE TIMESTAMP(6) SYSDATE Fecha de creación del registro
UPDATE_DATE TIMESTAMP(6) Fecha de última modificación del registro
VERSION NUMBER(38,0) 0 Versión del registro
Toda tabla de MEDIPi tiene los atributos arriba definidos. Si realizamos una traducción de lo anterior a
programación orientada a objetos, puede decirse que los atributos deleted, insert_date, update_date y version
son comunes a todo objeto que mapee una tabla de base de datos. Parecería lógica crear una clase abstracta que
definiera dichos atributos, de forma que los distintos objetos la extendieran, ahorrando al desarrollador repetirlos.
Esta es precisamente una muestra de la utilidad del enfoque ORM que se ha decidido emplear.
Descripción de la propuesta
98
98
@MappedSuperclass
public abstract class MedipiAbstractEntity {
@Column(name = "DELETED", nullable = false)
private boolean deleted = false;
@Column(name = "INSERT_DATE", nullable = false, length = 6)
private Date insertDate = new Date();
@Column(name = "UPDATE_DATE", length = 6)
private Date updateDate;
@Version
@Column(name = "VERSION", nullable = false, precision = 38, scale = 0)
private Long version;
// getters y setters de los atributos …
}
Mediante la etiqueta @MappedSuperclass indicamos a JPA que la clase solamente contiene un conjunto de
mapeos entre columnas de base de datos y atributos que se aplicarán a todas las clases que la hereden, sin
constituir una entidad en sí misma.
La etiqueta @Column permite indicar la correspondencia del atributo con una determinada columna de base de
datos, así como especificar otras opciones (nullable o no, precisión, escala, unicidad, etc.). Incluso se puede
realizar una definición de la columna mediante la opción ColumnDefinition empleando las instrucciones de
definición de columnas de SQL. Por su parte, la etiqueta @Version indica a JPA que ese campo se está utilizando
para indicar el número de versión del registro. El atributo asociado será manejado automáticamente por la
implementación de JPA, En nuestro caso, Hibernate lo inicializa en 0 y aumenta en uno su valor con cada
actualización sobre el registro.
Gracias al plugin de Maven configurado, siempre que se compile / construya el proyecto se genera la QClass
asociada a la entidad JPA MedipiAbstractEntity en el directorio target/generated-sources/java/paquete-de-la-
entidad. De esta parte solo nos interesa conocer qué son las QClass, como las generamos y cómo las utilizaremos
para realizar consultas. Por tanto, en este punto no hay necesidad de realizar un análisis detallado de las QClasses
generadas, más allá de observar que cada atributo de la entidad se transforma en otro atributo de un tipo análogo
en esta clase. Dichos tipos nos ofrecen, como se verá más adelante, un conjunto de métodos que nos ayudarán a
construir las consultas.
/**
* QMedipiAbstractEntity is a Querydsl query type for MedipiAbstractEntity
*/
@Generated("com.querydsl.codegen.SupertypeSerializer")
public class QMedipiAbstractEntity extends EntityPathBase<MedipiAbstractEntity> {
private static final long serialVersionUID = -925350210L;
public static final QMedipiAbstractEntity medipiAbstractEntity = new
QMedipiAbstractEntity("medipiAbstractEntity");
public final BooleanPath deleted = createBoolean("deleted");
public final DateTimePath<java.util.Date> insertDate =
createDateTime("insertDate", java.util.Date.class);
public final DateTimePath<java.util.Date> updateDate =
createDateTime("updateDate", java.util.Date.class);
public final NumberPath<Long> version = createNumber("version", Long.class);
public QMedipiAbstractEntity(String variable) {
99
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
99
super(MedipiAbstractEntity.class, forVariable(variable));
}
public QMedipiAbstractEntity(Path<? extends MedipiAbstractEntity> path) {
super(path.getType(), path.getMetadata());
}
public QMedipiAbstractEntity(PathMetadata metadata) {
super(MedipiAbstractEntity.class, metadata);
}
}
5.5.3.1.3.2 DBManager
El trabajo con JPA se sustenta en el objeto EntityManager, que contiene toda la información para crear
conexiones, manejar transiciones y otras funcionalidades de gestión de la base de datos. La creación y
configuración de este objeto, así como de otros puntos secundarios como la parametrización de la caché, se
ocupa esta sección del código de core-dom.
Para ello definimos una interfaz DBManager, con cuatro métodos básicos.
public interface DBManager {
/**
* Recupera el objeto EntityManager de JPA
*
* @return EntityManager
*/
EntityManager getEntityManager();
/**
* Libera el espacio reservado por la caché especificada
*
* @param cacheName
* Identificador de la caché
*/
void cleanCache(String cacheName);
/**
* Libera la conexión con la base de datos
*/
void shutdown();
/**
* Devuelve el identificador de la región en la que se ubica la caché
* de queries
*
* @return Identificador de la caché de queries
*/
String getQueryCacheRegion();
}
Cada aplicación manejará su propio DBManager, que implementará la interfaz anterior. Además, hay varia
lógica común a todas las aplicaciones que se centraliza en la siguiente clase abstracta.
public abstract class AbstractDBManager implements DBManager {
private EntityManagerFactory entityManagerFactory;
private CacheManager cacheManager;
private String queryCacheRegion;
public AbstractDBManager(String persistenceUnit, String queryCacheRegion) {
Descripción de la propuesta
100
100
entityManagerFactory =
Persistence.createEntityManagerFactory(persistenceUnit);
cacheManager = CacheManager.getInstance();
setQueryCacheRegion(queryCacheRegion);
}
public EntityManager getEntityManager() {
return entityManagerFactory.createEntityManager();
}
public void cleanCache(String cacheName) {
cacheManager.getCache(cacheName).removeAll();
}
public void shutdown() {
entityManagerFactory.close();
cacheManager.shutdown();
}
public String getQueryCacheRegion() {
return queryCacheRegion;
}
private void setQueryCacheRegion(String queryCacheRegion) {
this.queryCacheRegion = queryCacheRegion;
}
}
Se observa como se define un constructor al que indicar el identificador de la persistenceUnit y el de la región
de la región en la que se cachearán las consultas en base de datos. Con lo anterior se instancian los objetos
básicos: una factoría de EntityManager y un CacheManager, indicándole a este último cual es la región para
cachear las consultas. Junto al constructor observamos varios métodos básicos: obtención de un EntityManager,
limpieza de una caché indicada por el nombre de su región, cierre de la conexión con base de datos (y borrado
de las cachés), getters y setters de la región de caché para queries. La idea es centralizar todo el uso de los objetos
EntityManagerFactory y CacheManager en esta clase abstracta y en los DBManagers que extiendan de ella. Un
buen patrón de diseño implica separar funcionalidades: es altamente peligroso que se pueda alterar la conexión
a base de datos o jugar con las cachés en un punto cualquiera de una aplicación. Cada aplicación definirá su
DBManager heredando el abstract arriba definido, de forma que proporcione solo una serie de métodos al
exterior. En definitiva, solo en el DBManager se trabajará sobre los objetos básicos que gestionan la base de
datos, comportándose como una caja negra para el resto de la aplicación. La configuración de la base de datos
se lleva a cabo a través del fichero persistence.xml, mientras que parametrización de la caché la realiza
ehcache.xml. Como en este punto solo se han definido interfaces y abstract, no es necesaria la presencia de
ninguno de estos ficheros, los cuales comentaremos adecuadamente en cada una de las aplicaciones.
5.5.3.1.3.3 DAOs y Services
Aunque se ha comentado ya en capítulos previos, conviene recordar en qué consiste exactamente el objeto
EntityManager. Cada vez que queramos trabajar con la base de datos se creará un nuevo EntityManager, que se
encargará de llevar a cabo las acciones que sobre él se indiquen. Una vez finalizada la tarea para la que se creó,
el EntityManager se cierra. Si en el futuro se desean realizar más operaciones con la base de datos basta con
crear un nuevo EntityManager a través del método que proporciona el AbstractDBManager. Un objeto de tipo
EntityManager ofrece métodos básicos como persist, merge o remove que se corresponden con los clásicos
insert, update y delete. Sin embargo, una buena gestión de la comunicación con la base de datos implica mucho
más que llamar a esos métodos en el momento adecuado. Como mínimo se debe tener en cuenta la
transaccionalidad. En muchos casos será necesario realizar acciones múltiples sobre la base de datos, de forma
que deban ejecutarse atómicamente. Es decir, si se produjera algún error debería devolverse la base de datos al
estado previo. Surge el concepto de transaccionalidad: una transacción es una “sesión” en base de datos. Todas
las acciones ejecutadas para esa transacción forman una misma unidad de trabajo, de forma que o bien se
persisten en la base de datos conjuntamente o se rechazan todas. Un objeto EntityManager nos permite crear
transacciones, tal y como se observa en el siguiente fragmento de código.
101
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
101
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
try {
t.setDeleted(true);
t.setUpdateDate(new Date());
t = entityManager.merge(t);
tx.commit();
res = true;
} catch (Exception e) {
tx.rollback();
}
Toda acción realizada sobre un EntityManager tras el inicio de la transacción es gestionada temporalmente, y
solo es persistida en base de datos cuando se confirma (commit). Gracias a esta lógica, si se produce algún error
inesperado se pueden deshacer todos los cambios realizados en esta transacción (rollback), recuperando el estado
anterior de la base de datos.
Está claro que se debe seguir siempre una determinada estructura en el acceso a base de datos, por lo que
deberíamos controlar el acceso a la base de datos en la medida de lo posible, haciendo que pase siempre por una
determinada lógica común. Para ello se ha optado por emplear el patrón DAO [68] de acceso a la base de datos.
El patrón DAO es una opción de diseño para aplicaciones Java EE que ofrece un conjunto de normas para
permitir un acceso estructurado a la base de datos. Consiste en crear objetos de acceso a bases de datos – Data
Access Object (DAO) – quienes serán los encargados de trabajar con el framework de acceso a datos del sistema,
en nuestro caso JPA, para realizar las acciones indicadas. De esta manera, una vez más recurrimos a un patrón
de diseño que pretende modularizar la funcionalidad: un DAO se comporta también como una caja negra para
el resto de la aplicación, de tal forma que si se desea modificar alguna lógica relativa a la comunicación con base
de datos se hará solo en el DAO en vez de buscar por toda la aplicación en qué puntos se trabaja con la base de
datos y modificarlos uno a uno.
Figura 5-23. Patrón DAO en MEDIPi
En las aplicaciones de MEDIPi se ha optado por emplear el patrón DAO, con unas características propias:
- Un DAO está siempre asociado a una entidad de base de datos, de forma que un DAO solo contenga la
lógica relacionada con una tabla. Esto permite definir métodos comunes para todos los DAOs (insert,
update, delete, etc.) pero es menos útil cuando se pretenden operaciones algo más complejas. Por
ejemplo, si realizamos una consulta que implique varias tablas esta metodología de trabajo es menos
adecuada, pues habrá que ubicar la consulta en algún DAO. En nuestro caso ubicaremos la consulta en
el DAO que se considere más apropiado (en función del objeto a devolver o la tabla principal entre
todas las que intervienen).
- Los DAOs deben realizar operaciones casi exclusivamente de comunicación con la base de datos. La
lógica de gran complejidad debe ubicarse en capas superiores.
- El patrón de diseño DAO se complementa habitualmente con los servicios. Un servicio es una capa
Descripción de la propuesta
102
102
superior que realiza la lógica compleja, llamando a los DAOs que necesita para realizar las acciones
necesarias. En nuestro caso, estamos definiendo una arquitectura que permita a muchas aplicaciones
diferentes construirse sobre los elementos aquí definidos. Para facilitar esto no debemos llegar al
extremo de definir una única metodología de trabajo, algo que puede variar en función de las
necesidades de cada proyecto, solamente debemos cubrir la creación de una estructura común que
establezca una homogeneidad entre todos los módulos de la suite MEDIPi.
- Partiendo del punto anterior, se define como una buena política no emplear directamente los DAOs en
el código lógico, si no utilizar una capa más de abstracción. En el caso de las aplicaciones diseñadas se
ha optado por generar una clase, representada en el esquema superior como
“ApplicationConfigurationObject” y que se corresponde con “DDAConfiguration”,
“SaverConfiguration” o “ChartConfiguration” según la aplicación, que mantiene una instancia de cada
DAO empleado, ofreciendo al exterior métodos lógicos que llaman a los DAOs adecuados, Se tratan
de métodos normalmente estáticos que evitan tener que instanciar una clase en memoria cada vez que
se pretende usar la base de datos. Pueden verse como un servicio, comprendiendo que se trata de un
solo servicio por aplicación y que no realizan una lógica muy compleja, más allá de la llamada al DAO
correspondiente. Es importante destacar que se ha optado por este patrón porque permite centralizar el
acceso a los DAOs, controlar el acceso multi-hilo a los métodos y evitar instancias frecuentes de objetos
en memoria que se emplearán continuamente. En resumen, se ha considerado que esta metodología “de
servicio único” es la más adecuada para las aplicaciones diseñadas, pero no debe ser vista como una
parte “básica” de la estructura diseñada, ya que otros proyectos deben preparar su propia estructura
según sus necesidades. Parece evidente que crear servicios es una buena práctica, pero el diseño de los
mismos puede variar: uno centralizado como en el caso actual, uno para cada funcionalidad / petición,
etc.
Por tanto, en el proyecto core-dom se realiza la definición de la estructura común de los DAOs, creando para
ello una interfaz y una clase abstracta con la lógica común, como es habitual.
public interface DAO<T> {
EntityManager getEntityManager();
T insert(T t);
T update(T t);
boolean delete(T t);
}
public abstract class AbstractDAO<T extends MedipiAbstractEntity> implements DAO<T> {
private static final Logger LOG = Logger.getLogger(AbstractDAO.class);
private DBManager myDBManager;
public AbstractDAO(DBManager myDBManager) {
this.myDBManager = myDBManager;
}
public EntityManager getEntityManager() {
return myDBManager.getEntityManager();
}
public void cacheQuery(JPAQuery<?> query) {
query.setHint(DBConstants.ACTIVE_QUERY_CACHE, true);
query.setHint(DBConstants.ACTIVATE_QUERY_CACHE_REGION,
myDBManager.getQueryCacheRegion());
}
public T insert(T t) {
EntityManager entityManager = this.getEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
try {
t.setInsertDate(new Date());
103
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
103
entityManager.persist(t);
entityManager.flush();
entityManager.refresh(t);
tx.commit();
} catch (Exception e) {
try {
tx.rollback();
} finally {
LOG.error("Error inserting an object: " + e);
}
} finally {
entityManager.close();
}
return t;
}
...
}
Del fragmento de código anterior conviene resaltar tres puntos. En primer lugar, podemos ver cómo se manejan
las transacciones dentro de un DAO, de forma que cada método de un DAO supone una transacción con la base
de datos. Este es el diseño correcto. En segundo lugar, se ha creado un método que cachea una JPAQuery en la
caché correspondiente, de forma que todo DAO pueda usarla cuando lo considere conveniente. En último lugar,
comentar que se ha empleado el tipado genérico de Java para trabajar en este DAO base abstracto. Se define un
tipo genérico T con la única condición de extender de MedipiAbstractEntity, de tal forma que se puede trabajar
con los atributos comunes en el DAO abstracto. Si pretendemos crear un DAO para una clase concreta
realzaremos la siguiente declaración.
public class ConstantInfoDAO extends AbstractDAO<ConstantInfo>
De forma que los métodos del DAO accesibles a través de una instancia de ConstantInfoDAO deberán usar
entidades ConstantInfo.
5.5.3.1.3.4 Paquete Util
En este paquete se ubica simplemente una clase con todas las constantes empleadas en el presente proyecto. En
todos los proyectos desarrollados se ha seguido el mismo diseño: cada proyecto tiene una única clase en las que
se ubican todas las constantes empleadas. Se ha optado por esta solución porque suelen ser constantes bastante
genéricas, usables desde varios lugares de la aplicación, y además no hay una gran cantidad de ellas. Existen
varios patrones de diseño posibles, siendo el más habitual ubicar las constantes comunes en una clase general y
colocar las específicas como atributos de las clases que las utilicen. Al igual que ocurría con los servicios, se ha
optado por una metodología adecuada para la situación actual, no siendo necesario mantenerla a toda cosa.
public class DBConstants {
public static final String ACTIVE_QUERY_CACHE = "org.hibernate.cacheable";
public static final String ACTIVATE_QUERY_CACHE_REGION =
"org.hibernate.cacheRegion";
}
5.5.3.2 HEALTH-DOM
5.5.3.2.1 Funcionalidad de la libreria
En health-dom definimos las entidades JPA asociadas al esquema HEALTH de base de datos. Se ha optado por
separar esta librería del proyecto core-dom para que siempre exista una librería por cada esquema, que contenga
exclusivamente el mapeo de dicho esquema, las QClasses y las constantes relacionadas con ellas. Además, se
Descripción de la propuesta
104
104
abre la posibilidad de que se amplíe el modelo de datos y se considere que HEALTH no debe ser el esquema
básico, sino que es posible que depende de otro esquema más general. En ese caso, gracias a esta separación,
basta con cambiar las dependencias en el pom y no hay necesidad de tocar código.
Figura 5-24. Proyecto health-dom
5.5.3.2.2 Configuracio n de proyecto Maven
El fichero pom.xml de health-dom es extremadamente simple, similar al del core-dom. Además de indicar el
pom padre y definir el id del proyecto se establece la dependencia con core-dom.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-dom</artifactId>
<version>1.0</version>
</parent>
<artifactId>health-dom</artifactId>
<packaging>jar</packaging>
<name>health-dom</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core-dom</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
5.5.3.2.3 Co digo Java
La práctica totalidad del proyecto consiste en el mapeo de las tablas del esquema HEALTH. Comencemos con
la primera tabla mapeada, a modo de ejemplo.
@Entity
@Table(name = "PATIENT", schema = "HEALTH")
public class Patient extends MedipiAbstractEntity {
public Patient() {
}
public Patient(Long patientId, String pid, String dni, String name, String
surname1, String surname2, PersonSex sex) {
105
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
105
this.patientId = patientId;
this.pid = pid;
this.dni = dni;
this.name = name;
this.surname1 = surname1;
this.surname2 = surname2;
this.sex = sex;
}
@Id
@Column(name = "PATIENT_ID", unique = true, nullable = false, precision = 5,
scale = 0)
@SequenceGenerator(name = "HEALTH.PATIENT_ID_SEQ", sequenceName =
"HEALTH.PATIENT_ID_SEQ", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"HEALTH.PATIENT_ID_SEQ")
private Long patientId;
@Column(name = "PID", unique = true, nullable = false, length = 32)
private String pid;
@Column(name = "DNI", unique = true, nullable = false, length = 32)
private String dni;
@Column(name = "NAME", length = 32)
private String name;
@Column(name = "SURNAME1", length = 32)
private String surname1;
@Column(name = "SURNAME2", length = 32)
private String surname2;
@Enumerated(EnumType.ORDINAL)
@Column(name = "SEX", nullable = false, precision = 1, scale = 0)
private PersonSex sex;
// getters y setters
}
Se emplea la etiqueta @Entity para indicar que se trata de una entidad / tabla JPA. Para especificar qué tabla y
esquema se emplea @Table. Esto ocurre porque puede haber entidades que no se correspondan con una tabla
en base de datos. Por ejemplo, una vista es también una entidad. Incluso es posible que esa vista no tenga ninguna
correspondencia con base de datos, si no que sea una entidad interna utilizada por algún motivo concreto, aunque
no sea el caso más habitual.
La clave primaria de una entidad se marca con @Id. Todas las claves simples de nuestro modelo de datos
emplean una secuencia, que puede mapearse directamente en JPA con las etiquetas @SequenceGenerator y
@GeneratedValue. De esta manera, JPA/Hibernate se encarga de avanzar consecuentemente la secuencia
cuando se inserta un registro. Con @SequenceGenerator estamos creando el manejador de la secuencia para
JPA: le damos un id, le indicamos la secuencia de base de datos con la que se corresponde y el aumento que
debe realizar cada vez. Por su parte, con @GeneratedValue es donde estamos indicando el valor para ese
atributo. Podemos indicar valores autogenerados de tipo identidad, tabla y secuencia, informando además del id
del generador de secuencia creado anteriormente.
El resto de columnas nos mapeos de JPA, similares a los vistos en MedipiAbstractEntity, salvo el caso del
atributo sex. En ese caso se está indicando un mapeo con un enumerado de Java. Cuando eso ocurre basta con
indicar en la etiqueta @Enumareted si va a ser de tipo numérico o texto. En el primer caso el valor numérico en
base de datos (0, 1, 2...) se mapea con el valor que corresponde a su posición en el enumerado. En caso de tipo
ordinal (numérico) se busca una correspondencia entre el texto almacenado en base de datos y el indicado en el
enumerado. La utilización de enumerados puede ser bastante útil, permite transformar números en base de datos
en objetos con entidad y significado propio.
Descripción de la propuesta
106
106
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(referencedColumnName = "PATIENT_ID", name = "PATIENT_ID", nullable =
false)
private Patient patient;
En las líneas superiores observamos cómo se mapea la foreing key que hay en EPISODE hace PATIENT.
Gracias a JPA, no solo tenemos el PATIENT_ID que figura en el registro, si no que podemos acceder al objeto
Patient completo, algo tremendamente útil. Con @ManyToOne se especifica el tipo de relación entre las tablas,
en este caso un registro en EPISODE se corresponde con uno en PATIENT, pero un PATIENT puede tener
múltiples EPISODES. Además de esta etiqueta se pueden emplear otras según la cardinalidad de la relación:
@OneToOne, @OneToMany, @ManyToMany… En todas ellas se debe indicar si el FetchType es EAGER o
LAZY. Este atributo indica el tratamiento que debe hacer JPA/Hibernate de la relación. Si se indica EAGER el
objeto referenciado (en este caso Patient) se trae automáticamente con Episode, realizando JPA las consultas
que considere oportunas para poblar el objeto Patient. Sin embargo, si se indica LAZY el objeto no está
instanciado inicialmente, si no que hará falta llamar a su getter (getPatient() en el ejemplo) para que se lance la
consulta a base de datos y se forme el objeto. Ambos modos de trabajo tienen ventajas e inconvenientes. La
regla general empleada para este desarrollo es considerar EAGER aquellas relaciones que aportan sentido
funcional al objeto (como conocer el paciente de un episodio) y marcar como LAZY las que ofrecen información
opcional, aunque no es fácil establecer un criterio único.
@Entity
@Table(name = "CONSTANT", schema = "HEALTH")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region =
HealthConstants.CACHE_CONSTANT)
public class Constant extends MedipiAbstractEntity
Terminamos el detalle del desarrollo de health-dom comentando cómo se indica el cacheo de una tabla completa.
Para ello basta con emplear la etiqueta @Cache, en este caso de Hibernate. La configuración es bastante sencilla:
se indica la región correspondiente y la estrategia de concurrencia [69]. Las regiones se definen en el fichero
ehcache.xml en cada aplicación, tal y como veremos más adelante. Junto a un nombre o identificador de la
región se indicarán otros parámetros como tamaño, tiempo que se espera antes de borrar información antigua,
etc. En nuestro caso definimos aquí el nombre de la región, de forma que cada aplicación pueda diseñar la caché
según sus necesidades. Por otra parte, la estrategia puede ser:
- READ_ONLY: se usa cuando los datos no van a cambiar, por lo que solo se va a emplear en la consulta
de datos.
- READ_WRITE: se emplea cuando los datos necesitan ser actualizados, pero no se provee un
mecanismo de separación entre transacciones simultaneas. Por tanto, puede que las transacciones no
funcionen correctamente si hay muchas inserciones/actualizaciones a la vez, provocando “lecturas
fantasmas”, es decir, inconsistencias en la caché.
- NONSTRICT_READ_WRITE: similar a la anterior, solo que realiza un mayor control de la
transaccionalidad.
- TRANSACTIONAL: caché totalmente transaccional. EhCache no la implementa.
Evidentemente, solo nos interesará una caché READ_ONLY, pues la información que se va a escribir no se
deseará cachear, ya que no se consultará con frecuencia.
107
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
107
Conviene recordar que Hibernate mantiene una caché de primer nivel. Toda operación que se realiza sobre un
objeto EntityManager es cacheada y permanece en esta situación hasta que se elimina el objeto.
Junto al mapeo de las entidades el proyecto incluye las QClass autogeneradas y una clase HealthConstants en la
que solamente se indican las cachés diseñadas, para que otros proyectos puedan conocer el nombre de la región
y operar con ellas.
5.5.3.3 DATACQ-DOM
5.5.3.3.1 Funcionalidad de la librerí a
Este proyecto es el último de los dedicados a la base de datos. En él se lleva a cabo el mapeo de las tablas del
esquema DATA_ACQ.
Figura 5-25. Proyecto datacq-dom
5.5.3.3.2 Configuracio n de proyecto Maven
En el pom.xml definimos el proyecto Maven e indicamos la dependencia con health-dom, que a su vez incluirá
la librería core-dom.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-dom</artifactId>
<version>1.0</version>
</parent>
<artifactId>datacq-dom</artifactId>
<packaging>jar</packaging>
<name>datacq-dom</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>health-dom</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
Descripción de la propuesta
108
108
5.5.3.3.3 Co digo Java
El mapeo del esquema DATA_ACQ no es muy diferente al visto en el proyecto anterior. La única novedad con
respecto a lo ya comentado es remarcar que JPA nos permite crear atributos que no tienen una correspondencia
directa con ninguna columna de la tabla mapeada en base de datos. Veamos un ejemplo
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
@JoinColumn(referencedColumnName = "MEDIPI_INSTANCE_ID", name = "MEDIPI_INSTANCE_ID",
nullable = false)
private MedipiInstance medipiInstance;
@OneToMany(orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "medipiInstance",
cascade = CascadeType.ALL)
private Set<Allocation> allocations;
El primer atributo pertenece al objeto Allocation, mientras que el segundo lo hace a MedipiInstance. Con las dos
instrucciones estamos mapeando una misma relación, vista desde cada extremo. Desde Allocation definimos
una relación ManyToOne a la instancia a la que está asignada, mapeando la columna de base de datos
MEDIPI_INSTANCE_ID con el objeto que representa dicha tabla. Sin embargo, en el otro extremo no estamos
realizando ninguna vinculación entre el atributo y la base de datos. En su lugar se hace referencia al atributo que
constituye el otro extremo de la relación. Esto nos ofrece muchas ventajas. Podemos realizar una búsqueda sobre
la tabla MedipiInstance y luego acceder a todas sus asignaciones en una lista simplemente llamando al getter del
atributo. Hibernate hará el resto. Conviene comentar el significado de algunas opciones empleadas. Con
OrphanRemoval indicamos que, si existen Allocations en base de datos que no se encuentra en el objeto Java,
estos deben eliminarse al persistir el objeto MedipiInstance. De esta manera podemos recuperar las asociaciones
de una instancia, borrar una por el motivo que sea, y persistir el registro. Automáticamente Hibernate borrará el
registro huérfano de la tabla Allocation. Otra novedad con respecto a lo comentado en el proyecto anterior es la
opción cascade. Esta opción indica qué acciones efectuadas sobre un objeto deben aplicarse también sobre el
objeto relacionado. Si marcamos REMOVE en la clase MedipiInstance sobre la lista de asignaciones estamos
pidiendo a la implementación de JPA que borre todos los registros asociados a una instancia cuando esta sea
eliminada. Parece evidente que estas acciones son tremendamente útiles para extrapolar acciones sobre objetos
padres a sus objetos hijos. JPA nos permite aplicarlas también a la inversa, algo que debe ejecutarse con cuidado.
En nuestro caso, se indica ALL en la relación padre-hijo (one to many) y REFRESH en la hijo – padre (many
to one). Esto implica que toda acción sobre un padre se propagará sobre los hijos, mientras que a la inversa solo
se propagará la acción REFRESH. REFRESH hace referencia a una llamada de JPA para recargar el objeto,
generalmente antes de operar sobre él, por lo que es importante que esta acción se propague para evitar
incoherencias.
Se puede realizar una última observación de las líneas de código superiores: nos encontramos ante otro ejemplo
de utilización del fetch, en este caso a la inversa del expuesto en health-dom. En este caso se considera que no
será poco habitual consultar una instancia a partir de una asignación, ya que no suele ser el flujo de trabajo
habitual. Además de eso, hay una cosa que conviene tener clara en el desarrollo de mapeos con JPA: no se
pueden generar ciclos: si en ambas relaciones se marca EAGER se produce un ciclo: Allocation se trae siempre
el objeto MedipiInstance, que a su vez se trae siempre en la consulta inicial los Allocations, que a su vez… JPA
conoce estos ciclos y es capaz de cortarlos, pero no su presencia son una práctica totalmente desaconsejada.
5.5.4 MEDIPI-CLUSTER
Medipi-Cluster es un proyecto Maven diseñado para agrupar los elementos comunes en la compilación y
construcción de los proyectos java del área Cluster. Actualmente solo existe uno, core-cluster, pero dado que a
109
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
109
lo largo del diseño se ha hecho especial hincapié en diseñar una arquitectura software robusta que permita
fácilmente su ampliación se ha optado por crear un proyecto agrupador, de manera análoga a lo establecido en
medipi-dom y medipi-app.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-suite</artifactId>
<version>1.0</version>
</parent>
<artifactId>medipi-Cluster</artifactId>
<packaging>pom</packaging>
<modules>
<module>core-Cluster</module>
</modules>
<dependencies>
<!-- Kafka -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.9.0.1</version>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6.2</version>
</dependency>
</dependencies>
En esta ocasión no tenemos necesidad de aplicar ninguna lógica especial durante la fase de compilación del
productor, por lo que este pom no contiene más de los aspectos básicos habituales: definición del proyecto,
referencia a su pom padre, referencia a sus proyectos hijos y definición de las dependencias que todos ellos
deberán incluir.
5.5.4.1 CORE-CLUSTER
5.5.4.1.1 Funcionalidad de la librerí a
En el proyecto core-Cluster se ubica la parte del desarrollo que abarca la comunicación con el clúster de Kafka,
lo que incluye: definición de los objetos de transferencia de datos (Data Transfer Object, DTO) empleados,
configuración e implementación de los consumidores y productores de datos para dicho sistema de intercambio
de información. Al igual que ocurre en otros aspectos de la suite, la gran utilidad que proporciona core-Cluster
es el manejo de consumers y producers de Kafka, de tal forma que ofrece a las aplicaciones que así lo deseen un
conjunto reducido de métodos a emplear para que se produzca esta comunicación, sin tener que entrar en detalle
de la lógica existente bajo ellos.
Descripción de la propuesta
110
110
Figura 5-26. Proyecto core-Cluster
5.5.4.1.2 Configuracio n de proyecto Maven
Al tratarse del único hijo del proyecto maven medipi-Cluster toda la configuración especial y las dependencias
se vuelcan en el pom padre, dejando para el proye8cto core-Cluster poco más que los campos obligatorios.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-Cluster</artifactId>
<version>1.0</version>
</parent>
<artifactId>core-Cluster</artifactId>
<packaging>jar</packaging>
<name>core-Cluster</name>
5.5.4.1.3 Co digo Java
5.5.4.1.3.1 DTO – Data Transfer Objects
En el apartado 5.4.3. DTOs se definió los objetos que servirán como base para la información intercambiada a
través del clúster Kafka. Cada uno de ellos se corresponde con un objeto Java, ubicado en este proyecto.
public class LFConstantInfo {
public LFConstantInfo() {
}
public LFConstantInfo(Long constantId, Long episodeId, Date constantDate,
String value, Boolean manual) {
this.constantId = constantId;
this.episodeId = episodeId;
this.constantDate = constantDate;
this.value = value;
this.manual = manual;
}
private Long constantId;
private Long episodeId;
private Date constantDate;
private String value;
private Boolean manual;
// getters y setters
}
111
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
111
5.5.4.1.3.2 Producer Kafka
Instanciar un producer Kafka desde Java no es especialmente complicado, como ya se viró en el apartado 4.4.5.
Producers. Aun así, se ha querido centralizar lo máximo posible el código para evitar redundancias, de forma
que sea lo más modular posible. Al crear un MedipiProducer con una interfaz propia evitamos que los
desarrolladores de las aplicaciones tengan q tener conocimiento alguno de Kafka. Además, Kafka es una
tecnología en plena fase de desarrollo, no en vano todavía ni siquiera cuenta con una versión 1.0. Hemos
observado cómo se han producido algunos cambios importantes al avanzar de versiones, algunos estructurales,
otros de parametrización, que refuerzan la opción escogida. Con esta modularización solo hará falta aplicar los
cambios que impliquen nuevas versiones de Kafka en un único proyecto, de forma que estas transformaciones
sean totalmente transparentes a otros proyectos.
Se ha optado por generar dos clases producers MedipiLFProducer y MedipiHFProducer. Ambos extienden del
AbstractMedipiProducer, en el que se encuentra la mayoría de la lógica. En ambos casos su utilización es tan
sencilla como la expuesta en las líneas inferiores.
Properties props = new Properties();
props.put(KafkaConstants.PROP_SERVER_LIST,PropertiesReader.getProperty(KafkaConstants.
VALUE_SERVER_LIST));
. . .
MedipiLFProducer producerLF = new MedipiLFProducer();
producerLF.initialize(props);
producerLF.sendLFConstantInfo(info);
Con cuatro instrucciones básicas se puede mandar información al clúster. Es interesante remarcar que se ha
optado por crear dos producers, uno para cada topic, cuando esto no es necesario: un producer puede enviar
información a tantos topics como quiera. Los motivos de este cambio son dos: facilitar las labores al
desarrollador y ofrecer una interfaz coherente para el uso de Kafka. Como vamos a emplear dos consumers, uno
subscrito a cada topic, es más coherente ofrecer dos producers a los desarrolladores.
A continuación, se indica el código de las clases desarrolladas, en la línea del especificado en capítulos
anteriores.
public class AbstractMedipiProducer {
private Producer<Long, String> producer;
public void initialize(Properties props) {
producer = new KafkaProducer<Long, String>(props);
}
protected void sendKafkaMessage(String message, String topic) {
new ProducerThread(producer, new ProducerRecord<Long, String>(topic,
message)).start();
}
private class ProducerThread extends Thread {
private Producer<Long, String> producer;
private ProducerRecord<Long, String> producerRecord;
public ProducerThread(Producer<Long, String> producer,
ProducerRecord<Long, String> producerRecord) {
this.producer = producer;
this.producerRecord = producerRecord;
}
public void run() {
this.producer.send(producerRecord);
}
}
Descripción de la propuesta
112
112
public void shutdown() {
producer.close();
}
}
public class MedipiLFProducer extends AbstractMedipiProducer {
public void sendLFConstantInfo(LFConstantInfo info) {
Gson gson = new Gson();
String msg = gson.toJson(info);
String topic = KafkaConstants.TOPIC_LF;
sendKafkaMessage(msg, topic);
}
}
5.5.4.1.3.3 Consumer Kafka
Instanciar un consumer de Kafka es algo más complicado que hacer lo propio con un producer. Como en el
apartado 4.4.6 Consumers ya se definieron las instrucciones básicas que se han de emplear para consumir
información del clúster Kafka, nos centraremos aquí en justificar las decisiones tomadas en pos de empaquetar
y personalizar la lógica necesaria para utilizar adecuadamente un consumer Kafka desde los proyectos de
MEDIPi.
Por coherencia, la estructura empleada es similar a la de los MedipiProducers: tendremos dos consumers, uno
para cada topic o canal del que queramos recibir información, de tal forma que ambos extienden de una clase
abstracta AbstractMedipiConsumer en la que se ubica la lógica común.
Properties props = new Properties();
props.put(KafkaConstants.PROP_SERVER_LIST,
. . .
MedipiLFConsumer consumerLF = new MedipiLFConsumer() {
@Override
public void receiveLFConstantInfo(LFConstantInfo info) {
receiveDataToAll(info);
}
};
consumerLF.initialize(props);
Se ha puesto especial hincapié en que la utilización de estos MedipiConsumers sea lo más sencilla posible. En
este caso observamos que basta con instanciar el objeto, indicarle la configuración mediante un fichero
Properties y sobrescribir el método en el que se maneja el DTO recibido. Es destacable que un desarrollador no
tiene q conocer el funcionamiento de un consumer, de Kafka y ni siquiera la utilización de JSON para la
transformación del DTO en el mensaje que va por el clúster. Todo eso es totalmente transparente para él.
public abstract class AbstractMedipiConsumer {
protected KafkaConsumer<Long, String> consumer;
protected ExecutorService executor;
protected boolean end = false;
public void initialize(Properties props) {
consumer = new KafkaConsumer<>(props);
executor = Executors.newCachedThreadPool();
}
public void shutdown() {
end = true;
executor.shutdown();
}
}
113
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
113
public abstract class MedipiLFConsumer extends AbstractMedipiConsumer {
public void initialize(Properties props) {
super.initialize(props);
consumer.subscribe(Arrays.asList(KafkaConstants.TOPIC_LF));
startLFThread();
}
private void startLFThread() {
executor.execute(new Thread() {
public void run() {
while (!end) {
ConsumerRecords<Long, String> records =
consumer.poll(KafkaConstants.POLL_TS);
for (ConsumerRecord<Long, String> record : records) {
Gson gson = new Gson();
receiveLFConstantInfo(gson.fromJson(record.value(),
LFConstantInfo.class));
}
}
consumer.close();
}
});
}
public abstract void receiveLFConstantInfo(LFConstantInfo info);
}
Observamos que se arranca un hilo encargado solamente de leer del topic. Cuando se reciben datos se reconstruyen y se
llama al método que debe implementar el receptor.
5.5.4.1.3.4 Paquete Util
La presencia de un paquete .util es habitual en todos los proyectos de la suite y siempre incluye el mismo tipo
de código: una clase para almacenar todas las constantes empleadas a lo largo del proyecto.
5.5.5 MEDIPI-APP
Tal y como se ha realizado para las otras categorías, se ha decidido crear un proyecto maven que permita agrupar
las aplicaciones de MEDIPi y sus librerías comunes, centralizando dependencias y configuraciones comunes.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-suite</artifactId>
<version>1.0</version>
</parent>
<artifactId>medipi-app</artifactId>
<packaging>pom</packaging>
<modules>
<module>core-app</module>
<module>dda</module>
<module>saver</module>
<module>chart</module>
</modules>
<build>
<pluginManagement>
<plugins>
Descripción de la propuesta
114
114
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<filters>
<filter>src/main/filters/conf-${environment}.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<profiles>
<profile>
<id>development</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<environment>development</environment>
</properties>
</profile>
<profile>
<id>final</id>
<properties>
<environment>final</environment>
</properties>
</profile>
</profiles>
En las líneas superiores tenemos el contenido principal del fichero pom.xml en el que se define este proyecto
maven como un módulo de medipi-suite y padre, a su vez, de cuatro proyectos: dda, saver, chart y core-app.
La sección build de un pom.xml se emplear para informar de un conjunto de configuraciones o
parametrizaciones comunes a emplear durante la compilación del proyecto. En este caso, se está indicando el so
un nuevo plugin de maven a todos los proyectos hijos de medipi-app: maven-plugin-jar. Este plugin no hace
más que empaquetar en un fichero .jar las clases java compiladas. Cuando un proyecto se define como de tipo
jar Maven automáticamente llama a este plugin tras las fases iniciales, generando un jar. Sin embargo, puede
resultar de interés llamar al plugin en otros momentos. Tal y como vimos en el capítulo teórico de Maven, el
ciclo de trabajo de la herramienta pasa por la compilación y ejecución de las clases de pruebas (que por
convención se ubican, salvo modificación explícita, en src/test/…) pero no las empaqueta en un jar. Es aquí
donde entra la configuración realizada: en el pom estamos indicando a Maven que llame al plugin jar en la fase
de test-jar, provocando que se genere un fichero jar con todos los tests. Se pretende con esto que todas las
aplicaciones de MEDIPi tengan un catálogo de pruebas asociadas que puedan ser ejecutadas mediante la
invocación adecuada de este jar.
115
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
115
El siguiente punto son los filtros y los perfiles, que comentaremos conjuntamente. Para entender el trabajo
realizado vamos a comenzar comentando cada funcionalidad empleada de manera independiente.
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>health-dom</artifactId>
<version>${project.version}</version>
</dependency>
En las líneas superiores, correspondientes al pom.xml de datacq-dom, se está indicando que el proyecto health-
dom debe incorporarse como librería externa el proyecto actual para su correcto funcionamiento. Sin embargo,
no se está definiendo explícitamente el proyecto, si no que se están empleando “variables” que Maven es capaz
de entender y traducir. Cuando se indica ${project.groupId} Maven reconoce la sintaxis ${} y busca el valor
con el que se corresponde project.groupId. En este caso se trata de una variable propia de Maven, que hace
referencia al groupId del pom.xml del proyecto, pero es perfectamente posible definir nuevas “variables”, de
forma que puedan usarse no solo en el pom.xml, sino también en otros puntos del desarrollo. Para ello se emplea
la sección properties. A continuación, tenemos un ejemplo en el que definimos dos variables: Cluster (de valor
Kafka) y hardware (de valor Raspberry).
<properties>
<Cluster>Kafka</Cluster>
<hardware>Raspberry</hardware>
</properties>
Podemos definir un filtro indicando, con las etiquetas adecuadas en el pom.xml, el fichero de texto en el que se
ubican las variables que se quieren declarar y sus valores. Gracias a esto Maven las reconocerá y permitirá
realizar la traducción en el pom.xml siempre y cuando se utilicen las variables con la sintaxis adecuada. El
siguiente paso es no “traducir” solo en el pom.xml, si no tener la capacidad de utilizar estas variables en el resto
de recursos del proyecto. Para ello se activa la propiedad filtering a true en la sección resources.
Otra funcionalidad de maven empleada en este proyecto son los perfiles. Mediante la etiqueta profiles podemos
generar un catálogo de perfiles, identificados unívocamente por un id, que contienen cada uno una configuración
determinada. Por tanto, los perfiles nos permiten personalizar aún más la construcción del proyecto. Por ejemplo,
podemos crear un perfil denominado Pruebas y ubicar en él la configuración del plugin maven-jar-plugin que se
ha comentado. Cuando llevamos a cabo alguna fase de Maven es necesario indicar el perfil, excepto cuando solo
hay un perfil o alguno de ellos está marcado como activo por defecto.
Por último, los ficheros .properties son un tipo de recursos ampliamente usados en Java como método de
definición de variables o de parámetros de configuración, y tienen la siguiente estructura.
log_path = /opt/medipi/logs/
log_filename = dda.log
db_connection_ip = localhost
...
Todo lo anterior se va a combinar para formar un mecanismo de parametrización de las aplicaciones. No toda la
parametrización de una aplicación debe estar en base de datos, al contrario, solo tiene sentido que esté en base
de datos aquella que se pretenda modificar durante la ejecución del software. Para definir una serie de variables
u opciones de configuración que no se esperan que cambien es más adecuado utilizar los ficheros .properties.
El flujo de trabajo es el siguiente: al ejecutar el software desde el IDE de desarrollo o al generar el jar compilado
se debe especificar uno de los dos perfiles posibles: development o final. Si no se indica ninguno se toma
development como perfil seleccionado, al ser el valor por defecto. El perfil seleccionado da el valor
Descripción de la propuesta
116
116
correspondiente a la variable environmet. Observamos dónde se está usando esa variable: en la definición del
fichero que usaremos de filtro. Es decir, en el proyecto deben existir dos archivos filtros ubicados en
src/main/filters: conf-development.properties y conf-final.properties. Elegir un perfil u otro está repercutiendo
en elegir entre un filtro u otro. En dicho filtro definimos una serie de valores, como se ve en el siguiente ejemplo.
log_path_value = D:/MEDIPI/logs/
log_filename_value=chart.log
Los dos filtros deben tener las mismas variables, aunque puedan tener diferente valor. Maven ahora reconoce
las variables log_path_value, log_filename_value, etc. de forma que será capaz de sustituir
${log_filename_value} por chart.log tanto en el pom.xml como en los recursos, gracias a que activamos dicha
opción. ¿Qué utilidad tiene esto? Queremos poder cambiar la ruta del log, pero no es una buena práctica realizar
un desarrollo en el que leamos de un fichero u otro según el perfil seleccionado, si no que debemos acceder
solamente a uno y que este properties vaya cambiando de valores en función del perfil actual. Para ello se ha
definido un archivo denominado configuration.properties. Su contenido tiene el siguiente aspecto.
log_path = ${log_path_value}
log_filename = ${log_filename_value}
Al ser un recurso, Maven lo interpreta y traduce las variables que en él encuentre, por lo que desde el punto de
vista del código Java se vería de la siguiente manera,
log_path = D:/MEDIPI/logs/
log_filename = chart.log
Independientemente de cual sea el perfil, la aplicación solo tiene que leer el valor almacenado en el fichero
configuration.properties. En resumen, el fichero configuration.properties hace uso de las variables creadas en el
filtro (con un valor diferente según el perfil indicado), ofreciendo una capa de abstracción al código.
Hay que tener claro que el fichero configuration.properties no define nuevas variables Maven, no es un filtro,
solo es un recurso que leerá la aplicación Java como considere oportuno. Además, no estamos filtrando solo este
fichero, estamos filtrando todos los resources, por lo que podremos hacer uso de las variables declaradas en los
filtros en otros ficheros, algo que se verá más adelante.
5.5.5.1 CORE-APP
5.5.5.1.1 Funcionalidad de la librerí a
Al trabajar con varias aplicaciones que comparten una arquitectura común y pretenden pertenecer a una misma
suite de proyectos, es lógico plantear la necesidad de unificar aquellos desarrollos que se consideran estructurales
o que sabemos a ciencia cierta que se deben abordar por más de una aplicación. Es esta la utilidad del Proyecto
core-app.
117
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
117
Figura 5-27. Proyecto core-app
5.5.5.1.2 Configuracio n de proyecto Maven
Core-app queda definido como proyecto Maven mediante el sencillo pom.xml mostrado en las líneas posteriores.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-app</artifactId>
<version>1.0</version>
</parent>
<artifactId>core-app</artifactId>
<packaging>jar</packaging>
<name>core-app</name>
5.5.5.1.3 Co digo Java
5.5.5.1.3.1 Configuración del Log
Uno de los elementos comunes a todas las aplicaciones será la configuración del log. Mediante unas sencillas
instrucciones se carga el fichero de configuración de log y se inicia.
public class LogConfigurator {
public static void loadConfiguration(Class<?> initialClass) {
// Configure log4j
String log4jFile = AppConstants.LOG_CONF_FILE;
try {
Properties logProperties = new Properties();
logProperties.load(initialClass.getClassLoader().getResourceAsStre
am(log4jFile));
PropertyConfigurator.configure(logProperties);
} catch (Exception e) {
System.err.println("Error reading Log4j configuration file " +
log4jFile);
e.printStackTrace();
System.exit(1);
}
}
}
El único punto de interés en el desarrollo anterior es la ubicación del fichero de configuración del log, ya que
este método debe ser empleable por muchas aplicaciones a la vez. En la constante LOG_CONF_FILE se está
indicando, como su propio nombre indica, el fichero de configuración: log4j.properties. Sin embargo, ¿cómo
conocemos su ubicación? Para resolver esta pregunta deben tenerse en cuenta varios conceptos importantes.
Descripción de la propuesta
118
118
Figura 5-28. Estructura de un proyecto durante el desarrollo vs compilado
El método recibe la clase origen y recupera su ClassLoader. El Java Classloader (en español, cargador de clases
Java) es una parte del Java Runtime Environment que carga dinámicamente clases o recursos en la máquina
virtual, definiendo un recurso (resource) como un fichero de datos (imágenes, audio, texto, etc.) al que se puede
acceder desde el código java de una clase. El ClassLoader recuperado nos ofrece métodos para cargar recursos
como getResource() o getResourceAsStream(). Para ello debemos indicarle la ruta relativa desde el ClassLoader
(y no desde el class) hasta el fichero que se quiere leer. Un ClassLoader se ubica siempre en el classpath, en el
directorio base de nuestro proyecto. En la imagen superior, a la derecha, tenemos la estructura resultante de
compilar el proyecto Saver, donde observamos que el fichero log4j.properties está ubicado en el directorio base,
lugar desde donde arranca la ruta relativa del ClassLoader. Es por eso por lo que basta con indicar el nombre del
fichero. Si esta situación cambia, habrá que indicar las carpetas intermedias entre el directorio base y el fichero
a cargar. Solo queda por comprender una cosa: existe una evidente diferencia entre la estructura de carpetas de
la izquierda (durante el desarrollo) y la de la derecha (contenido del jar generado). Esto se debe a que la estructura
de carpetas src/main/java, src/test/java, src/main/resources, etc. son un conjunto de convenciones que establece
Maven para facilitar el desarrollo. Cuando se compila el proyecto la información contenida en
src/main/resources se mueve al directorio base, a no ser que se indique una configuración distinta en el pom.xml.
5.5.5.1.3.2 Properties
Toda aplicación de MEDIPi contará con un fichero denominado configuration.properties en el que los
desarrolladores ubicarán los parámetros de configuración que no vayan a ser modificados frecuentemente, El
proyecto core-app proporcionará una interfaz de acceso común a ese fichero; es decir, mantendrá una clase,
denominada PropertiesReader, con una serie de métodos que permitan acceder a los parámetros indicados en
ese fichero, de forma que se puedan usar sin necesidad de conocer la ubicación del mismo.
Se ha seguido este diseño por razones ya comentadas previamente: la clase PropertiesReader deberá ser
modificada si se realizan cambios estructurales como emplear más de un fichero de configuración, etc. Pero será
esta la única clase que deberá modificarse, no será necesario recorrer todo el desarrollo buscando dónde se leía
el fichero de configuración e ir haciendo las correcciones necesarias. Por tanto, se construye un modelo más
escalable que además es menos propenso a errores ante cualquier cambio o modificación.
En resumen, manejaremos una clase PropertiesReader encargada de leer el fichero configuration.properties.
Además, también se proporciona la capacidad de emplear un segundo fichero de configuración
(extra_configuration.properties). Este fichero se ubicará, en caso de existir, fuera del jar, permitiendo al
desarrollador sobrescribir los parámetros que considere necesarios.
Para llevar a cabo esta funcionalidad la clase mantendrá un atributo estático de tipo java.util.Properties. Este tipo
de objetos Java permiten interactuar con los ficheros .properties redactados en diversos formatos, entre ellos el
empleado para nuestros proyectos (nombre_parametro = valor_parametro).
Además, la clase contará con un bloque estático. Los bloques de código estáticos se ejecutan una sola vez, en la
primera llamada a cualquier método de la clase (incluyendo un constructor). Ahí se leerá del fichero de
119
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
119
configuración, cargando su contenido en el objeto Properties. Para ilustrarlo se incluye a continuación una
versión reducida del código.
private static Properties props;
static {
if (props == null) {
try {
loadProperties();
loadExtraProperties();
} catch (IOException e) {
LOG.error("Error loading configuration properties", e);
System.exit(1);
}
}
}
private static void loadProperties() throws IOException {
props = new Properties();
try {
InputStream inputStream = PropertiesReader.class.getClassLoader()
.getResourceAsStream(AppConstants.PROP_FILE_NAME);
props.load(inputStream);
} catch (Exception e) {
throw new FileNotFoundException("Property file '" +
AppConstants.PROP_FILE_NAME + "' incorrect or not found in the
classpath");
}
}
private static void loadExtraProperties() throws IOException {
File file = new File(AppConstants.EXTRA_PROP_FILE_NAME);
if (file.exists()) {
InputStreamReader inputStream = new InputStreamReader(new
FileInputStream(file), Charset.forName("UTF-8"));
props.load(inputStream);
}
}
Simplemente, en la primera llamada a cualquier método de la clase se invoca a los métodos encargados de poblar
el objeto properties tanto con el fichero de configuración regular como con el fichero de configuración extra.
Existen pequeñas pero importantes diferencias entre ellos. En loadProperties() se reinicia siempre el objeto
Properties antes de cargar la parametrización. Posteriormente se genera un InputStream del fichero, siguiendo
el mismo método de acceso visto previamente para el log (algo lógico pues ambos ficheros se encontrarán en el
mismo directorio). Como se está empleando un formato “estándar” de java, basta con llamar al método load del
objeto Properties. Por su parte, en loadExtraProperties() no se reinicia el objeto properties, por lo que los
parámetros que se lean del fichero de configuración extra se añadirán o sobrescribirán. En esta ocasión si el
fichero no existe no se provoca ningún error. Además, la instrucción para recuperar el InputStream asociado es
totalmente diferente. La instrucción new FileInputStream utilizará como ruta el “working directory”; es decir,
el directorio del sistema de ficheros desde el cual se invocó a la instrucción java. Por tanto, para que se carguen
adecuadamente sus parámetros, el fichero extra_configuration.properties deberá colocarse en el mismo
directorio en el que se ejecute la llamada a Java.
Para terminar, PropertiesReader contará con un método que recupere el valor de un parámetro a través de su
nombre.
public static String getProperty(String name) {
String value = props.getProperty(name);
if (value == null) {
LOG.error("Error reading " + name + " property . Stopping JVM");
}
return value;
Descripción de la propuesta
120
120
}
5.5.5.1.3.3 Paquete Util
En la clase AppConstants se declaran todas las constantes utilizadas por este proyecto.
5.5.5.2 DDA
5.5.5.2.1 Funcionalidad de la aplicacio n DDA
DDA son las siglas de Device Data Acquisition, el módulo de MEDIPi encargado de establecer comunicación
con los biodispositivos, a partir de un conjunto de protocolos específicos mayoritariamente propietarios,
recuperar la información biomédica del paciente, adaptarla a un formato propio y enviarla al Cluster de MEDIPi
para que pueda ser consumida por otras aplicaciones de la suite.
Figura 5-29. Proyecto DDA
5.5.5.2.2 Configuracio n de proyecto Maven
DDA es un proyecto módulo de medipi-app, que además empleará core-app como dependencia para poder usar
toda la funcionalidad común implementada por la misma. Además, tal y como vimos al principio del presente
apartado dedicado al Software, debe incluir core-Cluster para gestionar la comunicación con el Cluster Kafka
de MEDIPi.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-app</artifactId>
<version>1.0</version>
</parent>
<artifactId>dda</artifactId>
<packaging>jar</packaging>
<name>dda</name>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
</plugin>
</plugins>
</pluginManagement>
121
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
121
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>${project.groupId}-
${project.artifactId}</finalName>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Dependencias de MEDIPi -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>datacq-dom</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core-Cluster</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core-app</artifactId>
<version>${project.version}</version>
</dependency>
<!-- PROTOCOLOS -->
</dependencies>
En el pom.xml del proyecto se emplea un plugin de maven no usado hasta el momento: maven-assembly-plugin
asociado a la fase package de Maven. Este plugin construye una versión empaquetada de la solución de acuerdo
a una serie de reglas definidas en el archivo assembly.xml indicado. En este caso se pretende generar un fichero
.zip con los .jar necesarios para ejecutar DDA y los scripts de arranque (ubicados en src/main/resources), de
forma que sea más sencillo el despliegue y uso de la solución en el hardware de integración elegido.
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-
plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>package</id>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>/bin</outputDirectory>
<includes>
<include>*.sh</include>
Descripción de la propuesta
122
122
<include>*.bat</include>
</includes>
<lineEnding>unix</lineEnding>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>/lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>
El fichero comienza indicando un identificador único de la construcción configurada. Este id se añadirá al
nombre del proyecto (artifact-id) para dar lugar al nombre del archivo/s generado/s, en este caso dda-
package.zip. Su utilidad radica en que podemos tener varios ficheros de assembly (uno por construcción) de
forma que el id nos sirva para identificar la solución generada. Con la etiqueta formats se indica el formato o
formatos en los que se empaquetará la solución construida. Es decir, si quisiéramos generar un dda-
package.tar.gz bastaría con añadir este formato a la lista, sin necesidad de duplicar el assembly.
La sección fileSets sirve para parametrizar el tratamiento que se le va a realizar a uno o varios ficheros no Java
del proyecto. Para cada conjunto de ficheros con configuración común se debe declarar un fileSet. Para el
proyecto DDA es necesario que todos los ejecutables (aquellos ficheros .sh y .bat de la carpeta de resources de
Maven) se copien a una carpeta bin en el directorio raíz del zip. Es interesante destacar que se pueden realizar
varias configuraciones específicas sobre los ficheros tratados, como se observa en el assembly.xml cuando se ha
parametrizado que todos los ficheros tratados utilicen la codificación de fin de línea de unix.
Para terminar, se puede realizar un tratamiento parecido al visto con los ficheros no Java para las dependencias,
con dependencySets y dependecySet. En este caso basta con especificar que se quiere que todas las librerías
Java (incluyendo el jar correspondiente al presente proyecto) se ubiquen en un directorio determinado.
5.5.5.2.3 Co digo Java
DDA es, sin lugar a duda, la aplicación más compleja de las que componen la suite MEDIPi. Detallar sus
funcionalidades y las decisiones técnicas tomadas durante el desarrollo implicará tener primero una visión
general de todas las tareas implicadas.
A grandes rasgos, DDA cuenta con cuatro secciones o agrupaciones de funcionalidad diferenciadas:
- Configuración de las utilidades o elementos empleados: la base de datos, el log o el acceso a los
properties son elementos que se deben configurar, cada uno a su manera.
- ProtocolReader: cada instancia de DDA se identifica de forma unívoca. Mediante este identificador la
aplicación deberá ir a base de datos y conocer sus asignaciones actuales. Será necesario mantener un
conjunto de ProtocolReaders (implementaciones de protocolos), de acuerdo a las asignaciones actuales.
Todos los ProtocolReader emplearán una serie de convenciones que permitan estandarizar la
información recuperada.
- CommandListener: DDA arranca un servidor para atender a peticiones de actualización de
asignaciones, refresco de caché, etc. Por ejemplo, una aplicación externa informará al servidor de que
ha habido un cambio de dispositivo asignado. Gracias a esto DDA podrá llamar nuevamente al
ProtocolReaderManager para que compare los lectores actuales con los requeridos según las
asignaciones de la base de datos.
- DataSender: La información biomédica recuperada se envía a un DataSenderManager. Un DataSender
no es más que un conjunto de clases preparadas para recibir los datos y almacenarlas / enviarlas a un
lugar determinado. El clúster Kafka es el DataSender principal.
- Util: clase para métodos comunes
123
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
123
Figura 5-30. Esquema de DDA
Para facilitar las secciones comentadas se ha diseñado el esquema previo, en el que se muestra un diagrama de
flujo de los paquetes / clases / métodos fundamentales del proyecto. De un primer vistazo es posible ubicar cada
agrupación de funcionalidad en el gráfico, además de obtener una idea aproximada de las conexiones a base de
datos y al Cluster.
5.5.5.2.3.1 Configuraciones
5.5.5.2.3.1.1 Log
Para configurar el log simplemente basta con ubicar el fichero log4j.properties en la carpeta de recursos del
proyecto y llamar a la utilidad de core-app comentada en su apartado correspondiente.
LogConfigurator.loadConfiguration(Main.class);
5.5.5.2.3.1.2 Properties
Esta función también está regulada por core-app, solo que no necesita que se llame a ningún método explícito
para arrancarla. Como se vio anteriormente, basta con llevar a cabo la lectura de un parámetro para que se hagan
las operaciones iniciales necesarias. DDA tiene, al igual que el resto de aplicaciones que usan core-app, los
properties configuration.properties, configuration-development.properties y configuration-final.properties
necesarios para que la lógica de core-app funcione.
5.5.5.2.3.1.3 Base de datos
El funcionamiento de la base de datos es ligeramente parecido al de los properties. Previamente se detalló el
patrón de diseño empleado en el acceso a base de datos (4.5.3.1.3 DBManager y 4.5.3.1.3 DAOs y Services).
Descripción de la propuesta
124
124
En resumen, los DAOs realizan las acciones sobre la base de datos (y tenemos un DAO por entidad), siendo
todas ellas accesibles a través de un objeto Service denominado DDAConfiguration.
public class AllocationDAO extends AbstractDAO<Allocation> {
public AllocationDAO(DBManager myDBManager) {
super(myDBManager);
}
private static final Logger LOG = Logger.getLogger(AllocationDAO.class);
private static AllocationDAO instance = new
AllocationDAO(DDADBManager.getInstance());
public static AllocationDAO getInstance() {
return instance;
}
@SuppressWarnings("unchecked")
public List<Allocation> getActualAllocationsByInstance(Long medipiInstanceId) {
List<Allocation> res = new ArrayList<Allocation>();
QAllocation qallo = QAllocation.allocation;
EntityManager em = getEntityManager();
try {
Date now = new Date();
JPAQuery<?> query = new JPAQuery<Allocation>(em);
query.from(qallo);
query.where(qallo.medipiInstance.medipiInstanceId.
eq(medipiInstanceId));
query.where(qallo.startDate.before(now));
query.where(qallo.endDate.after(now).or(qallo.endDate.isNull()));
query.where(qallo.deleted.isFalse());
res = (List<Allocation>) query.fetch();
} catch (Exception e) {
LOG.error("Error executing a query: " + e);
}
em.close();
return res;
}
}
Todos los DAOs tienen una estructura similar: cuentan con un atributo estático denominado instance que
devuelve un objeto de esta clase. Este patrón de diseño se denomina singleton (instancia única en inglés) y está
diseñado para restringir la creación de objetos pertenecientes a una clase o el valor de un tipo a un único objeto.
Su intención consiste en garantizar que una clase sólo tenga una instancia y proporcionar un punto de acceso
global a ella. Esta instancia se crea con el constructor definido, que llama al constructor del AbstractDAO. Dicho
constructor requiere que se le informe de una implementación de la instancia. En el caso actual se empleará
DDADBManager. Junto a los métodos ya comentados se encontrarán los creados específicamente para realizar
una acción concreta, generalmente una consulta.
En getActualAllocationsByInstance(Long medipiInstanceId) podemos observar un caso concreto de QueryDSL
para generar una consulta. Basta con instanciar un objeto JPAQuery, indicarle la entidad principal de la consulta
y especificar el EntityManager. Los objetos JPAQuery nos proporcionan un conjunto de métodos para simular
la sintaxis SQL (from, where, etc.). Para hacer referencia a las entidades debe usarse las QClass autogeneradas,
las cuales también cuentan con un conjunto de métodos que nos permiten la comparación con valores u otros
atributos. Gracias a todo esto podemos lanzar una consulta con una sintaxis muy parecido a SQL, que nos
proporcionará los resultados en los objetos JPA mapeados.
. . .
private static AllocationDAO allocationDAO = AllocationDAO.getInstance();
. . .
125
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
125
public static synchronized List<Allocation> getActualAllocations() {
List<Allocation> res = new ArrayList<Allocation>();
try {
MedipiInstance myInstance = getMyInstance();
res = allocationDAO.getActualAllocationsByInstance
(myInstance.getMedipiInstanceId());
} catch (Exception e) {
LOG.error("Allocation for this instance can not be found", e);
}
return res;
}
El objeto DDAConfiguration simplemente se encarga de gestionar el singleton de cada DAO empleado,
ofreciendo a las clases un acceso común a través de sus métodos estáticos y sincronizados.
public class DDADBManager extends AbstractDBManager {
public DDADBManager(String persistenceUnit, String queryCacheRegion) {
super(persistenceUnit, queryCacheRegion);
}
private static DDADBManager instance = new
DDADBManager(DDAConstants.PERSISTENCE_UNIT,
DDAConstants.QUERY_CACHE_REGION);
public static DDADBManager getInstance() {
return instance;
}
public static void cleanProtocolConstantMapCache() {
instance.cleanCache(DataAcqConstants.CACHE_PROTOCOL_CONSTANT_MAP);
}
public static void cleanConstantCache() {
instance.cleanCache(HealthConstants.CACHE_CONSTANT);
}
public static void cleanQueryCache() {
instance.cleanCache(instance.getQueryCacheRegion());
}
}
En la clase de gestión de base de datos también se emplea el patrón singleton. Hasta ahora no se ha comentado
en qué punto de la aplicación se llama al constructor de DDADBManager, provocando que se inicialice la
comunicación con la base de datos. Al generarse estáticamente una instancia de esta clase se está llamando al
constructor del AbstractDBManager, especificando el persistenceUnit y la queryCacheRegion de este proyecto,
quien creará los objetos de gestión de base de datos y caché adecuados, tal y como se comentó adecuadamente
en su apartado correspondiente. Por tanto, cuando se realice la primera llamada a un método (estático) de
DDADBManager (como, por ejemplo, getInstance()) automáticamente se crea la instancia, llamando al
constructor del abstract e inicializando la conexión correctamente. La creación de todos los DAOs necesita
recibir una instancia de un objeto de interfaz DBManager, como se vio anteriormente, por lo que esa será con
frecuencia la instrucción que accederá a DDADBManager por primera vez, creando la instancia.
Por otra parte, no hay muchas más novedades con respecto al abstract: simplemente se proponen algunos
métodos para limpiar las cachés propias de esta aplicación.
Queda una parte importante de la gestión de la base de datos por comentar. Hasta ahora se ha ido desgranando
la configuración de la base de datos, comentando la capa correspondiente a cada librería o proyecto de MEDIPi.
Es por ese motivo por el que se ha dejado para el final la capa básica, aquella encargada de parametrizar o
configurar la conexión (indicar la ip de la base de datos, el esquema, las regiones de caché, etc.). Cada aplicación
podrá personalizar la suya, indicando simplemente el persistenceUnit y la queryCacheRegion al
Descripción de la propuesta
126
126
AbstractDBManager que actúa a modo de configurador común.
Para parametrizar una conexión a base de datos con JPA se ha de emplear un fichero denominado
persistence.xml y ubicado en directorio-base-proyecto/META-INF. En este fichero, mediante una sintaxis
determinada que veremos a continuación, se especifica toda la configuración necesaria.
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="medipi.dda" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- HEALTH -->
<class>medipi.health.dom.Patient</class>
<class>medipi.health.dom.Episode</class>
<class>medipi.health.dom.Constant</class>
<class>medipi.health.dom.ConstantInfo</class>
<!-- DATA_ACQ -->
<class>medipi.datacq.dom.Allocation</class>
<class>medipi.datacq.dom.Device</class>
<class>medipi.datacq.dom.DeviceModel</class>
<class>medipi.datacq.dom.DeviceType</class>
<class>medipi.datacq.dom.MedipiInstance</class>
<class>medipi.datacq.dom.Parameter</class>
<class>medipi.datacq.dom.Protocol</class>
<class>medipi.datacq.dom.ProtocolConstantMap</class>
<properties>
<!-- JPA 2.0 Standard -->
<property name="javax.persistence.jdbc.driver"
value="oracle.jdbc.driver.OracleDriver" />
<property name="javax.persistence.jdbc.url"
value="jdbc:oracle:thin:@${db_ip}:${db_port}:${db_sid}" />
. . .
</properties>
</persistence-unit>
</persistence>
En un persistence.xml podemos definir tantos persistence-unit como queramos. Cada persistence-unit es una
parametrización diferente, pudiendo variar cada uno de sus argumentos: base de datos, parámetros de hibernate,
caché, entidades reconocidas, etc. El nombre del persistence-unit es el identificador que el
EntityManagerFactory necesita conocer para poder generar las EntityManager. Junto al identificador se ha de
especificar el tipo de transacción empleada. Este es un asunto complejo e implica el conocimiento de varios
elementos de JPA a bajo nivel, como la caché interna de JPA [70]. Simplificando, con el tipo JTA se puede
instanciar EntityManagers (aunque nunca una EntityManagerFactory) y cada EntityManager estará asociada a
una transacción. Será necesaria la existencia de una capa de negocio o servicio (siendo el estándar EJB y lo más
empleado Spring Framework, etc.) que se comunicarán con JPA para gestionar dichas transacciones, pudiendo
el desarrollador indicar determinadas políticas de las mismas mediante anotaciones. Por otra parte,
RESOURCE_LOCAL delega en el desarrollador la tarea de crear los EntityManagers que considere necesarios
a partir de un EntityManagerFactory y de gestionar sus transacciones. Deberá crearse la transacción, realizarse
los commits y rollback adecuados según el programador considere. Esta opción es más artesanal, pero evita la
necesidad de implementar una capa de servicio innecesaria para los proyectos actuales. Además, ofrece mucha
más versatilidad al dar más control al desarrollador, motivos por los que se ha empleado. Conviene darse cuenta
de que está decisión es coherente con lo desarrollado en el AbstractDAO de core-dom, donde se gestiona la
127
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
127
transacción manualmente.
Para cada persistence-unit indicamos qué implementación de JPA se va a usar, pues JPA no es más que el
estándar, la API que Hibernate implementa. A continuación, se indican las entidades JPA que este persistence-
unit manejará.
La sección de properties es la principal: en ella se encuentra la mayor parte de la configuración. Para rellenar
muchos de los parámetros se emplean variables de Maven. Recordemos que el persistence.xml se encuentra en
una carpeta dentro de la ruta de resources de Maven y que dicho directorio está siendo filtrado, sustituyendo las
variables con sintaxis ${variable} por el valor indicado en el filtro correspondiente al perfil actual. De esta
manera podemos cambiar muchos parámetros de configuración de acuerdo al perfil, de una manera ágil y sin
inconsistencias.
Nivel Parámetro Valor Descripción
javax.persistence. jdbc.driver oracle.jdbc.driver.OracleDriver Driver de JDBC
javax.persistence. jdbc.url jdbc:oracle:thin:@${db_ip}:${
db_port}:${db_sid} Conexión a BBDD
javax.persistence. jdbc.user ${db_user} Usuario para la
conexión
javax.persistence. jdbc.password ${db_password} Password para la
conexión
hibernate. cache.provider_class org.cache.EhCacheProvider Proveeder de la caché
de segundo nivel.
hibernate. dialect org.dialect.Oracle10gDialect
Dialecto
(Especificación) de la
base de datos empleada
hibernate. default_schema ${db_user} Esquema
hibernate. generate_statistics true Generar estadísticas
hibernate. cache.use_structured_en
tries true
Almacena datos en un
formato más
visualizable / explotable
hibernate. cache.use_minimal_puts true Optimizador
hibernate. format_sql true
Formatear las consultas
SQL para una mejor
explotación
hibernate. show_sql ${hibernate_sql_value} Visualización de las
consultas en el LOG.
hibernate. cache.region.factory_cla
ss
org.cache.ehcache.EhCacheReg
ionFactory
Clase de la factoría de
regiones de caché
hibernate. cache.use_second_level
_cache true
Emplear caché de
segundo nivel
Descripción de la propuesta
128
128
hibernate. cache.use_query_cache true Usar caché de consultas
net.sf.ehcache. configurationResourceN
ame ehcache.xml Descriptor de la caché
De los parámetros solo es interesante destacar la configuración de la caché de segundo nivel de Hibernate,
indicándole que proveedor usaremos y en qué fichero se configura.
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<defaultCache eternal="false" maxElementsInMemory="100"
timeToLiveSeconds="86400" overflowToDisk="false" diskPersistent="false"
/>
<cache name="constant" maxElementsInMemory="100" eternal="false"
timeToLiveSeconds="86400" overflowToDisk="false" diskPersistent="false"
/>
<cache name="protocolConstantMap" maxElementsInMemory="50"
eternal="false" timeToLiveSeconds="86400" overflowToDisk="false"
diskPersistent="false" />
<cache name="queries" maxElementsInMemory="50" eternal="false"
timeToLiveSeconds="86400" overflowToDisk="false" diskPersistent="false"
/>
</ehcache>
La configuración de la caché es muy sencilla. EhCaché maneja un concepto denominado regiones de caché, de
forma que crea varias pequeñas cachés independientes. Cada una de ellas, incluyendo la caché por defecto, se
parametrizan aquí.
Nombre Descripción
name Identificador de la región de caché
eternal Caché permanente
maxElementsInMemory
Número de elementos máximos en memoria. Si una caché alcanza este número
de elementos, los nuevos registros se incluirán en la caché eliminando los más
antiguos.
timeToLiveSeconds Cuando un elemento supera este timeout se borra automáticamente de la caché.
overflowToDisk Si se supera el umbral definido por maxElementsInMemory se usará el disco
duro (y no la memoria RAM) como soporte para almacenar más datos.
diskPersistent Controla que la parte de la caché almacenada en el disco duro no se elimina al
parar la aplicación.
Para hacer referencia a estas regiones se han visto ya las dos opciones: indicar la región de la cache en el mapeo
de JPA (anotación @Cache) o indicar a una JPAQuery que debe incluir dicha consulta en una región
determinada (query. setHint (DBConstants. ACTIVE_QUERY_CACHE, true); y query. setHint (DBConstants.
ACTIVATE_QUERY_CACHE_REGION, myDBManager. getQueryCacheRegion());).
129
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
129
5.5.5.2.3.1.4 ReportNetwok
Cada instancia de DDA, cada ejecución de dicho programa se identifica unívocamente a través del hostname
del equipo sobre el que corre. Por tanto, la arquitectura de DDA no está pensada para arrancar dos aplicaciones
DDA en un mismo dispositivo. El motivo es claro: este módulo de MEDIPi se encarga de establecer una
comunicación con los biodispositivos a los que está conectado. En capítulos anteriores hemos definido que se
empleará una Raspberry Pi para correr nuestro programa, con la mayoría de los dispositivos conectados
directamente mediante una interfaz USB o RS232. Por tanto, no tiene sentido que haya dos programas a la vez
intentando comunicarse con un dispositivo, pues al leer o pretender escribir a la vez en un mismo puerto
provocaría que la información se corrompiera. Es por este razonamiento por el que se ha diseñado una
arquitectura basada en una ejecución de DDA, una máquina. Volviendo atrás, partimos de que se va a emplear
el hostname para identificar una instancia, pudiendo emplear dicho nombre de equipo para recuperar el registro
de base de datos de MEDIPI_INSTACE. A través de él podremos, por ejemplo, encontrar los registros en la
tabla ALLOCATION de dicha MEDIPI_INSTANCE asociados actualmente.
public static void reportNetwork() {
// Discover Ip
String ip = DDAUtils.discoverMyIpAddress();
String port = String.valueOf(PropertiesReader.
getProperty(DDAConstants.COMMAND_LISTENER_PORT));
String info = ip + DDAConstants.IP_PORT_SEPARATOR + port;
// If daemon has network connectivity report server port. If not report
// connection out
if (ip != null && !ip.isEmpty() && port != null && !port.isEmpty()) {
MedipiInstance myInstance = DDAConfiguration.getMyInstance();
myInstance.setIp(info);
DDAConfiguration.saveMedipiInstance(myInstance);
}
}
ReportNetwork no es más que un método ejecutado directamente desde el main de DDA para realizar la última
labor de configuración del sistema: identificar el hostname de su equipo y escribir en MEDIPI_INSTANCE la
dirección ip y el puerto con el que otros sistemas podrán comunicarse con él. Para ello se emplean dos sencillos
métodos: discoverMyIpAddress y getMyInstance. El primero es un método de DDAUtils que se encarga de
recuperar las interfaces de red (mediante NetworkInterface.getNetworkInterfaces()) y recorrerlas una a una hasta
encontrar aquella con una configuración IP válida. Por otro lado, getMyInstance recupera el hostname y acude
con él a base de datos para recuperar un objeto MedipiInstance que mapee dicho registro. Para recuperar el
hostname se lanza una instrucción en línea de comandos para posteriormente leer el resultado, mediante las
utilidades proporcionadas por Java para dicha tarea.
Process process = Runtime.getRuntime().exec("hostname");
BufferedReader reader = new BufferedReader(new
InputStreamReader(process.getInputStream()));
String hostname = reader.readLine().trim();
Basta con recuperar el puerto del CommandListener (ubicado en un properties y recuperable por tanto a través
de PropertiesReader) para tener toda la información necesaria antes de realizar la actualización en base de datos.
Descripción de la propuesta
130
130
5.5.5.2.3.2 ProtocolReader
Figura 5-31. ProtocolReader
El conjunto de clases que agrupamos bajo la categoría ProtocolReader constituye el núcleo de DDA, pues serán
las encargadas de realizar una implementación estructurada y estandarizada de los protocolos necesarios para
establecer la comunicación con los biodispositivos. En la imagen anterior observamos un esquema básico de
esta sección de DDA que iremos detallando a lo largo de este epígrafe. Atendiendo a dicha representación nos
encontramos con dos elementos claramente diferenciados.
5.5.5.2.3.2.1 ProtocolReader
Cada vez que se quiera establecer una comunicación con un dispositivo concreto será necesario llevar a cabo el
desarrollo Java de dicha integración. Para estandarizar esta tarea lo máximo posible se ha creado una interfaz
conocida como ProtocolReader, de forma que cada desarrollo de un protocolo concreto la implemente, teniendo
así una base común. De esta forma, el ProtocolReaderManager podrá trabajar con referencias a ProtocolReader,
independientemente de la clase concreta. Sin embargo, no nos bastará con una mera interfaz, será imprescindible
la creación de un Abstract que maneje la lógica común a todos los protocolos. Por ejemplo, todos los protocolos
deben construir un objeto representativo de la variable biomédica recuperada y enviarlo al apartado software de
DDA encargado de comunicarlo al clúster. Esta tarea será común a todos, por lo que es absurdo duplicar lógica
en las diferentes clases.
public interface ProtocolReader {
public ProtocolReader createAndConfigurate();
public void start();
public boolean stop();
public void sendObservation(String constantName, Object value, String unitName,
Date constantDate);
public void sendObservation(String constantName, Object[] value, String
unitName, Date startDate, Float periodobs);
public Allocation getAllocation();
public void setAllocation(Allocation allocation);
}
La interfaz fuerza la implementación de varios métodos. Los tres primeros (createAndConfigurate, start y stop)
se emplean simplemente para manejar el flujo de trabajo del protocolo. Justo tras instanciar una clase que
implemente ProtocolReader se llamará al método createAndConfigure donde cada protocolo realizará las tareas
necesarias para preparar la integración con el dispositivo. Por su parte, start y stop serán llamados por el
ProtocolReaderManager para arrancar o parar el ProtocolReader cuando sea necesario. Además de los métodos
131
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
131
ya comentados, ProtocolReader obliga a implementar getters y setters de la asignación, lo que implica que todo
lector de protocolos debe conocer la asignación concreta con la que se corresponde, U poco más adelante
veremos la utilidad de esta decisión. Para finalizar, define dos métodos sendObservation que los protocolos
emplearán cuando hayan recuperado una constante biomédica, realizando además su envío a otros puntos de
DDA.
Es el turno de analizar la clase AbstractProtocolReader. Esta clase va a contener solo dos atributos: un índice o
identificador único del ProtocolReader en el ProtocolReaderManager y la asignación de dispositivo a la que da
soporte. Este último venía prácticamente impuesto por la interfaz, que nos obligaba a implementar su getter y
setter. El índice solo será realmente útil cuando se vayan a instanciar dos o más ProtocolReaders iguales, de
forma que ambos tengan un identificador único que puedan emplear para instanciar otros objetos. Por su parte,
conocer la asignación es mucho más útil: es posible que dos modelos de dispositivos empleen el mismo
protocolo con algunas diferencias. En ese caso, el patrón de diseño recomendado es manejar el mismo
ProtocolReader, pero emplear el campo info del modelo para identificar sus particularidades. Para solucionar
esto el ProtocolReader conoce el dispositivo al que da soporte, sabiendo su modelo, tipo, etc.
protected int index;
protected Allocation allocation;
public AbstractProtocolReader(int index, Allocation allocation) {
this.index = index;
this.allocation = allocation;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public void setAllocation(Allocation allocation) {
this.allocation = allocation;
}
@Override
public Allocation getAllocation() {
return allocation;
}
Por otra parte, se definen los métodos sendObservation, encargados de recibir la información de una constante
biomédica del paciente, construir un objeto de tipo LFInfoConstant o HFInfoConstant y enviarlo a la clase
encargada de comunicarse con el clúster Kafka. La lógica realizada en estos métodos es muy sencilla: a partir
de los datos conocidos recuperamos la constante y el episodio al que hace referencia, construyendo el objeto
apropiado. En el caso de las LFInfoConstants se realiza una comprobación básica: si es una constante de tipo
numérica se ignorará la información si el valor se encuentra fuera de los rangos permitidos indicados en base de
datos. Tras esto, se envía el ConstantInfo al DataSenderManager, que se comentará más adelante.
@Override
public synchronized void sendObservation(String constantName, Object value,
String unitName, Date constantDate) {
String protocolCode = allocation.getDevice().getDeviceModel().
getProtocol().getCode();
Constant cnt = DDAConfiguration.
getConstantByProtocolMap(protocolCode, constantName);
Episode epi = allocation.getEpisode();
if (cnt != null && epi != null && valueFiltering(value, cnt)) {
LFConstantInfo observation = new
LFConstantInfo(cnt.getConstantId(), epi.getEpisodeId(),
constantDate, String.valueOf(value), false);
DataSenderManager.sendInfoToAll(observation);
Descripción de la propuesta
132
132
}
}
Con esto se define la arquitectura de clases de una implementación de un protocolo para la comunicación con
biodispositivos. Se han definido un conjunto de políticas y mecanismos que se deberán seguir mediante la
herencia de la clase AbstractProtocolReader. Al margen de eso, será tarea de cada implementación lidiar con las
particularidades de su protocolo.
5.5.5.2.3.2.2 ProtocolReaderManager
Clase encargada de ir a base de datos, recuperar los dispositivos asignados a la instancia actual, encontrar los
ProtocolReaders asociados a dichos dispositivos y realizar las operaciones necesarias para cuadrar los
ProtocolReaders actuales del sistema con los esperados de acuerdo a la configuración de base de datos. Es, por
tanto, una clase gestora, de forma que sus métodos servirán de punto de acceso para otras clases de DDA. Para
evitar tener que instanciar un objeto de esta clase cada vez que se quiere refrescar, parar o arrancar un lector de
protocolos se emplearán métodos y atributos estáticos.
La base de ProtocolReaderManager es sencilla: se va a mantener un mapa con todos los ProtocolReader que
corren actualmente en el sistema, de forma que podrá comparar dicho mapa con la información proporcionada
por la base de datos.
Map<Integer, ProtocolReader> protocolReaderMap = new LinkedHashMap<Integer,
ProtocolReader>();
ProtocolReader cuenta con un conjunto de métodos estáticos para cubrir la funcionalidad descrita:
- getProtocolReaderByAllocation: a través de un objeto Allocation (mapeo JPA de un registro de la
tabla ALLOCATION) es posible acceder al modelo, tipo y protocolo del dispositivo concreto asociado.
Con esta información, el método getProtocolReaderByAllocation se encarga de encontrar la
implementación de ProtocolReader más adecuada. Como es evidente, existirá una implementación de
ProtocolReader para cada protocolo, por lo que el núcleo del método no será más que un conjunto de
if/else en función del código del protocolo del dispositivo.
- startReaders: es probablemente el método más básico de ProtocolReaderManager. Como se puede
deducir, se encarga simplemente de recuperar las asignaciones actuales en base de datos y comprobar
si ya se están manejando mediante algún ProtocolReader. Para aquellas asignaciones nuevas se busca e
instancia el ProtocolReader adecuado (mediante una llamada a getProtocolReaderByAllocation). Este
ProtocolReader se añade al mapa para futuras comprobaciones.
- conditionalStopReaders: realiza la tarea inversa al método anterior: recorre el mapa de
ProtocolReaders actual para ver si hay alguna asignación que ya no esté en base de datos, procediendo
a pararla.
- forceStopReaders: para todos los ProtocolReader actualmente arrancados, independientemente de lo
que indique la base de datos.
- updateReaders: comprueba que los ProtocolReaders son los adecuados mediante una parada
condicional, una limpieza de la caché de consultas y el arranque de los readers.
- restart: fuerza la parada de todos los ProtocolReaders actuales, limpia todas las cachés del sistema y
vuelve a arrancar los lectores de protocolos.
- getProtocolReaderByIndex: simplemente accede al mapa, devolviendo el ProtocolReader
correspondiente al índice especificado.
- getters y setters del mapa protocolReaderMap.
133
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
133
Los distintos métodos de ProtocolReaderManager son llamados desde varios puntos de DDA, destacando la
llamada a startReaders desde el main y las posibles llamadas a updateReaders y restart por el servidor de
comandos.
5.5.5.2.3.3 CommandListener
DDA cuenta con un servidor de comandos preparado para escuchar en un puerto determinado un conjunto de
comandos soportados que provocarán las acciones oportunas en la aplicación. Para construirlo se va a emplear
una clase CommandListenerServer que herede de Thread, de forma que arrancamos un hilo para este servidor.
Además, los comandos no serán más que valores numéricos decimales que se enviarán entre un cliente y este
servidor. Es decir, se buscará leer un byte de información, transformando este byte en su representación decimal.
Este valor será el comando intercambiado.
Figura 5-32. CommandListener
Se trata de un servidor muy simple, construido directamente con Java:
ServerSocket servidor = new ServerSocket(puertoConexion);
Socket conexion = servidor.accept();
BufferedInputStream is = new BufferedInputStream(conexion.getInputStream());
InputStreamReader isr = new InputStreamReader(is);
int comando;
while (true) {
comando = isr.read();
selectCommand(comando, conexion);}
Se crea un socket y se acepta la conexión. La llamada al read() bloquea el hilo hasta que se recibe un byte. Cada
byte se castea a un int y se manda a un método encargado de realizar las acciones que se correspondan con dicho
comando. Las acciones soportadas se pueden ver en el esquema con el que comienza este apartado.
- refreshThreads: llama al método updateThreads() de ProtocolReaderManager. Devuelve ACK.
- refreshCache: limpia todas las cachés empleadas por DDA. Devuelve ACK.
- restart: llama al método restart de ProtocolReaderManager. Devuelve ACK.
- test: devuelve ACK sin realizar ninguna acción.
El valor con el que se corresponde cada uno de los comandos anteriores (incluyendo el ACK) se ha fijado en la
clase DDAUtils mediante el empleo de constantes.
Además, se ha añadido un cliente para este servidor entre los tests de DDA, bajo la denominación de
TestCommandListenerClient.
5.5.5.2.3.4 DataSender
Para dotar de escalabilidad a la solución se ha optado por entender el envío de datos al Kafka como uno de los
posibles destinos de la información biomédica. De esta manera, la comunicación con el clúster se comporta
Descripción de la propuesta
134
134
como uno de los posibles DataSenders de DDA, permitiendo al desarrollado añadir o eliminar nuevos. Por
ejemplo, podemos pensar en un nuevo DataSender muy sencillo: un fichero de texto en el que se vuelquen los
datos tal y como se van obteniendo. Por tanto, si ahora el envío de datos al clúster es solo uno de los posibles
destinos es necesario una capa intermedia que reciba los datos y se los pase a los DataSenders adecuados.
El patrón de diseño es exactamente el mismo al diseñado para ProtocolReader: contaremos con una interfaz que
define los métodos básicos (DataSender), implementaciones concretas (KafkaDataSender) y un organizador o
gestor (DataSenderManager). En este caso no es necesaria la existencia de una clase abstracta ya que no existe
lógica común a mantener.
Figura 5-33. DataSenderManager
5.5.5.2.3.4.1 DataSender
La interfaz DataSender define solamente dos métodos: sendInfo(LFConstantInfo) y sendInfo(HFConstantInfo).
Cada implementación concreta deberá implementar este método para recibir los objetos, procesarlos si es
necesario y almacenarlos / enviarlos a donde corresponda.
Por ejemplo, KafkaDataSender simplemente instancia y configura dos MedipiProducers (uno para cada topic)
y envía los objetos recibidos al clúster, gracias al diseño realizado en core-Cluster.
public class KafkaDataSender implements DataSender {
private MedipiLFProducer producerLF;
private MedipiHFProducer producerHF;
public KafkaDataSender() {
Properties props = new Properties();
props.put(KafkaConstants.PROP_SERVER_LIST,
PropertiesReader.getProperty(KafkaConstants.VALUE_SERVER_LIST));
props.put(KafkaConstants.PROP_KEY_SER,
PropertiesReader.getProperty(KafkaConstants.VALUE_KEY_SER));
props.put(KafkaConstants.PROP_VALUE_SER,
PropertiesReader.getProperty(KafkaConstants.VALUE_VALUE_SER));
props.put(KafkaConstants.PROP_RETRIES,
PropertiesReader.getProperty(KafkaConstants.VALUE_VALUE_RETRIES));
props.put(KafkaConstants.PROP_LINGER,
PropertiesReader.getProperty(KafkaConstants.VALUE_LINGER));
props.put(KafkaConstants.PROP_BATCH,
PropertiesReader.getProperty(KafkaConstants.VALUE_BATCH));
props.put(KafkaConstants.PROP_FIRST_TS,
PropertiesReader.getProperty(KafkaConstants.VALUE_FIRST_TS));
props.put(KafkaConstants.PROP_ACK_TS,
PropertiesReader.getProperty(KafkaConstants.VALUE_ACK_TS));
producerLF = new MedipiLFProducer();
producerLF.initialize(props);
producerHF = new MedipiHFProducer();
producerHF.initialize(props);
}
@Override
public void sendInfo(LFConstantInfo info) {
135
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
135
producerLF.sendLFConstantInfo(info);
}
@Override
public void sendInfo(HFConstantInfo info) {
producerHF.sendHFConstantInfo(info);
}
}
5.5.5.2.3.4.2 DataSenderManager
Es una clase con dos funciones claramente diferenciadas:
- Organizar los DataSenders, instanciando y manteniendo aquellos disponibles.
static {
instantiateDataSenders();
}
private static void instantiateDataSenders() {
try {
dataSenderList = new ArrayList<DataSender>();
dataSenderList.add(new KafkaDataSender());
} catch (Exception e) {
LOG.error("Error creating data senders.", e);
System.exit(1);
}
}
- Servir de intermediario al resto de la aplicación: cuando un ProtocolReader haya recuperado una
constante biomédica se la enviará al DataSenderManager, quien a su vez se la reenviará a todos los
DataSenders disponibles, evitando que los ProtocolReaders tengan que conocer el estado actual de cada
DataSender disponible en el programa.
public static void sendInfoToAll(LFConstantInfo constantInfo) {
for (DataSender ds : dataSenderList) {
ds.sendInfo(constantInfo);
}
}
public static void sendInfoToAll(HFConstantInfo constantInfo) {
for (DataSender ds : dataSenderList) {
ds.sendInfo(constantInfo);
}
}
5.5.5.2.3.5 Util
Como último comentario acerca de DDA se destaca que cuenta con dos clases de utilidades: una para métodos
comunes o considerados de propósito general (DDAUtil) y otro para almacenar las constantes del módulo de
acuerdo a la política general de la suite (DDAConstants).
5.5.5.3 SAVER
5.5.5.3.1 Funcionalidad de la aplicacio n
SAVER es el módulo de la suite MEDIPI encargado de consumir la información existente en el clúster para
procesarla y almacenarla en la base de datos. Solo se van a procesar las constantes discretas o a baja frecuencia,
pues son las únicas que tiene sentido almacenar en base de datos. El resto serán tratadas por otras aplicaciones
de la suite.
Descripción de la propuesta
136
136
Figura 5-34. Proyecto SAVER
5.5.5.3.2 Configuracio n de proyecto Maven
Como todo proyecto Maven cuenta con un descriptor pom.xml, en el que hace referencia al proyecto padre y se
especifica nombre del proyecto, versión y dependencias. En este caso las dependencias son solo internas: core-
app para poder seguir la arquitectura de las aplicaciones de la suite, core-Cluster para la comunicación con Kafka
y datacq-dom para poder trabajar con referencias al esquema DATA_ACQ de base de datos.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-app</artifactId>
<version>1.0</version>
</parent>
<artifactId>saver</artifactId>
<packaging>jar</packaging>
<name>saver</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>datacq-dom</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core-Cluster</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core-app</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
5.5.5.3.3 Co digo Java
SAVER es la aplicación más sencilla de la suite. Esto no solo se debe a que realiza una funcionalidad que no es
excesivamente compleja (leer las constantes del clúster, procesarlas y almacenarlas en base de datos), sino
también a que la estructura de clases diseñada para cubrir dicha funcionalidad es tremendamente similar a la
empleada en DDA:
- Configuración: será necesario configurar el log, el acceso a los properties, a la base de datos, etc.
- DataReader: recuperación de constantes de Kafka (y otros posibles puntos de entrada de datos).
137
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
137
- DataWriter: existirán un conjunto de clases encargados de guardar la información recuperada por los
DataReaders en el sistema de almacenamiento concreto de cada uno, realizando algún procesamiento
intermedio se así se desea.
- Util: constantes.
Figura 5-35. Esquema de SAVER
5.5.5.3.3.1 Configuraciones básicas de la aplicación
La configuración de Log, PropertiesReader y acceso a la base de datos es similar a la comentada en el caso de
DDA.
5.5.5.3.3.2 DataReader
El principal punto de acceso de SAVER es las constantes biomédicas recibidas del clúster, pero se ha empleado
el mismo patrón de diseño que en DDA para permitir múltiples puntos de acceso. Por tanto, se define una interfaz
(DataReader), una clase abstracta con lógica común (AbstractDataSender) y un organizador
(DataSenderManager).
5.5.5.3.3.2.1 DataReader
La interfaz define simplemente tres métodos: startReading(), readedInfo(LFConstantInfo info) y
readedInfo(HFConstantInfo info). Mientras que el primero arranca la lectura de datos, el resto serán llamados
cuando se hayan recuperado datos del origen.
Por su parte, AbstractDataReader simplemente implemente los métodos readedInfo, enviando la información
común al DataReaderWriter.
Bajo este patrón se ha creado el siguiente KafkaDataReader.
public class KafkaDataReader extends AbstractDataReader {
private Properties props;
private MedipiLFConsumer consumerLF;
Descripción de la propuesta
138
138
private MedipiHFConsumer consumerHF;
public KafkaDataReader() {
props = new Properties();
props.put(KafkaConstants.PROP_SERVER_LIST,
PropertiesReader.getProperty(KafkaConstants.VALUE_SERVER_LIST));
props.put(KafkaConstants.PROP_GROUP,
PropertiesReader.getProperty(KafkaConstants.VALUE_GROUP));
props.put(KafkaConstants.PROP_KEY_DESER,
PropertiesReader.getProperty(KafkaConstants.VALUE_KEY_DESER));
props.put(KafkaConstants.PROP_VALUE_DESER,
PropertiesReader.getProperty(KafkaConstants.VALUE_DESER));
props.put(KafkaConstants.PROP_OFFSET,
PropertiesReader.getProperty(KafkaConstants.VALUE_OFFSET));
consumerLF = new MedipiLFConsumer() {
@Override
public void receiveLFConstantInfo(LFConstantInfo info) {
readedInfo(info);
}
};
consumerHF = new MedipiHFConsumer() {
@Override
public void receiveHFConstantInfo(HFConstantInfo info) {
readedInfo(info);
}
};
}
@Override
public void startReading() {
consumerLF.initialize(props);
consumerHF.initialize(props);
}
}
Simplemente, cuando cualquier DataReader procesa una constante llama al readedInfo que la envía al
DataWriterManager. Podemos observar cómo se están recuperando información de los dos topics, a pesar de
que se ha indicado que no se va a guardar en base de datos las constantes a alta frecuencia. Se ha optado por
implementar el consumer del HFConstantInfoTopic para explicitar una idea: una cosa es la información
recuperada por el DataSender y otra la procesada por el DataWriter. En este caso, el DataReader puede recuperar
la información, pero el DataWriter puede descartarla si así lo necesita para cumplir su propósito.
5.5.5.3.3.2.2 DataReaderManager
El DataReaderManager es prácticamente una copia del DataSenderManager, realizando las adaptaciones
oportunas. El main de SAVER llamará al método startReceiving() del Manager, que se encargará de recorrer su
mapa e invocar a dicho método en cada DataReader. Como siempre, el manager tiene todos sus métodos y
atributos estáticos, para que no haya que instanciar un objeto de esta clase cada vez que se vaya a actuar sobre
los DataReaders.
5.5.5.3.3.3 DataWriter
5.5.5.3.3.3.1 DataWriter
Empleamos, nuevamente, una estructura análoga. En este caso no hay lógica común por lo que no hay necesidad
de crear un abstract.
DBDataWriter implementa los dos métodos de la interfaz: writeInfo((LFConstantInfo) y
writeInfo(HFconstantInfo). A través de ellos llegarán las constantes biomédicas procedentes de los DataReaders.
139
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
139
En este caso concreto, el método writeInfo de HFConstantInfo estará vacío, pues se descarta toda la información
de este tipo.
@Override
public void writeInfo(LFConstantInfo info) {
if (timeFiltering(info)) {
ConstantInfo ci = SaverConfiguration.
getConstantInfoByEpisodeAndConstant(info);
if (ci != null && ci.getValue() == null) {
ci.setConstantDate(info.getConstantDate());
ci.setManual(info.getManual());
ci.setValue(info.getValue());
SaverConfiguration.saveConstantInfo(ci);
}
}
}
Sí se implementa el writeInfo de las variables discretas, realizando un sencillo filtrado temporal. En timeFiltering
se tratan las variables recibidas para provocar que no se guarde toda la información recibida, solo un valor de
una misma constante médica para un episodio cada un determinado número de minutos. Dicha información
estará parametrizada en la base de datos. Por ejemplo, podemos colocar el valor del filtro en 5 minutos, de forma
que solo habrá un valor de una misma constante para un episodio cada 5 minutos. Por tanto, además de rechazar
datos se deberá tratar la fecha de la constante, poniendo una que sea representativa del periodo de filtrado (en
nuestro ejemplo serán 0’, 5’, 10’, etc.). Tras esto, se comprueba si existe un registro en base de datos que coincida
con el que se busca insertar, machacándolo con la información recibida en caso afirmativo.
5.5.5.3.3.3.2 DataWriterManager
DataWriterManager sirve para distribuir las constantes biomédicas recibidas por los DataSenders. Para ello
manejará una lista de DataWriters disponible y, al recibir un objeto de tipo LFConstantInfo o HFConstantInfo
llamará al writeInfo correspondiente de cada uno de ellos.
5.5.5.3.4 Paquete Util
En esta categoría SAVER solo cuenta con una clase de constantes denominada SaverConstants.
5.5.5.4 CHART
5.5.5.4.1 Funcionalidad de la aplicacio n
Chart es la aplicación de visualización de datos de la suite diseñada en el presente proyecto. Subscribiéndose al
clúster, mostrará las constantes vitales del paciente seleccionado en directo. Para ello, se empleará la librería
JavaFX, provocando que este sea el proyecto más diferenciado del resto de la suite. A pesar de ello, no deja de
ser una aplicación Java por lo que emplearemos la misma estructura que para el resto de aplicaciones.
Descripción de la propuesta
140
140
Figura 5-36. Proyecto CHART
5.5.5.4.2 Configuracio n de proyecto Maven
El pom.xml de CHART no se sale del patrón comentado hasta el momento: definición del proyecto actual,
referencia al padre y dependencias. Destaca que nos encontramos ante la única aplicación que no tiene
dependencias hacia datacq-dom, sino hasta health-dom. Simplemente no se emplea ninguna tabla de este
esquema, por lo que podemos ignorarlo y construir un proyecto más liviano gracias a la modularización.
<parent>
<groupId>medipi</groupId>
<artifactId>medipi-app</artifactId>
<version>1.0</version>
</parent>
<artifactId>chart</artifactId>
<packaging>jar</packaging>
<name>chart</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>health-dom</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core-Cluster</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core-app</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
5.5.5.4.3 Co digo Java
En esta aplicación debemos cambiar un poco el enfoque seguido hasta el momento, pues tener un objetivo
bastante diferenciado al resto provoca una estructura de software ligeramente distinta. Aun así, vamos a intentar
estructurar la explicación del desarrollo realizado de una manera lo más simular posible a la empleada en el resto
de aplicaciones.
141
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
141
Figura 5-37. Esquema de CHART
- Configuración: al igual que en el resto de aplicaciones se ha de inicializar el log, los properties y el
acceso a la base de datos.
- KafkaDataReader: en este caso no se ha empleado el mismo patrón de diseño que en DDA o SAVER.
Se ha optado por tener una sola clase que gestiona la entrada de datos.
- Pantalla principal: la propia clase main arrancará una ventana en la que se debe seleccionar el paciente
del que se quieren consultar sus constantes vitales.
- Ventana de constantes vitales de un paciente.
- Util: métodos comúnes y constantes.
5.5.5.4.3.1 Configuraciones básicas de la aplicación
La configuración de Log, PropertiesReader y acceso a la base de datos es similar a la comentada en el caso de
DDA y SAVER.
5.5.5.4.3.2 KafkaDataReader
En este caso se ha optado por un enfoque diferente al comentado hasta ahora, mostrando de esta manera cómo
sería y qué ventajas proporciona. Será el main quien directamente llame a la clase KafkaDataReader encargada
de arrancar los consumers y subscribirlos a los dos topics del clúster Kafka. Con este patrón de diseño se manejan
menos clases, se carga menos información en memoria y se facilita el código, aunque se pierde en escalabilidad.
La decisión se sustenta en que Chart debe representar la información recuperada en tiempo real, por lo que no
tiene sentido otro punto de entrada distinto al clúster.
public static synchronized void receiveDataToAll(LFConstantInfo constantInfo) {
getDataQByEpisodeId(constantInfo.getEpisodeId()).add(constantInfo);
}
public static synchronized ConcurrentLinkedQueue<LFConstantInfo>
getDataQByEpisodeId(Long episodeId) {
Descripción de la propuesta
142
142
if (dataQMap.get(episodeId) == null) {
dataQMap.put(episodeId, new ConcurrentLinkedQueue<>());
}
return dataQMap.get(episodeId);
}
En las líneas superiores observamos el tratamiento que se hace de las constantes a baja frecuencia recibidas del
clúster. El KafkaDataReader de CHART maneja un mapa de colas Java. Cada episodio tiene una cola de
LFConstantInfo que será consumida por otros puntos de la aplicación. Para las constantes a alta frecuencia se
realiza un pretratamiento que genera un conjunto de LFConstantInfo por cada valor recibido, enviándolas al
método comentado.
5.5.5.4.3.3 MainWindow
La clase Main de CHART es la responsable, no solo de la configuración y arranque del KafkaDataReader, sino
también de generar la ventana principal de la aplicación.
Para comentarla conviene partir de algunos conceptos básicos de Java FX.
- Application: las clases Java que son controladoras de una ventana deben extender la clase abstracta
javafx.application.Application. Dicha clase proporciona un flujo de trabajo y obliga a implementar un
conjunto de métodos para la construcción de la ventana.
- Stage: es el objeto que se corresponde con la ventana en sí. Por ejemplo, contiene propiedades como el
icono de la ventana o el tamaño. Toda clase que extiende de Application debe implementar un método
start(Stage) en el que ya recibe la Stage que se mostrará en el pc donde se está ejecutando la aplicación.
- Scene: a una Stage se le puede asignar una y solo una Scene. El objeto Scene es quien contiene la
estructura de elementos de la ventana: textos, combos, botones, imágenes, etc.
Se ha definido un abstract denominado MedipiChartWindow. Las dos ventanas de la aplicación (la ventana
principal defina en Main y la de constantes de un paciente) extienden de este abstract, en el que simplemente se
setea el icono de la aplicación.
public abstract class MedipiChartWindow extends Application {
@Override
public void start(Stage stage) {
stage.getIcons().add(new Image(Main.class.getClassLoader()
.getResourceAsStream(ChartConstants.ICON_FILE_NAME)));
initialize(stage);
stage.show();
}
public abstract void initialize(Stage stage);
}
El primer paso es generar los elementos que se van a mostrar en pantalla. JavaFX proporciona un objeto de cada
elemento habitual en la construcción de interfaces de usuario. Es decir, tenemos objetos ComboBox, Button,
etc. Se pueden emplear los métodos proporcionados por los objetos UI de JavaFX para indicarles un aspecto,
texto, etc. Algunos de ellos buscan asociar un método externo a una actuación o estado concreto del elemento
en pantalla. Por ejemplo, el método setOnAction de Button espera recibir un objeto de tipo EventHandler. Este
manejador de eventos simplemente define un método principal en el que se desarrolla una lógica determinada.
Cuando se pulse el botón, Java llamará al método principal de la clase asociada a la acción de pulsación (en este
caso onAction). Por tanto, puede entenderse como una suscripción a eventos proporcionados por los objetos. En
las líneas de código posteriores se muestra la definición de algunos de los elementos de la ventana principal.
143
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
143
final ComboBox<ComboEpisode> episodeComboBox = new ComboBox<ComboEpisode>();
episodeComboBox.getItems().addAll(refreshEpisodes());
episodeComboBox.valueProperty().addListener(new ChangeListener<ComboEpisode>(){
@Override
public void changed(ObservableValue<? extends ComboEpisode> observable,
ComboEpisode oldValue, ComboEpisode newValue) {
selected.setEpisodeId(newValue.getEpisodeId());
selected.setText(newValue.getText());
}
});
final Button refreshBtn = new Button(ChartConstants.REFRESH_BUTTON);
refreshBtn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
episodeComboBox.getItems().clear();
episodeComboBox.getItems().addAll(refreshEpisodes());
}
});
final Button selectBtn = new Button(ChartConstants.SELECT_BUTTON);
selectBtn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
if (selected.getEpisodeId() != null) {
Platform.runLater(new Runnable() {
public void run() {
try {
Long id = selected.getEpisodeId();
if (chartWindowMap.get(id) == null) {
ChartWindow win = new ChartWindow();
win.open(selected.getEpisodeId(),
selected.getText());
chartWindowMap.put(id, win);
}
} catch (Exception e) {
LOG.error("Error launching Chart Window: ", e);
}
}
});
}
}
});
Hacía al final del fragmento de código indicado arriba se observa la lógica que se realiza cuando se pulsa el
botón de seleccionar. Para entenderlo hay que comentar que la clase Main maneja un atributo básico: un mapa
de las ventanas de información de paciente abiertas actualmente (chartWindowMap) indexadas por el
identificador único de episodio. Al pulsar el botón de seleccionar, si no hay ya una ventana abierta para dicho
paciente se instancia y arranca la ventana adecuada.
GridPane grid = new GridPane();
grid.setVgap(7);
grid.setHgap(4);
grid.setPadding(new Insets(20, 20, 20, 20));
grid.add(img, 0, 0, 4, 1);
GridPane.setHalignment(img, HPos.CENTER);
grid.add(new Label(ChartConstants.EPISODE_LABEL), 0, 4);
grid.add(episodeComboBox, 1, 4);
grid.add(refreshBtn, 3, 4);
grid.add(selectBtn, 0, 6, 4, 1);
GridPane.setHalignment(refreshBtn, HPos.RIGHT);
Descripción de la propuesta
144
144
GridPane.setHalignment(selectBtn, HPos.CENTER);
Group root = (Group) scene.getRoot();
root.getChildren().add(grid);
stage.setScene(scene);
Una vez definidos los elementos se han de asociar a la Scene del Stage actual de acuerdo a un diseño
determinado. Para ello se ha empleado un Grid (cuadrícula) de 4x7. Al Grid se le van añadiendo los diferentes
objetos de la UI, mediante el método add. En él se especifican, de izquierda a derecha: la columna, la fila, el
número de columnas a ocupar (1 si no se indicada nada) y el número de filas a ocupar (1 por defecto). De esta
manera indicamos la ubicación de los diferentes elementos en la Scene, dotándola del aspecto que consideremos
adecuado. Una vez realizada esta tarea se ubica el grid en la raíz de la escena.
5.5.5.4.3.4 ChartWindow
Ya se ha visto como se instancia objetos ChartWindow desde la clase principal cuando se selecciona un paciente
determinado y se pulsa el botón de selección. La clase ChartWindow, que también hereda del abstract común
MedipiChartWindow, se sustenta en un objeto de tipo LineChart. Esta clase facilita la representación de gráficos
de tipo lineal, pues ofrece un conjunto de métodos que nos permiten indicar los datos a representar y personalizar
el aspecto que tendrá la gráfica. Basta con llamar al constructor de Scene indicándole un objeto de tipo LineChart
para que la ventana generada incluya una gráfica lineal.
Para añadir valores a representar a un LineChart se emplea el método getData().add(Series). Una serie no es más
que un conjunto de datos con sentido común, en nuestro caso formará una serie todos los datos de una misma
constante. Para controlarlas se empleará un mapa en el que las series serán identificadas por la id de la constante
a la que representan. Como se puede ver, las series creadas para Chart esperan recibir dos valores: uno de tipo
Date para el eje X y otro de tipo Number para el eje Y.
private Map<Long, Series<Date, Number>> graphMap = new HashMap<>();
Para controlar el desplazamiento del eje temporal se va a emplear un objeto de tipo
javafx.animation.AnimationTimer. Esta clase define un timer que será cargado en cada frame, en cada instante
temporal en el que se refresca la información en pantalla. Se trata de una clase abstracta que obliga a implementar
un método handle(long now). En dicho método se ubica la lógica que se ha de realizar para ese frame (que ocurre
en now milisegundos).
private void prepareTimeline() {
animation = new AnimationTimer() {
@Override
public void handle(long now) {
addDataToSeries();
cleanOldSeriesData();
}
};
animation.start();
}
private void addDataToSeries() {
while (!KafkaDataReader.getDataQByEpisodeId(selectedEpisodeId).isEmpty()) {
LFConstantInfo ci = KafkaDataReader.getDataQByEpisodeId
(selectedEpisodeId).remove();
Series<Date, Number> ser = getSeries(ci.getConstantId());
ser.getData().add(new Data<Date, Number>(ci.getConstantDate(),
Float.valueOf(ci.getValue())));
}
}
145
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
145
private void cleanOldSeriesData() {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.set(Calendar.SECOND, cal.get(Calendar.SECOND) –
ChartConstants.INTERVAL_IN_SECONDS);
for (Long id : graphMap.keySet()) {
ObservableList<Data<Date, Number>> obList = graphMap.get(id).getData();
if (obList != null && !obList.isEmpty()) {
obList.removeIf(p -> p.getXValue().before(cal.getTime()));
}
}
}
private Series<Date, Number> getSeries(Long cntId) {
if (graphMap.get(cntId) == null) {
Series<Date, Number> series = new LineChart.Series<Date, Number>();
series.setName(ChartConfiguration.getConstantById(cntId).
getDescription());
chart.getData().add(series);
graphMap.put(cntId, series);
}
return graphMap.get(cntId);
}
En cada frame se realizan dos fases: añadir datos a la serie y eliminar datos antiguos. En el primero se lee cada
dato disponible hasta el momento para la cola de este episodio manejada por KafkaDataSender. Con cada dato
se va al método getSeries para recuperar la serie de su constante. Es importante ver que en este método se crea
la serie por primera vez, metiéndola en el mapa y, más importante aún, añadiéndola a Chart. Es decir, la serie se
añade a la gráfica solo la primera vez, lo que estamos haciendo para cada frame será añadir y quitar datos. Tras
recuperar la serie se añade un punto XY con los datos recibidos, pasando después a la segunda fase, Para la fase
de eliminación se emplea la fecha en la que se ha parado el timer (recibida en milisegundos). Con esta fecha se
puede calcular una fecha umbral (la fecha actual menos determinado intervalo de tiempo configurable) y recorrer
toda la serie para eliminar los datos antiguos. La gráfica se expande / contrae automáticamente según el tamaño
del eje X que debe representar. Gracias a este control nos aseguramos de que represente siempre el mismo
intervalo temporal (definido por la constante INTERVAL_IN_SECONDS), evitando que el
redimensionamiento distorsione la representación de las constantes biomédicas del paciente.
Resumen de la descripción de la propuesta
A lo largo del presente capítulo se ha detallado con profundidad el proyecto realizado, comentando ampliamente
todas y cada una de las partes que componen esta aplicación (o mejor dicho aplicaciones) IoMT: desde la
Raspberry Pi 2 Modelo B sobre la que correremos nuestro software de integración con biodispositivos, pasando
por la base de datos Oracle y los servidores de colas de mensajes distribudas hasta llegar al último punto del
proyecto: una aplicación Java FX que permita a los facultativos observar las constantes médicas de los pacientes
en tiempo real. En algunos casos nos hemos centrado solamente en explicar el enfoque tomado y en detallar la
parametrización y configuración necesaria para adecuar los productos ya existentes a neustras necesidades. Sin
embargo, en los últimos epígrafes se ha analizado los pormenores del software creado exclusivamente para este
proyecto, comentando cuando, cómo y con qué finalidad se han utilizado todas y cada una de las tecnologías
implicadas.
Descripción de la propuesta
146
146
147
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
147
6 VALIDACIÓN
urante todos los capítulos que componen esta memoria se ha ido detallando, desde distintas perspectivas,
la realización del proyecto. Se puede comprobar que en todo momento se han comentado las decisiones
funcionales, tecnológicas o de diseño en relación a los objetivos que se marcaron al inicio del proyecto,
y que están presentes en el primer capítulo de este texto. Con ellos en mente se va a llevar a cabo una validación
del proyecto, probando su correcto funcionamiento.
Funcionamiento de la solución
El primer punto de este proyecto es la Raspberry Pi 2 que, conectada a los biodispositivos y ejecutando nuestro
software de recolección de datos (DDA – Device Data Acquisition), se encarga de interpretar los protocolos
propietarios, recuperar la información biomédica y mandarla al clúster de Kafka.
Figura 6-1. Raspberry Pi 2 Modelo B conectada a un biodispositivo mediante un adaptador USB – RS232
La Raspberry, que corre el sistema operativo Raspbian, debe contar con la última versión del software DDA,
encargado de establecer la comunicación con los biodispositivos implicados.
Figura 6-2. Módulo DDA desplegado en la Raspberry
Una vez se tiene preparada la Raspberry, debemos asegurarnos de que Base de Datos y clúster Kafka están
arrancados y funcionando adecuadamente.
D
Validación
148
148
Figura 6-3. Entorno de desarrollo SQL conectado correctamente a la base de datos
Figura 6-4. Nodos Zookeeper, Broker 1 y Broker 2 arrancados sobre una misma máquina Windows
Con todo preparado, basta con arrancar DDA ejecutando el fichero start.sh ubicado en la carpeta bin de la
solución. Inmediatamente, podemos comprobar cómo arranca la aplicación, mostrando por el log las incidencias
que ocurran de acuerdo a su configuración.
Figura 6-5. Log de ejecución, en modo Debug, de DDA en la Raspberry
De igual forma, los distintos nodos de Kafka indican por consola que la Raspberry está activa y enviando datos:
149
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
149
Figura 6-6. Log de un Broker Kafka
Con lo anterior ya se están recopilando datos, que se envían al clúster, para que puedan ser recibidos por otros
módulos de este proyecto o de terceros. Para comprobar que la comunicación mediante Kafka es correcta, vamos
a arrancar los otros dos componentes que conforman la aplicación.
Primero se inicia la aplicación SAVER, ejecutando el fichero jar correspondiente en cualquier máquina
Windows o Linux.
Figura 6-7. Log de SAVER
Comprobamos que la aplicación arranca correctamente e introduce datos procedentes de la Raspberry en la tabla
CONSTANT_INFO de la base de datos, aplicando el filtro temporal que está actualmente indicado la tabla de
parámetros (5 minutos).
Figura 6-8. Tabla CONSTANT_INFO con datos insertados por SAVER
Validación
150
150
Para terminar, arrancamos la aplicación Chart y comprobamos que podemos visualizar la información
biomédica recuperada por DDA y la Raspberry a tiempo real.
Figura 6-9. Ejecución de Chart, en la que podemos ver datos (simulados) a tiempo real
Las operaciones realizadas hasta ahora abarcan un ciclo completo de funcionamiento del conjunto de
aplicaciones software y componentes hardware que componen este proyecto.
Resumen de la validación
A partir de lo expuesto en el presente capítulo se puede concluir que se ha creado una solución completamente
funcional que satisface no solo la necesidad de recuperación de las constantes clínicas de los pacientes medidas
con biodispositivos, sino que también abarca la centralización y puesta a disposición de dicha información a
aplicaciones propias del proyecto o de terceros. Por tanto, la primera conclusión no puede ser más positiva, a la
espera de abordar con más detalle este punto, junto a las líneas futuras de mejora, en el último capítulo.
151
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
151
7 PLANIFICACIÓN
levar a cabo un proyecto como el presentado en esta memoria implica trabajar con numerosos frentes y
una amplia variedad de tecnologías distintas. Aunque es sencillo dividir todos los módulos y componentes
(tanto hardware como software) que conforman el proyecto en tres sectores o categorías (integración con
biodispositivos, almacenamiento / intercambio y visualización de la información clínica) no conviene olvidarse
que el núcleo central es la comunicación con los biodispositivos para recuperar las constantes médicas del
paciente. Esta tarea es, sin duda, la más compleja de cuantas abarca el proyecto, pues implica una importante
labor de documentación, investigación, desarrollo y pruebas. Parece evidente entender que una planificación
adecuada del proyecto debe dimensionar las distintas partes del mismo de acuerdo a su complejidad, no
escatimando en recursos para el análisis y estudio inicial. En todo proyecto de innovación partir de una base
adecuada es fundamental: cometer cualquier error de enfoque o análisis puede provocar que el trabajo realizado
no llegue nunca a buen puerto. Es por estos motivos por los que se ha apostado decisivamente por dotar de
mayor importancia a la fase de pre desarrollo y a todo aquello relacionado con la integración con biodispositivos
en la planificación del proyecto realizada, la cual pasaremos a comentar a continuación.
Fases de la planificación
Para abordar la planificación del proyecto vamos a dividir todas las tareas que se deben realizar / se han realizado
en seis grupos o fases:
Planteamiento del proyecto: primera fase en la que se define el proyecto a realizar, estableciendo una propuesta
concreta y elaborando los objetivos a cumplir. La mayor parte del tiempo dedicado a esta fase se emplea en
documentarse adecuadamente acerca del mundo de la IoT (Internet of Things) aplicado a la medicina,
recuperando la información necesaria como para entender las necesidades actuales del mercado. Solo cuando
esa etapa ha concluido y disponemos de la cantidad de información adecuada tiene sentido plantear una
propuesta completa de aplicación IoMT (Internet of Medical Things) para la monitorización de bioseñales.
Figura 7-1. Planificación Planteamiento
Fase de análisis: es la parte más importante de todo proyecto de innovación. Abarca todos los análisis y estudios
necesarios para determinar qué caminos y estrategias se han de seguir para poder realizar el proyecto planeado,
sí es posible hacerlo.
Figura 7-2. Planificación Fase de análisis
L
Planificación
152
152
Esta fase se inicia con un estudio completo de las alternativas existentes en el mercado y en la comunidad de
código libre al proyecto que se pretende realizar. Como resultado de lo anterior, se deben tratar inmediatamente
después todas las cuestiones relacionadas con los protocolos propietarios, pues ellos determinan el enfoque que
se ha de tomar en el desarrollo posterior. Tras esta fase, dedica a analizar en exclusiva todos los elementos
implicados en la integración, se pasa a estudiar los elementos que solo se tratarán sector del almacenamiento e
intercambio de datos. En ese punto, con toda la información recopilada hasta ahora a disposición de futuros
análisis, es el momento de plantear cómo serán las aplicaciones software que se van a desarrollar en este proyecto
y que deberán relacionarse con los elementos vistos hasta ahora.
Prueba de concepto: en este proyecto se ha denominado como pre desarrollo o prueba de concepto a una fase
intermedia entre el análisis y el desarrollo en sí. En ella se deben poner en práctica las ideas planteadas durante
los estudios y análisis ya realizados para comprobar que el desarrollo del proyecto es posible. Esta tarea incluye
la realización de un primer prototipo de DDA (Device Data Acquisition) que simplemente pruebe que es posible
establecer la comunicación con los biodispositivos y acceder a la información medida por los mismos. Para ello
se han dedicado varios días a configurar adecuadamente el hardware de integración, algo que abarca instalar el
sistema operativo, drivers, máquina virtual de Java, etc. La fase concluye satisfactoriamente con la realización
de la prueba.
Figura 7-3. Planificación Prueba de concepto
Desarrollo: es la fase central del proyecto, en la que se implementa la solución planeada para cumplir los
objetivos marcados. Dada la complejidad y diversidad de elementos implicados en este proyecto se ha dividido
en dos: arquitectura, que trata la instalación, configuración y parametrización de componentes como la base de
datos o el clúster de servidores de colas de mensajes, y el desarrollo de las aplicaciones en sí.
Figura 7-4. Planificación Desarrollo I
La primera parte del desarrollo se centra en diseñar la arquitectura del proyecto, las bases sobre las que construir
las aplicaciones software que necesitamos para satisfacer los objetivos que nos hemos marcado. Esto implica en
primer lugar tener claro cómo se van a relacionar todos y cada uno de los elementos que intervendrán en el
proyecto. Con esto sobre la mesa hay que dedicarse a la tarea de instalar y configurar la base de datos, lanzar los
scripts necesarios para parametrizarla convenientemente; así como instalar y preparar los nodos Zookeeper y
Brokers de Kafka. Como última etapa del diseño de la arquitectura se trata la estructura básica que deberá tener
el software, separando adecuadamente los proyectos para apostar por una modularización adecuada.
153
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
153
Figura 7-5. Planificación Desarrollo II
A esta fase se llega habiendo planteado ya los proyectos que compondrán nuestro software. En este punto se
tienen ya claros qué proyectos serán librerías, cuales aplicaciones y cuales meros proyectos Maven que nos
sirvan para agrupar otros módulos, dependencias y lógicas de construcción comunes. Como se observa, esta es
la fase que más tiempo requiere con diferencia, pues implica plasmar sobre el código todas las ideas planteadas
hasta el momento. El orden en el que se abordan los componentes del software se basa en la idea de construir
primero las bases (los proyectos denominados core), para ir, poco a poco, trabajando sobre ellas para desarrollar
las librerías más complejas. Como ya ocurría en otras etapas, existen momentos de solapamiento, pues la enorme
relación entre todos ellos ha provocado que se descubrieran errores de código o incluso de planteamiento en
algunos módulos al empezar el desarrollo de otros relacionados. Antes de comentar la fase final es importante
destacar que toda etapa incluye la realización de pruebas unitarias.
Postdesarrollo: fase de validación, pruebas, correcciones y realización de una memoria que permita plasmar
todo lo realizado para este proyecto.
Figura 7-6. Planificación Postdesarrollo
La fase final del proyecto se inicia con una etapa de pruebas integradas, en las que se mezclan todos y cada uno
de los componentes hardware y software que componen el proyecto para comprobar que no existen errores en
el código diseñado. De igual forma, se buscan y abordan los posibles puntos de mejora y optimización en la
solución realziada, de cara a generar la primera versión estable del proyecto. La primera versión estable del
proyecto pasa por una etapa de validación, en la que se comprueba qué objetivos inicales se han cumplido, cuales
no y bajo qué argumentos. Tras esto, solo queda la redacción de la presente memoria que de cuerpo y documente
adecuadamente todo el trabajo realizado.
Resumen de la planificación
Durante algo más de siete meses se ha planteado, analizado, desarrollado, probado y documentado
adecuadamente el proyecto “Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos
biomédicos”. Se ha partido siempre de la necesidad de satisfacer los requisitos u objetivos iniciales marcados en
el planteamiento del proyecto, siendo tarea del próximo capitulo estudiar el estado de los objetivos una vez
cumplida la planificación aquí comentada.
Planificación
154
154
155
Desarrollo de aplicación IoMT sobre Raspberry Pi para monitorización de datos biomédicos
155
8 CONCLUSIONES Y LÍNEAS FUTURAS
lo largo de esta memoria se ha detallado el proyecto realizado: un conjunto de aplicaciones informáticas
programadas en Java y centradas en la información biomédica del paciente. En torno a estos datos,
recuperados mediante una aplicación que corre sobre una Raspberry Pi, se ha diseñado una arquitectura
robusta, permitiendo construir sobre estas bases otras aplicaciones o módulos para la suite. Para ello, se ha optado
por una gran modularización, dividiendo las distintas funcionalidades en pequeños proyectos que puedan ser
empleados por aplicaciones actuales o futuras con facilidad y evitando duplicar desarrollos. Desde el principio
se ha trabajado teniendo en mente una idea clara: no se está diseñado una aplicación, sino una arquitectura, una
suite sobre la que sea posible construir el mayor número posible de proyectos diferentes. En este camino, ha sido
imprescindible apoyarse en tecnologías consolidadas (Maven, Oracle Database, JPA + Hibernate) pero también
se ha apostado por herramientas en fase de desarrollo temprano, algo ineludible al tratarse de un proyecto tan
enfocado a la innovación. La necesidad de dar una solución seria y realista a la cuestión del almacenamiento e
intercambio de las variables clínicas de los pacientes ha requerido el diseño de un clúster propio de servidores
de intercambio de mensajes, apoyado sobre la nueva (y cada vez más popular) herramienta de Apache, Kafka.
Esta decisión no solo ha dotado de más enjundia al proyecto, permitiéndole abarcar algunos aspectos más
alejados de la programación pura, sino que también ha sido vital para cumplir con la condición base del proyecto,
garantizando una arquitectura realmente preparada para crecer y abarcar nuevos productos. Junto a todo esto, y
para ofrecer un conjunto de programas reales, con capacidad de competir en el mundo real, se ha diseñado una
pequeña base de datos, cerrando el círculo que nos permite hablar de una verdadera suite de aplicaciones. Esto
se ha llevado hasta el extremo de diseñar una pequeña (y simple) aplicación visual que permitiera mostrar a los
interesados las interesantes aplicaciones prácticas que este proyecto puede tener en la vida real y la facilidad con
la que podrían extenderse sus funcionalidades gracias a un planteamiento modular.
Por otra parte, no se ha abordado las implementaciones de los protocolos propietarios empleados en la
comunicación con los biodispositivos. Esto hubiera implicado que el proyecto se centrara solo en ese proyecto,
provocando que se quedaran fueran otros aspectos más interesantes como podrían haber sido la creación de una
arquitectura multiaplicación o la construcción de un sistema de intercambio de información mediante un clúster
de servidores. En el capítulo dedicado al estado del arte, a las posibilidades punteras que existen en la comunidad
y el mercado de ha hablado de aplicaciones de código abierto que implementan alguno de estos protocolos,
pudiendo servir al lector interesado de punto de acceso a información más profunda sobre este tipo de protocolos.
Conclusiones
Con todo lo comentado anteriormente tenemos información más que suficiente para comprobar el estado de los
objetivos que nos marcamos al inicio del proyecto:
- La solución aquí diseñada gira entorno a las bioseñales de los pacientes, recuperadas a través de una
placa hardware (Raspberry Pi 2) y un software específico.
- La utilización de Kafka permite que todas las aplicaciones que así lo deseen puedan acceder fácilmente
a los datos, sin más que subscribirse al clúster. Esto ha quedado probado con los módulos SAVER y
CHART, que se comportan como aplicaciones independientes completamente separadas de DDA.
- Como consecuencia de lo anterior, las constantes clínicas del paciente se convierten en fuente de
información para un conjunto ampliable de aplicaciones del proyecto, o incluso de terceros si se
establece un protocolo para su conexión con nuestro clúster de colas de mensajes.
- Para ejemplificar que la información recuperada se realiza correctamente y se centraliza, estando
disponible en tiempo real para cualquiera que quiera acceder a ella se ha diseñado el módulo CHART.
- El clúster de servidores gestionado por Kafka es lo suficientemente versátil como para no limitar las
aplicaciones que quieran acceder a la información. Esto sí ocurriría si solo hubiéramos apostado por
una base de datos, ya que las bases de datos SQL no se comportan adecuadamente como fuente de
información de sistemas big data. Con la arquitectura diseñada no se limita la posible implantación de
A
Conclusiones y líneas futuras
156
156
un sistema big data sobre el proyecto diseñado, requisito que se había marcado antes del inicio del
proyecto.
- Además, se ha estudiado la posibilidad de permitir a alguna aplicación (propia o de terceros) modificar
el comportamiento de los biodispositivos. A pesar de haber optado por implementar los protocolos
propietarios que fueran necesarios, más proclives a esta funcionalidad que el estándar HL7 (Health
Level Seven), no se ha encontrado una cantidad mínima de protocolos que soporten esta característica
ni un conjunto de acciones comunes o habituales. Por tanto, este objetivo se ha descartado.
En resumen, el desarrollo del proyecto puede considerarse ampliamente satisfactorio, al haberse cumplido la
práctica totalidad de los objetivos marcados durante el planteamiento del mismo. La solución desarrollada cubre
la recuperación de la información biomédica de los pacientes medida mediante biodispositivos, siendo capaz de
centralizarla y ponerla a disposición de cualquier aplicación que pretenda visualizarla, explotarla, etc.
Líneas futuras
Enfocados a la creación de una aplicación robusta, han aparecido a lo largo de esta memoria distintas
funcionalidades interesantes que no han podido ser abordadas en este proyecto, algo lógico al tratar con un sector
tan extenso y complejo como el sanitario. Estas nuevas características, posibles nuevos proyectos a construir
sobre las bases aquí montadas, abarcan desde pequeños cambios o mejoras en el planteamiento hasta ideas que
implicarían nuevas aplicaciones, generalmente de gran complejidad.
En la primera categoría surgen varias posibilidades: realizar un tratamiento adecuado de las unidades en las que
se reciben las variables clínicas (algo que en lo que no se repara en el momento actual) o tener en cuenta las
posibles técnicas empleadas en la medición, algo que condiciona sin atisbo de duda el resultado obtenido.
La segunda categoría abre más posibilidades, las cuales además implican proyectos tan interesantes o más al
aquí comentado. Si se buscara continuar este trabajo hacia una solución implantarle en centros sanitarios reales
sería necesario desarrollar un programa de integración con el estándar HL7, para cubrir tres funciones
tremendamente útiles: recuperar la información de los dispositivos que lo empleen, poder enviar información en
este formato a terceros y emplear los mensajes recibidos como fuente de datos para poblar la base de datos.
Dentro de la misma lógica, aunque yendo un paso más allá, sería necesario construir un visualizador de datos
más potente, desarrollado probablemente en alguna tecnología front-end como Javascript, a ser posible
combinándolo con una aplicación de historia clínica propia u ofreciendo integración con alguna opción de
código libre existente en el mercado. Si pretendemos diseñar una suite de aplicaciones sanitarias en torno a los
datos clínicos del paciente es absurdo plantear que la información recuperada pueda mostrarse totalmente
disociada de otros valores imprescindibles como el diagnóstico del paciente, los resultados de sus últimos
estudios, los informes realizados por los distintos profesionales sanitarios que lo han atenido, etc. Hasta ahora
se han comentado mejores destinadas a obtener más información del paciente y a ofrecerla de la forma más
completa e integrada posible, pero no se ha seguido ahondando en las posibilidades que nos ofrece tener
informatizadas y centralizadas las constantes clínicas de muchos pacientes. No cuesta mucho imaginar un
escenario en el que toda esta información pueda ser explotada, tanto como fuente para estudios de profesionales
sanitarios como por un motor de big data que permita sacar estadísticas, reconocer patrones o incluso facilitar
la labor a los facultativos a partir de todo lo anterior. Esta última posibilidad se ve además reforzada por el amplio
uso de Kafka como fuente de datos para productos de tratamiento masivo de datos.
REFERENCIAS
[1] Health Level Seven International, «HL7,» [En línea]. Available: http://www.hl7.org/. [Último acceso:
Enero 2016].
[2] IHE International, «Integrating the Healthcare Enterprise,» [En línea]. Available: http://www.ihe.net/.
[Último acceso: Enero 2016].
[3] Capsule Tech, Inc, «SmartLinx,» 06 2016. [En línea]. Available: http://www.capsuletech.com/integration.
[4] Capsule Tech Inc, «SmartLinx Axon,» [En línea]. Available: http://webinfo.capsuletech.com/smartlinx-
axon-product-brief. [Último acceso: 06 2016].
[5] Capsule Tech, Inc., «SmartLinx Vitals Stream,» [En línea]. Available:
http://webinfo.capsuletech.com/smartlinx-vitals-stream-product-brief. [Último acceso: 06 2016].
[6] Capsule Tech, Inc, «SmartLinx Client,» [En línea]. Available: http://webinfo.capsuletech.com/medical-
device-integration-smartlinx-client. [Último acceso: 06 2016].
[7] Capsule Tech, Inc, «SmartLinx IQ,» [En línea]. Available: http://www.capsuletech.com/medical-device-
data-analytics. [Último acceso: 06 2016].
[8] Piscis Clinical Solutions, «Hawkeye,» [En línea]. Available: http://www.picis.com/medical-device-
integration.html. [Último acceso: 06 2016].
[9] The OpenICE Team, «OpenICE,» [En línea]. Available: https://www.openice.info. [Último acceso: 05
2016].
[10] MD PnP Program, «MD PnP,» [En línea]. Available: http://www.mdpnp.org/. [Último acceso: 05 2016].
[11] ASTM International, «ASTM final F-2761 - ICE Standard,» [En línea]. Available:
http://www.mdpnp.org/uploads/F2761_completed_committee_draft.pdf. [Último acceso: 06 2016].
[12] Real-Time Innovations, «RTI DDS,» [En línea]. Available: https://www.rti.com/products/dds/. [Último
acceso: 06 2016].
[13] RASPBERRY PI FOUNDATION, «Raspberry Pi 2,» [En línea]. Available:
https://www.raspberrypi.org/products/raspberry-pi-2-model-b/. [Último acceso: 01 2016].
[14] JAGUAR ELECTRONIC H.K. CO. Ltd , «Jaguar One,» [En línea]. Available:
http://www.jaguarboard.org/index.php/products/buy/jaguarboard/207/jaguarboard-
detail.html#specification. [Último acceso: 05 2016].
[15] Shenzhen Xunlong Software CO.,Limited, «Orange Pi One,» [En línea]. Available:
http://www.orangepi.org/orangepione/. [Último acceso: 05 2016].
[16] Hardkernel co., Ltd., «ODROID-C1+,» [En línea]. Available:
http://www.hardkernel.com/main/products/prdt_info.php?g_code=G143703355573. [Último acceso: 05
2016].
[17] SolidRun Ltd., «HummingBoard,» [En línea]. Available: https://www.solid-run.com/freescale-imx6-
family/hummingboard/. [Último acceso: 05 2016].
[18] BeagleBoard.org Foundation, «BeagleBoard Black,» [En línea]. Available: https://beagleboard.org/black.
[Último acceso: 05 2016].
[19] CubieBoard, «CubieBoard5,» [En línea]. Available: http://cubieboard.org/model/. [Último acceso: 05
2016].
[20] LinkSprite, «pcDuino4,» [En línea]. Available: http://www.linksprite.com/linksprite-pcduino/. [Último
acceso: 05 2016].
[21] The Raspberry Foundation, «Raspbian,» [En línea]. Available: https://www.raspbian.org/. [Último
acceso: 05 2016].
[22] Canonical Ltd, «Ubuntu MATE,» [En línea]. Available: https://ubuntu-mate.org/raspberry-pi/. [Último
acceso: 06 2016].
[23] Canonical Ltd, «Snappy Ubuntu Core for Raspberry Pi,» [En línea]. Available:
https://developer.ubuntu.com/en/snappy/start/#snappy-raspi2. [Último acceso: 05 2016].
[24] Microsoft Corporation, «Windows 10 IoT Core,» [En línea]. Available: http://ms-iot.github.io/content/en-
US/Downloads.htm. [Último acceso: 05 2016].
[25] RISC OS Open Limited, «RISC OS for the Raspberry Pi,» [En línea]. Available:
https://www.riscosopen.org/content/downloads/raspberry-pi. [Último acceso: 06 2016].
[26] The Apache Software Foundation, «Apache Kafka,» [En línea]. Available: http://kafka.apache.org/.
[Último acceso: 05 2016].
[27] The Apache Software Foundation!, «Kafka Log Compaction,» [En línea]. Available:
https://cwiki.apache.org/confluence/display/KAFKA/Log+Compaction. [Último acceso: 06 2016].
[28] Pivotal Software, Inc, «RabbitMQ,» [En línea]. Available: https://www.rabbitmq.com/. [Último acceso:
06 2016].
[29] solid IT gmbh, «DB-Engines Ranking,» [En línea]. Available: http://db-engines.com/en/ranking. [Último
acceso: 06 2016].
[30] Oracle Corporation, «Oracle Database,» [En línea]. Available:
https://www.oracle.com/es/database/index.html. [Último acceso: 06 2016].
[31] Oracle Corporation, «MySQL,» [En línea]. Available: http://www.mysql.com/. [Último acceso: 06 2016].
[32] Microsoft Corporation, «SQL Server,» [En línea]. Available: https://www.microsoft.com/es-es/server-
cloud/products/sql-server/. [Último acceso: 06 2016].
[33] Oracle Corporation, «SQL Developer,» [En línea]. Available:
http://www.oracle.com/technetwork/developer-tools/sql-developer/overview/index.html. [Último acceso:
06 2016].
[34] Dell, Inc, «TOAD,» [En línea]. Available: http://www.toadworld.com/. [Último acceso: 05 2016].
[35] Oracle Corporation, «Java,» [En línea]. Available: https://www.java.com/. [Último acceso: 07 2015].
[36] Python Software Foundation, «Python,» [En línea]. Available: https://www.python.org/. [Último acceso:
06 2016].
[37] Red Hat, Inc., «Hibernate,» [En línea]. Available: http://www.hibernate.org/. [Último acceso: 06 2016].
[38] Eclipse Foundation, «EclipseLinik,» [En línea]. Available: http://www.eclipse.org/eclipselink/. [Último
acceso: 06 2016].
[39] Apache Software Foundation, «OpenJPA,» [En línea]. Available: http://openjpa.apache.org/. [Último
acceso: 06 2016].
[40] The Querydsl Team, Mysema Oy, «QueryDSL,» [En línea]. Available: http://www.querydsl.com/.
[Último acceso: 06 2016].
[41] Apache Software Foundation, «iBatis,» [En línea]. Available: http://ibatis.apache.org/. [Último acceso: 06
2016].
[42] Apache Software Foundation, «Ant,» [En línea]. Available: http://ant.apache.org/. [Último acceso: 06
2016].
[43] Apache Software Foundation, «Maven,» [En línea]. Available: https://maven.apache.org/. [Último acceso:
06 2016].
[44] Heavybit, Inc, «Gradle,» [En línea]. Available: http://www.gradle.org/. [Último acceso: 06 2016].
[45] ZeroTurnaround, «Java Tools & Technologies Landscape,» 2014.
[46] Eclipse Foundation, «Eclipse,» [En línea]. Available: https://eclipse.org. [Último acceso: 07 2015].
[47] Eclipse Foundation, «SWT,» [En línea]. Available: https://wiki.eclipse.org/SWT. [Último acceso: 07
2015].
[48] Eclipse Foundation, «JFace,» [En línea]. Available: https://wiki.eclipse.org/JFace. [Último acceso: 07
2015].
[49] Eclipse Foundation, «JDT,» [En línea]. Available: https://wiki.eclipse.org/JDT. [Último acceso: 07 2015].
[50] JetBrains, «IntelliJ IDEA,» [En línea]. Available: https://www.jetbrains.com/idea/. [Último acceso: 07
2015].
[51] InfoWorld, «Top Java programming tools,» [En línea]. Available:
http://www.infoworld.com/article/2683534/development-environments/infoworld-review--top-java-
programming-tools.html. [Último acceso: 07 2015].
[52] Oracle Corporation, [En línea]. Available: https://netbeans.org/. [Último acceso: 07 2015].
[53] Oracle Corporation, «JDeveloper,» [En línea]. Available: http://www.oracle.com/technetwork/developer-
tools/jdev/overview/index-094652.html. [Último acceso: 07 2015].
[54] The Apache Software Foundation. , «Hadoop,» [En línea]. Available: http://hadoop.apache.org/. [Último
acceso: 05 2016].
[55] Health Level Seven Inc, «Especificación MLLP,» [En línea]. Available:
http://www.hl7.org/documentcenter/public_temp_73F3EA42-1C23-BA17-
0C011D0AAF40F217/wg/inm/mllp_transport_specification.PDF. [Último acceso: 06 2016].
[56] Oracle Corporation, «Oracle FAQ - Tablespaces,» [En línea]. Available:
http://www.orafaq.com/wiki/Tablespace#Default_tablespaces. [Último acceso: 02 2016].
[57] Oracle Corporation, «Oracle FAQ - Profiles,» [En línea]. Available:
http://www.orafaq.com/wiki/Profiles_and_password_management. [Último acceso: 03 2016].
[58] Oracle Corporation, «Oracle Help Center - Database Link,» 03 2016. [En línea]. Available:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5005.htm.
[59] Oracle Corporation, «Oracle Healp Center - Sequences,» [En línea]. Available:
https://docs.oracle.com/cd/B28359_01/server.111/b28310/views002.htm. [Último acceso: 03 2016].
[60] Oracle Corporation, «Oracle Help Center - Indexes,» [En línea]. Available:
https://docs.oracle.com/cd/E11882_01/server.112/e40540/indexiot.htm#CNCPT721. [Último acceso: 03
2016].
[61] Oracle Corporation, «Ask Tom - Indexes on foreign keys,» [En línea]. Available:
https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:292016138754. [Último
acceso: 03 2016].
[62] Oracle Corporation, «Oracle Help Center - Grant,» [En línea]. Available:
https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9013.htm. [Último acceso: 03
2016].
[63] The Apache Software Foundation, «Kafka Download,» [En línea]. Available:
http://kafka.apache.org/downloads.html. [Último acceso: 05 2016].
[64] The Apache Foundation, «Zookeeper Configuration,» [En línea]. Available:
https://zookeeper.apache.org/doc/r3.3.2/zookeeperAdmin.html#sc_configuration. [Último acceso: 05
2016].
[65] The Apache Foundation, «Apache Kafka: Broker Configuration,» [En línea]. Available:
http://kafka.apache.org/documentation.html#brokerconfigs. [Último acceso: 05 2016].
[66] The Apache Foundation, «Topic Configuration,» [En línea]. Available:
http://kafka.apache.org/documentation.html#topic-config. [Último acceso: 05 2016].
[67] The Apache Software Foundation, «Maven Central Repository,» [En línea]. Available:
https://repo1.maven.org/maven2/. [Último acceso: 05 2016].
[68] Oracle Corporation, «DAO,» [En línea]. Available:
http://www.oracle.com/technetwork/java/dataaccessobject-138824.html. [Último acceso: 05 2016].
[69] Red Hat, Inc., «Hibernate CacheConcurrencyStrategy,» [En línea]. Available:
https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/cache/ReadWriteCache.html. [Último acceso:
05 2016].
[70] The Apache Foundation, «JPA Transaction Type,» [En línea]. Available: http://tomee.apache.org/jpa-
concepts.html. [Último acceso: 05 2016].
Top Related