Doblamos productividad con Voyager
Albert Cervera i Areny
Hola,
la próxima semana te empezaré a contar cosas de los avances que hemos hecho en IA pero esta semana toca hablar de cómo creamos páginas web integradas en Tryton.
Lunas y tecnologías
El nombre de Tryton proviene de la combinación del nombre del lenguaje de programación Python y una de las lunas de Neptuno: Tritón.
Otras lunas del mismo planeta como Sao o Proteus han dado nombre a otros componentes del ecosistema Tryton.
Y antes de empezar a trabajar en NaN-tic, Raimon Esteve bautizó con el nombre de Galatea (efectivamente, otra de los satélites de Neptuno), un conjunto de componentes software que creó basándose en otras tecnologías bien establecidas (como Flask y Jinja) que nos ha permitido durante años ofrecer soluciones web 100% integradas en el ERP.
Con Galatea hemos creado páginas web, tiendas online, formularios de captación de datos y extranets.
La gran ventaja es que nos permite aprovechar lo que llamamos la "lógica de negocio". Es decir, todos los cálculos que ya realiza el ERP para realizar las tareas del día a día se aprovechan para crear la página web. Evitando muchos problemas de errores derivados de hacer las cosas dos veces con dos tecnologías distintas.
En algunas ocasiones hemos explorado otras tecnologías JavaScript (como Angular o Ionic), pero tienen el problema de que el mundo JavaScript cambia muy rápidamente, normalmente con pocos beneficios. Cada 2 años siempre hay alguien que descubre la sopa de ajo y decide cambiarlo todo. Y es por eso que ya hace unos años que estamos tratando de dejar de utilizar estas tecnologías.
Lo que mejor nos ha funcionado ha sido Galatea.
Simplemente funciona.
Web vs Tryton
Sin embargo no podíamos dejar de observar la diferencia de productividad que existe entre desarrollar una nueva funcionalidad para Tryton vs una web.
Cuando hacemos una mejora para un cliente en Tryton somos muy productivos y como te voy contando semanalmente trabajamos constantemente para seguir mejorando.
Es lógico ser más productivos con Tryton, porque trabajamos en un sistema concebido para hacer rápidamente y de forma robusta lo más habitual y lo hacemos con algunos sacrificios: quizás no tenemos la interfaz gráfica perfecta, pero el hecho de poderlo hacer mucho más rápido y económico compensa.
Además, el usuario está ya entrenado en el entorno, lo que facilita mucho el trabajo. Ni el usuario ni nosotros prestamos mucha atención a la estética: el foco es la productividad.
Pero cuando hacemos una web nos dirigimos a gente de fuera de la empresa y por tanto el paradigma cambia totalmente: la estética y usabilidad pasan a ser importantes, necesitamos tener mucha más flexibilidad y con ella disminuye la productividad.
Y esto es inevitable.
¿Verdad?
Bien pensado, una cosa es asumir que desarrollar webs será menos eficiente que desarrollar módulos para el ERP y otra cuestión es: ¿en qué proporción?
Si nuestro objetivo es desarrollar software a medida con la misma eficiencia que si fuera software estándar, ¿por qué limitarnos a aquellos desarrollos que presentamos a los usuarios del ERP?
¿Es el desarrollo web que hacemos actualmente con Galatea el equilibrio perfecto entre flexibilidad y productividad?
Como nada es perfecto, la respuesta es fácil: no.
Así que nos hicimos todas estas preguntas y analizamos qué prestaciones nos da Tryton que echamos de menos cuando desarrollamos webs.
¿Qué tecnologías y paradigmas de programación actuales podrían ayudarnos a reducir la brecha?
Y llegamos a esta relación:
- A diferencia de Tryton, el desarrollo web no permite reutilizar mucha parte del trabajo realizado entre proyectos
- La falta de tests automáticos produce más incidencias
- Hay mucha dispersión en los archivos de código, hacer un cambio puede implicar cambios en varios archivos y es difícil conocer bien todo lo que hay que tocar
- En un intento de permitir reutilizar código entre proyectos, se requiere mucha configuración inicial y hace que la lógica es más compleja que con el sistema de módulos de Tryton
- Es complicado ampliar funcionalidades existentes porque no es evidente las consecuencias que tendrá para las distintas webs
- Cuando necesitamos que la web sea muy interactiva (y en casi todos los proyectos hay al menos una parte) necesitamos trabajar con JavaScript, lo que complica aún más el desarrollo
- La gestión de sesiones de usuario es algo caótica, excesivamente flexible y se hace difícil saber dónde debe ir cierta información
- Dependemos de un número de tecnologías un poco demasiado grande, sería interesante simplificarlo
Voyager para turistas espaciales
Si has llegado hasta aquí pero te pierdes con los tecnicismos simplemente te contaré que hemos creado Voyager, un producto construido desde cero en NaN-tic que resuelve todos los puntos anteriores, manteniendo las ventajas de Galatea.
La sonda espacial Voyager 2 salió en 1977 para explorar los planetas del sistema solar exterior y entre otros muchos hitos, descubrió varias lunas de Neptuno y como no, Galatea.
Nuestro Voyager es el resultado de explorar y aprender de los aprendizajes de los últimos 10 años con Galatea, Flask, Angular, Ionic, React, ReactPy, Dominate, HTMX... y el propio Tryton.
Voyager es la tecnología que hemos creado para construir una nueva generación de soluciones web para nuestros clientes a partir de ahora.
Y no, no es un futurible.
Ya lo estamos utilizando para crear una tienda online para uno de nuestros clientes y la mejora es evidente y notable. Hasta el punto de que estamos viendo que tardamos menos de la mitad del tiempo que tardábamos antes.
Reducir drásticamente estos costes nos permitirá continuar incrementando el valor que aportamos a nuestros clientes.
Voyager para astronautas profesionales
Si decides leer esta sección ten en cuenta que aquí voy a contar la tecnología con pelos y señales. No es contenido para todos los públicos. Si no te va la cosa tan hardcore te recomiendo pasar directamente a la conclusión ;-)
Otras tecnologías y conceptos
Aparte de la lista de puntos de mejora que he mencionado anteriormente, también había puntos fuertes que queríamos mantener e influencias y aspectos interesantes que hemos visto en otras tecnologías:
- Utilizar el mínimo posible JavaScript, aunque puede que sea imprescindible en algunos casos, esto significa generar lo máximo posible en el lado del servidor.
- Los frameworks JavaScript tienen su gracia, especialmente hemos tenido experiencia interesante con React, pero igualmente requiere crear webservices que complican el desarrollo y siguen teniendo el problema de tener que tocar en varios archivos por cualquier cambio que se quiere hacer. Además, al poco que la web crece, React y otros frameworks se vuelven notablemente más complejos y con frecuencia requieren paradigmas nuevos y más difíciles de entender por todos los desarrolladores.
- El concepto de componentes de React es muy interesante y facilita la modularización de la aplicación
- De React también es muy interesante la forma en la que dentro de JavaScript se puede poner código HTML mediante JSX. ¿Podríamos tener algo parecido a la banda del servidor en Python?
- ReactPy trata de implementarlo pero parece un proyecto demasiado poco maduro y el stack, en el fondo, demasiado complejo
- HTMX parecía una tecnología muy interesante a explorar para minimizar el uso de JavaScript y se acopla bien al concepto de componentes
- También vimos que TailwindCSS ganaba adeptos en detrimento de Bootstrap que habíamos utilizado hasta ahora. Tailwind también ayuda a tener el código menos separado. Todo está en el mismo archivo.
La solución técnica
Hemos creado un módulo Tryton que hemos llamado voyager y con las siguientes características y herramientas:
- Depende del módulo
web_userde Tryton core, así dejamos de tener nuestra propia implementación del concepto usuario web - Implementa una clase
Componentque hereda deModelView. Esto significa que un componente se registra en el pool como el resto de clases Tryton y por tanto puede ser sobrecargado y llamado por cualquier otro módulo Tryton. - Un script de arranque que permite arrancar cualquier web que se quiera crear. El script está pensado para que no sea necesaria ninguna configuración en la base de datos antes de poder empezar a servir contenido. Es arrancar y funcionar.
- Un modelo
Websitepermite tener configuraciones que pueden ser necesarias para el entorno de producción, pero no imprescindibles para empezar a realizar pruebas. - Un modelo
Sessionsiempre disponible en el contexto de ejecución de las peticiones web para poder vincular registros que se creen en la base de datos con una sesión en concreto. La sesión también tiene unuser, que se llena si el usuario hace login. - El concepto
Triggerque permite implementar secciones dinámicas en la web sin necesidad de utilizar ni una línea de JavaScript.
Déjame que entre en detalle de los conceptos de componentes y triggers.
Componentes
Un componente de Voyager es cualquier parte de una página web. Sea la página entera, un formulario, un fragmento que permite mostrar un producto, etc.
Un componente debe implementar siempre una función render(self) que es la encargada de devolver el código HTML.
Este HTML, sin embargo, no debe devolverse como un string sino como un objeto dominate.
Dominate es una librería python muy interesante que permite crear código HTML con código python puro. Hace la función de JSX pero incluso tiene algunas ventajas adicionales como el hecho de que no requiere ningún preprocesador y el hecho de que no requiere cerrar tags, así que no existen errores de sintaxis tontos.
Además, ¡en muchos casos reduce el número de líneas de código comparado con el código HTML original hasta llegar a una reducción del 50% en algunos casos!
Echa un vistazo a este ejemplo, donde puedes ver un componente (CartWidget) haciendo uso de otro componente (LineWidget):
from trytond.modules.voyager import Component
class CartWidget(Component):
'Voyager Cart'
___name__ = 'voyager.cart'
def render(self):
pool = Pool()
LineWidget = pool.get('voyager.cart.line')
Sale = pool.get('sale.sale')
sale = Sale.get_sale_from_session()
main = div()
with main:
h1(_('Cart'))
for line in sale.lines:
LineWidget(product=line.product, quantity=line.quantity)
return main
Esto permite a un nuevo módulo modificar el render de voyager.cart.line sin tener que modificar voyager.cart.
Además, los Componentes soportan lazy loading por defecto. Esto significa que la página principal puede incorporar la cesta de la compra simplemente haciendo CartWidget(lazy_loading=True), lo que hará renderizar la página entera mostrando temporalmente un mensaje de Loading... en el lugar donde debería haber la cesta. Sin embargo, inmediatamente el navegador hará la llamada al servidor y actualizará su contenido.
Esto permite que la página inicial cargue muy rápidamente, dejando los elementos lentos para un poco más tarde. Si lo deseas, para cada componente puedes alterar el contenido que debe mostrarse mientras no se carga la página definitiva, simplemente sobrecargando el método lazy_content(self).
Este sistema de carga dinámica lo hemos implementado sin necesidad de incorporar ni una sola línea de código JavaScript gracias a utilizar HTMX. Y es que hemos decidido que éste será otro elemento básico de este framework.
Triggers
De hecho, también hemos integrado el sistema de triggers que permite HTMX.
Por ejemplo, basta con añadir una declaración y modificar una línea del código anterior para que el contenido de la cesta se actualice cada vez que se produzca algún cambio:
from trytond.modules.voyager import Component
class CartWidget(Component):
'Voyager Cart'
___name__ = 'voyager.cart'
updated = Trigger()
def render(self):
pool = Pool()
LineWidget = pool.get('voyager.cart.line')
Sale = pool.get('sale.sale')
sale = Sale.get_sale_from_session()
main = div(hx-trigger=f'{self.updated} from:body', hx-get=self.url())
with main:
h1(_('Cart'))
for line in sale.lines:
LineWidget(product=line.product, quantity=line.quantity)
return main
Entonces, cualquier otro punto de la aplicación podrá lanzar el trigger con Trigger.add_trigger(CartWidget.updated) y Voyager + HTMX ya se encargan del resto.
Fíjate, además, que hemos hecho que los triggers sean atributos de la clase. Esto lo hacemos porque así el propio intérprete de python ya nos detectará si cometemos cualquier error tipográfico y no debemos estar peleando con problemas derivados de haber puesto un texto incorrecto en hx-trigger.
No sé si te puedes llegar a imaginar la cantidad de código JavaScript + webservices que ahorra este mecanismo.
No voy a entrar en más detalles pero los componentes también admiten parámetros al igual que Tryton admite campos y endpoints adicionales, aparte del render().
Lo que nos ahorramos...
Con estos cambios pasamos a depender de Tailwind, Dominate y HTMX pero eliminamos muchas dependencias:
- Bootstrap
- Flask
- Flask-Login
- Flask-Babel
- Flask-WTF
- flask_tryton
- Flask-Session
- Flask-paginate
- wtforms-html5
Además, Dominate es una librería muy sencilla que nos ahorra cientos de líneas de código, HTMX también es pequeño y la documentación se lee en una hora. Y Tailwind es más grande, pero no más que Bootstrap, y el concepto se entiende fácilmente. El código resultante con Tailwind es más sencillo y flexible.
También hemos comprado la licencia de equipos de TailwindUI, que nos da acceso a +500 componentes y plantillas que podemos aplicar en minutos y que son fácilmente personalizables a las necesidades de cada cliente.
Creemos que además de darnos mayor velocidad de desarrollo, obtendremos un diseño mucho más atractivo y profesional de entrada. Probablemente los clientes también ahorraran en costes de diseñadores externos.
Conclusión
Se me hace complicado transmitir el adelanto que supone Voyager. Para nosotros será un antes y un después que se resume en:
- Doblaremos productividad en desarrollo
- Reduciremos líneas de código y abarataremos el coste de mantenimiento
- Ofreceremos webs más atractivas
¡Conseguir sólo una de estas tres cosas ya sería motivo de satisfacción pero poder hacer las tres a la vez nos hace realmente felices!
¡Salud y buen fin de semana!