Archive for the 'Express' Category

Programando MoneyPool (1)

Sunday, December 20th, 2015

La semana pasada disfruté escribiendo el proyecto MoneyPool:

http://github.com/ajlopez/MoneyPool

escrito usando NodeJS, Express, y con mucho TDD (Test-Driven Development). Es lo que se llamaría en inglés un P2P Lending website, un sitio para préstamos de dinero entre personas. Lo escribí dentro del ámbito del hackathon interno de Poincenot (donde estoy participando día a día en un proyecto), y al terminar, conseguí el permiso para publicarlo en mi cuenta. Sólo es un proyecto, no sé si le servirá de base a alguien, pero me gusta publicarlo porque me da material para escribir sobre algunas de las decisiones que tomo, alineadas con lo que vengo publicando en este blog. También para comentar cómo es de diferente el modo desarrollo ágil con respecto a un modo hackathon.

Hubo otros proyectos muy interesantes en el hackathon. Yo escribí este pensando en publicarlo, así que no incluí ninguna referencia a tecnologías o implementaciones internas de Poincenot. El “leit-motiv” del hackathon giraba alrededor de agregar valor al sitio ya en producción:

CashMoon

de préstamos a vendedores de Mercado Libre. Al principio, cuando fue anunciado hace unas semanas, me imaginé que era un hackathon era sobre FinTech (Financial Technologies), así que había preparado algunas ideas, como aplicar decisions trees para calificar a los solicitantes de préstamos, y algoritmos genéticos para crear estrategias de trading, o tomar información de la API de Mercado Libre. Voy a seguir con esas ideas, pero pueden ver ya algo en:

https://github.com/ajlopez/SimpleGA/tree/master/samples/trading
https://github.com/ajlopez/SimpleDT
https://github.com/ajlopez/MeliLib
https://github.com/ajlopez/BtraderLib

(lo de decision trees y algoritmos genéticos es una aplicación a #FinTech de otros ejemplos que mostré el año pasado en la JSConf de Argentina). Estoy compartiendo enlaces de FinTech, y apenas estoy comenzando a escribir sobre el tema.

Los hilos conductores del desarrollo de MoneyPool fueron (y son): simplicidad, guiado por casos de uso. Lo primero implica trabajar con los elementos más simples. Por ejemplo, el proyecto no necesita aún ni bower, ni grunt, así que no los tiene. Notablemente, para desarrollar lo que quería mostrar como resultado (el flujo de negocio de registrarse, como solicitante o inversor, solicitar un préstamo, invertir en una solucitud en forma de crowdfunding, aceptar la solicitud, pagar y repartir el retorno a los inversores), no necesité mucho. Ni siquiera tuve que agregar jQuery u otras librerías. Trabajé con Bootstrap 3.x simple, y apenas algún estilo. Esto repercute en un hackathon, porque lo que uno presenta no es atractivo en diseño visual. Me hubiera gustado tener la compañía de gente que conozca de diseño, pero eso se puede sumar cuando tenga los casos de uso más definidos e implementados.

Ese el otro hilo: desarrollar basado en los casos de uso. Lo que fui haciendo desde el principio, es escribir los servicios que iba a necesitar para los casos de uso que me había planteado alcanzar. Creo que no puse un método de servicio que no estuviera motivado por un caso de uso. Y todos esa lógica la escribí usando el flujo de trabajo de TDD (Test-Driven Development): escribir el test, escribir código que pasa el test, refactorizar.

Y de nuevo, combinado con la simplicidad, todo esto resulta en: no agrego un artefacto que no esté motivado por un caso de uso. Para mostrar un ejemplo concreto: dejé fuera de alcance el paginar los resultados, por ejemplo, la lista de usuarios registrados. No agregué jQuery porque no lo necesité: no había llamada Ajax que necesitara resolver. Una de las formas de resolver el cambio de página es usando una llamada Ajax para conseguir los nuevos datos, SIN refrescar la página completa. Como no había caso de uso que necesitara eso, no entró jQuery en el proyecto. Por lo mismo, no usé ni Angular ni Ember ni Backbone: no había caso de uso que los justificara (como por ejemplo alguno que implicar el uso de un dispositivo móvil y evitar la latencia de cambio de página).

Mi postura no es: no uso X. Sólo es: uso X cuando hay alguna fuerza, caso de uso concreto, de negocio, que agregue valor a lo que estoy haciendo. No agrego X porque esté “de moda” o porque sepa que “lo voy a necesitar” (y entonces romper soberanamente el YAGNI) o por hacerme la programación “más fácil”. Cada año que pasa de este milenio estoy más convencido de algo: lo “fácil” muchas veces conspira contra lo “simple”, y dado a elegir, elijo lo simple. Y que lo simple ayuda a conseguir más valor de negocio, haciendo posible el cambio, tan importante en un ambiente #FinTech de creación de startups.

El hackathon terminó el viernes 18 de diciembre. Y comenzó una semana antes, pudiéndose trabajar cuando no hubiera trabajo diario. Para el miércoles ya tenía casi todos los casos de uso implementados en servicios y TDD. Hubiera esperado haberlos terminado el domingo anterior, pero algunos casos eran un poco más complicados de lo que pensé. Como todo lo que quería mostrar implicaba tener muchas páginas implementadas, aunque con poca lógica visual, pasé a mitad de la semana a codificar la parte web, usando un simple MVC (Modelo Vista Controlador). De nuevo, simplicidad. Sé que se estila hoy tener una API y consumirla desde el cliente, en vez de armar el resultado de la página (render) en el servidor. Pero realmente soy más productivo ahora usando MVC que API + lógica en el cliente. Es parte de las decisiones que hay que tomar en hackathon: el tiempo es acotado, y no se negocia en general las features a incluir o no. Quería incluir todas las que me había propuesto, y al final lo conseguí. Pero tuve que hacer algún sacrificio: las acciones de los controladores fueron escritas sin seguir TDD. Es una deuda técnica que voy a solucionar en las próximas modificaciones que haga.

Un punto interesante: como en el proyecto Liqueed, usé un modelo en memoria, que fácilmente se puede pasar a MongoDB. Hasta tengo los tests preparados para modelo en memoria, versus, modelo en MongoDB, y hasta ahora funcionan (incluso los tests de negocio). Otra cosa que quiero incorporar pero quedó afuera por ser modo hackathon: implementar un DSL (Domain Specific Language) para escribir tests de negocios, ver como ejemplo lo que se hizo en Liqueed.

En los próximos posts iré describiendo más en detalle algunos de estos puntos, el flujo de negocio implementado, implementación interna, proceso, y otros que vayan surgiendo.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Proyecto Liqueed (6)

Friday, April 24th, 2015

Anterior Post

Sigamos viendo algun test de test/personapi.js en el proyecto:

https://github.com/liquid-co-ops/liqueed

Hay entradas en la API que se llaman por PUT o POST, y reciben datos en el cuerpo del mensaje enviado. En Express, usando el middleware adecuado, ese mensaje es analizado y convertido en un objecto JSON, y puesto en el campo body del objeto request original. Pues bien, si nuestra entrada en la API usa ese campo, se lo tenemos que entregar en el test. Un ejemplo:

exports['login person'] = function (test) {
    test.async();
    
    var request = {
        body: {
            username: persons[1].username,
            password: persons[1].username
        }
    };

    var response = {
        send: function (model) {
            test.ok(model);
            test.equal(model.id, persons[1].id);
            test.equal(model.name, persons[1].name);
            test.equal(model.username, persons[1].username);
            
            test.done();
        }
    };
    
    controller.loginPerson(request, response);
};

Como antes, en el response ponemos la función send, que esperamos se invoque asincrónicamente para dar por ejecutado el test.

Otro ejemplo donde no sólo enviamos body sino también parámetros (esos parámetros en general están en la ruta, y Express los toma de ahí y nos lo deja en request.params:

exports['change password first person'] = function (test) {
    test.async();
    
    var request = {
		params : {
			id : persons[0].id.toString()
		},
        body: {
            password: 'new' + persons[0].username
        }
    };

    async()
    .then(function (data, next) {
        var response = {
            send: function (model) {
                test.ok(model);
                test.strictEqual(model, true);
                
                next(null, null);
            }
        };
        
        controller.updatePassword(request, response);
    })
    .then(function (data, next) {
        var request = {
            body: {
                username: persons[0].username,
                password: 'new' + persons[0].username
            }
        };

        var response = {
            send: function (model) {
                test.ok(model);
                test.equal(model.id, persons[0].id);
                test.equal(model.name, persons[0].name);
                test.equal(model.username, persons[0].username);
                
                test.done();
            }
        };
        
        controller.loginPerson(request, response);
    })
    .run();
};
 

Próximos posts: pruebas del controlador MVC, pruebas levantando el servidor, pruebas usando un DSL (Domain Specific Language)

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Proyecto Liqueed (5)

Saturday, April 18th, 2015

Anterior Post

Examinemos dos tests de test/personapi.js. El segundo test es:

exports['get persons'] = function (test) {
    test.async();
    
    var request = {};
    var response = {
        send: function (model) {
            test.ok(model);
            test.ok(Array.isArray(model));
            test.ok(model.length);
            test.ok(model[0].id);
            test.ok(model[0].name);
            test.done();
        }
    };
    
    controller.list(request, response);
};

Una vez ya armado el modelo de dominio (en el primer test), ahora podemos probar un método del controlador, el getPersons. Como necesita recibir request y response, le pasamos dos objetos armados a propósito, y que tienen lo que el controlador necesita. El response tiene definido un método send, que se espera sea invocado por la acción del controlador. Ahí es donde reside el test de lo retornado, y el test.done() que hace que el test termine exitosamente. Se espera que devuelva un modelo, un objeto JSON que sea un arreglo, que contenga algunos datos.

El tercer test es:

exports['get first person'] = function (test) {
    test.async();
    
    var request = {
        params: {
            id: persons[0].id.toString()
        }
    };

    var response = {
        send: function (model) {
            test.ok(model);
            test.equal(model.id, persons[0].id);
            test.equal(model.name, persons[0].name);
            test.done();
        }
    };
    
    controller.get(request, response);
};

Esta vez se necesita algo en el objeto request, un parámetro adicional indicando la clave primaria de la persona. Sigue habiendo un método send en el response armado para el test, que controla que se devuelvan los datos de la persona pedida.

Vemos que acá no se está invocando HTTP con GET o cosas similares. Estamos probando directamente el código del controlador. De hecho, el código fue escrito luego del tests, y se ejecutó por primera vez desde los tests, sin necesidad de levantar un servidor HTTP.

Próximo post: veremos la prueba de otros métodos de la API, pasándoles objetos, y pruebas del controlador MVC

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez