Arquitectura de la aplicación
Diseño inicial de la arquitectura:
Clases
Tras el brainstorming de clases, llegamos a las siguientes:
Database (base de datos)
Descripción
Interfaz sobre las bases de datos para encapsular la funcionalidad que se requiera de las mismas.
Responsabilidades
Abstraer el comportamiento interno de las bases de datos al resto de la aplicación para no depender de ellas y ocultar la complejidad del SQL.
Journal
Descripción
Mantiene un listado de operaciones pendientes de realización a fin de poder recuperarnos de caídas del sistema y de no sobrecargar el sistema, permitiendo planificar.
Responsabilidades
- Escribir en la base de datos lo que le ha pedido Watcher.
- Borrar la tarea una vez terminada.
Colaboraciones
History (Historia)
Descripción
Guarda toda la información del sistema de snapshots.
Responsabilidades
- Mantener la información de todas las versiones que guarda el usuario, tanto locales como en dispositivos externos.
Colaboraciones
XDelta3 Wrapper
Descripción
Ofrece a HD Lorean la funcionalidad presente en la aplicación xdelta que cubre necesidades del programa y traduce entre la misma y las facilidades que ofrece xdelta. Parte de una factoría gestionada por storage manager.
Colaboraciones
LVM Wrapper
Descripción
Ofrece a HD Lorean la funcionalidad presente en el sistema de archivos LVM que cubre las necesidades del programa y traduce entre las mismas y las facilidades que ofrece LVM. Parte de una factoría gestionada por storage manager.
Por ahora no desarrollada por estar fuera del alcance.
ZFS Wrapper
Descripción
Ofrece a HD Lorean la funcionalidad presente en el sistema de archivos ZFS que cubre las necesidades del programa y traduce entre las mismas y las facilidades que ofrece ZFS. Parte de una factoría gestionada por storage manager.
Por ahora no desarrollada por estar fuera del alcance.
Snapshot manager (Administrador de snapshots)
Descripción
Administra los snapshots.
Responsabilidades
- Borrar un snapshot.
- Crear un snapshot.
- Recuperar determinada versión de un archivo en un instante dado.
- Busca una versión entre todas las almacenadas en un instante dado.
Colaboraciones
Storage manager (Administrador de almacenamiento)
Descripción
Interfaz para los distintos sistemas de archivos que puede soportar la aplicación.
Responsabilidades
Abstraer el comportamiento interno del sistema de archivos al resto de la aplicación para no depender de él.
Colaboraciones
Snapshot viewer (Visor de diferencias entre los snapshots)
Descripción
Permite la comparación visual entre dos snapshots.
No desarrollado por el momento por estar fuera del acance
Scheduler
Descripción
Establece el orden en que se realizan las operaciones de copia de seguridad y cuándo deben suceder.
Responsabilidades
- Atender al journal.
- Planificar los snapshots mediante los monitores de carga del sistema.
- Ordenar grabar y borrar los snapshots.
- Avisar a Watcher de que se ha finalizado una operación.
Colaboraciones
- Monitor de carga del disco duro
- Monitor de espacio libre en disco
- Monitor de carga de la CPU
- Monitor de estado de la batería
- Journal
- Snapshot manager (para ordenar que se haga un snapshot)
Watcher
Descripción
Atiende a los cambios que se hayan realizado en el sistema (ya sea por eventos periódicos del planificador del propio sistema operativo, esto es cron, por cambios en archivos vigilados que se deban almacenar, usando inotify, o bien por orden del usuario). Asímismo ordena realizar backups.
Responsabilidades
- Escribir en el journal cuando se reciba un evento.
- Actualizar inotify.
Colaboraciones
Metadata Indexer (Indexador de metadatos)
Descripción
Indexa información útil para la posterior búsqueda de archivos en el sistema (tanto mediante su contenido como mediante metadatos).
Por ahora no desarrollada por estar fuera del alcance.
Help (ayuda)
Descripción
Muestra el manual de la aplicación.
Stats (Estadísticas)
Descripción
Recopila información útil sobre el uso de HD Lorean que permite elaborar estadísticas y predicciones (por ejemplo, sobre el uso del disco).
Por ahora no desarrollada por estar fuera del alcance.
Usuario
Descripción
Representa los datos del usuario y estadísticas sobre el mismo.
Por ahora no desarrollado por estar fuera del alcance.
Colaboraciones
Preferences (Diálogo de preferencias)
Descripción
Permite configurar la aplicación.
Responsabilidades
- Permitir la configuración de todo tipo de opciones, como indexar y/o excluir nuevos contenidos (carpetas, archivos, patrones…), periodicidad, uso de disco, etc.
Colaboraciones
Config wizard (Asistente de configuración)
Descripción
Permite fijar de manera sencilla y rápida las opciones de configuración más comunes del programa.
Por ahora no desarrollada por estar fuera del alcance.
Responsabilidades
- Obtener todos los datos de configuración necesarios.
- Almacenar esos cambios.
- Avisar al backend de que se han producido.
Colaboraciones
Config file manager (lector de archivo de configuración)
Descripción
Lee el archivo de configuración y lo traduce a parámetros de la aplicación.
Responsabilidades
- Validación de los datos.
- Escritura de opciones.
- Crear lista de archivos monitoreados.
Colaboraciones
En función de la implementación, con regexp parser.
Regexp parser (Parser de expresiones regulares).
Descripción
Interpreta expresiones regulares y ofrece funcionalidad basada en las mismas.
Inotify handler
Descripción
Interpreta las señales que envía inotify y ofrece una API para su manejo.
Responsabilidades
- Añadir o eliminar notificadores.
- Notificar los eventos recibidos.
Cron handler (manejador cron)
Descripción
Ofrece una API para interactuar con el demonio cron de planificación de tareas del sistema operativo.
D-Bus Manager
Descripción
Permite la comunicación vía paso de mensajes entre el frontend (o GUI) y el backend de la aplicación.
Responsabilidades
- Transformar los mensajes de D-Bus en órdenes internas del programa => enviar órdenes.
Colaboraciones
Battery monitor (control de la batería)
Descripción
Monitoriza el estado de la batería.
Parte de una factoría, aún no implementada en su conjunto.
HD Load monitor (Control de carga del disco duro)
Descripción
Monitoriza la carga del disco duro (nivel de operaciones de entrada/salida del sistema) a fin de proporcionar información al planificador sobre cuándo conviene efectuar las operaciones.
Por ahora no desarrollada por estar fuera del alcance.
Posiblemente parte de una factoría para unificar las interfaces.
CPU load monitor (control de carga de la CPU)
Descripción
Monitoriza el estado de carga de la CPU. Posiblemente parte de una factoría.
Por ahora no desarrollada por estar fuera del alcance.
Storage monitor (Monitor de almacenamiento)
Descripción
Supervisa el espacio de almacenamiento y proporciona información sobre el mismo. Posiblemente parte de una factoría.
Responsabilidades
- Comprobar si hay espacio en disco para escribir.
- Comprobar cuánto va a ocupar el backup que se almacene.
FUSE adapter (Adaptador FUSE)
Descripción
Clase que permite traducir la información almacenada en nuestro sistema de snapshots a una vista compatible con las operaciones de archivo estándar de linux.
Por ahora no desarrollada por estar fuera del alcance.
HAL manager (notificador de cambios en el hardware)
Descripción
También conocido como "HAL 9000", se encarga de notificar los cambios en las unidades conectadas al sistema que puedan afectar a los backups (por ejemplo si se extrae un disco sobre el que se está efectuando un backup).
Por ahora no desarrollada por estar fuera del alcance.
Device manager (administrador de dispositivos externos)
Descripción
Gestiona las copias de los snapshots de HD Lorean en medios de almacenamiento externos como discos ópticos o memorias externas.
Por ahora no desarrollada por estar fuera del alcance.
Optical Media manager (Administrador de la grabadora)
Descripción
Permite exportar snapshots a medios ópticos. Posiblemente parte de una factoría para unificar las interfaces.
External API (API pública del sistema)
Descripción
Exporta la funcionalidad de HD Lorean y permite su uso por parte de aplicaciones de terceros.
Por ahora no desarrollada por estar fuera del alcance.
UI (Interfaz de usuario)
Descripción
Implementa la interfaz de usuario.
Nautilus integration (Integración con Nautilus)
Descripción
Se encarga de la integración con Nautilus, a modo de adaptador entre la interfaz que ofrezca el API de Nautilus y la información que proporciona HD Lorean.
Por ahora no desarrollada por estar fuera del alcance.
Colaboraciones
Snapshot finder (Buscador de versiones)
Descripción
Busca entre las versiones almacenadas, posiblemente por contenido.
Por ahora no desarrollada por estar fuera del alcance.
Colaboraciones
Beagle integration (Integración con Beagle)
Descripción
Se encarga de la integración con Beagle para permitir al sistema de indexación de contenidos que indexe nuestros archivos y posiblemente integrarlos con sus resultados para realizar nuestras búsquedas.
Por ahora no desarrollada por estar fuera del alcance.
Interacción clases
Introducción
HD Lorean posee dos bloques bien diferenciados, uno es el backend y el otro el frontend o interfaz de usuario. El primero se encarga de la comunicación entre el hardware y las tecnologías que permiten la gestión de snapshots y la monitorización de ficheros. El otro se encarga de la comunicación con el usuario.
Entre ellos, pero formando parte de backend, se encuentra el módulo D-Bus Manager. Esta clase no se limita únicamente a encapsular d-bus sino que su misión es la de traducir los mensajes que reciba en un conjunto de órdenes internas de HD Lorean, las cuales enviará a los distintos módulos de la aplicación para que se realice el trabajo necesario.
Dentro del backend, a su vez, es posible distinguir dos subgrupos importantes que separan además dos cursos de colaboración frecuentes. Por un lado, el grupo formado por las clases config file manager, watcher, scheduler, inotify handler y cron handler forman la ruta de planificación y configuración. Por otro, el formado por snapshot manager y el conjunto de clases que utilizan como interfaz storage manager, que forman la ruta de gestión de snapshots.
Ruta de comunicación con el usuario
Gran parte de los casos de uso son desencadenados por el usuario y por tanto requieren la comunicación con HD Lorean. Para ello existen las clases pertenecientes al frontend. En general, el comportamiento de esta ruta es el siguiente.
- Frontend (alguna de sus clases: el diálogo de preferencias, el asistente de configuración, el visor de snapshots…).
- D-Bus Manager (recibe un mensaje con la tarea requerida por el usuario).
- Ruta de gestión de planificación y configuración
La sencillez de la ruta se apoya en la necesidad de mantener lo más separado posible la interfaz de usuario del backend.
Por último, existe también una ruta inversa (puede denominarse Ruta de comunicación con el usuario inversa) que permite que resultados internos de HD Lorean lleguen en forma de mensajes al frontend y este pueda exponerlos al usuario de alguna forma. Ésta es:
Ruta de comunicación con el usuario inversa
- Ruta de gestión de planificación y configuración (desde aquí se emite algún resultado que debe mostrarse al usuario).
- D-Bus Manager (dbus recibe el resultado y transmite un mensaje al frontend; alternativamente el frontend escucha mensajes del backend y presenta la información según sea necesario).
- Frontend.
Ruta de planificación y configuración
Como se dijo anteriormente, la ruta de planificación engloba las colaboraciones entre config-file-manager, watcher, scheduler, inotify-handler y cron-handler. Config file manager se encarga de la gestión del archivo de configuración, tanto de su versión física escrita en disco como de su versión virtual a la que el resto de módulos puede acceder para conocer diversos aspectos de la configuración. Los inotify handler y cron handler disparan eventos de inotify y cron que permiten monitorización de cambios en tiempo real y bajo planificación respectivamente. También disponen de acceso, a través de la clase clases, a una base de datos rápida o journal donde anotan las operaciones que han de realizarse próximamente, para poder planificarlas y reiniciarlas (a modo de transacciones semiatómicas) a fin de poder recuperarse de caídas del sistema o del programa, planificadas o no.
El scheduler por otro lado se encarga de gestionar y priorizar las órdenes que le llegan desde los disparadores o directamente desde el módulo de dbus, en función de la carga del sistema recogida de diversos monitores. También se comunica con la ruta de gestión de snapshots y atiende a sus resultados como veremos en breve.
La ruta de colaboraciones típica se muestra a continuación:
- D-Bus Manager (dbus tiene algún mensaje que convertir en órdenes)
- Config file manager (desde dbus se indica si ha de modificarse alguna configuración)
- Scheduler (desde dbus se informa al scheduler de alguna orden recibida desde el frontend)
- Ruta de gestión de snapshots
Desde aquí, dependiendo de la orden, el planificador accedería a la ruta de gestión de snapshots bien para ordenar la creación de un nuevo snapshot, bien para recuperar la información de alguno de los existentes, bien para eliminar algunos de ellos. A esta ruta la llamaremos ruta de usuario.
Como HD Lorean actúa en su mayor parte del tiempo sin necesidad de interacción por parte del usuario, incitado por inotify o cron, otra ruta alternativa probablemente más frecuente que la anterior es la siguiente:
- Cron handler o inotify handler (se ha producido algun cambio en un fichero o directorio que debe ser guardado, o se ha producido una señal planificada).
- Watcher (escritura al journal y tratamiento del suceso en función de si es cron o inotify)
- Scheduler (prioriza las señales recibidas de los módulos anteriores)
- Ruta de gestión de snapshots
A esta última ruta la llamaremos ruta de monitorización.
Igual que en el caso anterior, existe una ruta inversa que parte de los resultados de la ruta de gestión hasta llegar a dbus.
Ruta de monitorización inversa
- Ruta de gestión de snapshots (la ruta emite algún resultado en forma de objeto Snapshot)
- Snapshot manager (objeto con los resultados de la ruta de gestión)
- Scheduler (recibe el resultado de una orden)
- D-Bus Manager (recibe, si procede, los resultados un mensaje con los resultados de la operación).
Ruta de gestión de snapshots
Esta última ruta está formada por las clases snapshot manager, y el conjunto de storage manager. La primera, snapshot-manager, ofrece toda la funcionalidad relativa a la gestión de snapshots como la creación, eliminación o lectura de los mismos. Esta clase hace uso del conjunto storage manager que ofrece el nivel más bajo de funcionalidad a través de una API común. Los integrantes del módulo codifican esta API dependiendo de si el soporte de snapshots es un sistema de archivos (como LVM o ZFS), una aplicación externa (como xdelta3) o un modelo propio (como snapshot-core), para tratar de permitir el uso del sistema más eficiente según dónde se desplegase la aplicación, así como para obligarnos a separar ese componente crítico del resto de funcionalidad de la aplicación.
La clase Snapshot sirve de puente de comunicación entre Scheduler y la ruta de gestión que nos ocupa. La clase representa un Snapshot como resultado con capacidad para autoarchivarse dentro de una posible base de datos y con toda la información útil que fuese necesaria. Es el resultado de las operaciones de lectura, escritura o eliminación de la ruta de gestión.
La ruta de colaboraciones típica es la siguiente:
- Snapshot manager (la orden llega desde el scheduler)
- Storage manager (entre ellos, el más adecuado al medio)
- Snapshot manager (recibe la terminación del módulo que corresponda)
- Snapshot (se crea un objeto snapshot con información relevante asociada a la operación)
- Ruta de planificación y configuración
NOTA: En esta última se ha incluido el retorno inverso hasta la comunicación con la ruta de planificación. Hay que notar, que gran parte de estos retornos son implícitos debido al retorno de llamadas a función.
Rutas por casos de uso
Con todas las rutas especificadas, la elaboración de las colaboraciones por casos de uso es mucho más sencilla y clara.
Añadir una nueva carpeta a indexar
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (cambio en la configuración y órdenes)
3. ruta de gestión de snapshots (creación del snapshot)
Borrar todas las versiones de un archivo
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (cambio en la configuración y órdenes)
3. ruta de gestión de snapshots (Cambios a disco, bases de datos)
Borrar una versión de un archivo
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (cambio en la configuración y órdenes)
3. ruta de gestión de snapshots (cambios a disco, bases de datos)
Configurar exclusión
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (cambio en la configuración y órdenes para borrar el patrón; posible feedback al usuario)
3. ruta de gestión de snapshots (consultas a bases de datos, posiblemente borrar, devolver el feedback que sea necesario)
Configuración inicial
1. Ruta de comunicación con el usuario (mediante el asistente)
2. Ruta de planificación y configuración (cambio en la configuración y actualizar estado actual)
3. ruta de gestión de snapshots (si es necesario realizar trabajo o añadir algo)
Copiar al lado
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (cambio en la configuración y órdenes)
3. ruta de gestión de snapshots (lectura y posible reconstrucción del snapshot)
Eliminar una carpeta a indexar
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (cambio en la configuración y actualizar estado actual)
3. ruta de gestión de snapshots (escrituras a disco y base de datos)
Sincronizar
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (registro en base de datos)
Exportar a un dispositivo externo
1. Ruta de comunicación con el usuario, posiblemente colaborando con HAL (en el backend, para detectar dispositivo)
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (registro en base de datos de dónde se encuentra la información; posible eliminación local para ahorrar espacio).
Importar de un dispositivo externo
1. Ruta de comunicación con el usuario, posiblemente colaborando con HAL (en el backend, para detectar dispositivo)
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (añadido a base de datos de la nueva información, comprobando inconsistencias; ver el caso de uso).
Guardar un backup
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (escrituras a disco y base de datos).
Guardar versiones automáticamente
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (posible actualización del estado).
Backup inicial
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (creación de estructuras de datos pertinentes; primer backup).
Sobreescribir última versión
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (lectura del snapshot, reconstrucción del mismo).
Buscar contenidos en backup
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (búsqueda en base de datos).
3.a Alternativamente, además usar clases.
Almacenamiento extra
No cubierto inicialmente por no ser en absoluto crítico.
Interrupción del backup
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (órdenes)
3. ruta de gestión de snapshots (según queden o no tareas pendientes)
Aplicaciones de terceros
No cubierta inicialmente al depender de un estado de desarrollo bastante más avanzado de la aplicación y no ser crítico. En cualquier caso el componente dbus ofrece un api de cara a otras interfaces de usuario para la aplicación.
Ver versiones de carpetas
1. Ruta de comunicación con el usuario
2. Ruta de planificación y configuración (órdenes, recibir información)
3. ruta de gestión de snapshots (lectura de los listados de base de datos)
UML
Definición
Lenguaje Unificado de Modelado (UML1, por sus siglas en inglés, Unified Modeling Language) es el lenguaje de modelado de sistemas de software más conocido y utilizado en la actualidad. Es un lenguaje gráfico para visualizar, especificar, construir y documentar un sistema de software. UML ofrece un estándar para describir un "plano" del sistema (modelo), incluyendo aspectos conceptuales tales como procesos de negocios y funciones del sistema, y aspectos concretos como expresiones de lenguajes de programación, esquemas de bases de datos y componentes de software reutilizables.
Lista de diagramas de clase
Diagrama de clases general
En este diagrama aparecen representadas todas las clases de la aplicación, incluso algunas que no han sido desarrolladas por estar fuera del alcance.
Diagrama de clases de GUI
En este diagrama aparecen representadas todas las clases de la interfaz gráfica del usuario (GUI).
Diagrama de clases del backend
En este diagrama aparecen representadas todas las clases que forman parte del backend (que incluye a los módulos Watcher, SnapshotManager, Logger y Database).
Diagrama de componentes
En él se muestran los componentes que forman la aplicación.
Diagrama de estado
Diagrama de estados del demonio HDLorean. En él se muestran los distintos estados en que se puede encontrar la aplicación.
Diagrama de despliegue
Diagrama de despliegue de la aplicación. Se muestran los distintos equipos que estarían implicados en el proceso, los clientes y el servidor.
Lista de diagramas de actividad
Diagrama de actividad de añadir carpeta a indexar.
El usuario elige una carpeta para añadir a las ya vigiladas. El caso para un archivo sería similar.
Diagrama de actividad de eliminar una carpeta indexada
El usuario elige una dejar de vigilar una carpeta previamente añadida. El caso para un archivo sería similar.
Diagrama de actividad de excluir una carpeta a indexar.
El usuario elige una carpeta para excluir de las vigiladas. El caso para un archivo sería similar.
Es útil para el caso en que se ha añadido una carpeta de la cual se quiere vigilar casi todo su contenido excepto una parte, que sería lo excuído.
Diagrama de actividad de borrar una versión.
El usuario elige borrar una versión.
Diagrama de actividad de restaurar una versión sin sobreescribir.
El usuario elige restaurar una versión sin borrar la actual.
Diagrama de actividad de crear una versión.
El usuario, cron o inotify crean una versión en ese momento.
Diagrama de actividad de preferencias.
El usuario decide cambiar una preferencia. Este es el caso general.
Lista de diagramas de secuencia
Diagrama de secuencia de makeSnapshot cuando lo llevan a cabo cron o inotify.
Cron o inotify detectan un evento que les indica que deben crear una versión.
Diagrama de secuencia de makeSnapshot cuando lo lleva a cabo el usuario.
El usuario elige crear una nueva versión.
Diagrama de secuencia de getPeriodically.
El usuario da al botón preferencias. La aplicación crea está ventana, para lo cual necesita recuperar los datos almacenados en el fichero de configuración. Este es un caso genérico.
Diagrama de secuencia de setPeriodically.
El usuario elige cambiar la periodicidad con que se harán las copias de seguridad. Ha de modificarse, por tanto, el fichero de configuración. En este caso, también habrá que actualizar el crontab.
Diagrama de secuencia de setEveryChange.
El usuario elige crear una nueva versión cada vez que se produzcan cambios (activar inotify). Es el caso genérico de cambiar una preferencia.
Diagrama de secuencia de restWithoutOverwrite.
El usuario elige recuperar una versión sin borrar la actual.
Diagrama de secuencia de scheduler.
Scheduler es el planificador del programa. Atendiendo a varios aspectos de la configuración elige el momento idóneo para llevar a cabo los distintos procesos del sistema, como puede ser crear nuevas versiones.
Patrones de diseño
Definición
En Ingeniería del Software, un patrón de diseño2 es una solución a un problema de diseño. Para que una solución sea considerada un patrón debe poseer ciertas características. Una de ellas es que debe haber comprobado su efectividad resolviendo problemas similares en ocasiones anteriores. Otra es que debe ser reutilizable, lo que significa que es aplicable a diferentes problemas de diseño en distintas circunstancias.
Lista de patrones de diseño
Base de datos como Adapter
Nombre del patrón: Adapter.
Clasificación del patrón: Estructural.
Intención: Se utiliza para transformar una interfaz en otra, de tal modo que una clase que no pudiera utilizar la primera, haga uso de ella a través de la segunda.
Convierte la interfaz de una clase en otra interfaz que el cliente espera. Adapter permite a las clases trabajar juntas, lo que de otra manera no podrían hacerlo debido a sus interfaces incompatibles.
También conocido como: Wrapper.
Motivación: Se pretendía usar pySqlite para gestionar las bases de datos, para lo cual se ha tenido que crear un adaptador que se adecue a las necesidades del programa.
Aplicabilidad: Este patrón se usa cuando:
- Se desea usar una clase existente, y su interfaz no se iguala con la necesitada.
- Cuando se desea crear una clase reutilizable que coopera con clases no relacionadas, es decir, las clases no tienen necesariamente interfaces compatibles.
Así, se han podido usar las tecnologías antes mencionadas, adaptándolas para que fuese posible trabajar con ellas en la aplicación.
Estructura:
Participantes:
Target: Journal y History. Define la interfaz específica del dominio que Client usa.
Client: Colabora con la conformación de objetos para la interfaz Target.
Adaptee: PySqlite. Define una interfaz existente que necesita adaptarse.
Adapter: DatabaseWrapper. Adapta la interfaz de pySqlite a Journal y History.
Colaboraciones: Client llama a las operaciones sobre una instancia Adapter. De hecho, el adaptador llama a las operaciones de Adaptee que llevan a cabo el pedido.
Implementación: Se ha creado una nueva clase que será el Adaptador (DatabaseWrapper), que extienda del componente existente (pySqlite) e implemente la interfaz obligatoria. De este modo, se tiene la funcionalidad que deseada y se cumple la condición de implementar la interfaz.
Del mismo modo se puede decir que Journal y History adaptan DatabaseWrapper para que un cliente, ya sea Watcher, Scheduler, o SnapshotManager, puedan hacer uso de él.
SnapshotManager como Abstract Factory
Nombre del patrón: Abstract Factory.
Clasificación del patrón: Creacional.
Intención: Crear diferentes familias de objetos.
Motivación: El patrón de diseño Abstract Factory aborda el problema de la creación de familias de objetos que comparten toda una serie de características comunes en los objetos que componen dichas familias.
Desde un principio, el equipo de desarrollo supo que se centraría en una factoría concreta, xDelta3, pero la intención de incluir nuevas familias, por ejemplo ZFS y LVM motivó a desarrollar este diseño para facilitar este futuro acoplamiento.
Aplicabilidad:
- El uso de este patrón está recomendado para situaciones en las que se tiene una familia de productos concretos y se preveé la inclusión de distintas familias de productos en un futuro.
- Un sistema debe ser independiente de cómo se crean, componen y representan sus productos.
- Un sistema debe ser configurado con una familia de productos entre varias.
- Una familia de objetos producto relacionados está diseñada para ser usada conjuntamente y es necesario hacer cumplir esa restricción.
- Se quiere proporcionar una biblioteca de clases producto y sólo se quiere revelar sus interfaces y no sus implementaciones.
Participantes: La estructura del patrón Abstract Factory es la siguiente:
Client: SnapshotManager. Sólo conoce la clases abstracta, IstorageWrapper, de los componentes.
AbstractFactory: Define métodos abstractos para crear instancias de clases Producto concretas.(IStorageWrapper)
ConcreteFactory: Implementan los métodos definidos por la superclase AbstractFactory para crear instancias de clases Producto concretas.
Métodos de creación de los productos genéricos en la interfaz de la fábrica (makeSnapshot, recoverSnapshot, recoverBackups…) que retornan una versión en un momento dado.
Son cada uno de los distintos wrappers (xDelta3Wrapper, LVMWrapper, ZFSWrapper) para crear el objeto de la tecnología pertinente.
Colaboraciones: Normalmente, una instancia de la clase ConcreteFactory es creada en tiempo de ejecución. Esta fábrica crea objetos de Producto concretos teniendo en cuenta una implementación particular. Para crear objetos de Producto diferentes, los clientes deberían usar una fábrica concreta distinta.
Al instanciar SnapshotManager se elige por parámetro la factoría a utilizar.
La clase abstracta AbstractFactory (IstorageWrapper) difiere la creación de objetos Producto a su subclase ConcreteFactory (los distintos wrappers).
Consecuencias:
- Aísla clases concretas: Este patrón ayuda a controlar los productos que una aplicación crea porque encapsula la responsabilidad y el proceso de creación de objetos y aísla a los clientes de las implementaciones.
- Se puede cambiar de familia de productos : Para cambiar de productos se debe cambiar de fábrica concreta.
- Promueve la consistencia entre productos: Cuando los productos son diseñados para trabajar en conjunto, es importante que en una aplicación se utilicen objetos de una familia a la vez.
- Desventaja: es difícil dar cabida a nuevos tipos de productos. Para crear nuevos tipos de productos es necesario crear una nueva fábrica concreta e implementar todas las operaciones que ofrece la superclase AbstractFactory.
Como conclusión se puede decir que este patrón está aconsejado cuando se prevé la inclusión de nuevas familias de productos, pero puede resultar contraproducente cuando se añaden nuevos productos o cambian los existentes.
Usos conocidos: Creación de familias de interfaces gráficos en las cuales los elementos (productos) del interfaz se mantienen constantes (por ejemplo labels, botones, cajas de texto …) pero el dibujado de dichos elementos puede delegarse en distintas familias (por ejemplo QT, GTK, etc) de forma que, en función de la fábrica seleccionada obtenemos unos botones u otros.
Patrones relacionados: Cuando se usa en conjunción con Singleton se crea una única fábrica, de modo que no habrá usos inconsistentes de los productos en el programa.
En este caso se usará el patrón Monostate en lugar del Singleton.
SnapshotManager como Façade
Nombre del patrón: Façade.
Clasificación del patrón: Estructural.
Intención: El patrón de diseño façade sirve para proveer de una interfaz unificada sencilla que haga de intermediaria entre un cliente y una interfaz o grupo de interfaces más complejas.
Motivación:
- Crear un intermediario y realizar llamadas a la biblioteca sólo o, sobre todo, a través de él.
- Crear una API intermedia, bien diseñada, que permita acceder a la funcionalidad de las demás.
- Un objetivo de diseño general es minimizar la comunicación y dependencias entre subsistemas. Un modo de lograrlo es introducir un objeto fachada que proporciona una interfaz unificada a la funcionalidad del subsistema.
Aplicabilidad: Úsese el patrón Façade cuando se desee:
- Proporcionar una interfaz simple para un subsistema complejo, evitando así que la mayoría de clientes tengan que conocer todas las clases internas del subsistema.
- Desacoplar un subsistema de sus clientes y otros subsistemas, promoviendo así la independencia entre subsistemas (y, por tanto, la portabilidad).
Participantes:
Façade: IstorageWrapper, sabe qué clases del subsistema son las responsables de llevar a cabo cada petición. Delega las peticiones de los clientes a los objetos apropiados del subsistema.
Clases del subsistema: Los distintos wrapper, que implementan la funcionalidad del subsistema.
Colaboraciones: IstorageWrapper es un interfaz que hace que los wrappers tengan una fachada común.
Consecuencias:
- Reduce el número de objetos con el que tienen que tratar los clientes, haciendo que el subsistema sea más fácil de usar.
- Promueve un bajo acoplamiento entre el subsistema y los clientes.
- Permite variar sus componentes sin que los clientes se vean afectados.
- Ayuda a estructurar en capas un sistema.
Implementación: Reducir el acoplamiento entre cliente y subsistema: Haciendo que el Façade sea una clase abstracta o una interfaz.
O configurándolo con diferentes objetos del subsistema.
Clases del subsistema públicas o privadas: Sería útil hacer que determinadas clases del subsistema fueran privadas, aunque pocos lenguajes lo permiten.
Patrones relacionados: Adapter.
SnapshotManager como Adapter
Nombre del patrón: Adapter.
Clasificación del patrón: Estructural.
Intención: Se utiliza para transformar una interfaz en otra, de tal modo que una clase que no pudiera utilizar la primera, haga uso de ella a través de la segunda.
Convierte la interfaz de una clase en otra interfaz que el cliente espera. Adapter permite a las clases trabajar juntas, lo que de otra manera no podrían hacerlo debido a sus interfaces incompatibles.
También conocido como: Wrapper.
Motivación: Se quería usar xDelta3, ZFS y LVM para crear las versiones, para lo cual se ha tenido que crear un adaptador para cada una de ellas, de forma que cumplan la funcionalidad requerida.
Aplicabilidad: Este patrón se usa cuando:
- Se desea usar una clase existente, y su interfaz no se iguala con la necesitada.
- Cuando se desea crear una clase reutilizable que coopera con clases no relacionadas, es decir, las clases no tienen necesariamente interfaces compatibles.
De esta forma, se han podido usar las tecnologías antes mencionadas, adaptándolas para que fuese posible trabajar con ellas en la aplicación.
Participantes:
Target: IStorageWrapper. Define la interfaz específica del dominio que Client usa.
Client: Colabora con la conformación de objetos para la interfaz Target. (SnapshotManager)
Adaptee: xDelta3, ZFS y LVM. Definen las interfaces existentes que necesitan adaptarse.
Adapter: xDelta3Wrapper, ZFSWrapper, LVMWrapper. Adaptan la interfaz de xDelta3, ZFS y LVM respectivamente.
Colaboraciones: Client llama a las operaciones sobre una instancia Adapter. De hecho, el adaptador llama a las operaciones de Adaptee que llevan a cabo el pedido.
Implementación: Crear una nueva clase que será el Adaptador, que extienda del componente existente e implemente la interfaz obligatoria. De este modo se tiene la funcionalidad deseada y se cumple la condición de implementar la interfaz.
Patrones relacionados: Façade.
SnapshotManager como Monostate
Nombre del patrón: Monostate.
Clasificación del patrón: Creacional.
Intención: Asegura que todas las instancias tengan un estado común.
Motivación: Se accede a las versiones desde distintos puntos, y el estado ha de ser el mismo para todas.
Aplicabilidad: Se utiliza cuando se debe tener el mismo estado independientemente del acceso.
Participantes:
Monostate: SnapshotManager.
Colaboraciones:
Consecuencias:
Implementación:
Patrones relacionados: Abstract Factory, Singleton.
Estructura del módulo SnapshotManager:
ConfigFileManager como Singleton
Nombre del patrón: Singleton.
Clasificación del patrón: Creacional.
Intención: Garantizar que una clase sólo tenga una instancia y proporcionar un punto de acceso global a ella
Motivación: Se accede a la configuración del programa desde distintos puntos, y siempre hemos de referirnos a la misma instancia.
Aplicabilidad: Se utiliza cuando deba haber exactamente una instancia de una clase y deba ser accesible a los clientes desde un punto de acceso conocido.
Las situaciones más habituales de aplicación de este patrón son aquellas en las que dicha clase controla el acceso a un recurso físico único o cuando cierto tipo de datos debe estar disponible para todos los demás objetos de la aplicación.
Estructura: Código que implementa el patrón, controlando una única instancia:
def __new__(self,watcher=None): # if the class has not been instanciated before do it # else return instance. if self.__instance is None: self.__configFilePath = os.getenv("HOME")+"/.hdlorean/config.cfg" self.__instance = object.__new__(self) if watcher is not None: self.__watcher = watcher return self.__instance
Participantes:
Singleton: ConfigFileManager, define un método instancia que permite que los clientes accedan a su única instancia. Instancia es un método de clase estático.
Colaboraciones: Los clientes acceden a una instancia de un Singleton exclusivamente a través de un método instancia de éste.
Consecuencias:
- Acceso controlado a la única instancia.
- Espacio de nombres reducido: no hay variables globales.
- Puede adaptarse para permitir más de una instancia.
Implementación: El patrón Singleton se implementa creando en una clase un método que crea una instancia del objeto sólo si todavía no existe alguna. Para asegurar que la clase no puede ser instanciada nuevamente se regula el alcance del constructor (con atributos como protegido o privado).
Patrones relacionados: Abstract Factory, Monostate.
Demonio hdloreand como Singleton
Nombre del patrón: Singleton.
Clasificación del patrón: Creacional.
Intención: Garantizar que una clase sólo tenga una instancia y proporcionar un punto de acceso global a ella
Motivación: Se accede a la configuración del programa desde distintos puntos, y siempre hemos de referirnos a la misma instancia.
Aplicabilidad: Se utiliza cuando deba haber exactamente una instancia de una clase y deba ser accesible a los clientes desde un punto de acceso conocido.
Las situaciones más habituales de aplicación de este patrón son aquellas en las que dicha clase controla el acceso a un recurso físico único o cuando cierto tipo de datos debe estar disponible para todos los demás objetos de la aplicación.
Estructura: Código que implementa el patrón, controlando una única instancia:
def __new__(self): if self.__instance is None: self.__instance = object.__new__(self) db = Journal.Journal() db.createTable() self.__watcher = Watcher.Watcher() self.__scheduler = Scheduler.Scheduler() bp = BackendProxy.BackendProxy(self.__watcher,self.__scheduler,self.__instance) self.__cfm = ConfigFileManager.ConfigFileManager() return self.__instance
Participantes:
Singleton: hdloreand es el demonio de la aplicación, define un método instancia que permite que los clientes accedan a su única instancia. Instancia es un método de clase estático.
Colaboraciones: Los clientes acceden a una instancia de un Singleton exclusivamente a través de un método instancia de éste.
Consecuencias:
- Acceso controlado a la única instancia.
- Espacio de nombres reducido: no hay variables globales.
- Puede adaptarse para permitir más de una instancia.
Implementación: El patrón Singleton se implementa creando en una clase un método que crea una instancia del objeto sólo si todavía no existe alguna. Para asegurar que la clase no puede ser instanciada nuevamente se regula el alcance del constructor (con atributos como protegido o privado).
Patrones relacionados: Abstract Factory, Monostate.
PyInotifyHandler como Adapter
Nombre del patrón: Adapter.
Clasificación del patrón: Estructural.
Intención: Se utiliza para transformar una interfaz en otra, de tal modo que una clase que no pudiera utilizar la primera, haga uso de ella a través de la segunda.
Convierte la interfaz de una clase en otra interfaz que el cliente espera. Adapter permite a las clases trabajar juntas, lo que de otra manera no podrían hacerlo debido a sus interfaces incompatibles.
También conocido como: Wrapper.
Motivación: El equipo quería usar Inotify para gestionar los eventos que ocurren sobre un archivo vigilado. Se investigó su uso y se decidió utilizar el módulo pyInotify, incluído en python.
Aplicabilidad: Este patrón se usa cuando:
- Se desea usar una clase existente, y su interfaz no se iguala con la necesitada.
- Cuando se desea crear una clase reutilizable que coopera con clases no relacionadas, es decir, las clases no tienen necesariamente interfaces compatibles.
Estructura:
Participantes:
Target: PyinotifyHandler. Define la interfaz específica del dominio que Client usa.
Client: Watcher. Colabora con la conformación de objetos para la interfaz Target.
Adaptee: PyInotify. Define una interfaz existente que necesita adaptarse.
Adapter: Pyinotifyhandler. Adapta la interfaz del pyInotify.
Colaboraciones: Client llama a las operaciones sobre una instancia Adapter. De hecho, el adaptador llama a las operaciones de Adaptee que llevan a cabo el pedido.
Implementación: Python tiene el módulo pyInotify, el cual nos ofrece una manera genérica y abstracta para manipular las funcionalidades de inotify. Se ha creado una nueva clase, Pyinotifyhandler, que prepara las estructuras de datos y configura inotify para que la aplicación, (Watcher en particular) pueda gestionar los eventos de forma acorde al objetivo del programa.
El sistema de paso de mensajes DBUS como Mediator
Nombre del patrón: Mediator.
Clasificación del patrón: De comportamiento.
Intención: Definir un objeto que encapsule como interactúa un conjunto de objetos.
Motivación: Cuando muchos objetos interactúan con otros objetos, se puede formar una estructura muy compleja, con objetos con muchas conexiones con otros objetos. En un caso extremo cada objeto puede conocer a todos los demás objetos. Para evitar esto el patrón Mediator encapsula el comportamiento de todo un conjunto de objetos en un solo objeto.
Aplicabilidad: Usar el patrón Mediator cuando:
- Un conjunto grande de objetos se comunica de una forma bien definida, pero compleja.
- Reutilizar un objeto se hace difícil por que se relaciona con muchos objetos.
- El comportamiento de muchos objetos que esta distribuido entre varias clases, puede resumirse en una o varias por subclasificación.
Participantes:
Mediator (BackendProxy y FrontendProxy): Implementa el comportamiento cooperativo entre los colegas (como se comunican entre ellos). Además los conoce y mantiene. Debido a las peculiaridades de DBUS, FrontendProxy recibe las peticiones de GUI y UI y se las transmite a BackendProxy, que es el encargado de enviarla al colega destinatario.
Colleagues: Cada colega conoce su mediador, y usa a este para comunicarse con otros colegas.
Colaboraciones: Los colegas envían y reciben requerimientos (requests) de un objeto mediador. El mediador implementa como se comunican los colegas.
Consecuencias: El patrón Mediator tiene los siguientes beneficios y desventajas:
- Desacopla a los colegas: el patrón Mediator promueve bajar el acoplamiento entre colegas. Se puede variar y reutilizar colegas y mediadores independientemente .
- Simplifica la comunicación entre objetos: Los objetos que se comunican de la forma "muchos a muchos" puede ser remplazada por una forma "uno a muchos" que es menos compleja y más elegante. Además esta forma de comunicación es más fácil de entender.
- Abstrae como los objetos cooperan: Haciendo a la mediación un concepto independiente y encapsulándolo en un objeto permite enfocar como los objetos interactúan. Esto ayuda a clarificar como los objetos se relacionan en un sistema.
- Centraliza el control: El mediador es el que se encarga de comunicar a los colegas, este puede ser muy complejo, difícil de entender y modificar.
Implementación:
Omitir la clase abstracta Mediator. No es necesario crear una clase abstracta Mediador cuando los objetos solo trabajan con un mediador. El acoplamiento abstracto de dicha clase permite que los objetos trabajen con diferentes subclases Mediator y viceversa.
Comunicación Objeto y Mediador. Los objetos se comunican su mediador cuanto tiene lugar un evento. Las clases de objetos cada vez que cambian su estado envían notificaciones al mediador. El mediador responde propagando los efectos de dichos eventos a los otros objetos.
Otra forma define al Mediador una interfaz de notificación especializada que permite a los objetos ser mas directos en su comunicación.
Usos conocidos: La arquitectura de Smalltalk/V para Windows usan objetos parecidos a mediadores entre los útiles de los diálogos.
Patrones relacionados: Un patrón muy parecido a éste es el Façade que se diferencia en que abstrae un sistema de objetos proporcionado una interfaz mas conveniente, utilizando un protocolo unidireccional (Fachada realiza solo peticiones a las clases del subsistema pero no a la inversa), mientras que el Mediator usa un protocolo mutidireccional.
Con el patrón Observer los objetos pueden comunicarse con el mediador.
El sistema de paso de mensajes DBUS como Observer
Nombre del patrón: Observer.
Clasificación del patrón: De comportamiento.
Intención: Definir una dependencia entre un objeto y un conjunto de ellos, de modo que los cambios en el primero se vean reflejados en los otros.
También conocido como: Spider.
Motivación: Frontend ha de recibir señales desde el backend que nos informen de que ha habido un cambio en algún archivo vigilado y así refrescar el treeview de GUI, por ejemplo.
Aplicabilidad: Se usa en casos en que se desea desacoplar la clase de los objetos clientes del objeto, aumentando la modularidad del lenguaje, así como evitar bucles de actualización (espera activa o polling).
Participantes:
Sujeto: Mantiene una lista de observadores y proporciona una interfaz para su gestión.
Observador: Define una interfaz para actualizar los objetos que deben reflejar los cambios en el sujeto.
Observador concreto: Mantiene una referencia a una sujeto concreto, almacenando parte de su estado e implementado la interfaz de Observador.
Colaboraciones: El Sujeto notifica a sus observadores de los cambios que sufre. Los observadores concretos solicitan a su sujeto los datos necesarios para mantener la consistencia con su nuevo estado.
Consecuencias:
- Permite reutilizar sujetos y observadores por separado.
- Permite añadir nuevos observadores sin modificar al sujeto o a otros observadores.
- Que el sujeto no informe a sus observadores de qué cambio ha sufrido permite mantener el acoplamiento en un nivel bajo, puesto que el observador sólo pide los datos del estado del sujeto que le interesan.
- Aunque el observador no esté interesado en ciertos cambios del sujeto será notificado de ellos.
- Se pueden realizar implementaciones con observadores que coordinan información sobre varios sujetos.
- Los cambios en el sujeto pueden ser solicitados por objetos que no son observadores.
Implementación: El backend generará una señal que recibirá el frontend, que previamente se había suscrito a ella.
Usos conocidos: Este patrón suele observarse en los marcos de interfaces gráficas orientados a objetos, en los que la forma de capturar los eventos es suscribir 'listeners' a los objetos que pueden disparar eventos.
El sistema de paso de mensajes DBUS usando Proxys
Nombre del patrón: Proxy.
Clasificación del patrón: Estructural.
Intención: Proporciona un representante o sustituto de otro objeto para controlar el acceso a éste.
Motivación: La interfaz gráfica y la consola necesitan interaccionar con el demonio de la aplicación. Para ello se crea un sustituto de los distintos objetos a los que se ha de acceder.
Aplicabilidad:
- Un proxy remoto proporciona un representante local de un objeto remoto.
- Un proxy virtual crea objetos costosos sólo cuando es necesario.
- Un proxy de protección controla el acceso al objeto original.
- Una referencia inteligente es un sustituto de un simple puntero que realiza alguna acción adicional:
- Contar el número de referencias al objeto original.
- Cargar un objeto persistente en memoria cuando es referenciado por vez primera.
- Bloquea el acceso al objeto real para que no sea modificado por otro objeto.
Participantes:
Client: Elemento que desea acceder al objeto.
Proxy: Mantiene una referencia para acceder al objeto original.
* Proporciona una interfaz idéntica a la del Subject.
* Controla el acceso al objeto y puede encargarse de crearlo, borrarlo…
BackendProxy proporciona un representante de distintos objetos reales.
Subject: Define el objeto real representado por el Proxy. Watcher, Scheduler, SnapshotManager.
Colaboraciones: Client accede a Subject a través de Proxy.
Patrones relacionados: Singleton.
BackendProxy y FrontendProxy como Singleton
Las clases BackendProxy y FrontendProxy están implementadas con el patrón Singleton para que se use siempre la misma instancia.
Estructura del sistema de paso de mensajes entre backend y frontend:
HDLogger como Adapter
Nombre del patrón: Adapter.
Clasificación del patrón: Estructural.
Intención: Se utiliza para transformar una interfaz en otra, de tal modo que una clase que no pudiera utilizar la primera, haga uso de ella a través de la segunda.
Convierte la interfaz de una clase en otra interfaz que el cliente espera. Adapter permite a las clases trabajar juntas, lo que de otra manera no podrían hacerlo debido a sus interfaces incompatibles.
También conocido como: Wrapper.
Motivación: Se quería hacer un módulo de logger para centralizar la recogida de información relevante para la depuración de problemas. Para ello se ha decidido adaptar el módulo de logger de python.
Aplicabilidad: Este patrón se usa cuando:
- Se desea usar una clase existente, y su interfaz no se iguala con la necesitada.
- Cuando se desea crear una clase reutilizable que coopera con clases no relacionadas, es decir, las clases no tienen necesariamente interfaces compatibles.
De esta forma, se han podido usar las tecnologías antes mencionadas, adaptándolas para que fuese posible trabajar con ellas en la aplicación.
Estructura:
Participantes:
Target: HDLogger. Define la interfaz específica del dominio que Client usa.
Client: Colabora con la conformación de objetos para la interfaz Target. Son todas aquellas clases que hacen uso del logger.
Adaptee: librería logging de Python. Define la librería que ha de adaptarse.
Adapter: HDLogger. Adaptan el logger de python.
Colaboraciones: Client llama a las operaciones sobre una instancia Adapter. De hecho, el adaptador llama a las operaciones de Adaptee que llevan a cabo el pedido.
Implementación: Crear una nueva clase que será el Adaptador, que extienda del componente existente e implemente la interfaz obligatoria. De este modo se tiene la funcionalidad deseada y se cumple la condición de implementar la interfaz.