Debe ser imposible representar estados ilegales

Este es el tercer post en esta serie sobre arquitectura de software. En los posts anteriores he hablado de mi experiencia aprendiendo sobre este tema y sobre algunos de los problemas que he enfrentado en situaciones donde no hay una arquitectura clara.

En este post espero empezar a introducir algunas ideas y técnicas concretas que he usado para comenzar a resolver mis problemas.

Estados ilegales

Cuando digo “que sea imposible representar estados ilegales”, lo que quiero decir es que nuestra meta debería ser que nuestro programa nunca esté en un estado inválido. O sea que sea imposible hacer algo que llevará a nuestro programa a estar en un estado inesperado o no definido.

Eso significa que si nuestro programa es un carrito de compras y tenemos una regla de negocio que permite un máximo de tres artículos en carrito, debería ser imposible añadir un cuarto artículo al carrito.

En el pasado he atentido este tipo de reglas de negocio, poniendo validaciones en el perímetro de mi programa. Usualmentemeste trabajo en aplicaciones web, así que esto signfica, validación en un controller o algo directamente relacionado al endpoint que recibe el request del usuario para añadir un artículo al carrito. No hay nada malo con esto, solo que si la petición de añadir un cuarto artículo al carrito llega de otra parte, nada evitará que suceda, poniendo nuestro programa en un estado inválido o ilegal. Este podría ser un efecto deseado en algunos casos pero en este ejemplo estamos supiniendo que no queremos que esto pase nunca.

Este ejemplo me ayuda a ilustrar lo que queremos alcanzar, pero este post no cubrirá todo lo necesario para lograrlo. Me voy a enfocar en la pieza mas pequeña de un sistema que no permite representar estados ilegales.

Value Objects

No voy a ponerme a definir términos así que puedes mirar aquí y aquí para buenas definiciones. De lo que si voy a hablar es sobre como uso VOs dentro de mis programas para aplicar reglas de negocio.

Lo primero es decidir cuando usar un VO. Mi respuesta es que todo valor dentro de tu programa por defecto, debería ser un VO. La ventaja de este acercamiento es que te da la oportunidad de establecer reglas para cada valor individualmente. El ejemplo obvio de un VO es una cantidad de dinero (Amount).

Si tenemos un endpoint en un web app que recibe una cantidad de dinero probablemente tendremos un payload JSON como este.

{
 "amount": 100  
}

Este objeto muestra el amount con un valor de 100 centavos o $1.00. Cómo nuestro programa puede manejar varios tipos de moneda, rápidamente nos damos cuenta que necesitamos saber la moneda de esa cantidad así que añadimos ese campo.

{
 "amount": 100,
 "currency": "USD"
}

Ok, ahora si tenemos todo lo que necesitamos. Lo próximo que hacemos es validar el input en nuestra aplicación así que podemos escribir el siguiente código.

app.post(async (req, res) => {
    let {amount, currency} = req.body;

    if (typeof amount !== "number") {
        return res.send(422);
    }

    if (amount < 0 || amount > 1000000) {
        return res.send(422);
    }

    if (typeof currency !== "string") {
        return res.send(422);
    }

    if (!["USD", "EUR"].includes(currency)) {
        return res.send(422);
    }

    try {
        let response = await controller(amount, currency);
        return res.json(response);
    } catch (error) {
        // do some error handling
        return res.send(400);
    }

});
async function controller(amount, currency) {
    // do stuff with amount and currency
}

Obviamente esto se puede hacer de forma mucho mas declarativa usando algo como JSON Schema o cualquier otra librería de validación de objetos.

Pero hacer ese tipo de validación no nos ayuda a recordar que ya hicimos esa validación. Las funciones que reciban esto valores no tienen forma de saber que ya fueron validados y por lo menos en mi caso esto crea un poco de duda. ¿Que tal si no valido el input en esta funcion y alguien pasa valores sin verificar? Esto puede producir bugs e incluso problemas de seguridad. Este problema se puede resolver con VOs.

De este punto en adelante voy a usar TypeScript para los ejemplos. El sistema de types de TS nos va a ayudar a garantizar el contenido de nuestro VOs. Primero vamos a definir este VO Amount.

class Amount {
    readonly value: number;
    readonly currency: string;

    constructor(value: number, currency: string) {
        this.value = value;
        this.currency = currency;
    }
}

Este es el primer paso. Si te das cuenta definí una clase con ambos valores. Esto es una decisión de diseño importante. Como sabemos estos dos valores están completamente conectados. No se puede cambiar uno, sin que el otro cambie. $1 USD y 1€ EUR no son lo mismo. Así que estos dos valores en realidad son uno solo. Esto es lo primero que añade este VO a nuestro programa. Nos está dejando representar dos datos como un solo objeto conectándolos para siempre y ayudándonos a recordar que no podemos cambiar uno sin pensar como se afecta el otro. Sigamos.

Ahora el problema que veo es que si creo una instancia de esta clase no hay nada impidiendo que ponga valores que no puedo aceptar en mi programa. Por ejemplo si estamos haciendo un carrito de compras, ningún producto puede tener como precio un número negativo pero este VO nos permite pasar un número negativo. Como esta, hay otras reglas de negocio, que podemos implementar en este objeto para asegurarnos de que siempre que lo usemos, estas reglas sean aplicadas y si alguien o algo trata de romper estas reglas nuestro programa no lo permita.

A mi me gusta poner estas reglas en el constructor y si alguna de estas reglas no se cumple, lo que hago es throw una excepción para impedir la creación de la instancia. Esto hace imposible que exista una instancia que no cumpla con las reglas que establecimos.

class Amount {
    readonly value: number;
    readonly currency: string;

    constructor(value: number, currency: string) {
        // Check value
        if (typeof value !== "number") {
            throw new Error("Amount value must be a number");
        }

        if (value < 1 || value > 1000000) {
            throw new Error("Amount value must be between 1 and 1,000,000");
        }

        // Check currency
        if (typeof currency !== "string") {
            throw new Error("Amount currency must be a string");
        }

        if (currency.length !== 3) {
            throw new Error("Amount currency must be a valid ISO-4217 alpha code")
        }

        if (!["USD", "EUR"].includes(currency)) {
            throw new Error("Amount currency can only be USD or EUR");
        }

        this.value = value;
        this.currency = currency;
    }

}

Ok, ahora este VO es bastante más estricto, verifica los tipos de los argumentos que recibe y también se asegura que este objeto sigue nuestras reglas de negocio. Como hace throw de una excepción si algo está mal, no es posible crear una instancia en un estado ilegal.

Por otro lado, si tenemos una instancia de este objeto, sabemos que tiene que ser válida porque logró pasar por todas las pruebas (guards) que establecimos. Así que una vez tenemos una instancia de este objeto podemos estar seguros que es válida y podemos confiar en lo que contiene. No tenemos que volver a verificar nada porque sabemos todo.

Ahora donde quiera que necesitemos este valor en vez de pasar un number y un string podemos pasar un Amount y estar confiados.

Se que esto puede parece increíblemente obvio para algunos, especialmente los que están acostumbrados a trabajar en lenguajes strongly typed pero les puedo asegurar que para los que llevamos toda la vida en lenguajes dynamically typed esto es una revelación.

Que más podemos hacer con un VO. Pues habrás notado declare los fields de Amount como readonly. Una de las caracteristicas más importantes de un VO es que sea inmutable y que una vez creado, no se pueda cambiar. Esto nos da muchas garantías sobre el estado interno de nuestro objeto.

Una acción que seguro vamos a necesitar hacer con nuestro Amount es sumarle otro Amount pero como dije antes no podemos modificar un VO una vez creado. Así que lo que podemos hacer es crear un método que recibe el Amount que queremos sumar y crea un VO nuevo que contiene el resultado.

add(amount: Amount): Amount {
    if (amount.currency !== this.currency) {
        throw new Error("Amounts of different currency cannot be added");
    }

    return new Amount(amount.value + this.value, this.currency);
}

Nuevamente aquí podemos ver que estamos validando que ambos VOs son del mismo currency antes de sumarlos. Una vez sabemos que son del mismo currency creamos una nueva instancia de Amount con el resultado de la suma.

Otra cosa importante es poder comparar dos VOs por su valor así que vamos a escribir un método para esto.

equals(amount: Amount): boolean {
    return amount.value === this.value && amount.currency === this.currency;
}

Fácil. Ahora podemos comparar dos VOs y saber si contienen el mismo valor. Es importante notar que estamos comparando tanto el value como el currency porque sabemos bien que un Euro y un Dólar no son lo mismo.

VOs en mi web app

Creo que esos son suficientes ejemplos simples. Ahora un último ejemplo de como se puede usar este VO en el contexto de nuestra aplicación web.

app.post(async (req, res) => {
    let {amount, currency} = req.body;

    try {
        let amount = new Amount(amount, currency);
        let response = await controller(amount);
        return res.json(response);
    } catch(error) {
        // do some error handling
        return res.send(400);
    }
});
async function controller(amount: Amount) {
    // do stuff with Amount
}

Ahí está. Ahora Amount nos ayuda a validar el input que estamos recibiendo y luego de que tenemos una instancia sabemos que es un valor que no necesitamos validar nuevamente. El resto de nuestro programa, siempre y cuando reciba una instancia de Amount, puede trabajar con el valor de forma segura y sin preocupación.

Además este VO nos ayuda a organizar el código y mantener las reglas de negocio cerca del los datos que las necesitan. La clase Amount tiene todo el código necesario para crear una instancia válida y las acciones (método) que podemos tomar con ese objeto.

Result

Como notaste, cada vez que queremos crear un VO, necesitamos hacer new y pasar los argumentos que hagan falta para crear la instancia. Como nuestro constructor puede throw siempre tenemos que asegurarnos de hacer try/catch para manejar cualquier error al momento de la instanciación.

Aunque esto no es el fin del mundo, bien rápido se puede hacer algo tedioso. Dicho eso, hay formas de mejorar la experiencia al usar VOs.

Todos mis VOs tienen un método estático llamado create. Este método me permite instanciar VOs sin temor a una excepción pero en vez de devolver una instancia de Amount, devulve una instancia de un objeto llamado Result que sirve para envolver el Amount que se creo o un Error si hubo problemas al crear el VO.

Esta idea de usar un objecto para envolver el resultado es algo muy común en otros lengajes como Rust y nos permite hacer cosas interesantes. Los detalles sobre Result los voy a dejar para un post futuro donde también hablare sobre como uso Maybe en mis programas.

Resumen

  • Los VOs nos ayudan a implementar reglas de negocio para un valor dentro de nuestro programa.
  • Los VOs deben ser inmutables.
  • Debe ser imposible instanciar un VO en un estado ilegal.
  • Una vez tenemos una instancia de VO podemos estar seguros que contiene un estado válido dentro de nuestro programa.

Foto: Unsplash

Published
Categorized as Default

Htmx y Lit

En nuestro equipo, tener un SPA para cada app es mucho trabajo adicional con muy poco beneficio. Muchas veces pasamos trabajo adicional para lograr que nuestros SPAs se comporten como web apps tradicionales con templates en el servidor. Muchos de los beneficios están relacionados a equipos grandes y ese no es nuestro caso.

Llevo un tiempo buscando cómo consolidar la lógica de negocio y compartirla entre los servidores y los SPAs para que siempre estén en perfecto sync, pero como todo el que ha tratado sabe es muy difícil. De vez en cuando haces un cambio en el cliente o en el servidor y se olvida actualizar su contra parte produciendo bugs.

Estos dos proyectos me parecen interesantes y les quiero dedicar más tiempo. Pienso que podrían ser una solución bastante completa para muchos de los web apps que manejo.

Htmx, escuché por primera vez sobre este proyecto hace unas semanas en un podcast. Esto no es una idea nueva pero creo que esta solución se ve aplicable a muchos casos comunes. En resumen la idea es crear interactividad del tipo que nos han acostumbrado los SPAs pero sin tener que crear un cliente completo. Básicamente es volver a como hacíamos las cosas antes cuando las siglas AJAX eran cool. Haces click en el UI, se envía un request al servidor y se responde con HTML nuevo que se injecta directamente en el DOM. Para sorpresa de los más jóvenes se puede lograr mucho con esta simple técnica.

Lit, aunque aparenta ser un rebranding de Polymer, se ve bastante bien y prometedor. Creo que la idea de usar Web Components, con el mínimo runtime posible en los lugares donde algo como Htmx no sea suficiente puede ser una buena alternativa. Siempre se puede usar el API nativo del browser pero parece que Lit ayuda a que el código sea mas declarativo y eso es bueno.

Cuando haga mas pruebas les contaré.

Published
Categorized as Default

Bolsas llenas de funciones

En este post menciono functional programming (FP) y object-oriented programing (OOP) en el contexto de JavaScript (JS). Hablo de mis experiencias con los dos paradigmas en un proyecto bastante grande. El título del post no es FP vs OOP y no tengo en interés en ese tipo de discusión.

Las ideas de FP llevan varios años tomando fuerza en el mundo de JS. Mi interés en el tema llegó algunos años antes del lanzamiento de React. En eso días ya habían librerías y developers muy activos evangelizando sobre FP en JS y sus beneficios.

Aunque había algo de interés antes de que React estuviera en el panorama no es hasta el lanzamiento de React y su enorme popularidad que la gran mayoría de devs se interesaron en el tema.

Los vendedores (developer advocates) de React presentaron buenos argumentos sobre las ventajas de FP, compartieron ejemplos convincentes y escribieron librerías super populares justo en la raíz de lo que luego se convirtió en la librería más popular e influyente en el mundo de JS desde jQuery.

En este momento no se conocían codebases grades y open source que sirvieran de ejemplo para los millones de developers que ahora decidieron escribir a lo FP. Los ejemplos disponibles en muy pocos casos pasaban del ya trillado to-do list o el casi cómico counter que consiste en dos botones que al presionarlos aumentan o disminuyen un número. Quedaba como asignación para el o la developer el definir e implementar una arquitectura escalable en un paradigma poco familiar.

Más o menos para esta misma época ya había escrito algunos proyectos usando un estilo, estrictamente funcional o lo más funcional posible dentro de las limitaciones que impone JS. Admito que eran proyectos pequeños con un solo developer, pero eran prometedores.

Organizar todo como pure functions, data inmutable y evitar side effects se sentía limpio. Estos proyectos eran básicamente treinta o cuarenta funciones trabajando en conjunto. Las mayoría de las pruebas eran fáciles de escribir y solo tenía una o dos funciones que creaban side effects así que todo era hermoso.

Como muchos me deje seducir por la aparente simpleza de este paradigma. Mi error fue pensar que esto que funcionaba para proyectos pequeños podría funcionar en proyectos mucho más grandes. Estas experiencias me dieron confianza en FP JS y me hice de la idea de que se podría usar en proyectos más grandes con los mismos resultados. 

No funcionó. Cuando traté de llevar lo que había aprendido de FP JS a un proyecto mucho más grande, todo lo que se sentía limpio poco a poco se volvió complicado y difícil de entender. Tenía muy poca experiencia y no conocía suficientes técnicas para manejar esta escala. Teníamos funciones que llaman funciones, que llaman a otras funciones y cada una llama más funciones.

Mientras escribíamos el código no veíamos problemas pero cuando había que leer ese código meses más tarde, daba mucho trabajo entenderlo. Los code reviews tomaban mucho tiempo o simplemente se volvían muy superficiales. Hubo muchas señales de que estábamos haciendo algo mal pero las ignoramos.

Como buenos programadores de FP, mantuvimos la data separada del comportamiento. Nuestro app recibía objetos simples (POJOs), los transformaba y producía nuevos objetos simples. Suena fácil, pero cuando tienes decenas de funciones, manipulando estos objetos, saber en que estado está la data en todo momento puede ser complicado. Muchas veces nos veíamos obligados a usar el debugger para poder inspeccionar el estado de un objeto en un lugar específico. En otros momentos teníamos que correr y ejecutar la aplicación para entender las estructuras de data. El código no era fácil de entender. 

En este proyecto siempre tuvimos una idea de “modelos” pero, no eran realmente modelos. Mas bien eran una capa muy delgada por encima de una pequeña abstracción de la base de datos. Nuestros modelos no mantenían estado, simplemente nos daban acceso a la base de datos y servían para guardar algunas funciones que estaban muy ligadas a la entidad específica.

Uno de los síntomas más claros de que teníamos un problema es que en el proyecto teníamos una carpeta que se llamaba utils con cientos de files y cada file con dos o tres funciones. Muchas de estas funciones contenían lógica de negocio y hacían cosas realmente importantes y centrales para la aplicación. Estos archivos terminaron en esta carpeta porque eran funciones que queríamos reutilizar y no teníamos una idea clara de donde ponerlas. Después de todo queríamos tratar de mantener la lógica y la data separada y estas funciones eran lógica.

Más de una vez durante un review, encontramos que alguien había escrito una función que ya existía. En este proyecto es difícil encontrar código existente para reutilizar. Es fácil imaginar como terminamos con funciones duplicadas que hacen las cosas un poco diferente la una de la otra. Demás está decir que esto produce bugs.

En otros casos las funciones estaban muy ligadas a un pedazo de data o a un evento de la aplicación, así que esas funciones se escribían en el mismo archivo donde se usaban, complicando más aún el encontrar funciones para reutilizar y mantener la lógica centralizada.

La combinación de estas cosas y la falta de experiencia sobre como diseñar un proyecto FP de gran escala nos llevó a tener una arquitectura que solo se puede describir como una montaña de bolsas llenas de funciones.

Estas bolsas de funciones se organizan de muchas formas, alguna veces de acuerdo al tipo de acciones que hacen, otras de acuerdo a la data que modifican y otras como utils cuando no sabemos que hacer con ellas. Sin duda, haber tenido un mejor esquema de organización pudo haber ayudado mucho a reducir la complejidad pero estábamos aprendiendo mientras construíamos nuestro programa y no teníamos muchas referencias.

Nuestro problema con este proyecto no es un problema producido por FP, pero si un problema al que es fácil llegar si se tiene poca experiencia con este paradigma y si no existen suficientes recursos para aprender y tomar ideas sobre como diseñar la arquitectura de tu aplicación. Creo que este es el caso de la mayoría de los y las devs que conozco. Les interesan las ideas de FP, se ven fáciles de entender y aplicar pero no tenemos idea de como usarlas efectivamente en un proyecto de gran escala.

Pensar que solo con usar funciones puras, tener data inmutable y evitar los side effects es suficiente para crear software de cualquier escala es una trampa.

Aprendemos a usar map, reduce y filter y nos creemos genios del FP. Vemos un proyecto con dos o tres pantallas escritas con React y solo funciones y pensamos que eso puede escalar a un proyecto de cualquier tamaño. Nos decimos que si todo es una función pura y fácil de entender, será imposible que se complique. Es tan simple que esa es la única abstracción que hace falta.

Cualquier proyecto de suficiente tamaño se puede salir de control si no se tiene una arquitectura clara que nos ayude a organizar el código y a comunicar de forma efectiva lo que hace nuestro programa.

Hoy, sigo usando muchas técnicas que aprendí en mis días de FP pero también uso ideas de OOP y me alegra reportar que nadie ha muerto en el proceso. He encontrado un punto medio que usa ideas de ambos paradigmas y que me permite crear aplicaciones con una arquitectura clara, fácil de entender, que me ayuda a moverme rápido y hacer cambios con tranquilidad.

En un post futuro hablaré mas en detalle sobre algunas de estas técnicas e ideas de arquitectura.

Foto: Unsplash

Published
Categorized as Default

¿Dónde está el programa?

¿Dónde está el programa? Esa fue la pregunta que hice a un programador de nuestro equipo. ¿Dónde está el código que describe como funciona nuestra aplicación? No pudo contestar y no esperaba que pudiera. En el proyecto que estábamos trabajando, la respuesta era bastante complicada. El programa estaba en todas partes y en ninguna a la misma vez.

Cuando comenzamos en este proyecto, lo primero que hicimos fue un prototipo. Bueno, el prototipo lo hice yo y sí, era “founder code”. Se escribió en poco tiempo y funcionó para validar la idea y para poder movernos adelante a conseguir clientes. Nunca me detuve a pensar en arquitectura o si podría escalar.

El único objetivo era que funcionara y que convenciera a clientes del potencial del producto. Misión cumplida.

Cuando le hice la pregunta al programador, al que llamaré Carlos, ya el proyecto era viejo en años de software y del “founder code” original quedaba muy poco, pero la falta de una arquitectura clara y definida todavía estaba presente. Luego de lograr los primeros clientes, en vez de repensar todo, optimizamos para velocidad y acumulamos mucha deuda técnica. Poco a poco los atajos que tomamos para movernos rápido nos estaban pasando factura.

Cundo le hice la pregunta a Carlos, ya llevaba unos meses estudiando sobre arquitectura de software. Antes de meterme en este tema cuando me hablaban de arquitectura pensaba en nuestra infraestructura en AWS. Que servicios estamos usando, si usamos Lambda, si estamos haciendo load balancing o estamos usando un “queue” y este tipo de cosas, nunca pensaba en la estructura de nuestro codebase.

Antes de esto tenía algunas nociones de arquitectura de software. Cuando conocí Ruby on Rail aprendí sobre Model, View, Controller y cuando empecé a hacer front end “en serio” usando Backbone.js hablábamos de client side MVC y MVVM. Desde entonces todos los proyectos que trabajé usaban algo parecido a MVC pero nunca me detuve a pensar por qué y si había algo mejor. Parecía funcionar y si todo el mundo lo está usando, creo que estamos ok.

Luego trabajé mucho con Django y uno de los “best practices” de la comunidad era la idea “fat models”. Lo que esto quiere decir es que lo que se puede implementar como un método de un modelo, lo debemos implementar como un método del modelo. En esos días, estaba muy pendiente a que no tuviéramos lógica regada por la aplicación y cuando había dudas sobre donde poner algo decía casi automáticamente, “fat models”. Tuve mucho éxito haciendo esto pero parecería que no aprendí nada.

Hoy llevo mas de un año estudiando sobre arquitectura de software. He aprendido mucho sobre como pensar acerca de la estructura de un proyecto de software y sobre que cosas funcionan en los codebases que trabajo y con los equipos que trabajo.

De todo lo que he estudiado lo más que me ha hecho sentido han sido acercamientos que se pueden considerar principalmente “object-oriented” pero vale la pena mencionar que lo que aprendí en mis años en el culto de “functional programming”, sigue siendo útil y lo uso todos los días. Una de las grandes ventajas de JavaScript, el lenguaje que uso principalmente, es que es multi paradigma y nos deja mezclar lo mejor de OOP y FP sin problema.

Cuando aprendí sobre domain-drive design o DDD, me hizo click inmediatamente. Todos los problemas que describían los había vivido y las soluciones que planteaban, no solo eran mejor para el codebase si no que también ayudaban a facilitar la comunicación entre el equipo e incluso los clientes. No voy ni a intentar definir DDD, pero es un tema que creo todo el mundo que le interese escribir software “mantenible” y “testiable” debe estudiar.

DDD, resultó ser una especie de puerta a un mundo de patrones de diseño y arquitecturas que ponen en el centro el “domain”.

El “domain” es la capa que contiene el programa. En el domain hay objetos que están diseñados para representar todo lo que existe en la aplicación, en el domain están todas las acciones que esos objetos pueden hacer y en el domain es imposible tener data en un estado inválido. En el domain no hay código específico de ningún framework o base de datos o cualquier otra dependencia de la infraestructura. Es lógica pura.

Esta era la respuesta que estaba buscando. De ahí me moví a aprender más sobre arquitecturas que usan esta idea de el domain en el centro de todo. Aprendí sobre Clean Architecture, Onion Architecture y Hexagonal Architecture o Ports and Adapters Architecture que es como prefiero referirme a este diseño. Todas estas arquitecturas aportan ideas valiosas y todas ponen el domain en el centro.

Para nuestro equipo llegamos a un diseño inspirado principalmente por Ports and Adapters Architecture. La experiencia hasta el momento ha sido positiva. Hacer cambios es fácil, es obvio donde están las reglas de negocio, nos obliga a ser mucho más explícitos y sentimos que escribimos menos bugs. Además hacer pruebas es fácil, rápido y casi divertido.

Recientemente, trabajando en un proyecto nuevo, después de semanas de haber comenzado, decidimos cambiar el web server library de Express.js a Fastify. El cambio tomó poco más de un día de trabajo y fue fácil lograr que todo funcionara luego del cambio. En el código del domain no cambiamos ni una sola linea.

Estoy seguro que ahora estarás pensando: ¿Pero cada cuánto tiempo se cambia de web server library? La repuesta es casi nunca, pero si puedo decir que el diseño que nos ayuda a hacer este tipo de cambios fácilmente, también nos ayuda de otras formas.

Por ejemplo, tenemos varias implementaciones de base de datos. Una para la base de datos de producción y otra que usamos en el ambiente de pruebas, que almacena los datos en memoria. También tenemos implementaciones de queues en SQS, Redis y una basada en archivos JSON en el file system. Durante development usamos la versión basada en archivos, en staging la versión Redis y en producción la de SQS y el código del domain siempre es el mismo. Los límites de cada capa y las interfaces entre ellas está claramente definidas y esto tiene muchos beneficios.

Esta experiencia me ha dado muchas herramientas nuevas y ha revivido mi entusiasmo por la disciplina de crear software.

Hoy todavía es difícil contestar la pregunta ¿dónde está el programa? pero todos en el equipo sabemos donde debe estar y cada día que pasa nos acercamos más a la meta. En algunos meses espero poder responder: “En el centro, el programa está en el centro”.

Foto: Unsplash

Published
Categorized as Default

Silicon Graphics made awesome computers

A long time ago, I used to work as a video editor and motion graphics artist. In those days, Silicon Graphics made the most incredible hardware which ran all the best and most advanced software for creating visual effects.

I never got to work with any of these machines but I always loved the way they looked. I wish someone brings back this design aesthetic. I’m kinda bored with how all computers look now days.

You have Apple’s, extreme simplicity on one side and crazy liquid cooled flashing LED clear case on the other. What Silicon Graphics did looked modern, fun and clean.

Published
Categorized as Default

User-defined type guards in TypeScript

Decided to write this blog post because I couldn’t find any documentation or online examples like the one I’m sharing here. Every example I’ve seen the user-defined type guard is used to replace the whole type definition and not specific properties.

Here’s the code without type guard.

interface Config {
  foo: string;
  bar: string;
}

class Thing {
  name: string;
  config?: Config;

  constructor(name: string, config?: Config) {
    this.name = name;
    this.config = config;
  }

  // 👀 returning boolean, will change on next example
  hasConfig(): boolean {
    if (this.config) {
      return true;
    }
    return false;
  }
}

let firstThing = new Thing("Apple", { foo:"one", bar: "two" });

if (firstThing.hasConfig()) {
  // #1 - Here firstThing.config is `Config | undefined`
  console.log(firstThing.config.foo);
}

if (firstThing.config) {
  // #2 - Here firstThing.config is `Config`
  console.log(firstThing.config.foo);
}

On #1 firstThing.config. is Config | undefined even though we know it can’t be undefined because our hasConfig method made sure the value is truthy.

On #2 firstThing.config is Config as expected.

So in order to help TypeScript detect the correct type we can use a type predicate as the return type.

interface Config {
  foo: string;
  bar: string;
}

class Thing {
  name: string;
  config?: Config;

  constructor(name: string, config?: Config) {
    this.name = name;
    this.config = config;
  }

  // 👀 notice the return type
  hasConfig(): this is { config: Config } {
    if (this.config) {
      return true;
    }
    return false;
  }
}

let firstThing = new Thing("Apple", { foo:"one", bar: "two" });

if (firstThing.hasConfig()) {
  // Now firstThing.config is `Config`
  console.log(firstThing.config.foo);
}

Now firstThing.config is Config as expected. Have in mind that even though the type guard says this is { config: Config }, TypeScript will merge this type with the original type definition of Thing.

Thanks to the nice folks at the TypeScript Discord who helped me solve this issue. Hope this helps more people.

Published
Categorized as Default

The Scanimate

Source: YouTube

Amo este tipo de equipo análogo para video.

Aprendí editar video con equipos de esta generación y la verdad es que los extraño mucho. Recuerdo claramente el sonido de las VTR haciendo “rewind” y “pre-roll” para comenzar a grabar donde habías puesto el mark in. Recuerdo los switchers y controles de efectos.

Fue una época muy divertida y de aprender mucho.

Published
Categorized as Default