Mostrando las entradas con la etiqueta angular. Mostrar todas las entradas
Mostrando las entradas con la etiqueta angular. Mostrar todas las entradas

viernes, agosto 30, 2024

Me "ghostearon" como asesor externo


El término "ghostear" es la verbalización de la palabra ghost en inglés que significa fantasma. Y normalmente aplica cuando en una comunicación no presencial, ejemplo correos, mensajería, redes sociales, etc., de manera súbita tu interlocutor  literalmente desaparece del mapa, haciendo que todo  intento de contacto se pierda en el infinito.

Literalmente te hacen un > /dev/null

Bueno, les cuento la historia.
Nota al margen: Ojala no se aburran porque yo no creo en la historia corta, me voy a dar mil vueltas, voy a explicar mil cosas antes, y seguro va a parecer que me olvidé de lo que tenía que contar.

Algún tiempo atrás (en una galaxia muy lejana) me contacta un ex-compañero de trabajo, a quien le hice  más de un par de asesorías. La típica "¿en qué estás?" y "¿qué disposición tienes para  un trabajo extra?". La verdad es que el espíritu del mercenario nunca me ha abandonado, y el desafío se veía entretenido. Días después me contacta la jefa de proyecto que trabaja con él y me dice que "me vendieron" como alguien que puede solucionar cualquier problema. Debo confesar que la falsa humildad evita que yo me venda de esa manera, sin embargo mi arrogancia interna sabe que la maldita combinación de  inquietud intelectual y TOC normalmente  conducen a soluciones.

Trás un par de conversaciones acordamos una tarifa  x HH y me presentaron formalmente el desafío: Un sistema de facturación electrónica, cuyo backend está en Java con Spring Boot y el frontend en Angular.
Mi misión (que obviamente decidí aceptar) corregir/mejorar/hacer funcionar el formulario principal en la aplicacion Angular. Mi intervención sería solamente en el front. 

Diagnóstico inicial

Trás una revisión del código me quedaron claras varias cosas:

  1. Quienes estuvieron a cargo de  hacer las definiciones iniciales no conocen bien las capacidades del framework (Angular).
  2. La escuela inicial es Java, y si bien es cierto hay similitudes conceptuales entre la programación Java y Typescript, hay diferencias. No es llegar y traducir las estructuras. Hay cosas que se resuelven de otras maneras (GOTO 1)
  3. Delegaron el desarrollo de partes sensibles de la aplicación a pollos digo.... desarrolladores menos  experimentados, ergo el desastre que empecé a ver en el código:
    • Repetición de funciones.
    • Funciones con múltiples responsabilidades (al bosillo la S de SOLID).
    • Cero comprensión de los requerimientos, derivando en código  enredado, desordenado y complicado para requerimientos sencillos (golpeando a la fuerza con un martillo siempre se puede meter una pelota en el espacio de un cubo).
    • Cero estándares de programación, desde la manera de nombrar las variables y funciones. indentación, tipo de comillas, etc.
    • Cero documentación, sobretodo en aquellas partes "complejas".
    • Desconocimiento importante de los conceptos básicos de OOP. 
    • Desconocimiento de patrones de diseño.
    • UN SÓLO gran componente con TODA la lógica de negocio en él. Más de 800 líneas de código, toda una obra de arte.
    • Ausencia de una política clara de control de versiones. Usan git, así como  usamos calcetines.

Iban a ser jornadas de muuuucha diversión.

Cuando pesan las decisiones del pasado

Y no a mi en este caso. 

Empecemos:
Angular no es un framework sencillo. Tampoco es que sea taaaan complicado, pero no es sencillo, hay una base de OOP importante, y se le saca provecho en proyecto complejos (como el de este equipo), y más aún cuando hay conocimiento del framework.

Por ejemplo, uno de los pecados mortales que encontré, escribieron, o más bien arrastraron un servicio completo que extiende el HttpClient y agrega el token JWT de cada petición. ¡Sí! va a funcionar, pero... el HttpClient de Angular tiene soporte para los Interceptors, que van a actuar como middleware y pueden modificar las peticiones, no es necesario usar un httpGet propio que  agregue el token JWT a los headers. Lo peor de todo no fue tanto que lo arrastraran, sino que para peor, NO lo usaron en ningún lado y que cada una de las peticiones del sistema agregaba a mano el token.

Otro pecado mortal, servicios viejito, servicios... Salvo algunas contadas excepciones, claramente desarrolladas por otro pajarito (al menos eso decía el git log) TODAS las peticiones estaban dentro del giga-componente.

A ver, muy a grosso modo (siempre en el contexto de Angular):

  • Componente: Unidad modular, habitualmente "tonta", sólo se encarga de desplegar información y reaccionar a ciertos eventos. No tiene lógica de negocio.
  • Servicio: Es quien se encarga del "trabajo sucio". Los servicios saben que es lo que se tiene que hacer. Aquí hago una pequeña distinción, porque saber que se tiene que hacer es diferente a saber como hacerlo. Entonces tenemos 2 tipos de servicios que caerían en esta definición:
    • Facade: Fachada, que sabe que se tiene que hacer
    • Servicio (implementación específica): Sabe como hay que hacer loque se tiene que hacer.
      Y si, los 2 son servicios...

Y aquí seguro explotaron algunos cerebros, las cosas dejan de ser sencillas. Ya nada es como el tutorial de la PokeAPI o como el proyecto del Bootcamp.

Pero no sufran tanto, voy con un ejemplo.
Supongamos que su componente tiene que traerse datos desde "algún lugar", la respuesta que  reciben y saben procesar siempre tiene la misma forma (tipo de datos conocido), solo cambia el "desde donde" se van a traer los datos. Entonces el instinto te dice:

"Ahhhh! un servicio que tenga 2 metodos:

  1. getDataHttp()
  2. getDataLocal()"

Y si, va a funcionar, pero se acuerdan de la  O de SOLID, Open-closed principle abierto a la extensión, cerrado a la modificación, vale decir que si el comité creativo se le ocurren 20 origenes de datos diferentes, o te llenas de funciones  o te llenas de ifs.

Lo más razonable en términos de arquitectura es ver una implementación tipo Strategy o Factory, donde todo el resultado de tener un conexteto acotado

Nos fuimos a la B... no compa, me hubiera dedicado a la pastelería...

Y acá si me pongo a desarrollar el tema puedo escribir demasiado, SOLID da para  mucho.

Bueno, de SOLID el desarrollo base las pelotas. La idea me dio la sensación que fue buena, pero en la medida que pasó el tiempo y que no supieron mantener la consistencia en el código (y de paso de mantenerse al día cono cómo se desarrollaban los proyectos en cada nueva versión de Angular) los destrozos eran algo de esperar.

Entonces nuevamente se juntan los ingredientes claves para que Santa cobre caro y tenga  pega el desarrollo de un proyecto se empiece a volver caótico:

  1. Bases mal definidas
  2. Escaso conocimiento en el Framework
  3. Equipo poco/mal capacitado

Si todo lo que pudiste decidir mal; por que no sabías o no te  supiste asesorar, o derechamente no te asesoraste ni investigaste; lo decidiste mal,  y seguiste una formula "conocida" que funcionó alguna vez, de seguro todas esas malas decisiones te van a cobrar factura después.

¿Qué fue lo que hice?

Literalmente lo de la imagen. El formulario principal era inicialmente un caos del que era cosa de gastarse unos minutos, dibujarlo a mano en papel y darse cuenta que se podía separar en componentes mucha más sencillos. 

Claro que alrededor tocó generar nuevos tipos de datos, definir servicios, extirpar funcionalidades, optimizar algunos procesos, ordenar el código, ordenar el código... Cuando visualicé la separación lógica para esto empecé a recortar el original, cada  "bloque" en su propio componente. Documentación adecuada, explicaciones a ciertas implementaciones oscuras, poco a poco  fue tomando sentido.

Pero no estuvo exento de dolores de cabeza, cosas que me hicieron pensar "¿Pero porqué este animalito  lo hizo así si <explicación razonable que hasta ChatGPT  indica sin fallas>?"

El plazo de entrega  era acotado, me demandó una jornada continua de 12 horas de programación, y finalmente  pospusieron esa reunión. Hubo algo de espacio extra para afinar detalles y de paso me pidieron "un extra".

El extra

Necesitaban que el generador de documentos funcionara. 

Contexto:
Los documentos tributarios electrónicos (DTE) pueden ser de diferentes tipos, factura, factura exenta, factura de exportación, boleta, nota de crédito, nota de débito y más de alguno que se me debe estar olvidando. Cada uno de estos tipos de  DTE se identifica con un código específico, y si bien comparten en forma muchas de las propiedades del documento, también difieren en algunas propiedades, según sea el tipo de documento. Eso ¿les da algún indicio de  por donde puede ir la cosa?

Vamos por parte:

  1. Documentos "parecidos en forma"
  2. Documentos "parecidos, pero con ciertas diferencias"
  3. Documentos identificables según su tipo

Mi opinión se vuelve  algo sesgada (biased) al respecto, pero

¿tiene pinta de OOP o no tiene pinta de OOP? 

Y acá me pusieron pila, la idea era hacer un generador relativamente simple, relativamente fácil de entender y extender, pero por sobretodo una implementación elegante/medianamente elegante (aunque considerando la base casi cualquier cosa era mejora).

Entonces generé un estructura parecida a esta:

Entonces a través de un Factory rescataba la implementación  especifica para el documento que  quería generar. La interfaz del Documento me  indicaba los métodos que cada implementación  debía implementar. Eso más un Builder para poder generar instancias de cada documento usando los datos de cada sección del formulario, y  un Adapter para poder ajustarlos en forma para que el objeto sea "compatible" con lo que sabe manejar el backend.

Eso más mucha magia de Typescript, que también es algo que me llama demasiado la atención, y dentro de todo experticias que siempre fueron necesarias en este desarrollo.

Todo documentado, código "bonito" (y no porque lo diga yo), hasta un video les hice para explicar el proceso de la implementación. Claro que no profundicé en los patrones de diseño usados, eso literalmente da para un curso de  un semestre de universidad, y revisando el video un par de veces (no porque me guste el eco de como resuena mi propia voz) el video no es taaaaaaaan simple de entender, no sin una base medianamente sólida de OOP.

Quemé  más de un par de neuronas en el proceso.

Finalmente -> me "ghostearon"

Después de  caerse de espaldas con la factura que les mandé, porque no les salió barato (siendo que me aceptaron el precio x HH y que trabajé efectivamente cada hora que cobré) me consultaron si podía generarles un "proyecto base".

¿Qué sería este "proyecto base"?

De acuerdo a las mismas palabras de la JP "es un proyecto que tenga lo básico para poder empezar un proyecto, incluyendo buenas prácticas, etc."

Independiente de si trabajana o no en desarrollo de software es claro que TODOS los proyectos son diferentes, por mucho que estén basados en el starter-project, que de alguna manera busca ser esto.

Respecto a los starter-project, la idea no es mala, pero si tienes un triste precedente  de que aún con  ciertos lineamientos (buenos o malos, eso da lo mismo) de estructura de proyectos, tu equipo hizo un desastre, entonces por muy buenas prácticas que quieras poner en este starter-project  van a dejar el caos igual. Seamos realistas, por iniciativa propia un equipo rara vez se dedica a entender y aprender de  las buenas prácticas de un starter-project; habitualmente es un programemos encima a lo brutito, y si no hay un área de arquitectura que audite el desarrollo o una cultura de peer reviewing, no hay manera de sostener una arquitectura limpia.

Propuse en cambio capacitar al equipo, enseñarles  cómo se deben hacer las cosas, o quizás no exactamente el "cómo" que  puede resultar una posición impositiva, sino más bien qué enfoque se debe adoptar para poder llegar a una buena solución de software (mantenible, escalable, entendible, elegante...). sostuve mi propuesta con los argumentos que ya mencioné.

Y literalmente fue lo último que supe de esta empresa.
Llegó a ser  irónico pués  me  quitaron los accesos a los proyectos en git, salvo uno, y  ni siquiera me contestaron cuando se los indiqué.

¿De quién fue la culpa? (no eres tu soy yo...)

Mi 1er sospechoso es un poco la cultura de empresa, y esa mala valoración que le dan al trabajo y tiempo experto. Aquí yo sé que cobré caro, es la tarifa consultor habitual que suelo cobrar, precisamente porque me encargo que el producto que entrego valga lo que cuesta. 

Aquí tengo el ejemplo donde en cierta oportunidad quisieron negociar mi tarifa  x HH de consultoría  de manera equivalente a la HH a contrato de plazo fijo. En esa oportunidad mi cara de ¿cómo te lo explico sin que te sientas ofendido? fue épica.

Y mi 2do sospechoso fue  una frase final dentro del video donde explicaba la implementación del generador de documentos. Si volvemos atrás en este artículo nos daremos cuenta que  el generador es básicamernte contexto teórico, no hay nada que sea exclusivamente de frontend, por lo que puede ser  fácilmente aplicado al backend y lo más seguro es que  tenga los mismos beneficios relativos a la arquitectura de la solución.

Textualmente dije "...en vez de llenarnos de ifs, utilizamos  orientación a objetos como corresponde, hacemos distintas implementaciones y se nos simplifica infinitamente la existencia."

Me salió del alma ese cierre para el video. Ese tipo de verdades sin anestesia que no nos gusta escuchar sin decoración bonita.

Decirles a desarrolladores senior que programan en Java que no saben OOP y que no saben de  patrones de diseño es tan ají en el culo como decirle a un taxista que no sabe manejar... bien...

jueves, junio 05, 2014

AngularJS UI Router, resolve & Unknown service provider ERROR

La noche de ayer me quedé en la oficina terminando de corregir algunos aspectos de una aplicación. Una aplicación móvil híbrida con AngularJS, Angular UI Router y Bootstrap. El código estaba bien, pero uno de mis controladores estaba gatillando el error Unknown service provider.

Angular UI Router  permite conceptualizar una aplicación web como una máquina de estados.
Cada acción que se realiza puede ser un estado separado, y cada estado, desde el enfoque de desarrollo que estamos adoptando, tiene sus propias vistas.
A su vez, cada vista esta manejada por su propio controlador, y eventualmente antes de cargarse puede requerir que resuelva determinada petición (resolve).

Ejemplificando con código:

app.config(function ($stateProvider, $urlRouterProvider) {
    $urlRouterProvider
          .when('/', '/login')
          .otherwise('/');


    $stateProvider
        .state('login', {
            url: '/login'
            , views: {
                'login': {
                    templateUrl: 'include/login.include.html'
                    , controller: 'loginCtrl'
                }
            }
            , onEnter: function() {
                loggedin = false;
                localStorage.setItem('loggeduser', null);
            }
        })
        .state('plan', {
            url: '/plan'
            , views: {
                'menu': {
                    templateUrl: 'include/menu.include.html'
                    , controller: 'menuCtrl'
                } 

                , 'plan': {
                    templateUrl: 'include/plan.include.html'
                    , controller: 'planCtrl'

                }
                , 'client': {
                    templateUrl: 'include/client.include.html'
                    , controller: 'clientCtrl'
                    , resolve: {
                        clients: ['pouchWrapper', function(pouchWrapper){
                            var cliMap = function(doc){
                                if(doc.dbtable === 'cli'){
                                    emit(doc, null);
                                }
                            }

                            // Rescata el listado de clientes desde un repositorio local
                            return pouchWrapper.retrieveList(cliMap);
                        }]
                    }                  
                }            }           
            , onEnter: function($location) {              
                if(!loggedin){
                    console.log('No ha ingresado al sistema');                                       
                    $location.path('/logout');
                }
            }
        }) ;

});

Si se dan cuenta algunas plantillas (template) tienen indicado cual es su correspondiente controlador (controller).

Y en el controlador clientCtrl

app.controller('clientCtrl', ['$scope', '$location', 'clients', 
    function($scope, $location, clients){
        // ... aqui va mucho código
    }]);

El problema es que estando bien codificada la máquina de estados, y cargando cada vista como correspondía, AngularJS reportaba el error Unknown service provider para clients, sin indeicar origen del error ni líneas de referencia (es una maravilla depurar código a ciegas :-/ ).
Después de mucho buscar y leer en la Wiki y sección de Issues del Github de Angular UI Router, llegué a un post en StackOverflow (lo siento, perdí el enlace  original), donde explicaban que
el error se genera cuando el estado tiene el controlador referenciado tanto en la configuración como dentro de la plantilla.


En la plantilla include/client.include.html

<div data-ng-controller="clientCtrl">
    <!-- ... aquí va el contenido de la plantilla para el bloque client -->
</div>


El error lo he destacado en negrita, el controlador o está referenciado en la configuración del estado, o está referenciado dentro de la plantilla, pero NO en ambos.

Después de ver la solución, y corregir el problema (eliminando la referencia al  controlador en la plantilla) es entendible lo que está sucediendo. Si recuerdan, el controlador recibe como tercer parámetro clients (que es lo indicamos en el atributo resolve del estado, que queremos resolver antes de desplegar) . Al poner la referencia directamente en la plantilla, clients queda vacío, ergo Unknown service provider.




miércoles, mayo 28, 2014

Spring REST Services y el error Required String parameter is not present

Desarrollando una API REST utilizando Spring MVC, y consumiéndola con AngularJS, me enfrenté al problema "Required String parameter is not present". Así es como lo solucioné.

Hace poco "vendí" (en rigor sólo convencía  mi jefe que es buena idea tomar este enfoque) una idea para abordar nuevos desarrollos. Consiste en desarrollar una API REST, utilizando el generador de código Telosys, y generar las interfaces con HTML5, CSS3 y JavaScript de modo de consumir los servicios con AngularJS.
Nada nuevo dirán, pero ofrece la ventaja que el mismo desarrollo web puede ser portado sin demasiado esfuerzo a una aplicación móvil usando Phonegap, o bien a una aplicación de escritorio utilizando Node-Webkit.

Este tipo de desarrollo resulta bastante rápido, y si logras una combinación entre Angular-UI-router y plantillas web con controladores propios (los controllers de AngularJS), se obtiene una modularidad bastante atractiva.

El problema

El código no tenía problemas, pero al tratar de realizar una llamada POST con Restangular a pesar de que el JSON con los datos que debía recibir la petición estaba bien construido, la llamada arrojaba el error:
Required String parameter 'usr' is not present

Soluciones propuestas

De las soluciones propuestas, después de realizar muchas búsquedas, ninguna funcionó:
  • Forzar que todas las peticiones POST indicaran en sus headers
    Content-Type = 'application/x-www-form-urlencoded' con el código:
  • $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';   
  • Agregar el atributo enctype="application/x-www-form-urlencoded" a la etiqueta form.
  • Realizar la llamada con AngularJS nativo vs usar Restangular.

Hasta pensé en cambiar el enfoque y usar HATEOAS, pero significaba realizar demasiados cambios a la aplicación (en rigor regenerar el código, Telosys se encarga del trabajo sucio).

Solución

Dí con este artículo de StackOverflow, donde explican que el servicio debe recibir un objeto que tenga todos los parámetros de la petición. Algo como:
@RequestMapping(value = "events/add", method = RequestMethod.POST)
public void addEvent(@RequestBody CommandBean commandBean){
    //some code
}

donde se debe indicar que el objeto (puede ser un POJO) CommandBean es el  @RequestBody. Y Spring se encargará de capturar los parámetros adecuadamente.
Y eso funcionó sin problemas.

domingo, diciembre 15, 2013

¿Es el fin de las aplicaciones de escritorio?

Hace poco me encomendaron  el desarrollo de un sistema de software. Inicialmente pensé en usar Java, pero terminé con algo "más web".



Cuando nos enfrentamos al desarrollo de software, aparte del diseño acorde a la captura de requerimientos que se haya realizado, y las interfaces de usuario; entre muchas otras cosas; se debe escoger la tecnología adecuada.

La elección de que tecnología utilizar normalmente va ligada a la instalación, vale decir en las características de la máquina del cliente donde se instalará la aplicación. En la propuesta inicial pensé en realizar el desarrollo usando Java y una base de datos H2. Logré un avance para la generación medianamente rápida de la GUI, sin embargo me di cuenta que para conseguir interfaces de usuario sencillas, y por sobretodo intuitivas, Java me limitaba demasiado, aún considerando Swing, JavaFX y muchas otras librerías de terceros que entregan infinitas alternativas.



La alternativa:

Hacer un desarrollo "más web", vale decir desarrollar el FrontEnd en HTML y el backend en modo "servidor". Entre las elecciones "lógicas" estaba utilizar un servidor Apache Tomcat, un Jetty, o algo para contener JSP y Servlets, o usar Apache (o similar) con PHP.
El problema de esas opciones es que el computador huésped, la máquina del cliente y usuario final, de una u otra manera termina convirtiéndose en un servidor, eso sin considerar la cantidad de dependencias que se deben considerar:
  • JDK de Java
  • Servidor web
  • Motor de base de datos
  • Plugins varios
  • etc.

Pensé ¿.NET será una alternativa? y el problema es similar al de Java, necesitas previamente instalar muchas dependencias. Si el PC del cliente no tiene todo lo necesario, una aplicación que pesa un par de megas significa finalmente descargar sobre 40 MB de instaladores anexos. ¿Cómo evitamos eso?

De vuelta al enfoque inicial

¿Una WebApp local quizás? Con eso se soluciona el peso de la aplicación, aunque hay 2 problemas:
  1. El almacenamiento de datos usando WebDB está limitado a las restricciones del navegador que se utilice. Eso y los riesgos que el usuario utilice distintos navegadores, generando inconsistencias en la información, o que haga una limpieza agresiva del caché del navegador (lo que eliminaría la BD).
  2. La versión del navegador es un elemento que no se puede ignorar, y créanlo, aún hay usuarios de Internet Explorer 6...

Busqué alternativas disponibles, y ahorrándoles el proceso de análisis y selección de la a mi parecer mejor, finalmente llegué a node-webkit.
Aprovechando mi reciente incursión con Node.js decidí hacer algunas pruebas, que luego de resolver un par de problemas han resultado en una aplicación que cumple todas mis expectativas. Lo que utilicé:


Y una vez llegado a esto empiezo a cuestionar que tanto vale la pena desgastarse tratando de hacer una aplicación de escritorio, con miles de dependencias y limitaciones. Más considerando que hoy en día todo avanza hacia convertirse en Web:
  • La comunicación entre las personas se vuelve más y más Web, sólo piensen en las redes sociales.
  • La experiencia de juego cada vez se enlaza más a la Web.
  • Aplicaciones móviles desarrolladas de manera híbrida, vale decir HTML5+CSS3+JavaScript+API nativas. Phonegap lleva mucho avanzado en esta línea.
  • Las aplicaciones de Windows 8, que muchas veces están desarrolladas 100% usando tecnologías web.
  • Los temas de escritorio de GnomeShell utilizan CSS para muchas de sus definiciones.
  • Muchos widgets de escritorio de cada uno de los distintos sistemas operativos
  • El mismo Node.js que es JavaScript en el servidor.
  • Me arriesgaría a decir que más del 90% de los desarrollos de Google.

Incluso escalar este el modelo de aplicación de escritorio web no es tan difícil, pensando en un escenario en el que se decida masificar el uso de la aplicación en la organización.

Estoy claro que esta línea puede no ser la respuesta a todos los problemas que podemos enfrentar en desarrollo de software, pero al menos a mi me hace dar una segunda mirada cada vez que me hablan de desarrollar una aplicación de escritorio.

Si tienen dudas sobretodo respecto a como resolver la persistencia de datos con node-webkit, Sequelize y SQLite no duden en hacerlas.