Friday, November 27, 2009

Interfaces: Resource

¡Hola!

Creo que es hora de ir pensando en cosas más profundas. Por ejemplo, en definir las interfaces que hemos visto hasta ahora. Por estos lares ando con un poco de miedo, pues la causa del "fracaso" del anterior diseño fue esto mismo: un mal diseño. Meter la pata a estas alturas nos va a costar mucho posteriormente.

De todos modos, por algún sitio hay que empezar. Y supongo que con la experiencia anterior, podremos ahorrarnos algunos errores. ¿Y qué tal si empezamos por los Recursos? Ok.

Ya hemos definido lo que es un recurso. También se comentó lo interesante que sería que todos los recursos compartiesen una misma interfaz. Así, todos los tipos de Modelo que pueda haber son accesibles usando cualquier mecanismo de acceso.

Un inciso: propongo cambiar la nomenclatura: recurso por acceso, pues me parece más próximo a la realidad. En este post, usaré Access como el mecanismo de acceso al recurso (por ejemplo, HTTP, FTP, File, etc).

Por ejemplo, si tenemos un AccessHttp, y añadimos un Modelo nuevo, con sus correspondientes inspectores, no es necesario hacer nada especial para que este modelo use el AccessHttp. Esto es una ventaja considerable, pero para ello, tenemos que ver si sería posible acceder a todos los recursos existentes usando una misma interfaz.

Podemos intentar ver si TODOS los casos son factibles. O mejor, encontrar uno que no encaje. Veamos, el mecanismo de acceso que se utiliza más comunmente es el consabido par get/set. Supongamos que dotamos al acceso de esta interfaz:
class Recurso:
    def get(): pass
    def set(blob): pass
Para HTTP, FTP, File... es válida. Son accesos que te proporcionan el recurso entero. Si las condiciones te lo permiten, es viable. Veamos un caso más complejo: Ice/CORBA/etc. Aquí, dependemos del tipo del recurso. Si es un entero o un byteseq, se puede seguir usando. Pero, ¿y si el "objeto" tiene una interfaz más compleja, por ejemplo AVStreams? Tendríamos un problema. Claramente no nos sirve, porque a lo que estamos accediendo no es a un recurso que se pueda tratar como un fichero o una imagen.

Vemos que una úinca interfaz, aunque interesante, no es posible. Tendríamos que crear una para cada tipo de recurso. Ya no es dependiente del mecanismo de acceso, porque es posible acceder a una fichero zip usando Ice, o a un objeto Bool usando File. Tendríamos un Access para recursos de tipo, llamemosle Data, que cubre un amplio espectro de uso. Pero también habría un Access para otros tipos de recursos (hay que pensar cuales).

Esto complica la estructura. Ya no solo clasificamos los Access por el mecanismo de acceso (FTP, etc.), sino también por el tipo de recurso al que se accede (un "Data", etc.). Quizá no sea esto lo que queremos.

¿Qué pensáis? Necesito ideas...

[Edición] Se me ocurre que en el caso de objetos Ice con interfaces complejas, podríamos considerar que la interfaz es parte intrínseca del Objeto, y que es el inspector quien sabe como usarla. Así, tendríamos un Access que provee la capacidad de "llamar" a métodos Ice (ya sea usando Ice, HTTP o File), sin conocer la interfaz real (es decir, un Access con dynamic Ice). Delegamos en el inspector el tratamiento del recurso. Esto se parece a lo que teníamos, pero con la salvedad de que ahora el acceso al recurso es transparente para el inspector (el inspector pide los ice_ids del objeto, y si el AccessX los obtiene de una base de datos, eso no importa). Y lo que es más, el inspector no tiene porqué conocer siquiera si el objeto está implementado en Ice o en CORBA. Eso es tarea del Access.

Quizá este tratamiento se pueda generalizar. Así tendríamos sólo tres o cuatro tipos de Access.
La siguiente figura es un ejemplo de cómo lo veo:



Un Model concreto podría dar soporte a una interfaz Access, por ejemplo, a DataAccess, siendo independiente del Access real que se use (DAFile, DAIce, etc.).

¿Como lo véis? ¡¡Feedback!!

Wednesday, November 25, 2009

Cuestión de conceptos

Bueno, dado el anterior post de David, ahora tengo claros algunos conceptos más. Pero, para mi sigue siendo un montón de ideas en el aire, con relaciones parciales entre ellas. Necesito ponerlas en algún sitio, establecer esas relaciones y verlo todo como un conjunto conexo. Por eso, me gustaría empezar a distinguir y definir conceptos. Por supuesto, las cosas que falten, ya sabéis... comentarios.

A primera vista, los conceptos son: Recurso, Modelo, URI, Factoría, Inspector, Skin... TwinPanel :P De momento, un conjunto de nombres. Pongámosle caras.
  • Recurso: es el "qué". Aquello que se quiere utilizar, es la entidad a la que se desea acceder (no se me ocurren palabras más genéricas que realmente digan algo).  Por ejemplo, un fichero zip en un directorio ftp de cierta máquina de nuestra red (como URL, sería "ftp://abox/ftp/afile.zip"). A nivel de TP, el recurso se puede considerar como un "proxy", un representante de lo que se quiere utilizar, y que sabe como acceder a él. El recurso (para TP) encapsula el mecanismo de acceso a esa entidad.
  • Modelo: especifica cómo se debe manipular el recurso. En el ejemplo anterior, tendríamos un fichero comprimido. El "modelo" es quien sabe lo que se puede hacer con ese fichero comprimido: leer su contenido, descomprimirlo, añadir otros ficheros, etc. El modelo es una entidad abstracta, que será instanciada por los Inspectores.
  • URI: es un identificador del recurso. Bien puede decir qué es (URN), o bien dónde está (URL). Este concepto es el más claro, aunque su estructura no la tenga tan clara... :D
  • Factoría: es la entidad encargada de proveer los componentes necesarios para inspeccionar determinado recurso. Antes, era un componente bien conocido, pero puede que cambie un poco su "personalidad".
  • Inspector: es el encargado de tratar con el contenido del recurso. Es la instanciación de un modelo. Para cada modelo puede haber varios inspectores, dependiendo de factores como la tecnología usada para tratar el recurso. Así, para el zip, tendríamos un inspector que usase "zip", y podríamos tener otro que se sirviera de un mecanismo online para ver el fichero, por ejemplo.
  • Skin: es la representación del recurso de cara al usuario: la vista del mismo. Este concepto tampoco ha variado mucho desde sus inicios, aunque sí la tecnología subyacente (¡ahora puede estar distribuido!). Utiliza un inspector para acceder al recurso.
Voy a intentar desempolvar los conceptos de UML para dibujar un ejemplo de clases.


Aquí se representan sólo las relaciones entre recursos, modelos e inspectores, y con ejemplos para verlo un poco más claro.

Ahora, podemos discutir cosas. Por ejemplo, la interfaz que debe conocer un modelo de cierto recurso. Si es diferente para cada recurso, un modelo dado debe ser consciente de los recursos para los que está disponible, algo que no es desable. Entonces, debe ser la misma para todos los recursos. Habría que ver si es posible una interfaz que sea práctica para todos los casos posibles.

De todos modos, ¿qué pensáis?

Thursday, November 5, 2009

Recursos y Modelos

Uno de los problemas de diseño que tiene el TP actual es que no separamos claramente dos cosas:
  • El acceso al recurso
  • La forma de interpretar ese recurso
Eso nos llevó a varias soluciones basadas en métodos, dominios, inspectores y otras abstracciones que nunca acabaron de cuajar.

Pongo un ejemplo para que se vea claro. Imagina que tienes un fichero "trabajo.zip" en el directorio "cosas" en tú máquina y que estás sirviendo a la vez ese mismo directorio con un servidor web y uno ftp. Eso te da dos URI's posibles para acceder al fichero.
  • http://example.org/cosas/trabajo.zip
  • ftp://example.org/cosas/trabajo.zip
Para conseguir el fichero (el "recurso" en un sentido más general) puedes utilizar dos métodos distintos, pero no estás asumiendo aún nada sobre el contenido o estructura del fichero. Para eso necesitas un modelo (por medio de un inspector).

Ahora imagina que tienes un inspector capaz de visualizar un fichero .zip como si fuera un directorio, pudiendo ver el nombre de los ficheros y sus atributos. Esto no es nada nuevo, gvfs + file-roller hacen eso mismo hoy día en GNOME (o lo intentan).

La conclusión es que el mecanismo de acceso al recurso y la forma de entender el recurso DEBEN ser cosas diferentes y desacopladas. Eso no lo logramos en la versión actual de TP y es fuente de muchos problemas.

Así que yo distinguiría entre Recursos e Inspectores, de modo que los inspectores necesitan una referencia a un recurso y nunca acceden "físicamente" a ningún fichero ni dispositivo. Esto encaja perfectamente con el modelo de tres capas en el que el Recurso estaría en la capa de persistencia/acceso y el Inspector en la capa de modelo.
  • Ejemplos de Recursos: ftp, http, bluettooth, ssh, ice, etc.
  • Ejemplos de inspectores: zip, camera, json, media, hg, etc.
Desde el punto de vista de la plataforma eso es todo. Se asume que "alguien" crea un Recurso a partir de una URI y después le pasa la referencia a un Inspector. Algo como:

zip = InspectorZip(ResourceHTTP("http://example.org/cosas/trabajo.zip"))

Pero desde el punto de vista de TP debe haber una forma automágica de crear Recursos a partir de URI, algo que es en principio muy sencillo, basta mirar lo que hay antes del ://.

Pero además debe haber una forma de elegir el Inspector más adecuado y también tener la posibilidad de cambiarlo si el usuario quiere. Un .tgz se puede ver con un inspector de "gzip" pero también con uno de "unp".

En muchos casos eso se puede hacer mirando la extensión del fichero pero en otros no es tan fácil. Por ejemplo, un directorio que contiene un repo subversion... ¿cómo se sabe que lo es? "ssh://example.org/repos/mi_proyecto". Lo que hace subversion concretamente es crear una URI del tipo "svn+ssh://example.org/repos/mi_proyecto". Bueno, es una opción, pero estaría bien algo un poco más inteligente si es posible.

Otro caso es un objeto tipo "ice://objeto1 @ adaptador". Aquí el Recurso es Icd pero ¿qué Inspector usas? La forma de determinarlo es invocar el método ice_ids() del objeto y buscar inspectores que lo puedan manejar. Algo que es MUY distinto a mirar la extensión o el prefijo de la URI porque implica USAR el recurso.

Obviamente puede haber Inspectores que solo tengan sentido con un Recurso concreto pero eso no significa que deban estar acoplados, precisamente porque puede haber muchos Inspectores que pueden usar un mismo tipo de Recurso y algunos de ellos pueden no tener esa limitación.

Y para terminar, imagina que el zip del que hablaba antes contuviera a su vez un .tgz o una base de datos sqlite... También queremos verla...

Bueno, cómo véis, más problemas que soluciones... pero bueno, estamos capturando requisitos.

Recapitulemos

Creo que podemos hacer un breve resumen de las cosas que tenemos hasta ahora. Por ejemplo, seguiremos pensando en usar patrones, pues eso nunca fué mala idea. Así pues, el MVC se seguirá respetando. Con lo que las entidades "inspector" y "skin" (por lo menos, lo que representan) seguirán estando presentes.

Twin Panel seguirá siendo un sistema plugable, por lo que no podemos dejar de usar una factoría, aunque su estructura cambie bastante.

Otra cuestión interesante que discutir, y que siempre ha sido bastante difusa, es la relación entre los métodos de acceso (los antes llamados "methods"), y los dominios; conceptos que siempre han dado que hablar.

Creo que una muy buena manera de ver los componentes y sus relaciones es utilizando un diagrama de secuencia. Voy a intentar esbozar uno, y con la ayuda de todos, lo vamos mejorando, ¿vale? :p Intentaré ser lo más genérico posible, puesto que todavía no hay nada determinado.
  1. Un usuario desea explorar su carpeta personal. Para ello, lanzaría TP de una forma parecida a esta:  $ tp file:///home/user
  2. tp (el binario/script que se ejecute) deberá analizar la URL, convertirla a la estructura que sea necesario, y pedir a "alguien" que se encargue de instanciar/despertar/... los componentes que entrarán en juego para la tarea. Lo podemos llamar "factoría", con comillas, pues puede que me cuele. 
  3. La "factoría" determina los componentes a usar, siempre que no se hayan especificado antes (como opciones de configuración, u otra cosa). Esta se encargará de que esos componentes estén accesibles, bien instanciándolos, bien despertándolos, etc. 
  4. Alguien deberá encargarse de configurar estos componentes. Por ejemplo, al "inspector" deberá decirsele la URL que está inspeccionando, o al skin dónde debe pintar.
Mmm, creo que faltan muchas cosas intermedias, pero seguramente que en estas cuatro básicas, ya he metido la pata...

Bueno, empieza la audiencia :D

Wednesday, November 4, 2009

Casos de uso

En vista de los cambios que pronto afrontaremos, debemos tener siempre presente aquello para lo que, en principio, queremos usar Twin Panel. Este post intentará recoger los casos de uso, para así capturar requisitos y poder emprender el diseño.

Twin Panel debe ser una herramienta que permita explorar conjuntos de objetos. Casi cualquier cosa puede ser tratada como un objeto, desde un fichero hasta una máquina, o un mensaje. Dada esta pluralidad, es muy difícil concretar todos los casos de uso. Si encuentras alguno que falta, puedes comentarlo y se tendrá en cuenta.

Consideramos "contenedor" al elemento del sistema que posee al resto de elementos, siendo posible que estos, a su vez, sean "contenedores". Para evitar duplicar el texto, definimos los grupos de acciones permitidas:
  • Acciones Mínimas: listar elementos del "contenedor".
  • Acciones Básicas: las acciones mínimas, más: copiar, cortar, pegar, crear y eliminar elementos, entre nodos del mismo "contenedor".
  • Acciones Ampliadas: acciones básicas entre nodos de "contenedores" diferentes (compatibles).
La lista de casos de uso es la siguiente:
  • Exploración local de ficheros. Es el caso más obvio. Deber permitir explorar cualquier sistema de ficheros local (obviamente, que esté soportado por el kernel). Debe soportar las Acciones Ampliadas. El mecanismo de acceso podría ser GVFS.
  • Exploración remota de ficheros. La extensión al caso anterior. Los mecanismos de acceso pueden ser variados: SSH, FTP, Ice... Debe soportar las Acciones Ampliadas
  • Explorar el conjunto de máquinas de una red. Deberían poderse listar tanto las máquinas (con sus nombres, IP's u otros identificadores), como los servicios que proveen. El conjunto de acciones permitido serían las Acciones Mínimas.
  • Visualizar los elementos que componen un documento web. Dependiendo del método de acceso, debería soportarse el conjunto de Acciones Básicas o Acciones Ampliadas.
Podríamos tener descripciones similares para los siguientes casos:
  • Objetos Ice
  • Elementos de UPnP
  • Servidor POP3
  • Dispositivos Bluetooth
  • Nodos ZigBee
  • Redes y AP's WiFi
  • Procesos del sistema
  • Puertos abiertos con sus servicios asociados
  • Bibliotecas multimedia (picassa, flickr, youtube...)
  • Entornos sensibles a localización (GIS)
  • Dispositivos X10 de un entorno
  • ...
Examinando estos casos, surgen algunas cuestiones que tratar:
  1. Debemos definir un mecanismo preciso para la interacción entre diferentes mecanismos de acceso a estos "contenedores" (por ejemplo, para copiar un fichero desde un SF remoto a uno local, usando SSH), algo que todavía no se había abordado.
  2. Debemos ser muy estrictos con las interfaces que cada "plugin" debe cumplir. Uno de los problemas que presenta la versión actual es que no hay un conjunto bien definido de interfaces, lo que permite registrar plugins incompletos que desestabilizan el sistema.
  3. Es necesario disponer de una buena documentación desde el principio. Si un desarrollador hace un plugin, pero no dispone de toda la documentación necesaria, perderá mucho tiempo, hará cosas duplicadas, y posiblemente causará otros problemas al resto del sistema.
  4. Pruebas. He aprendido (por fin :D) que es un aspecto clave a la hora de desarrollar software. Como dice incansablemente David: "las pruebas merecen la pena", y yo añado "sin pruebas, se pierde tiempo y es muy difícil abordar problemas complejos (sin causar otros)".
Con el nuevo diseño que se está planteando, algunas de estas cosas se resuelven. Por ejemplo, el uso de interfaces; utilizando Zeroc-Ice, se fuerza la utilización de interfaces.

Sigamos discutiendo aspectos del diseño.

Requisitos nuevos flamantes

Con vistas en el nuevo TP estamos acordando algunos requisitos bastante llamativos en relación a la implementación actual. A saber:
  • El núcleo de la aplicación van a ser los inspectores y los skins. La interfaz principal como tal va a perder algo de protagonismo. De hecho, la idea es poder manipular skins e inspectores como componentes de modo que pueda haber otras formas de cargarlos, incluso como programas individuales.
  • Queremos evitar la dependencia de GTK. Vamos a usar GTK como interfaz pero queremos poder usar otras cosas. Es decir, queremos poder hacer skins web o de consola, por ejemplo. También queremos que un cambio en GTK no sea demasiado traumático ni afecte a la implementación de los inspectores, solo a los skins. Esto tiene fuertes y traumáticas implicaciones:
    • No podremos usar los stores de GTK como almacén para los modelos de los inspectores. Tendremos que definir una interfaz para acceder a los datos del modelo. Esto supondrá varias repercusiones en el rendimiento porque implica copiar (o indireccionar) el contenedor de datos del modelo a la vista.
    • No podemos usar el UIManager para gestionar las "Actions" que pueden ofrecer los inspectores. Sin embargo, este sistema es muy flexible y quizá habría que copiarlo. De hecho ya lo copiamos en su momento sin saberlo.
  • Y agarraos los machos! Queremos que esos "componentes" sean distribuidos. Es decir, algo como lo que GNOME intentó con BONOBO, pero nosotros lo vamos a hacer bien :-S Y claro, para eso vamos a usar Ice. Cada skin e inspector posible ES un servicio. Y nuestra amada/odiada factoría es substituida (en parte) por IceBox. Él va a instanciar y eliminar skins y inspectores.
Lo más llamativo puede ser lo de usar Ice, aunque no es tan sorprendente. Paco lo dijo desde el principio de los tiempos, a magmax también le convencía y a mi me llamaba la idea sobre todo por el tema de imponer interfaces férreas gracias al slice, aunque siempre me ha preocupado la penalización que van a tener las invocaciones remotas (aunque sean locales) dado que es «otra capa de mierda» y me temo que se hará cierta esa frase que dice

«Cualquier problema en ciencias de la computación puede ser solucionado con otra capa de indirección… pero usualmente creará otro problema» ― David Wheeler

Pero ciertamente tiene muchas ventajas que espero que podamos explotar:
  • Hacer componentes en muchos y variados lenguajes. En particular esto nos permite prototipar en Python (aprovechando el código actual) y después las partes que sean estables, probadas y que sean críticas en cuanto a rendimiento se pueden pasar a C++.
  • Manipular inspectores remotos o locales de forma transparente, de modo que se puedan interponer «inspectores-caché» o hacer aplicaciones GUI puras para dispositivos móviles.
  • El modelo de datos puede estar perfectamente establecido por medio de un pequeño conjunto de interfaces, que como veremos se parece mucho a DUO, pues sigue el mismo principio: son interfaces de acceso puro a los datos, la semántica de los mismos la pone el que los interpreta.
  • Es posible utilizar inspectores ya creados por el sistema u otro usuario.
  • Hay muchas otras cosas que se pueden delegar a Ice:
    • Persistencia de la configuración
    • Gestión de la instanciación/liberación de los componentes
    • Instalación/actualización de componentes. El sistema de plugins ahora es IceBox.
Pero usar Ice también plantea muchos problemas que tendremos que ir viendo y que trataremos en siguientes posts. Este es más que nada para que lo vayáis digiriendo. ;-)

La primera conclusión es que ahora queremos dos cosas: Un programa para interacción entre colecciones de objetos cualesquiera (TP) y una plataforma de componentes (que no tiene nombre aún).

Saludos