Giovanni Collazohttps://gcollazo.comRSS Feed for Giovanni CollazoFri, 09 Jan 2026 20:33:32 +0000gcollazo.comPython N00bhttps://gcollazo.com/python-n00b<p>
Hace mas de dos años he estado interesado en aprender
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Python_%28programming_language%29"
>Python</a
>. Este lenguaje se caracteriza por ser fácil de escribir y sobre todo fácil
de leer. Tiene la capacidad de mezclar poder con <i>friendliness</i>.
</p>
<p></p>
<p>
Hace poco menos de un mes decidí que ya era tiempo de añadir esta herramienta
a mi arsenal así que me compré unos cuantos libros y desde entonces he estado
sumergido en el mundo de Python. He aprendido sobre
<a href="proxy.php?url=https://www.python.org/~guido/">Guido van Rossum</a>,
<a href="proxy.php?url=http://www.jython.org/">Jython</a>,
<a href="proxy.php?url=http://pyjs.org/">Pyjamas</a>,
<a href="proxy.php?url=http://ironpython.net/">IronPython</a>,
<a href="proxy.php?url=http://codespeak.net/pypy/dist/pypy/doc/">PyPy</a>,
<a href="proxy.php?url=https://code.google.com/p/unladen-swallow/">unladen-swallow</a>,
<a href="proxy.php?url=https://www.python.org/dev/peps/pep-3000/">Python 3000</a>,
<a href="proxy.php?url=http://www.crummy.com/software/BeautifulSoup/">Beautiful Soup</a>,
<a href="proxy.php?url=http://www.tornadoweb.org/">Tornado</a> y muchas otras personas,
tecnologías y herramientas que nunca hubiera conocido en otras circunstancias.
</p>
<p></p>
<p>
De esta experiencia aprendí mucho. Primero que saber programar no tiene nada
que ver con lenguajes de programación. Saber programar es una destreza
completamente separada del lenguaje que se conozca y que la noción de que se
es mejor programador con un lenguaje que con otro es una falacia. La realidad
es que se es mejor programador con mas práctica y el ejercicio de aprender un
nuevo lenguaje es un gran ejercicio. Como resultado cuando se aprende un nuevo
lenguaje se es mejor programador.
</p>
<p>
En mi caso personal Python me enseño a tratar de ser mas económico en la
cantidad de código que uso, a ser mas conciso. Ahora cuando escribo JavaScript
o PHP en el día a día uso las cosas nuevas que he aprendido al estar expuesto
a Python.
</p>
<p>
Finalmente quiero decir que Python me parece una herramienta súper poderosa.
Es un lenguaje muy utilizado y con una comunidad vibrante de desarrollares
produciendo todo tipo de packages. En las pocas semanas que llevo aprendiendo
y practicando me he dado cuenta que hay un package para casi cada cosa que se
me ocurre. Además he descubierto un proyecto que se llama
<a href="proxy.php?url=http://pyobjc.sourceforge.net/">PyObjC</a> que es un puente entre
Python y <a href="proxy.php?url=https://en.wikipedia.org/wiki/Objective-C">Objective-C</a> /
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Cocoa_%28API%29">Cocoa</a>, lo que
permite escribir aplicaciones nativas para Mac OS X usando solo Python. Un
ejemplo de una aplicación escrita de esta manera es
<a href="proxy.php?url=http://www.checkoutapp.com">Checkout</a>, un sistema de POS. Pienso
que mi primer proyecto será una aplicación para mac escrita en Python.
</p>
<p>
Mas adelante hablaré un poco sobre los libros que compré cuales son buenos y
cuales malos.
</p>
<p>
Quiero saber tu experiencia aprendiendo nuevos lenguajes de programación, si
sabes Python y que te gusta o no de este lenguaje. Deja un comentario abajo.
</p>[email protected] (gcollazo)Tue, 15 Dec 2009 00:00:00 -0000https://gcollazo.com/python-n00bmongoDB.apphttps://gcollazo.com/mongodb-app<p>
<img src="proxy.php?url=/public/media/mongodbapp.png" />
</p>
<p>
A few days ago I released my first ever “real” swift app. The app is called
MongoDB.app and it’s just a thin wrapper around the official MongoDB binaries
that you can download from their site. The app will add a status menu icon
which will allow you to start and stop a local MongoDB server visually. All
data saved by the server will be stored to <em>~/Documents/MongoData</em>.
</p>
<p><!--more--></p>
<p>
The idea was to create the simplest possible experience for MongoDB beginners.
My inspiration was the great
<a href="proxy.php?url=http://postgresapp.com/">Postgres.app</a> which does an amazing job
at running PostgreSQL for your mac development environment.
</p>
<p>
Currently the app is using the latest stable release of mongo which at this
moment is <a href="proxy.php?url=https://www.mongodb.org/downloads">v2.6.7</a>. I plan to
keep the app up to date so expect frequent updates which for now I will post
on the <a href="proxy.php?url=https://github.com/gcollazo/mongodbapp/">GitHub repo</a>
<del datetime="2016-01-10T23:03:35+00:00"
>until I figure out how to do automated updates using the
<a href="proxy.php?url=http://sparkle-project.org/">Sparkle framework</a>. I might use
some help doing this
</del>
. The app will check for updates at startup and allow one-click download and
update using Sparkle framework.
</p>
<p>
Inside the actual application package you will be able to find all the
standard binaries that ship with mongodb including:
</p>
<ul>
<li>bsondump</li>
<li>mongo</li>
<li>mongod</li>
<li>mongodump</li>
<li>mongoexport</li>
<li>mongofiles</li>
<li>mongoimport</li>
<li>mongooplog</li>
<li>mongoperf</li>
<li>mongorestore</li>
<li>mongos</li>
<li>mongosniff</li>
<li>mongostat</li>
<li>mongotop</li>
</ul>
<p>
If you want to have access to all of these from your terminal you should add
the application’s vendor folder to your path by adding the following to
~/<em>.bash_profile</em>.
</p>
<pre><code class="bash"># Add MongoDB.app to path
PATH="/Applications/MongoDB.app/Contents/Resources/Vendor/mongodb:$PATH"
</code></pre>
<p>
<strong
><a href="proxy.php?url=https://github.com/gcollazo/mongodbapp/releases"
>Download</a
></strong
><br />
<img
decoding="async"
src="proxy.php?url=https://img.shields.io/github/release/gcollazo/mongodbapp.svg"
/>
</p>
<p>
To get the latest release go to the
<a href="proxy.php?url=https://github.com/gcollazo/mongodbapp/releases"
>repo’s releases page</a
>. Please let me know if you find this useful.
</p>[email protected] (gcollazo)Fri, 16 Jan 2015 00:00:00 -0000https://gcollazo.com/mongodb-app¿Quieres aprender a programar? Empieza aquíhttps://gcollazo.com/quieres-aprender-a-programar-empieza-aqui<p>
<span
>Con cierta frecuencia amigos, familiares y personas en Twitter o Facebook
me preguntan sobre cómo aprender a programar. Algunos me dicen “quiero
aprender hacer apps” refiriéndose a las aplicaciones en teléfonos
móviles y otros me dicen que quieren hacer un website para su
negocio.</span
>
</p>
<p>
Mi respuesta casi automática es la que creo que muchos otros
programadores tienen, enviar unos cuantos enlaces a uno de los muchos
<a href="proxy.php?url=https://www.freecodecamp.org/">sites de cursos online</a> o
<a href="proxy.php?url=https://eloquentjavascript.net/">algún libro</a>. Lo que nunca he
hecho es sentarme y pensar cuidadosamente como fue que yo aprendí y tratar de
extraer lecciones de mi experiencia.
</p>
<p>
Esta es la intención de este post. Compartir las lecciones de mi
experiencia en los últimos 15 años tratando de aprender a programar. Este es
mi mejor intento de producir una contestación correcta para la pregunta: ¿Cómo
aprendo a programar?
</p>
<p>
<span
><b> 1. Necesitas un primer proyecto<br /></b></span
>Decide que quieres construir antes de empezar a aprender. Mi recomendación es
que trates de clonar un app o website que conoces bien y usas todo el tiempo.
Una mala idea es querer hacer un video juego 3D, Microsoft Excel o Facebook.
Escoge algo simple, no te compliques la vida.
</p>
<p>
<span
>Aprender a programar es suficientemente complicado como para tratar de ser
ambiciosa / ambicioso en este punto. Si no se te ocurre nada trata con una
calculadora de propinas o haciendo un website o portafolio online.</span
>
</p>
<p>
<span
>Yo aprendí mucho tratando de clonar
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Minesweeper_(video_game)"
>Minesweeper</a
>, el juego que venía con Windows 95 o 98, no recuerdo bien.</span
>
Años después hice un website que usaba una base de datos para generar páginas
dinámicamente. Esta experiencia me ayudó a aprender a usar bases de datos en
el contexto de aplicaciones web.
</p>
<p>
<span
><b> 2. Aprende lo menos posible<br /></b></span
>Enfócate en aprender sólo lo necesario para ejecutar tu primer proyecto. Hay
una cantidad prácticamente infinita de información sobre programación. Si
tratas de entender todo antes de ejecutar algo concreto, nunca terminarás.
Este fue mi problema principal tratando de aprender y la razón por la cual me
tomó años en vez de meses. La estrategia es aprender de programación general
mientras te enfocas en los problemas específicos que plantea cada proyecto que
haces. Esta es la única estrategia efectiva que conozco.
</p>
<p>
<span
><b> 3. Define una fecha de entrega<br /></b></span
>Necesitas tener una fecha concreta para terminar tu primer proyecto y tienes
que esforzarte para lograr cumplir con esa fecha. Piensa que es un trabajo y
que te están pagando para hacerlo. Si puedes dejar de tratar y aprender cuando
se ponga difícil, nunca vas a terminar. Esto también me pasó muchas veces y
como lo resolví fue prometiendo a un amigo que le mostraría la aplicación
terminada en 3 semanas. Un poco de presión social ayuda.
</p>
<p>
<span><b> 4. Busca </b></span
><b>ayuda<br /></b>Identifica personas que te puedan ayudar. Todos los
programadores que conozco les encanta ayudar a personas que están empezando.
Me atrevo a decir que es casi parte de la cultura. No tengas miedo, haz
preguntas. Si no conoces a nadie puedes contactar a
<a href="proxy.php?url=https://twitter.com/jpadilla_">@jpadilla_</a>,
<a href="proxy.php?url=https://twitter.com/skfroi">@skfroi</a> o a mi
<a href="proxy.php?url=https://twitter.com/gcollazo">@gcollazo</a> /
<a href="proxy.php?url=mailto:[email protected]">[email protected]</a> y te
ayudaremos con gusto.
</p>
<p>
<span
><b> 5. No te rindas<br /></b></span
>Aprender a programar es increíblemente difícil, no importa lo que nadie diga.
La buena noticia es que ahora hay muchos recursos y comunidades diseñadas
especialmente para ayudarte. Cuando sientas que no quieres seguir porque es
muy difícil o estás cansada/o, toma un descanso y cuando tengas más energía
sigue tratando. Busca ayuda y no te rindas, es difícil pero posible.
</p>
<p>
<span
><b> 6. Proyecto #1: Done<br /></b></span
>Una vez completes tu primero proyecto todo se vuelve mucho más fácil. Cuando
terminé mi primero proyecto, me di cuenta que todas las aplicaciones son una
combinación diferente de los mismos principios que ya había aprendido. Para
hacer el segundo proyecto sólo tienes que aprender un poquito más y usar lo
que ya sabes.
</p>
<p>
<span
><b> 7. Nunca dejes de aprender<br /></b></span
>Ya hiciste uno o dos programas así que es hora de empezar de nuevo y repetir
el proceso para poder aprender un poco más. Es lo que todos hacemos. Si nunca
dejas de estar en este proceso serás una/uno de las/los mejores en esto.
</p>
<p>
<span
><b> ¿Qué tecnologías debo aprender?<br /></b></span
>Aunque para mi lo más importante de este post son las 7 lecciones que están
arriba no podía faltar una recomendación específica en cuanto a que
tecnologías aprender. La lista de abajo está en el orden que creo se deberían
estudiar pero esto puede variar para cada cual.
</p>
<ol>
<li>
<b></b><span><b>HTML</b> – Sirve para hacer páginas web estáticas.</span>
</li>
<li>
<b></b
><span
><b>CSS</b> – Sirve para hacer que tus páginas web se vean bien.</span
>
</li>
<li>
<b></b
><span
><b>JavaScript </b>para el browser – Sirve para añadir interactividad
a tus paginas web. Si dominas JS, podrás aprovecharte de todas las
capacidades que tienen los browsers, que son muchísimas.</span
>
</li>
<li>
<span
><b>Node.js </b>– es una plataforma y no un lenguaje. Node.js te permitirá
crear servers para generar páginas web dinámicamente. El lenguaje que se
utiliza para programar con Node.js es JavaScript. La ventaja es que en
este punto ya deberías saber suficiente JavaScript.</span
>
</li>
<li>
<b></b
><span
><b>PostgreSQL</b> – es un programa de base de datos. Le permitirá a tu
servidor guardar y buscar datos de forma fácil.</span
><b></b>
</li>
</ol>[email protected] (gcollazo)Sat, 21 Mar 2015 00:00:00 -0000https://gcollazo.com/quieres-aprender-a-programar-empieza-aquiThe Security Footgun in etcdhttps://gcollazo.com/the-security-footgun-in-etcd<p>
From an application security perspective databases are the most valuable parts
of our systems. They store the data that gives value to our apps and
companies. This data which has been entrusted to us by our users should be
kept safe and away of the hands of criminals.
</p>
<p>
Every developer I talk to is very aware of this. Their MySQL, PostgreSQL and
MongoDB databases are treated with caution and security is definitely a
thought. It doesn’t always works but at least everyone is aware and tries
their best.
</p>
<p>
But what happens with databases that don’t feel like “regular databases”? I’m
talking about Memcached, Redis and of course etcd. This kind of databases are
often used for a single use case and treated without much care.
</p>
<p>
The other day I was reading the CoreOS documentation and came across etcd. I
have read about etcd before but in the context of CoreOS is really
interesting. Etcd is an integral part of their clustering system. They use it
to do orchestration and a lot of other stuff.
</p>
<p>
Etcd is really cool, It uses the raft consensus algorithm to keep all the
nodes in sync, it has a very simple to use CLI tool and best of all it has an
HTTP API. I really like etcd and it sure feels like we are going to use it in
the near future for some of our orchestration needs.
</p>
<p>
Reading about etcd made me remember the news about
<a
href="proxy.php?url=https://thenextweb.com/insider/2017/01/08/mongodb-ransomware-exists-people-bad-security/"
>MongoDB security issues</a
>
that turned out to be people not enabling authentication which is not enabled
by default. It really beats me why this is the happy path but hey, I don’t
build databases I just use them.
</p>
<p>
Here’s an excerpt from the etcd’s documentation talking about their
authentication mechanism.
</p>
<blockquote>
<p>
“etcd before 2.1 was a completely open system; anyone with access to the API
could change keys. In order to preserve backward compatibility and
upgradability, this feature is off by default.”
<a href="proxy.php?url=https://coreos.com/etcd/docs/latest/v2/authentication.html"
>Read more</a
>
</p>
</blockquote>
<p>
Yes. The same thing, etcd has an authentication mechanism which is disabled by
default and it also has a very nice RESTful API as it’s main interface, what
could go wrong right. People are smart and they will keep their etcd services
from leaking to the open internet. Wrong!
</p>
<p>
I did a
<a href="proxy.php?url=https://www.shodan.io/search?query=etcd">simple search on shodan</a>
and came up with <strong>2,284 etcd servers on the open internet</strong>. So
I clicked a few and on the third try I saw what I was hoping not to see.
CREDENTIALS, a lot of CREDENTIALS. Credentials for things like cms_admin,
mysql_root, postgres, etc.
</p>
<p>
In order to try to get a sense of the issue I downloaded the full shodan
report and wrote a very simple script that basically called the etcd API and
requested all keys. That’s basically equivalent to doing a database dump but
over their very nice REST API.
</p>
<pre><code>GET http://<ip address>:2379/v2/keys/?recursive=true</code></pre>
<p>
This will return all the keys stored on the servers in JSON format. So my
script basically went down the list and created a file for each IP
(127-0-0-1.json) with the contents of etcd. I stopped the script at about 750
MB of data and 1,485 of the original IP list.
</p>
<p>
Then I performed a few basic searches to get a sense of what was there and
found a bunch of credentials. Passwords for databases of all kinds, AWS secret
keys, and API keys and secrets for a bunch of services. Also came across a few
certificates, you name it. There’s a lot of stuff there.
</p>
<figure>
<table>
<tbody>
<tr>
<td>password</td>
<td>8781</td>
</tr>
<tr>
<td>aws_secret_access_key</td>
<td>650</td>
</tr>
<tr>
<td>secret_key</td>
<td>23</td>
</tr>
<tr>
<td>private_key</td>
<td>8</td>
</tr>
</tbody>
</table>
</figure>
<p>
I did not test any of the credentials but if I had to guess I would guess that
at least a few of them should work and this is the scary part. Anyone with
just a few minutes to spare could end up with a list of hundreds of database
credentials which can be used to steal data, or perform a ransomware attacks.
</p>
<p>
In order to prevent this from happening just enable authentication and if is
not strictly required please get your etcd servers off the open internet. Use
a security group or set a firewall rule to avoid random people from reading
and writing to your etcd server.
</p>
<p>
Finally, I did not test this but since all of these etcd servers are
completely open I’m almost certain that an attacker could write to them using
the same API. An attacker might use it to change the data in etcd and mess
with configuration and even maybe authentication or it could be used to store
exfiltrated data from other attacks.
</p>
<p>
I really hope that the etcd team would reconsider their position and make a
breaking change soon to enable authentication by default. As we learned from
the MongoDB experience this is a huge footgun that can be easily removed an
otherwise awesome software.
</p>
<hr />
<p>
Update: Twitter user
<a href="proxy.php?url=https://twitter.com/BradErzwh">@BradErzwh</a> reminded me to mention
that etcd is “used as Kubernetes’ backing store for all cluster data”. In most
cases that data includes secrets encoded as base64 strings.
<a href="proxy.php?url=https://kubernetes.io/docs/concepts/overview/components/#etcd"
>More info</a
>
</p>[email protected] (gcollazo)Fri, 16 Mar 2018 00:00:00 -0000https://gcollazo.com/the-security-footgun-in-etcdSobre como ser mejor developerhttps://gcollazo.com/sobre-como-ser-mejor-developer<p>
En abril pasado mi amigo Froilan Irizarry publicó un
<a
href="proxy.php?url=https://medium.com/@skfroi/quieres-ser-considerado-un-desarrollador-senior-se-un-mentor-primero-b92d0e330a57"
>blog post</a
>
hablando de sobre como convertirse en un “senior developer” y hace unos días
en Facebook me encontré con
<a
href="proxy.php?url=https://www.facebook.com/groups/programadorespuertorico/permalink/915314515296277/"
>un post</a
>
preguntando “¿Cuál ha sido su estrategia para sobresalir en el campo laboral?”
</p>
<p>
Ambas publicaciones me hicieron pensar en cual es mi respuesta a esta pregunta
de sobre como mejorar y sobresalir en la industria de desarrollo de software.
</p>
<p>
A continuación los factores que considero más importantes para lograr ser un
mejor developer y con suerte sobresalir.
</p>
<p>
<strong>Enfocarse en el usuario y su experiencia<br /></strong>Todos mis
proyectos comienzan con dibujos detallados de lo que será la solución final y
de ahí trabajo para determinar cuales son los requisitos técnicos para lograr
la solución. El diseño define los requisitos técnicos. Al cliente no le
importa si usas C#, Haskell, PHP, VBScript o “whatever” para solucionar el
problema. Lo que importa es la solución y en eso hay que enfocarse.
</p>
<p>
<strong
>Los “soft skills” son tanto o más importantes que las capacidades
técnicas</strong
><br />Entender el problema que quiere resolver el cliente, poderlo explicar a
otros, poder plantear posibles soluciones, escuchar otros puntos de vista con
respeto y empatía, ponerse en los zapatos de otros y ser tolerante.
</p>
<p>
<strong>Saber de lo que estás hablando<br /></strong>Ningún argumento le gana
a una <strong>experiencia del mundo real</strong> en un escenario similar.
Hacer muchos proyecto es la mejor forma de tener éxito. Todos los proyectos
tendrán problemas y serán difíciles de completar pero si tienes muchos por lo
menos algunos serán buenos y poco a poco con el tiempo aprenderás a reconocer
y utilizar los patrones que te llevan a lograr proyectos exitosos.
</p>
<p>
<strong>Compartir lo que has aprendido</strong><br />Para mi la mejor forma de
aprender y aclarar ideas es tratando de compartirlas. Trato de compartir lo
que aprendo con todo el que le interese. Para esto escribo blog posts, hago
presentaciones en “meetups” y conferencias y recientemente rants en Twitter.
</p>
<p>
<strong>“Under promise and over deliver”</strong><br />Ser conservador en
cuanto a estimados de tiempo y “features”. Luego si el tiempo permite,
sorprender al cliente con una experiencia mejor a la esperada. Este funciona
todas las veces.
</p>[email protected] (gcollazo)Sat, 21 Mar 2015 00:00:00 -0000https://gcollazo.com/sobre-como-ser-mejor-developerUn bug en el app CESCO Digital expone la información de todos los conductores registrados en Puerto Ricohttps://gcollazo.com/bug-app-cesco-digital<h4>Antes de empezar</h4>
<p>
Reporté este
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Software_bug">bug</a> dos veces con la
intención de que se resolviera el problema y se protegiera la información de
los ciudadanos. La primera vez que reporté el problema recibí confirmación de
que sería atendido, pero ocho meses más tarde el problema no había sido
resuelto.
</p>
<p>
Recientemente, reporté el bug por segunda vez e hice énfasis en el riesgo
potencial de este tipo de vulnerabilidad y en mi intención de hacer público el
problema siguiendo el modelo de “<a
href="proxy.php?url=https://en.wikipedia.org/wiki/Responsible_disclosure"
>responsible disclosure</a
>”. Esta vez había gente diferente a cargo del otro lado y se atendió la
situación con prontitud hasta corregir el problema.
</p>
<p>
A cambio de este reporte no se pidió recompensa, ni contratos para asesoría,
ni contratos para programar, ni ninguna otra forma de compensación directa o
indirecta. Tengo trabajo y estoy bastante ocupado con eso pero siento que es
mi responsabilidad reportar este tipo de problema cuando lo encuentro para que
se pueda resolver y evitar situaciones que lamentar.
</p>
<p>
En el pasado he reportado problemas como este a compañías pequeñas y grandes
dentro y fuera de Puerto Rico. Esta es mi primera vez haciendo “responsible
disclosure” con una entidad del Gobierno de Puerto Rico, lo que me ha tenido
algo nervioso durante los últimos días.
</p>
<p>
Como todos sabemos, en nuestras islas cualquier cosa que hace o no hace el
gobierno es una controversia y estoy seguro que algunos me criticarán por mi
acercamiento y otros dirán lo bien que lo hice según les convenga para
adelantar su punto de vista.
</p>
<p>
Me siento satisfecho y convencido de que hice lo correcto y logré conseguir el
resultado deseado.
</p>
<span id="more-4060"></span>
<h4>El bug</h4>
<p>
Según <a href="proxy.php?url=https://en.wikipedia.org/wiki/OWASP">OWASP</a>, el nombre
técnico de este bug es un “<a
href="proxy.php?url=https://www.owasp.org/index.php/Top_10_2007-Insecure_Direct_Object_Reference"
>insecure direct object reference</a
>“. Este tipo de bug permite a un usuario autorizado, cambiar el valor de un
parámetro para tener acceso a un objeto para el cual no tiene autorización.
</p>
<p>Trataré de explicarlo con un ejemplo simple.</p>
<p>
Cuando usted usa Google Drive y está viendo un documento, el browser muestra
arriba la dirección web de ese documento. Para propósitos de esta explicación
digamos que la dirección es
<em>https://drive.google.com/documentos/101</em>. La última parte de esa
dirección (101) muestra el nombre o el valor que identifica a ese documento
dentro de Google Drive.
</p>
<p>
Si usted modifica la dirección y reemplaza el <strong>101</strong> por
<strong>102</strong> casi seguro verá un error en la pantalla. El error será
algo como “no se encontró el documento” o “no tiene permiso para ver este
documento”.
</p>
<p>
Lo que está pasando en este ejemplo es que Google Drive usa la última parte de
la dirección para identificar cada documento que almacena para sus usuarios.
El documento <strong>101</strong> te pertenece, así que lo puedes ver, pero el
documento <strong>102</strong> probablemente sea de otro usuario así que no
tienes permiso para verlo.
</p>
<p>
El
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Application_programming_interface"
>API</a
>
que usa el app de
<a href="proxy.php?url=https://itunes.apple.com/us/app/cesco-digital/id1389951990"
>CESCO Digital</a
>
para acceder la información de los ciudadanos tenía un bug que permitía ver la
información de cualquier ciudadano con solo cambiar la parte final de la
dirección, tal y como expliqué en el ejemplo anterior. Si su información
personal era visible en la dirección que termina en <strong>101</strong> y
usted cambia esa dirección para que termine en <strong>102</strong>, usted
vería la información de otro ciudadano.
</p>
<p>
Un atacante al darse cuenta de esta vulnerabilidad podría escribir un programa
que de forma automática modifique la dirección empezando por el número 1,
luego 2, 3, 4, 5 y así consecutivamente hasta llegar a ver y almacenar los
datos de todos los usuarios.
</p>
<p>
Este ataque se podría completar en horas pero si se tratara de atacante un
poco mas sofisticado, podría estirar el proceso a lo largo de multiples días o
incluso semanas para asegurarse de no levantar alarmas o sospechas de los
administradores del sistema.
</p>
<p>Entre lo datos de los ciudadanos que quedaron expuestos están:</p>
<ul>
<li>Nombre legal del ciudadano</li>
<li>Fecha de nacimiento</li>
<li>Dirección física</li>
<li>Últimos 4 dígitos del número de seguro social</li>
<li>Número de licencia de conducir</li>
<li>Fecha de expiración de licencia de conducir</li>
<li>
Autos registrados con marca, modelo, color, tablilla y
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Vehicle_identification_number"
>VIN</a
>
</li>
</ul>
<h4>Un poco más técnico</h4>
<p>
Para detectar el problema lo único que había que hacer era usar un
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Proxy_server">proxy</a> e inspeccionar
los
<a
href="proxy.php?url=https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message"
>requests</a
>
que hace la aplicación al API. En mi caso uso
<a href="proxy.php?url=https://itunes.apple.com/us/app/charles-proxy/id1134218562"
>Charles Proxy</a
>
en iOS y <a href="proxy.php?url=https://mitmproxy.org">mitmproxy</a> en macOS.
</p>
<figure>
<video
width="100%"
controls=""
poster="/public/media/[email protected]"
src="proxy.php?url=/public/media/Vulnerabilidad-en-app-CESCO-Digital-oHAULBHGjyg.mp4"
></video>
<figcaption>
En este vídeo se demuestra el bug antes de ser corregido
</figcaption>
</figure>
<p>
Lo primero que llama la atención es que desde el primer request al API se
puede ver un valor en el
<a href="proxy.php?url=https://en.wikipedia.org/wiki/List_of_HTTP_header_fields">header</a>
de “Authorization”. Esto es inesperado porque no he entrado ningún dato y el
app ya está haciendo llamadas autenticadas al API. Aunque es poco común, no es
necesariamente un problema.
</p>
<pre><code>GET /config HTTP/1.1
Host: cescodigital.dtop.gov.pr
Authorization: Basic dGVzdDoxMjM0NTppb3NDZXNjb0RpZ2l0YWw=
User-Agent: dtop_mobile/1 CFNetwork/976 Darwin/18.2.0
Connection: keep-alive
Accept: application/json
Accept-Language: en-us
Accept-Encoding: gzip, deflate, br</code></pre>
<p>
Al inspeccionar el valor que se está enviando me doy cuenta que esta usando
“<a href="proxy.php?url=https://en.wikipedia.org/wiki/Basic_access_authentication"
>basic access authentication</a
>” y se que en esos casos se envía el valor codificado en
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Base64">base 64</a>. Copié el valor y
lo descodifiqué para darme cuenta que definitivamente era un valor “<a
href="proxy.php?url=https://en.wikipedia.org/wiki/Hard_coding"
>hard coded</a
>” dentro del app. El valor descodificado era “test:12345:iosCescoDigital”.
</p>
<pre><code>$ echo "dGVzdDoxMjM0NTppb3NDZXNjb0RpZ2l0YWw="|base64 -D
test:12345:iosCescoDigital</code></pre>
<p>
Al darme cuenta de esto pensé que quizás una vez se completara el cuestionario
que hace el app para autorizarme se crearía algún tipo de
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Session_(computer_science)">sesión</a>
que me identificaría en los otros requests del app.
</p>
<p>
Entré mi número de licencia de conducir, últimos 4 dígitos del seguro social y
mi fecha de nacimiento. El API validó mis datos y respondió con alguna
información personal y un “uid” que me identifica dentro del sistema.
</p>
<pre><code>{
"uid": "1234567",
"name": "DEL PUEBLO, JUAN",
"entityType": "ssid"
}</code></pre>
<p>
Inmediatamente el app cambia de pantalla y muestra mis datos personales.
Cuando miro el request que hace el app puedo ver que la dirección que usa
contiene el mismo “uid” del request anterior y cuando me fijo en los headers
me doy cuenta que no hay ninguna información de sesión o algo parecido para
identificarme. Lo único que puedo ver es el mismo authorization header “hard
coded” del principio.
</p>
<pre><code>GET /entity/1234567 HTTP/1.1
Host: cescodigital.dtop.gov.pr
Authorization: Basic dGVzdDoxMjM0NTppb3NDZXNjb0RpZ2l0YWw=
User-Agent: dtop_mobile/1 CFNetwork/976 Darwin/18.2.0
Connection: keep-alive
Accept: application/json
Accept-Language: en-us
Accept-Encoding: gzip, deflate, br
{
"principal": {
"uid": "1234567",
"name": "DEL PUEBLO, JUAN",
"email": null,
"entityType": "ssid"
},
"license": {
"category": "3",
"expirationDate": 946699200000,
"dateOfBirth": 946699200000,
"address": {
"line1": "123 MAIN STREET",
"line2": "",
"city": "SAN JUAN",
"state": "PR",
"zipCode": "00901"
},
"unpaidFines": 0,
"alert": {
"type": "Warning",
"text": "RenewLicense"
},
"id": XXXXXX
},
"vehicles": [
{
"vinNumber": "XXXXXXXXXXXXXXXXX",
"registrationNumber": "XXXXXXXX",
"year": XXXX,
"make": "XXXXXX",
"model": "XXXXXX",
"color": "XXXXX",
"plate": "XXXXXX",
"tag": {
"number": "XXXXXXXX",
"expirationDate": XXXXXXXXXXX,
"id": XXXXXXXX
},
"unpaidFines": X,
"alert": null,
"canRenewTag": false,
"id": XXXXXXXX
}
]
}</code></pre>
<p>
En este punto ya estaba casi seguro del problema por lo que hice “log out” y
le dije a mi esposa que hiciera “login” para confirmar el problema. Siguió el
mismo proceso que yo y tan pronto entró pude ver que sus datos personales
venían de una dirección diferente pero estaba usando la misma información de
autenticación que yo. Esto confirmó el bug.
</p>
<h4>Primer intento</h4>
<p>
Tan pronto confirmé el bug traté de reportarlo y logré conseguir una
confirmación de que fue recibido y que se atendería a la mayor brevedad
posible. Dejé el tema ahí y lo di por resuelto. Me olvidé de este asunto.
</p>
<p>
Hace más o menos una semana tuve que entrar al app para buscar la licencia
nueva de mi auto. Me tocaba renovar el marbete. Cuando entro al app veo que
todo se ve igual y recordé el bug, así que me dio curiosidad mirar a ver si
resolvieron el problema que reporté.
</p>
<p>
Activé Charles Proxy para inspeccionar la comunicación del app con el API.
Para mi sorpresa habían pasado <strong>ocho meses</strong> y no se había
resuelto el problema. La información de todos los ciudadanos seguía expuesta y
disponible para el que la quisiera obtener.
</p>
<h4>Segundo intento</h4>
<p>
Esta vez decidí tomar un acercamiento un poco más fuerte y proveer los
incentivos necesarios para que las personas a cargo se vieran motivadas a
resolver el problema con la prisa que requería.
</p>
<p>
Cuando reporté el problema esta vez expliqué nuevamente los riesgos y mi
intención de hacer “responsible disclosure”. En otras palabras, que era
importante ponernos de acuerdo en un “timeline” en el que las personas a cargo
resolverían el problema y que en un tiempo específico yo publicaría este blog
post que estas leyendo, explicando el problema en detalle.
</p>
<p>
Esto significa que si no lograran resolver el problema en un tiempo
previamente acordado, la única opción que tendrían sería apagar el servicio
por completo para evitar que atacantes que lean sobre la vulnerabilidad la
exploten.
</p>
<p>
En ese momento no sabía cómo se tomaría la idea y si se volvería una situación
incómoda pero para mi sorpresa se me escuchó y se le prestó la atención que
pienso era necesaria. Durante el proceso hubo comunicación y cuando se me
pidió, saqué tiempo y compartí todo lo que sabía sobre el problema con los
involucrados del otro lado.
</p>
<p>
Para mantenerme honesto, tan pronto reporté el bug a las personas encargadas,
también alerté de forma confidencial a miembros de la prensa bajo la condición
de que no se publicara nada hasta por lo menos una semana después del reporte
original.
</p>
<p>
Una semana me pareció un tiempo justo y suficiente para hacer los ajustes
necesarios o apagar el servicio. Recordemos que el problema fue reportado ocho
meses antes.
</p>
<p>
Tres días más tarde un equipo de desarrolladores resolvió el problema y al día
siguiente y por petición de ese equipo, pude confirmar que el problema estaba
resuelto.
</p>
<h4>La solución</h4>
<p>
Los desarrolladores implementaron una solución bastante ingeniosa que permite
proteger los datos de los ciudadanos de este ataque sin la necesidad de
actualizar el app.
</p>
<p>
Ahora, luego del cambio, cuando un usuario completa el proceso de login, el
app recibe una respuesta idéntica a la anterior pero en el campo de “uid” el
valor es ahora un
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Cryptographic_hash_function">hash</a>
<a href="proxy.php?url=https://en.wikipedia.org/wiki/SHA-2">SHA256</a> en vez de un número.
</p>
<pre><code>{
"uid": "afa27b44d43b02a9fea41d13cedc2e4016cfc...",
"name": "Juan del Pueblo",
"entityType": "ssid"
}</code></pre>
<p>
En vez de tener direcciones como <em>/entity/1234567</em> ahora tienen
direcciones como esta <em>/entity/afa27b44d43b02a9fea41d13cedc2e4…</em>
</p>
<p>
No he visto el código pero de mi conversación con los developers y las pruebas
que pude hacer me atrevo a adivinar lo siguiente:
</p>
<ul>
<li>
El hash se calcula usando algún valor secreto, la fecha y/o data random.
</li>
<li>
El hash expira luego de un tiempo, forzando al usuario a entrar nuevamente
su información de login para continuar usando el app.
</li>
<li>
El tiempo de validez es aproximadamente 30 minutos así que no debe tener
impacto en la experiencia de los usuarios al usar el app.
</li>
<li>
Posiblemente están persistiendo el hash en la base de datos para poder
expirarlo.
</li>
<li>
Es posible que también estén persistiendo la relación entre el hash generado
y el ID interno del usuario.
</li>
</ul>
<p>
En resumen, es suficientemente difícil adivinar un hash que sea válido para
que valga la pena intentarlo.
</p>
<p>
Esta solución está cumpliendo el rol de una sesión que confirma que el usuario
completó el proceso de login (autenticación) y que tiene permiso para ver
ciertos datos (autorización) en todos los requests al API.
</p>
<h4>Timeline</h4>
<table>
<tbody>
<tr>
<td width="150">2018-06-26</td>
<td>Se reportó el problema por primera vez.<br /></td>
</tr>
<tr>
<td>2018-06-26</td>
<td>
Recibí confirmación de que el reporte fue recibido y que se
canalizaría.<br />
</td>
</tr>
<tr>
<td>2019-02-04</td>
<td>Se reportó el problema por segunda vez.<br /></td>
</tr>
<tr>
<td>2019-02-04</td>
<td>
Recibí confirmación de que el reporte fue recibido y que se trabajaría
de inmediato.<br />
</td>
</tr>
<tr>
<td>2019-02-04</td>
<td>
Se compartió el bug confidencialmente con miembros de la prensa.<br />
</td>
</tr>
<tr>
<td>2019-02-07</td>
<td>
Se le explicó el problema al equipo de desarrollo, confirmaron el bug y
dijeron que lo resolverían ese mismo día.<br />
</td>
</tr>
<tr>
<td>2019-02-08</td>
<td>Confirmé que se resolvió el problema.</td>
</tr>
<tr>
<td>2019-02-12</td>
<td>Se publicó este blog post.</td>
</tr>
</tbody>
</table>
<br />
<h4>Hace falta un proceso</h4>
<p>
Todo software tiene bugs. Esto siempre ha sido cierto y siempre será cierto.
Algunos de estos bugs tienen implicaciones de seguridad y deben ser atendidos
de una forma diferente. Nuestro gobierno es guardian de muchos datos
sensitivos y deberían prepararse mejor para atender situaciones en las que
estos datos estén en riesgo.
</p>
<p>
Hay muchas formas de hacer esto, pero hay que empezar por algún lado. Mi
recomendación es publicar un proceso para reportar bugs de seguridad en el que
los que reportan se sientan a salvo. Este documento debe contener nombres e
información de contacto de las personas que atenderán los casos que se
reporten. Además debe especificar términos de tiempo y los pasos que se
seguirán para manejar la situación.
</p>
<p>
Luego de tener esto, las personas encargadas deben respetar ese proceso y
colaborar con las personas que hagan los reportes. Tal y como pasó en mi
segundo intento.
</p>
<p>
Como punto de partida pueden copiar
<a href="proxy.php?url=https://www.gasolinamovil.com/security/"
>el proceso que usamos en Gasolina Móvil</a
>
que aunque no es perfecto, funciona cuando el volumen de reportes es bajo. Si
lo usan recuerden cambiar los nombres e información de contacto. 😉
</p>
<p>
Una vez tengan algo simple funcionando, se podría pensar en implementar un
programa de “<a href="proxy.php?url=https://en.wikipedia.org/wiki/Bug_bounty_program"
>bug bounties</a
>” aunque no se pague dinero por los reportes. La recompensa podría ser algo
tan simple como tener una página web que contenga una lista de las personas
que más bugs han reportado. Como los “leaderboards” que tienen algunos juegos
de video.
</p>
<p>
Estoy seguro que hacer algo en esta dirección motivaría a personas con la
capacidad de buscar, documentar y reportar responsablemente este tipo de
problemas, logrando mejorar la seguridad y funcionamiento de los sistemas del
gobierno.
</p>[email protected] (gcollazo)Tue, 12 Feb 2019 00:00:00 -0000https://gcollazo.com/bug-app-cesco-digitalEspañol de Puerto Ricohttps://gcollazo.com/espanol-de-puerto-rico<p>
Hey, si tu o alguien que conoces maneja un website escrito en Español y
producido en Puerto Rico, estaría super cool que lo marcan con el idioma y
región correcta.
</p>
<pre><code><html lang="es-PR"></code></pre>
<p>
Esto ayudaría mucho a
<a
rel="noreferrer noopener"
href="proxy.php?url=https://t.co/wY5WxvdIIf?amp=1"
target="_blank"
>http://tesoro.pr</a
>
en varios inventos futuros.
</p>
<p>
Por mi parte todavía estoy pensando cómo manejar el tema en este site. Lo
mismo escribo en Español que en Inglés.
</p>
<p>Links con más información técnica:</p>
<p>
<a
href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang"
>https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang</a
>
</p>
<p>
<a
href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html#Example"
>https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html#Example</a
>
</p>
<p>
<a href="proxy.php?url=https://www.ietf.org/rfc/bcp/bcp47.txt"
>https://www.ietf.org/rfc/bcp/bcp47.txt</a
>
</p>[email protected] (gcollazo)Wed, 02 Dec 2020 00:00:00 -0000https://gcollazo.com/espanol-de-puerto-ricoUser-defined type guards in TypeScripthttps://gcollazo.com/user-defined-type-guards-in-typescript<p>
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
<a
href="proxy.php?url=https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates"
>user-defined type guard</a
>
is used to replace the whole type definition and not specific properties.
</p>
<p>Here’s the code without type guard.</p>
<pre><code>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);
}</code></pre>
<p>
On #1 <code>firstThing.config</code>. is <code>Config | undefined</code> even
though we know it can’t be <code>undefined</code> because our
<code>hasConfig</code> method made sure the value is truthy.
</p>
<p>On #2 <code>firstThing.config</code> is <code>Config</code> as expected.</p>
<p>
So in order to help TypeScript detect the correct type we can use a
<em>type predicate</em> as the return type.
</p>
<pre><code>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);
}</code></pre>
<p>
Now <code>firstThing.config</code> is <code>Config</code> as expected. Have in
mind that even though the type guard says
<code>this is { config: Config }</code>, TypeScript will merge this type with
the original type definition of <code>Thing</code>.
</p>
<p>
Thanks to the nice folks at the TypeScript Discord who helped me solve this
issue. Hope this helps more people.
</p>[email protected] (gcollazo)Tue, 09 Mar 2021 00:00:00 -0000https://gcollazo.com/user-defined-type-guards-in-typescript¿Dónde está el programa?https://gcollazo.com/donde-esta-el-programa<p>
¿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.
</p>
<p>
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.
</p>
<p>
El único objetivo era que funcionara y que convenciera a clientes del
potencial del producto. Misión cumplida.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
DDD, resultó ser una especie de puerta a un mundo de patrones de diseño y
arquitecturas que ponen en el centro el “domain”.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
Esta experiencia me ha dado muchas herramientas nuevas y ha revivido mi
entusiasmo por la disciplina de crear software.
</p>
<p>
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”.
</p>[email protected] (gcollazo)Mon, 15 Mar 2021 00:00:00 -0000https://gcollazo.com/donde-esta-el-programaBolsas llenas de funcioneshttps://gcollazo.com/bolsas-llenas-de-funciones<p>
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
<strong>no </strong>es <em>FP vs OOP</em> y no tengo en interés en ese tipo de
discusión.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
En este momento no se conocían <em>codebases</em> grades y
<em>open source</em> 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 <em>to-do list</em> o el casi cómico
<em>counter</em> 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.
</p>
<p>
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.
</p>
<p>
Organizar todo como <em>pure functions</em>, 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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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 <em>utils</em> 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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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 <em>una montaña de bolsas llenas de funciones</em>.
</p>
<p>
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 <em>utils</em> 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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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 <em>punto medio</em> 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.
</p>
<p>
En un post futuro hablaré mas en detalle sobre algunas de estas técnicas e
ideas de arquitectura.
</p>[email protected] (gcollazo)Sun, 11 Apr 2021 00:00:00 -0000https://gcollazo.com/bolsas-llenas-de-funcionesDebe ser imposible representar estados ilegaleshttps://gcollazo.com/debe-ser-imposible-representar-estados-ilegales<p>
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.
</p>
<p>
En este post espero empezar a introducir algunas ideas y técnicas concretas
que he usado para comenzar a resolver mis problemas.
</p>
<h2>Estados ilegales</h2>
<p>
Cuando digo <em>“que sea imposible representar estados ilegales”</em>, 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.
</p>
<p>
Eso significa que si nuestro programa es <em>un carrito de compras</em> 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.
</p>
<p>
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 <em>endpoint</em> que recibe el <em>request</em> 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.
</p>
<p>
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.
</p>
<h2>Value Objects</h2>
<p>
No voy a ponerme a definir términos así que puedes mirar
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Value_object">aquí</a> y
<a href="proxy.php?url=https://www.martinfowler.com/bliki/ValueObject.html">aquí</a> para
buenas definiciones. De lo que si voy a hablar es sobre como uso VOs dentro de
mis programas para aplicar reglas de negocio.
</p>
<p>
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
(<code>Amount</code>).
</p>
<p>
Si tenemos un <em>endpoint</em> en un web app que recibe una cantidad de
dinero probablemente tendremos un <em>payload</em> JSON como este.
</p>
<pre><code>{
"amount": 100
}</code></pre>
<p>
Este objeto muestra el <code>amount</code> con un valor de
<code>100</code> 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.
</p>
<pre><code>{
"amount": 100,
"currency": "USD"
}</code></pre>
<p>
Ok, ahora si tenemos todo lo que necesitamos. Lo próximo que hacemos es
validar el <em>input</em> en nuestra aplicación así que podemos escribir el
siguiente código.
</p>
<pre><code>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);
}
});</code></pre>
<pre><code>async function controller(amount, currency) {
// do stuff with amount and currency
}</code></pre>
<p>
Obviamente esto se puede hacer de forma mucho mas declarativa usando algo como
<a href="proxy.php?url=https://json-schema.org">JSON Schema</a> o cualquier otra librería de
validación de objetos.
</p>
<p>
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 <em>input</em> en esta funcion y alguien pasa valores
sin verificar? Esto puede producir <em>bugs</em> e incluso problemas de
seguridad. Este problema se puede resolver con VOs.
</p>
<p>
De este punto en adelante voy a usar
<a href="proxy.php?url=https://www.typescriptlang.org">TypeScript</a> para los ejemplos. El
sistema de <em>types</em> de TS nos va a ayudar a garantizar el contenido de
nuestro VOs. Primero vamos a definir este VO Amount.
</p>
<pre><code>class Amount {
readonly value: number;
readonly currency: string;
constructor(value: number, currency: string) {
this.value = value;
this.currency = currency;
}
}</code></pre>
<p>
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.
</p>
<p>
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.
</p>
<p>
A mi me gusta poner estas reglas en el <code>constructor</code> y si alguna de
estas reglas no se cumple, lo que hago es <code>throw</code> 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.
</p>
<pre><code>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;
}
}</code></pre>
<p>
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 <code>throw</code> de una excepción si algo está
mal, no es posible crear una instancia en un estado ilegal.
</p>
<p>
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.
</p>
<p>
Ahora donde quiera que necesitemos este valor en vez de pasar un
<code>number</code> y un <code>string</code> podemos pasar un
<code>Amount</code> y estar confiados.
</p>
<p>
Se que esto puede parece increíblemente obvio para algunos, especialmente los
que están acostumbrados a trabajar en lenguajes <em>strongly typed</em> pero
les puedo asegurar que para los que llevamos toda la vida en lenguajes
<em>dynamically typed</em> esto es una revelación.
</p>
<p>
Que más podemos hacer con un VO. Pues habrás notado declare los fields de
<code>Amount</code> como <code>readonly</code>. Una de las caracteristicas más
importantes de un VO es que sea <em>inmutable</em> y que una vez creado, no se
pueda cambiar. Esto nos da muchas garantías sobre el estado interno de nuestro
objeto.
</p>
<p>
Una acción que seguro vamos a necesitar hacer con nuestro
<code>Amount</code> es sumarle otro <code>Amount</code> 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 <code>Amount</code> que queremos sumar y crea un
VO nuevo que contiene el resultado.
</p>
<pre><code>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);
}</code></pre>
<p>
Nuevamente aquí podemos ver que estamos validando que ambos VOs son del mismo
<code>currency</code> antes de sumarlos. Una vez sabemos que son del mismo
<code>currency</code> creamos una nueva instancia de <code>Amount</code> con
el resultado de la suma.
</p>
<p>
Otra cosa importante es poder comparar dos VOs por su valor así que vamos a
escribir un método para esto.
</p>
<pre><code>equals(amount: Amount): boolean {
return amount.value === this.value && amount.currency === this.currency;
}</code></pre>
<p>
Fácil. Ahora podemos comparar dos VOs y saber si contienen el mismo valor. Es
importante notar que estamos comparando tanto el <code>value</code> como el
<code>currency</code> porque sabemos bien que un Euro y un Dólar no son lo
mismo.
</p>
<h2>VOs en mi web app</h2>
<p>
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.
</p>
<pre><code>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);
}
});</code></pre>
<pre><code>async function controller(amount: Amount) {
// do stuff with Amount
}</code></pre>
<p>
Ahí está. Ahora <code>Amount</code> 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 <code>Amount</code>, puede trabajar con el valor de
forma segura y sin preocupación.
</p>
<p>
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
<code>Amount</code> tiene todo el código necesario para crear una instancia
válida y las acciones (método) que podemos tomar con ese objeto.
</p>
<h2>Result</h2>
<p>
Como notaste, cada vez que queremos crear un VO, necesitamos hacer
<code>new</code> y pasar los argumentos que hagan falta para crear la
instancia. Como nuestro <code>constructor</code> puede
<code>throw</code> siempre tenemos que asegurarnos de hacer
<code>try/catch</code> para manejar cualquier error al momento de la
instanciación.
</p>
<p>
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.
</p>
<p>
Todos mis VOs tienen un método estático llamado <code>create</code>. Este
método me permite instanciar VOs sin temor a una excepción pero en vez de
devolver una instancia de <code>Amount</code>, devulve una instancia de un
objeto llamado <code>Result</code> que sirve para envolver el
<code>Amount</code> que se creo o un <code>Error</code> si hubo problemas al
crear el VO.
</p>
<p>
Esta idea de usar un objecto para envolver el resultado es algo muy común en
otros lengajes como
<a href="proxy.php?url=https://doc.rust-lang.org/std/result/enum.Result.html">Rust</a> y nos
permite hacer cosas interesantes. Los detalles sobre <code>Result</code> los
voy a dejar para un post futuro donde también hablare sobre como uso
<code>Maybe</code> en mis programas.
</p>
<h2>Resumen</h2>
<ul>
<li>
Los VOs nos ayudan a implementar reglas de negocio para un valor dentro de
nuestro programa.
</li>
<li>Los VOs deben ser inmutables.</li>
<li>Debe ser imposible instanciar un VO en un estado ilegal.</li>
<li>
Una vez tenemos una instancia de VO podemos estar seguros que contiene un
estado válido dentro de nuestro programa.
</li>
</ul>[email protected] (gcollazo)Sun, 16 May 2021 00:00:00 -0000https://gcollazo.com/debe-ser-imposible-representar-estados-ilegalesResult y Maybehttps://gcollazo.com/result-y-maybe<p>
En el post anterior mencioné brevemente que mis
<a
href="proxy.php?url=https://gcollazo.com/debe-ser-imposible-representar-estados-ilegales/"
>value objects</a
>
implementan un método estático <code>create</code> que utilizo para crear
instancias sin temor a excepciones. Este método devuelve un objeto llamado
<code>Result</code> que sirve para envolver el resultado de llamar al método
<code>create</code>.
</p>
<p>
En este post vamos a ver cómo funciona <code>Result</code> y al final vamos a
hablar un poco de <code>Maybe</code> que es muy similar.
</p>
<h2>Create</h2>
<p>Vamos directo al código, así implemento <code>create</code> en mis VOs.</p>
<pre><code>// amount.ts
static create(value: number, currency: string): Result<Amount, Error> {
try {
return ok(new Amount(value, currency));
} catch(error) {
return err(error);
}
}</code></pre>
<p>Esté método estático se puede llamar escribiendo:</p>
<pre><code>Amount.create(value, currency)</code></pre>
<p>
La única diferencia es que en vez de devolver una instancia de
<code>Amount</code> como haría:
</p>
<pre><code>new Amount(value, currency)</code></pre>
<p>
Este método devuelve un <code>Result</code> que podría ser un
<code>Ok</code> o un <code>Err</code>.
</p>
<p>
Veamos el contenido de <code>create</code> en detalle. En el
<code>try</code> intento crear una instancia de <code>Amount</code>. Si no hay
problema utilizo la función <code>ok</code> para crear una instancia de
<code>Ok</code> que envuelva el <code>Amount</code> que acabo de crear.
</p>
<p>
En el <code>catch</code> atrapo el error si es que el
<code>constructor</code> de <code>Amount</code> lanza una excepción. Sabemos
que las excepciones que lanza el <code>constructor</code> son de tipo
<code>Error</code>. Si este es el caso uso la función <code>err</code> para
crear una instancia de <code>Err</code> que envuelva nuestro error.
</p>
<p>
Tanto <code>Ok</code> como <code>Err</code> son <code>Result</code>. Por lo
que el tipo que devuelve este método estático siempre es <code>Result</code>.
</p>
<p>
Ahora vamos a mirar <code>Result</code> en detalle para entender mejor. No te
preocupes si en este punto no entiendes todo, he omitido algunos detalles que
iré revelando según vamos avanzando.
</p>
<h2>Result</h2>
<p>
Un <code>Result</code> es un objeto que puede contener solo uno de dos tipos.
<code>Ok</code> o <code>Err</code>. El primero lo usamos para envolver
resultados positivos o sin problemas y el segundo lo usamos para envolver
errores.
</p>
<p>
La idea aquí es que en vez de lanzar una excepción cuando instanciamos un VO,
devolvemos un valor que podemos inspeccionar e incluso pasar a otras partes
del código sin preocupación de excepciones.
</p>
<p>Vamos a ver como se implementa <code>Result</code> en TypeScript.</p>
<pre><code>// result.ts
export type Result<T, E> = Ok<T, E> | Err<T, E>;
export class Ok<T, E> {
constructor(readonly value: T) {
this.value = value;
}
isOk(): this is Ok<T, E> {
return true;
}
isErr(): this is Err<T, E> {
return !this.isOk();
}
unwrap(): T {
return this.value;
}
}
export class Err<T, E> {
constructor(readonly error: E) {
this.error = error;
}
isOk(): this is Ok<T, E> {
return false;
}
isErr(): this is Err<T, E> {
return !this.isOk();
}
unwrap(): T {
throw new Error("Called `unwrap` on an Err");
}
}
// Utility functions
export function ok<T, E>(value: T): Ok<T, E> {
return new Ok(value);
}
export function err<T, E>(err: E): Err<T, E> {
return new Err(err);
}</code></pre>
<p>
Vamos a romper este archivo en pedazos para entender todo lo que está pasando.
Empecemos con <code>Ok</code>.
</p>
<pre><code>class Ok<T, E> {
constructor(readonly value: T) {
this.value = value;
}
isOk(): this is Ok<T, E> {
return true;
}
isErr(): this is Err<T, E> {
return !this.isOk();
}
unwrap(): T {
return this.value;
}
}</code></pre>
<p>
Como ves el <code>constructor</code> recibe un valor y lo guarda en el campo
<code>value</code> de la clase. Además tiene algunos métodos simples como
<code>isOk</code> que siempre responde <code>true</code> porque todas las
intancias de la clase <code>Ok</code> están ok. También tiene lo opuesto
<code>isErr</code> que obviamente hace lo contrario que <code>isOk</code>.
Estos métodos los usamos para verificar que contiene un <code>Result</code>.
</p>
<p>
Finalmente tenemos el método <code>unwrap</code> que se encarga de sacar el
valor de dentro de nuestro <code>Ok</code>. Este método lo usamos para sacar
el valor que nos interesa de dentro de un <code>Result</code> que sabemos está
<code>Ok</code>.
</p>
<p>Ahora veamos la clase <code>Err</code>.</p>
<pre><code>class Err<T, E> {
constructor(readonly error: E) {
this.error = error;
}
isOk(): this is Ok<T, E> {
return false;
}
isErr(): this is Err<T, E> {
return !this.isOk();
}
unwrap(): T {
throw new Error("Called `unwrap` on an Err");
}
}</code></pre>
<p>
<code>Err</code> es muy similar a <code>Ok</code>. En este caso el
<code>constructor</code> recibe un error como único valor. Tenemos los mismos
métodos que en <code>Ok</code> porque queremos que estas dos clases tengan la
misma interface.
</p>
<p>
Algo interesante es que el método <code>unwrap</code> de <code>Err</code> hace
<code>throw</code> de una excepción si se llama. Esto es porque cuando tenemos
un error no tenemos un valor que podamos sacar así que no hay otra cosa que
podamos hacer.
</p>
<p>
Para evitar caer en esta situación siempre tenemos que usar los métodos
<code>isOk</code> o <code>isErr</code> para verificar que no vamos a tener
problema cuándo llamemos <code>unwrap</code>.
</p>
<h3>Generics</h3>
<p>
Lamentablemente este objeto <code>Result</code> tienen que hacer uso de un
<em>feature</em> avanzado de TS llamado
<a href="proxy.php?url=https://www.typescriptlang.org/docs/handbook/2/generics.html"
>generics</a
>
pero te puedo jurar que vale la pena entender que está pasando.
</p>
<p>
Tanto <code>Ok</code> como <code>Err</code> tienen unos
<code><T, E></code> al lado del nombre de la clase. Esto lo que hace es
que le dice a TS que cuando instanciemos estas clases queremos poder decidir
que <code>type</code> van a tener los valores que vamos a envolver más
adelante. En resumen es una forma de reutilizar estas clases sin tener que
saber los <em>types</em> de antemano.
</p>
<p>Aquí un ejemplo.</p>
<pre><code>new Ok<number, Error>(1);</code></pre>
<p>
Acabo de crear una instacia de <code>Ok</code> que puede aceptar un
<code>number</code> o un <code>Error</code> aunque la clase <code>Ok</code> no
usa el tipo <code>Error</code>. Esto va hacer sentido más tarde.
</p>
<p>Otro ejemplo.</p>
<pre><code>new Err<number, Error>(new Error('Something wrong'));</code></pre>
<p>
Aquí cree un <code>Err</code> y nuevamente pasé <code>number</code> y
<code>Error</code> como <em>type variables</em> a la clase para que TS sepa
los types que puede aceptar.
</p>
<h3>Result type alias</h3>
<p>
Si te fijas el objeto <code>Result</code> no es una clase, en realidad es un
<em>type alias</em>. Aquí está la definición.
</p>
<pre><code>type Result<T, E> = Ok<T, E> | Err<T, E>;</code></pre>
<p>
Voy a tratar de traducir esto. <code>Result</code> de <code>T</code> y
<code>E</code> es igual a <code>Ok</code> de <code>T</code> y <code>E</code> o
<code>Err</code> de <code>T</code> y <code>E</code>. Por esto es que ambos
<code>Ok</code> y <code>Err</code> aceptan dos <em>type variables</em> aunque
cada uno usa solo uno. <code>Result</code> puede ser cualquiera de estos dos
tipos. O sea, un <code>Ok</code> que envuelve un valor de tipo
<code>T</code> o un <code>Err</code> que envuelve un error de tipo
<code>E</code>.
</p>
<h3>Utility functions</h3>
<p>
Las últimas piezas de este rompecabezas son las funciones <code>ok</code> y
<code>err</code> que están al final del archivo. Nota que son en minúscula a
diferencia de las clases <code>Ok</code> y <code>Err</code> que usan pascal
case.
</p>
<p>
Estas funciones son con lo que interactuamos la mayoría de las veces. En el
ejemplo del método <code>create</code> al principio de este post se puede ver
como las usamos.
</p>
<h2>Usando Result</h2>
<p>Regresemos al método <code>create</code> del principio.</p>
<pre><code>static create(value: number, currency: string): Result<Amount, Error> {
try {
return ok(new Amount(value, currency));
} catch(error) {
return err(error);
}
}</code></pre>
<p>
Cómo se puede ver aquí este método devulve un objeto <code>Result</code> de
<code>Amount</code> y <code>Error</code>. En otras palabras este método puede
devolver una instancia de <code>Ok</code> que contendrá un valor de tipo
<code>Amount</code> o una instancia de <code>Err</code> que contendrá un error
de tipo <code>Error</code>.
</p>
<p>
Vuelve a leer esa dos oraciones anteriores. Creo que es importante llamar la
atención sobre toda la información que estamos “codificando” dentro del
<em>type system</em>. Estamos poniendo los <em>types</em> a ayudarnos a
programar y detectar problemas en nuestro programa.
</p>
<p>
Sobre las funciones <code>ok</code> y <code>err</code> no creo que tenga que
decir mucho. Simplemente se encargan de crear una instancia de un objeto
<code>Ok</code> o <code>Err</code>. Las usamos cuando estamos implementando
algún método que queramos devuelva un <code>Result</code>. Siempre usamos
<code>ok</code> cuando la operación tiene éxito y usamos
<code>err</code> cuando algo falla.
</p>
<p>
Veamos cómo se ve cuando usamos el método <code>create</code> ahora que
entendemos mejor como se comporta <code>Result</code>.
</p>
<pre><code>let priceOrErr = Amount.create(1, "USD");
if (priceOrErr.isErr()) {
console.error(priceOrError.error);
return res.send(422);
}
let price = priceOrErr.unwrap();</code></pre>
<p>
En la primera linea <code>priceOrErr</code> es una instancia de
<code>Result<Amount, Error></code>, así que puede ser un
<code>Ok</code> o un <code>Err</code> y no tenemos forma de saber. En la
próxima linea hacemos el check con <code>isErr</code>, si es cierto sabemos
que el contenido es un error. Pero si logramos pasar ese <em>check</em>, en
las lineas subsiguientes sabemos que <code>priceOrErr</code> tiene que ser un
<code>Ok</code> y podemos hacer <code>unwrap</code> sin tenemor a ninguna
exepción.
</p>
<p>
Lo bueno de esto es que no solo nosotros sabemos que en este punto
<code>priceOrErr</code> es un <code>Ok</code>, también TS lo sabe y nos ayuda
con los completions. Además como le dimos mucha información a TS, el
<em>type system</em> sabe que es un <code>Ok</code> de <code>Amount</code>.
Por lo que cuando hacemos <code>unrap</code> TS sabe que lo que devuelve es un
<code>Amount</code>.
</p>
<p>
De la misma forma dentro del <code>if</code> TS sabe que
<code>priceOrErr</code> es una instancia de <code>Err</code> y que contiene un
valor de tipo <code>Error</code>. Gracias TypeScript.
</p>
<h2>Result en todas partes</h2>
<p>
Espero que ya veas el valor que puede añadir usar <code>Result</code> en tu
programa.
</p>
<p>
El uso de <code>Result</code> no tiene que limitarse a la creación de VOs.
Puedes usar un <code>Result</code> en cualquier parte de tu programa donde
realizas una operación que puede fallar. O sea en muchas partes.
</p>
<p>
Por ejemplo cuando hacemos queries a la base de datos, en vez de devolver la
data directamente, envolvemos el resultado en un <code>Result</code>. De esta
forma podemos verificar si está <code>Ok</code> antes de continuar.
</p>
<p>
<code>Result</code> nos permite usar el <em>type system</em> para “codificar”
información importante sobre el programa.
</p>
<h2>Combine</h2>
<p>
Además de las funciones <code>ok</code> y <code>err</code> uso una función
adicional que me ayuda a verificar varios <code>Result</code> en un solo paso.
A esta funcion le llamo <code>combine</code> porque combina todos los
<code>Result</code> en un solo <code>Result</code> que me dice si todos los
<code>Result</code> están <code>Ok</code>.
</p>
<p>
<code>combine</code> recibe un <code>Array</code> de <code>Result</code>,
itera por ellos y si alguno es un <code>Err</code> devuelve un
<code>Err</code>, si todos son <code>Ok</code>, devuelve un <code>Ok</code>.
</p>
<pre><code>function combine(
results: Result<unknown, Error>[]
): Result<unknown, Error> {
for (let result of results) {
if (result.isErr()) {
return result;
}
}
return ok(results);
}</code></pre>
<p>Así usamos <code>combine</code>.</p>
<pre><code>let priceOrErr = Amount.create(100, "USD");
let discountOrErr = Amount.create(1, "USD");
let taxOrErr = Amount.create(1, "USD");
let validationOrErr = combine([
priceOrErr,
discountOrErr,
taxOrErr
]);
if (validationOrErr.isErr()) {
console.error(validationOrErr.error);
return res.send(422);
}
let price = priceOrErr.unwrap();
let discount = discountOrErr.unwrap();
let tax = taxOrErr.unwrap();</code></pre>
<p>
Una vez pasamos el <code>if</code> sabemos que todos los valores son
<code>Ok</code> y podemos <code>unwrap</code> sin problema.
</p>
<h2>Extendiendo Result</h2>
<p>
No hay ninguna regla que diga que <code>Result</code> no puede tener más
métodos. Incluso si miramos la documentación de
<a href="proxy.php?url=https://doc.rust-lang.org/std/result/enum.Result.html">Rust</a> para
su tipo <code>Result</code> no damos cuenta que implementa algunos método
interesantes como <code>unwrap_or_else</code>.
</p>
<p>
Ese método lo podemos implementar en nuestro <code>Result</code> para poder
sacar nuestro valor o un valor default si resulta que nuestro
<code>Result</code> es un <code>Err</code>.
</p>
<pre><code>let defaultAmount = new Amount(0, "USD");
let priceOrErr = Amount.create(req.body.price, "USD");
let price = priceOrErr.unwrapOr(defaultAmount);</code></pre>
<p>
De esta forma si el <code>Result</code> de <code>priceOrErr</code> es
<code>Err</code>, <code>price</code> será el valor de
<code>defaultAmount</code>.
</p>
<p>
Hay muchas cosas interesantes que se pueden hacer con <code>Result</code>, por
ejemplo podemos añadir un método <code>map</code> que aplique una funcion al
valor que contenien el <code>Result</code> y devuelva un nuevo
<code>Result</code> con el valor modificado. Este <code>map</code> debe ser lo
suficientemente inteligente como para no devolver un error si resulta que el
<code>Result</code> lo que contiene es un <code>Err</code>.
</p>
<p>
Con este método de <code>map</code> se pueden hacer muchas cosas interesantes,
especialmente hacer cadenas de método que todos devueven un
<code>Result</code> y todos esperan como <em>input</em> un
<code>Result</code>. Pero de esto podemos hablar otro día.
</p>
<h2>Maybe</h2>
<p>
Finalmente hablaré brevemente del objeto <code>Maybe</code>. Este objeto es
muy similar a <code>Result</code>. Usamos <code>Maybe</code> como valor para
operaciones que pueden devolver o resultado o no.
</p>
<p>
Por ejemplo imagina que tienes una funcion <code>notJuan</code> que recibe una
<code>Array</code> de nombres de personas y devuelva un <code>Array</code> de
nombres que no son Juan. Esta función podría devolver un <code>Array</code> de
resultado pero también podría devovler un <code>Array</code> vació, o sea
ningún resultado.
</p>
<p>
No tengo nada en contra de los Juanes, simplemente este fue el mejor ejemplo
que se me ocurrió.
</p>
<p>
Esta función la podemos implementar usando <code>Maybe</code> como el
<em>return type</em>.
</p>
<p>
Un <code>Maybe</code> puede devolver un <code>Some</code> o un
<code>None</code>. Devuelve <code>Some</code> cuando tenemos un valor que
devolver y <code>None</code> cuando no tenemos un valor que devoler. Similar a
<code>Result</code> tenemos funciones <code>some</code> y
<code>none</code> para crear <code>Maybe</code> fácilmente.
</p>
<p>Así implementaríamos la función <code>notJuan</code>.</p>
<pre><code>function notJuan(names: string[]): Maybe<string> {
let validNames = names.filter((n) => n !== "juan");
if (validNames.length === 0) {
return none();
}
return some(validNames);
}
let friends = ["María", "Luis", "Juan"];
let filteredFriends = notJuan(friends);
if (filteredFriends.isNone()) {
return res.send(400);
}
let friendsExceptJuan = filteredFriends.unwrap();</code></pre>
<p>
En la última linea sabemos que <code>friendsExceptJuan</code> contiene algunos
nombres y que ninguno es “Juan”.
</p>
<h2>Result y Maybe</h2>
<p>
En mis programas mas recientes he empezado a utilizar tanto
<code>Result</code> como <code>Maybe</code>. Siento que me ayudan a expresar
mejor la intención de los métodos en mi programa.
</p>
<p>
Utilizo <code>Result</code> como <em>return value</em> para cualquier
operación que puede devolver un error y utilizo <code>Maybe</code> para
cualquier operación que puede devolver datos o no.
</p>
<p>
En algunos casos me he visto usando ambos en cojunto por ejemplo puedes
imaginar un método <code>findById</code> que busca en la base de datos un
<em>record</em> usando el <em>id</em>.
</p>
<p>
Esta al ser una operación de la base de datos puede fallar por muchas razones,
podemos tener un error en la conexión o quizás la petición tarda mucho y
falla. Para capturar esa información uso un <code>Result</code>.
</p>
<p>
Si todo sale bien lo próximo que puede pasar es que no exista un objeto con
ese <em>id</em> en la base de datos y para comunicar esto uso un
<code>Maybe</code>. O sea que cuando hago una operación como
<code>findById</code> eso devuelve algo como esto.
</p>
<pre><code>Result<Maybe<User>, Error></code></pre>
<p>
Admito que puede ser complicado pero pienso que de esta forma estoy
codificando lo más posible dentro del <em>type system</em>. No quiero decir
que esta es <em>“the one true way”</em> pero por ahora nos funciona.
</p>
<p>
Seguiremos experimentando con esto y seguirmos evolucionando pero por ahora
estamos bastante satisfechos.
</p>
<h2>Referencias</h2>
<p>
Es increíble que hemos llegado hasta aquí sin usar la palabra
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Monad_(functional_programming)"
>monad</a
>. Aquí te dejo algunos enlaces que me han ayudado mucho.
</p>
<ul>
<li>
<a
href="proxy.php?url=https://khalilstemmler.com/articles/enterprise-typescript-nodejs/handling-errors-result-class/"
>Flexible Error Handling w/ the Result Class</a
>
</li>
<li>
<a
href="proxy.php?url=https://dev.to/_gdelgado/type-safe-error-handling-in-typescript-1p4n?signin=true"
>Type-Safe Error Handling In TypeScript</a
>
</li>
<li>
<a href="proxy.php?url=https://gist.github.com/s-panferov/5269524dcf23dad9a1ef"
>Result type in TypeScript</a
>
</li>
<li>
<a
href="proxy.php?url=https://codewithstyle.info/advanced-functional-programming-in-typescript-maybe-monad/"
>Advanced functional programming in TypeScript: Maybe monad</a
>
</li>
<li>
<a href="proxy.php?url=https://github.com/patrickmichalina/typescript-monads"
>TypeScript monads: Better TypeScript Control Flow</a
>
</li>
<li>
<a href="proxy.php?url=https://github.com/hqoss/monads"
>Monads: Type safe Option, Result, and Either types</a
>
</li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=F9bznonKc64"
>Get value out of your monad</a
>
</li>
</ul>
<h2>Próximo</h2>
<p>
En un próximo post lo que me interesa discutir es el rol de los VOs en la
validación de request en un web app y cómo podemos complementar usando otros
tipos de validación con librerías como JSON Schema.
</p>[email protected] (gcollazo)Sat, 22 May 2021 00:00:00 -0000https://gcollazo.com/result-y-maybeLogging is importanthttps://gcollazo.com/logging-is-important<p>
Getting application
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Log_file">logging</a> right is crucial
for every software project. When done correctly it makes the maintainers work
a lot simpler. There’s no worst feeling than getting an error report and
searching for logs to try to understand what happened and not finding a single
entry. This is especially bad with difficult to reproduce production errors.
</p>
<p>
Like everything else, having good logs requires a little bit of thought and
consistency. But is not that hard.
</p>
<p>
I’m not an expert on this subject. This post contains my notes on what has
worked for me in the past in the context of backend web applications.
</p>
<h2>Use a real logger</h2>
<p>
Every language has a very simple way to send strings to <code>stdout</code>.
That’s not a “real” logger.
</p>
<p>
In Node you have <code>console.log()</code> and in Python
<code>print()</code>. These work great for simple scripts and print debugging.
On a real production application, you want to use a “real” logger.
</p>
<p>
For Node, I prefer using <a href="proxy.php?url=https://getpino.io/#/">pino</a>. It’s fast,
has great support, and is very easy to use.
</p>
<h2>What to log</h2>
<ul>
<li>
<strong>Request/Response</strong> – You must log every request and response.
For requests, I like to include all headers, except the
<code>Authorization</code> header. For responses, I always include the HTTP
status code and the response time. Both request and response include a
unique request ID to correlate them.
</li>
<li>
<strong>App Errors</strong> – every <code>catch</code> in your code must
include an error log describing what went wrong. Also whenever your app gets
into a state that will result in an HTTP status code greater than 299 you
should have some sort of application-level error log, describing why your
request ended in an error.
</li>
<li>
<strong>Security events</strong> – Always log when a login fails, or when a
user tries to access a page or an object is not supposed to. Always log
password changes, email changes or any other security credentials change.
Make sure you don’t log the actual password or sensitive information.
</li>
<li>
<strong>Business events</strong> – This is optional but it’s a good idea. If
your app is a shopping cart, create a log entry every time you close a sale.
The idea here is that you might want to create alerts based on these log
events. If you usually see 100 sales per hour and suddenly see zero, it
might be a good idea to take a look and understand what it’s going on.
</li>
</ul>
<h2>A good log line</h2>
<p>
Having an unsearchable pile of logs is not useful. All my logs are in JSON
format. This is very useful if you use a service that allows you to query JSON
logs. I use AWS CloudWatch logs which is not perfect, but it does a decent job
of helping me find what I’m looking for if I created the logs correctly.
</p>
<p>Every log line must have the following.</p>
<ul>
<li>
<strong>Log level</strong> – Usually a number to indicate what kind of log
it is. Info, error, and debug are the most common.
</li>
<li>
<strong>Message</strong> – This is the most important part of the log entry.
Try to make this explicit and easy to understand for your future self or
your fellow team members. Try to make messages unique so they are easy to
search on your codebase. Having multiple error logs with the same message
makes things harder than they have to be.
</li>
<li>
<strong>Group</strong> – All of my logs have a <code>group</code> field that
use to indicate what part of the application is producing the log entry.
Most of the time this group is used to indicate which specific file is
creating the log entry. This facilitates searching.
</li>
<li>
<strong>Context</strong> – Application error logs usually require some
context that will help you determine what is the problem without logging all
of your application state. It is very important to include the minimum
amount of data possible on every log entry but enough to determine what is
going on. For example if your input validation failed it might be a good
idea to log what exactly went wrong. For example if you only allow names of
a certain length, it might be a good idea to log the length of the submitted
name that produced the error.
</li>
<li>
<strong>Timestamp</strong> – Almost every logger adds a timestamp. Just make
sure that the format used works for you and make sure you include the time
zone or that your are very certain of what zone it’s being used.
</li>
</ul>
<h2>Security concerns</h2>
<p>
If your system deals with
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Personal_data">PII</a> or
<a
href="proxy.php?url=https://en.wikipedia.org/wiki/Payment_Card_Industry_Data_Security_Standard"
>CHD</a
>, make sure you are not logging sensitive information. Pino and every decent
logger have a feature to
<a href="proxy.php?url=https://getpino.io/#/docs/redaction?id=redaction">redact</a> logs.
</p>
<p>
Also, make sure that you periodically check your logs for sensitive data. This
is something not obvious and I’ve seen it go wrong multiple times.
</p>
<p><strong>Don’t log sensitive or secret information</strong>.</p>
<h2>Final thoughts</h2>
<p>
I know logs are boring but they are an essential part of every app. A lot of
thought is put into many aspects of software development but I rarely find
people writing or discussing this subject. You should put some thought on your
logging practices. You future self will thank you.
</p>
<p>In summary:</p>
<ul>
<li>
Having decent logs will greatly improve the maintenance of your software.
</li>
<li>Error logs are the most important kind of log.</li>
<li>Always include context on error logs.</li>
<li>Log every request and response and include status codes.</li>
<li>Make sure you don’t log sensitive information.</li>
</ul>[email protected] (gcollazo)Tue, 01 Jun 2021 00:00:00 -0000https://gcollazo.com/logging-is-importantEl Lenguaje de Programación Perfectohttps://gcollazo.com/el-lenguaje-de-programacion-perfecto<p>
En mi trabajo escribo principalmente
<a href="proxy.php?url=https://www.typescriptlang.org/">TypeScript</a> sobre
<a href="proxy.php?url=https://nodejs.org/en/">Node.js</a> y aunque no es mi lenguaje
favorito, no me molesta, tiene muchas cosas buenas. Para el tipo de proyecto
que trabajo, el tener un “<a
href="proxy.php?url=https://en.m.wikipedia.org/wiki/Type_system"
>type system</a
>” es altamente valioso.
</p>
<p>
También escribo bastante <a href="proxy.php?url=https://www.python.org/">Python</a> (de mis
lenguajes favoritos) para “scripts” locales y otras cosas que no
necesariamente terminan en un ambiente de producción. Algún día escribiré un
poco más sobre porque terminamos corriendo en Node.js y
<a href="proxy.php?url=https://en.m.wikipedia.org/wiki/MongoDB">MongoDB</a>.
</p>
<p>
No creo que exista el lenguaje de programación perfecto, perdón por el
“clickbait”, pero para mi el lenguaje ideal es una mezcla entre
<a href="proxy.php?url=https://www.python.org/">Python</a>,
<a href="proxy.php?url=https://www.typescriptlang.org/">TypeScript</a>,
<a href="proxy.php?url=https://go.dev/">Go</a> y
<a href="proxy.php?url=https://www.rust-lang.org/">Rust</a>.
</p>
<p>
En este post enumero los <em>ingredientes</em> que me gustaría tomar prestados
para un lenguaje nuevo imaginario. Seguramente este lenguaje jamás existirá y
posiblemente eso sea lo mejor. Si hay algo que NO soy, es diseñador de
lenguajes de programación.
</p>
<h2>Python</h2>
<p>
De Python me encanta que es fácil de leer y escribir y que tiene un “<a
href="proxy.php?url=https://docs.python.org/3/library/index.html"
>standard library</a
>” bastante completo. Cuando quiero resolver un problema que se puede resolver
corriendo un pequeño “script” local, Python siempre me da los mejores
resultados. Además hay muchas librerías para manejar datos y visualizaciones
que son fáciles y útiles.
</p>
<p>
En cuanto “web apps” que es lo mas que hago, creo que
<a href="proxy.php?url=https://www.djangoproject.com/">Django</a> es de lo mejor que he
usado. Todo lo importante está incluido y lo que falta es fácil de añadir. Me
gustaría que fuera un poco más flexible en cuanto a como organizar los
proyectos para hacer mas fácil implementar una
<a href="proxy.php?url=https://en.m.wikipedia.org/wiki/Hexagonal_architecture_(software)"
>arquitectura hexagonal</a
>
pero al final del día no tengo quejas mayores.
</p>
<ul>
<li>Fácil de leer y escribir.</li>
<li>El “standard library” es bastante completo.</li>
<li>Hay muchas librerías para análisis y visualización de datos.</li>
<li>Me encanta <a href="proxy.php?url=https://jupyter.org/">Jupyter Notebook</a>.</li>
<li>Django es mi web framework favorito.</li>
</ul>
<h2>TypeScript y JavaScript</h2>
<p>
De TypeScript me gusta mucho el “<a
href="proxy.php?url=https://www.typescriptlang.org/docs/"
>type system</a
>” y principalmente la idea de que puedes añadir los tipos poco a poco en un
“codebase” viejo. Se que
<a href="proxy.php?url=https://docs.python.org/3/library/typing.html#module-typing"
>Python tiene su propio “type system”</a
>
pero de lo que he visto el de TypeScript es mas poderoso y no es mucho más
difícil de aprender.
</p>
<p>
Otro feature de TypeScript y JavaScript que me gusta mucho es la sintaxis para
los “<a
href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions"
>arrow functions</a
>” <code>(args) => body</code>. Además de facilitar el crear funciones
anónimas creo que hace que el código sea más fácil de leer, especialmente
cuando se usa
<a
href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"
>map</a
>
y
<a
href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter"
>filter</a
>.
</p>
<p>
Python tiene los
<a
href="proxy.php?url=https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions"
>list y dictionary comprehensions</a
>
que me gustan bastante, pero creo que la sintaxis de los “arrow functions” es
un poco mejor. Tambien en Python hay
<a
href="proxy.php?url=https://docs.python.org/3/tutorial/controlflow.html?#lambda-expressions"
>lambdas</a
>
para definir funciones anónimas pero nunca recuerdo como se escriben. Sin duda
los “arrow functions” son mejor.
</p>
<ul>
<li>El “type system” es buenísimo y opcional.</li>
<li>
Me parece que el “type system” es mas poderoso que lo que ofrece Python en
versiones mas recientes.
</li>
<li>Es fácil de aprender a sacarle provecho a los “types”.</li>
<li>
Los “arrow functions” en JavaScript me gustan más que los list y dictionary
comprehensions de Python.
</li>
<li>Hacer funciones anónimas es mucho más natural que en Python.</li>
</ul>
<h2>Go</h2>
<p>
De Go lo más que me gusta es el modelo de concurrencia. Los goroutines hacen
que sea fácil escribir código concurrente sin errores, algo que es un poco más
complicado en Node y ni hablar de Python.
</p>
<p>
Otra cosa que me parece buenísima es la posibilidad de generar binarios
“statically linked” para Windows, Linux y Mac. Un solo archivo binario con
todas sus dependencias hace posible crear contenedores de Docker pequeños
usando <code>FROM scratch</code>.
</p>
<p>
El tema de “performance” aunque no es una de las cosas que más me emocionan de
un lenguaje, no se debe ignorar. Go al ser un lenguaje compilado produce
programas que ejecutan mucho más rápido que programas escritos para Node o
Python. Sin duda esto es algo positivo especialmente cuando estás haciendo
“hosting” en la nube.
</p>
<ul>
<li>
El modelo de concurrencia facilita escribir código concurrente con menos
errores.
</li>
<li>
Compilar a un solo binario “statically linked” facilita “deploys” y el
manejo de dependencias.
</li>
<li>
Poder hacer un contenedor <code>FROM scratch</code> permite tener imágenes
pequeñas y con la menor superficie de ataque posible.
</li>
<li>Mejor “performance” cuando se compara con Python o Node.</li>
</ul>
<h2>Rust</h2>
<p>
De Rust me gustan muchas cosas pero el enfoque del lenguaje en producir
programas que sean fácil de analizar estáticamente, ha empujado ese lenguaje a
tomar decisiones conservadoras sobre como manejar los datos.
</p>
<p>
El que todo sea
<a href="proxy.php?url=https://en.m.wikipedia.org/wiki/Immutable_object">inmutable</a> por
“default” sin duda es bueno para facilitar el análisis estático del programa y
ayuda al programador a no tener sorpresas.
</p>
<p>
Los tipos <a href="proxy.php?url=https://doc.rust-lang.org/std/option/">Option</a> y
<a
href="proxy.php?url=https://doc.rust-lang.org/std/result/enum.Result.html"
target="_blank"
rel="noreferrer noopener"
>Result</a
>
son otra cosa que me encanta de Rust. Estos dos tipos se encargan de manejar
errores y funciones que pueden no devolver el valor que esperas. Esto te
obliga a ser extremadamente explicito manejando errores y los casos donde el
programa puede fallar. Nuevamente esto ayuda al programador a no tener
sorpresas.
</p>
<ul>
<li>Toda la data es inmutable por “default”.</li>
<li>
Al igual que Go los errores son valores que hay que atender de forma
explícita.
</li>
<li>
Los tipos <code>Option</code> y <code>Result</code> son enormemente útiles y
me gustan mas que el <code>if err != nil {}</code> que se ve en todas partes
en Go.
</li>
<li>
Los “features” de “<a
href="proxy.php?url=https://doc.rust-lang.org/rust-by-example/flow_control/match.html"
>pattern matching</a
>” en combinación con <code>Option</code> y <code>Result</code> son una
maravilla.
</li>
</ul>
<h2>Conclusión</h2>
<p>
Ahí está, mi lenguaje de programación ideal es una mezcla de algunas ideas y
“features” de estos cuatro lenguajes. ¿Te gustaría un lenguaje así?
</p>[email protected] (gcollazo)Mon, 13 Dec 2021 00:00:00 -0000https://gcollazo.com/el-lenguaje-de-programacion-perfectoDespués de Gasolina Móvilhttps://gcollazo.com/despues-de-gasolina-movil<p>
Hace mas de siete años y sin avisar me llamó Arnaldo para contarme de un
invento que tenía. La idea era simple, vamos a hacer un app para que la gente
pueda pagar la gasolina.
</p>
<p>
Inmediatamente me pareció una buena idea. Estaba seguro que los consumidores
les encantaría poder pagar en la bomba. Ahí, en esa misma llamada acordamos
hacer el app y ver qué pasaba.
</p>
<h2>Desde cero</h2>
<p>
Dos o tres semanas más tarde nos encontramos en una estación de gasolina en
Caguas. El dueño de la estación nos permitió probar el app ahí en un ambiente
real. En este punto Arnaldo tenía casi todo el software de su lado terminado y
yo tenía un app que parecía que debería funcionar. Lanzamos la primera
transacción pero falló porque yo cometí un error configurando la bomba de
gasolina. En el segundo intento la transacción llegó a la estación pero la
bomba no encendió. Luego de tres o cuatro intentos más, la bomba encendió y
logramos servir gasolina. ¡Funciona!
</p>
<p>
Desde ese día hemos estado pensando y trabajando sobre esta idea. Han pasado
muchas cosas desde entonces, en su mayoría buenas y otras menos buenas pero
hemos logrado seguir adelante. Hoy nuestros apps funcionan en la mayoría de
las estaciones de servicio en Puerto Rico y el negocio sigue creciendo.
</p>
<p>
Tenemos clientes en República Dominicana, Colombia, Islas Vírgenes Americanas,
Bahamas y Estados Unidos. Ahora el enfoque es Centro América así que muy
pronto habrá noticias sobre este esfuerzo.
</p>
<h2>Siempre aprendiendo</h2>
<p>
Trabajando en este proyecto he tenido muchas oportunidades de aprender.
Aprendí a hacer servicio al cliente a gran escala, a contratar personal
técnico y personal no técnico, sobre ciberseguridad y sobre cómo manejar un
equipo remoto de forma efectiva mucho antes que eso fuera la norma.
</p>
<p>
En los últimos años me he enfocado en tres areas: arquitectura de software,
ciberseguridad y procesos de desarrollo de software.
</p>
<p>
Mucho de mi aprendizaje ha sido producto del éxito súbito e inesperado de
nuestro negocio. Como suele pasar el “demo” se volvió el producto y había que
moverse rápido. La velocidad produce deuda técnica que si no se atiende con
prontitud se puede volver un problema.
</p>
<p>
Hace un tiempo nos dimos cuenta que nos estábamos moviendo un poco más lento
de lo usual. La deuda técnica estaba empezando a ser un tema importante. Desde
entonces hemos estado poco a poco “refactorizando” las areas donde hay más
deuda y empezando a usar nuevas arquitecturas para evitar problemas en el
futuro. Todos en el equipo hemos aprendido mucho, el software está en mucho
mejor forma que cuando empezamos y tenemos una ruta clara para seguir
mejorando.
</p>
<p>
Sobre el tema de ciberseguridad es de lo menos que sabía y a lo mas que le he
dedicado tiempo. Desde antes de entrar en este negocio me interesaba la
ciberseguridad pero nunca fue mi enfoque. En este negocio la ciberseguridad es
posiblemente el tema más importante. Por suerte lo teníamos claro desde el
principio y le hemos dedicado el tiempo y los recursos necesarios. Ha costado
mucho dinero y ha tomado mucho tiempo pero lo hemos hecho bastante bien.
</p>
<p>
Pasar por el proceso de auditoría de
<a
href="proxy.php?url=https://en.m.wikipedia.org/wiki/Payment_Card_Industry_Data_Security_Standard"
>PCI</a
>
por primera vez fue casi traumático pero ya en este punto me parece lo mas
normal del mundo. Además, desde el principio y sin saber, muchas de nuestras
políticas y medidas de seguridad superaban por mucho los requisitos de PCI. Lo
que más trabajo nos dio fue aprender a documentar cómo esperan los auditores,
para poder demostrar lo que en gran medida ya estábamos haciendo
correctamente.
</p>
<p>
Estos años han sido una escuela y me siento en un gran momento profesional.
Tengo mucho conocimiento y muchas experiencias del mundo real que son
increíblemente valiosas. Le he sacado el jugo a esta experiencia y estoy muy
complacido.
</p>
<h2>De cero otra vez</h2>
<p>
<strong
>Hace mas o menos un año tomé la decisión de dejar mi posición en Alias
Payments (Gasolina Móvil) y moverme a hacer algo nuevo.</strong
>
</p>
<p>
En Alias Payments tomamos una simple idea y la llevamos a ser un gran negocio.
En este punto ya siento que he logrado todo lo que me propuse y que es momento
de llevar lo aprendido a otros lugares.
</p>
<p>Empezar de cero otra vez.</p>
<p>
En el momento que escribo esto, todavía no tengo claro que voy a hacer pero
hay tres areas generales que me interesan y estoy explorando:
</p>
<ul>
<li>
Empezar una compañía de software para negocios (B2B) con un modelo “software
as a service” en algo relacionado a ciberseguridad.
</li>
<li>
Armar un equipo de ingenieros especializados en “secure coding practices”
para hacer consultoría a empresas y startups. El enfoque sería hacer
auditorias de seguridad y ayudar a establecer políticas de “secure coding”.
</li>
<li>
Explorar la idea de hacer consultoría a nivel ejecutivo a tiempo parcial con
varias compañías a la vez, comúnmente conocido como “fractional CTO / CISO”.
</li>
</ul>
<p>
Para ser honesto no tengo una idea favorita y no sé si terminaré haciendo
alguna cosa totalmente diferente. Desde hoy empezaré a hablar con mis
contactos y a explorar diferentes ideas para poder ir acercándome a una
decisión.
</p>
<p>
Estoy claro que lo que sea que escoja hacer será un proyecto de tres o cuatro
años mínimo así que no quiero apresurarme. Dicho eso, antes de que acabe el
año espero saber y compartir a qué me voy a dedicar.
</p>
<h2>Beluga es un “hobby”</h2>
<p>
Llevo unos meses trabajando en un app para iOS que se llama Beluga. Este app
es “un Twitter” que funciona sin necesidad de centralizar todos los datos en
los servidores de una sola compañía. Es un Twitter descentralizado y antes de
que me pregunten, no usa blockchain.
</p>
<p>
Mi meta es recrear “el Twitter del principio”, donde había mucha menos gente y
las interacciones eran mas saludables. Había un momento cuando la gente en
Twitter no estaba buscando de qué quejarse o de cómo montarsela a alguien que
dijo una tontería. Tampoco había anuncios en cada tres tweets y no había un
feed algorítmico tratando de robar toda tu atención para vender anuncios.
</p>
<p>
Otro de los objetivos de este proyecto es diseñar un protocolo que otros apps
puedan adoptar y no quedarme en construir un app que solo funciona si una
empresa tiene el control total.
</p>
<p>
Beluga no es mi próximo negocio o por lo menos dudo que lo sea. Por diseño,
este proyecto hace bastante difícil que una sola entidad pueda “adueñarse” de
los datos de los usuarios dificultando la mayoría de los modelos de negocio
tradicionales para este tipo de apps.
</p>
<p>
Beluga es un “hobby” y dudo que se vuelva un negocio. Si se vuelve un negocio
sería un grata sorpresa pero lo dudo. Pronto compartiré mas información sobre
Beluga y anunciaré cuando lo publique.
</p>
<h2>Agradecimiento</h2>
<p>
No quiero terminar este post sin darle las gracias a todas las personas que
han trabajado y que trabajan en Alias Payments y Artisoft Labs por lo mucho
que me han enseñado, lo profesionales que son y sus buenos deseos. También
quiero darle gracias a José Padilla quien fue uno de nuestros socios
originales cuando arrancamos el negocio. Gracias a todos.
</p>
<p>
Finalmente, quiero agradecer a mi amigo desde por lo menos 1999 y socio en
este negocio Arnaldo Rivera. Sin su iniciativa, conocimiento y su ética de
trabajo sería imposible haber llegado donde hemos llegado. Durante todos estos
años que hemos trabajado juntos hemos tenido sorprendentemente pocas
diferencias y cuando han surgido siempre se han resuelto rápidamente y con
mucho respeto. Hemos trabajado mucho y la hemos pasado muy bien. Ha sido
genuinamente una gran experiencia en gran medida por el liderato de Arnaldo.
Gracias.
</p>
<h2>Próximo</h2>
<p>
Tan pronto sepa lo que voy a hacer estoy seguro que lo comunicaré por aquí.
Por ahora voy a explorar las alternativas y escucharé todas las propuestas que
reciba.
</p>
<p>
Estoy increíblemente entusiasmado por empezar algo nuevo y sentir nuevamente
la adrenalina de esos primeros días.
</p>
<p>
Déjame tus comentarios escribiendo a
<a href="proxy.php?url=mailto:[email protected]">[email protected]</a>
</p>[email protected] (gcollazo)Mon, 18 Jul 2022 00:00:00 -0000https://gcollazo.com/despues-de-gasolina-movilFavorite Tiny Desk Concertshttps://gcollazo.com/favorite-tiny-desk-concerts<p>Ordered alphabetically:</p>
<ul>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=XfzpYcwiUrA">Adele</a></li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=O70s9aV_9_Q">Café Tacvba</a></li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=j82L3pLjb_0">Coldplay</a></li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=SW6L_lTrIFg">C. Tangana</a></li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=F4neLJQC1_E">Dua Lipa</a></li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=wTqCthvtL8k">Hermanos Gutiérrez</a>
</li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=rZjRU5ElGrk">iLe</a></li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=wSl5_RDCfrQ">Jessie Reyez</a>
</li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=ze4xcmBFvaE">Jon Batiste</a></li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=aWjihS2LHLs"
>Juanes & Mon Laferte</a
>
</li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=DFiLdByWIDY">Lizzo</a></li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=QrR_gm6RqCo">Mac Miller</a></li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=GYJ03MIPoIk"
>Megan Thee Stallion</a
>
</li>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=Dy4pEFFbFsA">Mon Laferte</a></li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=JGL-eQAAxGs">Monsieur Periné</a>
</li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=JODaYjDyjyQ">Natalia Lafourcade</a>
</li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=KGczofguB0c">Olivia Rodrigo</a>
</li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=ysdjtyV_DuY">The Lumineers</a>
</li>
</ul>[email protected] (gcollazo)Thu, 18 May 2023 00:00:00 -0000https://gcollazo.com/favorite-tiny-desk-concertsRunning a Bug Bounty Program Without Spending a Fortunehttps://gcollazo.com/running-a-bug-bounty-program-without-spending-a-fortune<p>
Bug bounty programs have emerged as a crucial cybersecurity measure, allowing
organizations to harness the skills of the security community in identifying
and resolving vulnerabilities before malicious actors can exploit them. While
many opt for popular bug bounty platforms, these can come with hefty price
tags. In this blog post, we’ll explore the steps to establish a successful and
cost-effective bug bounty initiative without relying on expensive vendors.
</p>
<h4>Clearly Define Objectives and Scope</h4>
<p>
Before starting your bug bounty program, set clear objectives and scope.
Determine which assets and systems are in-scope for testing and be transparent
about your expectations from participants. Outline the types of
vulnerabilities you’re interested in and specify any restrictions or rules
during testing.
</p>
<h4>Prepare Legal and Ethical Guidelines</h4>
<p>
Craft comprehensive legal agreements and ethical guidelines that participants
must follow throughout the bug bounty program. These documents should outline
the rules, the reward structure, and the responsibilities of both your
organization and the researchers. Consult with legal professionals to ensure
compliance with local regulations and to safeguard all parties’ interests.
</p>
<h4>Build a Secure Platform for Submission</h4>
<p>
Create a secure platform where researchers can submit their findings and
communicate with your security team. There are many alternatives here but
keeping it simple for researchers while deterring spammers is the holly grail.
I have tried a bunch of solutions and to be honest the best so far is a well
crafted Google form.
</p>
<h4>Respond to Reports</h4>
<p>
Having an internal security team to manage and assess submissions is vital for
the success of your program. They should be skilled in cybersecurity, capable
of verifying reported vulnerabilities, and assigning appropriate rewards.
Utilizing your existing team can be more cost-effective than relying on
external vendors.
</p>
<h4>Set Reasonable Rewards</h4>
<p>
Offering competitive rewards is essential for attracting talented researchers
to your program. Conduct market research to gauge average payouts for various
types of vulnerabilities and set rewards accordingly. Remember that
recognition and swag, in addition to monetary rewards, can also be appealing
incentives for researchers.
</p>
<h4>Foster a Positive Community Culture</h4>
<p>
Promote a positive and respectful culture within your bug bounty community.
Promptly acknowledge submissions, maintain open lines of communication, and
express gratitude for researchers’ efforts. Treat participants as valuable
allies in securing your systems, not adversaries.
</p>
<h4>Utilize Cost-Effective Promotion Strategies</h4>
<p>
Without the need for popular vendors’ expensive platforms, focus on
cost-effective promotion strategies. Utilize your company’s website, blog, and
social media to spread the word about your bug bounty program. Engage with the
cybersecurity community through forums and mailing lists to raise awareness
effectively.
</p>
<h4>Continuous Improvement</h4>
<p>
Regularly review and refine your bug bounty program based on feedback from
researchers and your internal security team. Continuous improvement is
essential to keep the program effective and relevant in the ever-changing
cybersecurity landscape, and it doesn’t require additional costs associated
with external platforms.
</p>
<h4>You Can Do It</h4>
<p>
Running a cost-effective bug bounty program without relying on expensive
vendors demands thoughtful planning, commitment, and investment in your
internal resources. By clearly defining objectives, setting up a secure
submission platform, and fostering a positive community culture, your
organization can leverage the security community’s expertise to identify and
resolve vulnerabilities without breaking the bank. Remember, the success of
your bug bounty program lies in the trust you build with researchers and the
dedication to making your digital assets more secure in a financially
responsible manner.
</p>
<p>
If you’re looking for a user-friendly and cost-effective solution to create
and run your bug bounty program, consider using
<a href="proxy.php?url=https://blimp.io">Blimp</a>. We offer a budget-friendly alternative
to popular bug bounty platforms, enabling you to efficiently manage
submissions, engage with the security community, and continuously improve the
security of your systems while keeping costs in check.
</p>[email protected] (gcollazo)Wed, 19 Jul 2023 00:00:00 -0000https://gcollazo.com/running-a-bug-bounty-program-without-spending-a-fortuneOptimal SQLite settings for Djangohttps://gcollazo.com/optimal-sqlite-settings-for-django<p>
Requires:
<strong
><a
href="proxy.php?url=https://docs.djangoproject.com/en/5.1/ref/databases/#setting-pragma-options"
>Django >= 5.1</a
></strong
>
</p>
<hr />
<p>
There’s plenty of information out there on how to scale Django to handle
numerous requests per second, but most of it doesn’t really apply to the kind
of software I work on.
</p>
<p>
For the past few years, I’ve been developing custom software for smaller
organizations. All my deployments have fewer than 100 users and operate on a
single AWS instance. Initially, I chose PostgreSQL because I know it well and
Django’s documentation suggests that SQLite isn’t suitable for production.
</p>
<blockquote>
<p>
SQLite provides an excellent <em>development</em> alternative for
applications that are predominantly read-only or require a smaller
installation footprint.
</p>
<cite
><a href="proxy.php?url=https://docs.djangoproject.com/en/5.0/ref/databases/#sqlite-notes"
>Django Documentation</a
></cite
>
</blockquote>
<p>
However, my previous experience with SQLite has taught me that it’s much more
capable than what the Django docs suggest, offering better performance and
simpler administration. So, I decided to migrate. To achieve the best possible
performance, I had to configure Django and SQLite correctly.
</p>
<p>
After extensive research, testing, and trial and error, here are my
recommended settings for Django web apps using SQLite as the primary database.
I anticipate these settings will evolve over time, and I will update them
accordingly. My goal is to have these settings ready to copy and paste into
every new Django project, ensuring they are the most current and optimal for
most web apps.
</p>
<pre><code>DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
"OPTIONS": {
"init_command": (
"PRAGMA foreign_keys=ON;"
"PRAGMA journal_mode = WAL;"
"PRAGMA synchronous = NORMAL;"
"PRAGMA busy_timeout = 5000;"
"PRAGMA temp_store = MEMORY;"
"PRAGMA mmap_size = 134217728;"
"PRAGMA journal_size_limit = 67108864;"
"PRAGMA cache_size = 2000;"
),
"transaction_mode": "IMMEDIATE",
},
},
}</code></pre>
<p>
<em
>If you’re looking for help with custom web apps or need cybersecurity
consulting, feel free to <a href="proxy.php?url=https://blimp.io">reach out</a>. I’m
available for consulting work.</em
>
</p>
<p>
The rest of this post contains information about each setting, my suggestion
for a simple backup strategy and a lot of reference links.
</p>
<hr />
<h3>PRAGMA foreign_keys = ON</h3>
<p>
By default, SQLite does not enforce foreign key constraints. To enable foreign
key enforcement, you need to activate it manually.
</p>
<p><a href="proxy.php?url=https://www.sqlite.org/foreignkeys.html">Read more</a></p>
<h3>PRAGMA journal_mode = WAL</h3>
<p>
The WAL journaling mode uses a write-ahead log instead of a rollback journal
to implement transactions. The WAL journaling mode is persistent; after being
set it stays in effect across multiple database connections and after closing
and reopening the database.
</p>
<p><a href="proxy.php?url=https://www.sqlite.org/wal.html">Read more</a></p>
<h3>PRAGMA synchronous = NORMAL</h3>
<p>
When synchronous is NORMAL, the SQLite database engine will still sync at the
most critical moments, but less often than in FULL mode.
</p>
<p>
<a href="proxy.php?url=https://www.sqlite.org/pragma.html#pragma_synchronous">Read more</a>
</p>
<h3>PRAGMA busy_timeout = 5000</h3>
<p>
The busy_timeout setting in SQLite configures the database to wait
for a specified amount of time when a database file is locked, rather than
returning an error immediately. This is particularly useful in scenarios like
web apps where multiple requests are trying to access the database
simultaneously.
</p>
<p>
<a href="proxy.php?url=https://www.sqlite.org/pragma.html#pragma_busy_timeout">Read more</a>
</p>
<h3>PRAGMA temp_store = MEMORY</h3>
<p>
When temp_store is MEMORY temporary tables and indices are kept as if they
were in pure in-memory databases.
</p>
<p>
<a href="proxy.php?url=https://www.sqlite.org/pragma.html#pragma_temp_store">Read more</a>
</p>
<h3>PRAGMA mmap_size = 134217728</h3>
<p>
Stolen from
<a
href="proxy.php?url=https://github.com/rails/rails/blob/ed76d0fbc74545f0275506b4ebf2dfd375fdda18/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L81-L88"
>Rails SQLite settings</a
>.
</p>
<p>
This is the maximum number of bytes of the database file that will be accessed
using memory-mapped I/O
</p>
<p>
<a href="proxy.php?url=https://www.sqlite.org/pragma.html#pragma_mmap_size">Read more</a
><br /><a
href="proxy.php?url=https://oldmoe.blog/2024/02/03/turn-on-mmap-support-for-your-sqlite-connections/"
>Turn on mmap support for your SQLite connections</a
>
</p>
<h3>PRAGMA journal_size_limit = 67108864</h3>
<p>
Stolen from
<a
href="proxy.php?url=https://github.com/rails/rails/blob/ed76d0fbc74545f0275506b4ebf2dfd375fdda18/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L81-L88"
>Rails SQLite settings</a
>.
</p>
<p>
… in <a href="proxy.php?url=https://www.sqlite.org/wal.html">WAL mode</a>, the
write-ahead log file is not truncated following a <a
href="proxy.php?url=https://www.sqlite.org/wal.html#ckpt"
>checkpoint</a
>. Instead, SQLite reuses the existing file for subsequent WAL entries since
overwriting is faster than appending.
</p>
<p>
<a href="proxy.php?url=https://www.sqlite.org/pragma.html#pragma_journal_size_limit"
>Read more</a
>
</p>
<h3>PRAGMA cache_size = 2000</h3>
<p>
Stolen from
<a
href="proxy.php?url=https://github.com/rails/rails/blob/ed76d0fbc74545f0275506b4ebf2dfd375fdda18/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L81-L88"
>Rails SQLite settings</a
>.
</p>
<p>
… maximum number of database disk pages that SQLite will hold in memory at
once per open database file.
</p>
<p>
<a href="proxy.php?url=https://www.sqlite.org/pragma.html#pragma_cache_size">Read more</a>
</p>
<h3>“transaction_mode”: “IMMEDIATE”</h3>
<blockquote>
<p>
By default, SQLite starts transactions
in <code>DEFERRED</code> mode: they are considered read only. They
are upgraded to a write transaction that requires a database lock in-flight,
when query containing a write/update/delete statement is issued.
</p>
<p>
The problem is that by upgrading a transaction after it has started, SQLite
will immediately return a <code>SQLITE_BUSY</code> error without
respecting the <code>busy_timeout</code> previously mentioned, if
the database is already locked by another connection.
</p>
<p>
This is why you should start your transactions with <code
>BEGIN IMMEDIATE</code
> instead of only <code>BEGIN</code>. If the database is locked
when the transaction starts, SQLite will
respect <code>busy_timeout</code>.;
</p>
<cite><a href="proxy.php?url=https://kerkour.com/sqlite-for-servers">Source</a></cite>
</blockquote>
<p>
<br /><a href="proxy.php?url=https://www.sqlite.org/lang_transaction.html">Read more</a>
</p>
<h2>Backups</h2>
<p>
This is the simplest solution for backup. It’s a little bash script that can
be scheduled to run every hour using a cron job. This script will create a
backup in a safe way even if your database is receiving a high volume of
writes, as it captures a snapshot from a single transaction. Once the backup
file is created it’s uploaded to an S3 bucket.
</p>
<pre><code>#!/usr/bin/env bash
#
# Run using cron to backup db every hour
#
set -euf -o pipefail
sqlite3 /path/to/db "VACUUM INTO '/path/to/backup'"
tar --zstd -cf "/path/to/backup.tar.zst" "/path/to/backup"
# 1-day, rolling hourly backup
aws s3 cp /path/to/backup.tar.zst s3://mybucket/backup-`date +%H`.tar.zst
# 1-month, rolling daily backup
aws s3 cp /path/to/backup.tar.zst s3://mybucket/backup-`date +%d`.tar.zst
# 1-month, rolling hourly backup
aws s3 cp /path/to/backup.tar.zst s3://mybucket/backup-`date +%d%H`.tar.zst</code></pre>
<p><a href="proxy.php?url=https://litestream.io/alternatives/cron/">Source</a></p>
<p>
If you need something more robust than that you should look into the great
<a href="proxy.php?url=https://litestream.io">Litestream</a>, which will continuously stream
changes to an object store like S3 or any of it’s clones.
</p>
<h2>Other references and related links</h2>
<ul>
<li>
<a href="proxy.php?url=https://kerkour.com/sqlite-for-servers"
>Optimizing SQLite for servers</a
>
</li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=XcAYkriuQ1o"
>Building Production Applications Using Go & SQLite</a
>
</li>
<li>
<a href="proxy.php?url=https://tailscale.com/blog/database-for-2022"
>A database for 2022</a
>
</li>
<li>
<a href="proxy.php?url=https://www.youtube.com/watch?v=B-_P0d1el2k"
>The change Pieter Levels did to speed up SQLite</a
>
</li>
<li>
<a
href="proxy.php?url=https://fractaledmind.github.io/2023/09/07/enhancing-rails-sqlite-fine-tuning/"
>Enhancing your Rails app with SQLite: Fine-tuning your database</a
>
</li>
<li>
<a
href="proxy.php?url=https://blog.pecar.me/sqlite-django-config"
data-type="link"
data-id="https://blog.pecar.me/sqlite-django-config"
>This blog post</a
>
has info on how to implement this on Django<5.1
</li>
</ul>
<p>
Do you have any suggestions on how to improve this configuration? Send me an
<a href="proxy.php?url=mailto:[email protected]">email</a>.
</p>[email protected] (gcollazo)Wed, 12 Jun 2024 00:00:00 -0000https://gcollazo.com/optimal-sqlite-settings-for-djangoSoftware hecho a la medidahttps://gcollazo.com/software-hecho-a-la-medida<p>
Todos los negocios usan software que es <em>casi-casi</em> perfecto, pero
siempre falta algo. Siempre hay una parte de algún proceso que depende de una
hoja de Excel que se envía por email o se comparte en Google Drive y que nadie
sabe exactamente quien modificó o quien tiene la última versión. Como esto hay
muchos <em>paper cuts</em> que hacen que los negocios pierdan el tiempo
dándole la vuelta a soluciones de software que no están optimizadas para su
negocio y su forma de trabajar.
</p>
<p>
Estos negocios deben considerar la opción de adquirir
<em>custom software</em>. Gracias a los <em>economics</em> del
<strong>cloud</strong> y a que la mayoría de los negocios en Puerto Rico
tienen una escala relativamente pequeña, es posible entregar software hecho a
la medida a un costo accesible para muchos negocios que en el pasado no tenían
esta posibilidad.
</p>
<p>
A diferencia del software enlatado, el software hecho a la medida
<strong>se puede adquirir sin necesidad de modelos SAAS</strong> que obligan a
los negocios a pagar todos los meses para poder tener acceso. Gracias a una
serie de avances tecnológicos (cloud, Docker, Go, SQLite, etc.) y técnicas de
desarrollo recientes, es posible desplegar este tipo de software de forma que
requiera un mínimo de mantenimiento, permitiendo que el negocio pague una sola
vez por una <strong>licencia permanente</strong>. Muchos de los negocios con
los que hablo quieren dejar de pagar por rentar el software que necesita su
negocio para operar.
</p>
<p>
Poder diseñar y dirigir la dirección del software que usa el negocio abre la
puerta a muchas optimizaciones y eficiencias que no son posibles con el
software enlatado que usa la mayoría. Es por esto que los negocios grandes
siempre crean sus propias soluciones y lo consideran una ventaja competitiva
estratégica.
</p>
<p>
Estoy convencido que para que los negocios locales tengan una posibilidad real
de competir en el mercado es necesario que tengan herramientas de software
similares a las que usan sus competidores más grandes. Me da esperanza que
finalmente tenemos un camino para hacerlo posible.
</p>
<p>
Si tienes un negocio y te interesa explorar la posibilidad de adquirir
software hecho a la medida me puedes contactar
<a href="proxy.php?url=mailto:[email protected]">[email protected]</a> directamente o
visitar <a href="proxy.php?url=https://blimp.io/">blimp.io</a>
para que conozcas algunos de los clientes para los que he trabajado y los
servicios que ofrezco.
</p>[email protected] (gcollazo)Mon, 15 Jul 2024 00:00:00 -0000https://gcollazo.com/software-hecho-a-la-medidaEssential BBOT Commands for Reconhttps://gcollazo.com/essential-bbot-commands-for-recon<p>
<a
href="proxy.php?url=https://github.com/blacklanternsecurity/bbot"
rel="noreferrer noopener"
target="_blank"
>BBOT</a
>
(BEE·bot) is a powerful recursive internet scanner designed for
reconnaissance, bug bounties, and attack surface management. Think of it as
your all-in-one tool information gathering and security assessment.
</p>
<p>
I prefer running BBOT through Docker for consistent behavior across
environments:
</p>
<pre><code># BBOT: Automated reconnaissance framework
bbot() {
docker run --rm -it \
-v "$HOME/.bbot:/root/.bbot" \
-v "$HOME/.config/bbot:/root/.config/bbot" \
blacklanternsecurity/bbot:stable "$@"
}</code></pre>
<p>
Add this function to your shell configuration file, and you’re ready to go.
</p>
<h2>Essential Commands</h2>
<p>Here are some BBOT commands I regularly use in my security assessments:</p>
<h3>Full Subdomain Enumeration</h3>
<pre><code># Comprehensive subdomain discovery
bbot -t example.com -p subdomain-enum</code></pre>
<p>
Perfect for initial reconnaissance of a target domain. This command leverages
multiple sources to build a complete picture of the target’s subdomain
landscape.
</p>
<h3>Passive Subdomain Reconnaissance</h3>
<pre><code># Non-intrusive subdomain discovery
bbot -t example.com -p subdomain-enum -rf passive</code></pre>
<p>
Ideal for situations requiring stealth or when active scanning isn’t
appropriate. This method relies solely on external data sources without
directly interacting with the target.
</p>
<h3>Enhanced Domain Visualization</h3>
<pre><code># Combine subdomain enumeration with port scanning and web screenshots
bbot -t example.com -p subdomain-enum -m portscan gowitness</code></pre>
<p>
This command creates a comprehensive visual map of your target’s attack
surface, combining port scanning with web interface documentation.
</p>
<h3>Basic Web Assessment</h3>
<pre><code># Non-intrusive web technology enumeration
bbot -t example.com -p subdomain-enum web-basic</code></pre>
<p>
Gathers essential information about web technologies while maintaining a light
touch. Includes technology fingerprinting and robots.txt analysis.
</p>
<h3>Targeted Web Crawling</h3>
<pre><code># Controlled depth web crawling with automated analysis
bbot -t www.example.com \
-p spider \
-c web.spider_distance=2 web.spider_depth=2</code></pre>
<p>
Efficiently maps web application structure while automatically identifying
sensitive information like emails and potential secrets.
</p>
<h3>Comprehensive Scan</h3>
<pre><code># Full-spectrum reconnaissance
bbot -t example.com -p kitchen-sink</code></pre>
<p>
When you need the full picture, this command combines subdomain enumeration,
email discovery, cloud bucket identification, port scanning, web analysis, and
vulnerability scanning with nuclei.
</p>
<p>
This post is just scratching the surface. For more detailed information, check
out the
<a href="proxy.php?url=https://github.com/blacklanternsecurity/bbot"
>official BBOT repository</a
>
and
<a href="proxy.php?url=https://www.blacklanternsecurity.com/bbot/">documentation</a>.
</p>[email protected] (gcollazo)Wed, 15 Jan 2025 00:00:00 -0000https://gcollazo.com/essential-bbot-commands-for-reconDemo Days: A Simpler Approach to Engineering Leadershiphttps://gcollazo.com/demo-days<p>Weekly demo days started as an experiment: a regular time when anyone can demo anything to the rest of the team. No
slides allowed, no status reports, just a live demo.</p>
<p>The format was straightforward: 3 to 4 engineers would each get up to 15 minutes to demonstrate
their
work in progress or whatever they like. Attendees can ask questions and the facilitator is responsible for
maintaining a respectful and professional atmosphere.</p>
<p>While most demos focused on current work projects, the format was open to anything that might interest the team - a
cool new technology, a competitor's product, a side project, or a game
someone built over the weekend.</p>
<p>This freedom to share whatever they found exciting kept the sessions fun, engaging and low pressure. Despite how
good it
worked and felt, demo days gradually faded from our routine as we defaulted back to standard "agile" ceremonies.</p>
<p>Reading Ken Kocienda's "<a href="proxy.php?url=http://creativeselection.io">Creative Selection</a>" about Apple's product
development process
reignited my interest in demo days. His book showed me how this approach could scale beyond small teams. Kocienda
describes their process eloquently:</p>
<blockquote> "We always started small, with some inspiration. We made demos. We mixed in feedback. We listened to
guidance from
smart colleagues. We blended in variations. We honed our vision. We followed the initial demo with another and then
another. We improved our demos in incremental steps. We evolved our work by slowly converging on better versions of
the vision. Round after round of creative selection moved us step by step from the spark of an idea to a finished
product."
</blockquote>
<p>Here was one of the world's most successful technology companies using regular demos to drive their product design
and as a cornerstone of their
engineering culture.</p>
<p>Then I recently learned about Oxide Computer Company's <a href="proxy.php?url=https://oxide.computer/blog/engineering-culture">experience
with demo days</a>
- a team I deeply respect for their engineering excellence. Oxide's journey with demos particularly resonates with
me. As they describe it:</p>
<blockquote> "...our weekly Demo Friday, an hour-long unstructured session to demo our work for one another. Demo Friday
is such
an essential part of Oxide’s culture that it feels like we have always done it, but in fact it took us nearly two
years into the company’s life to get there: over the spring and summer of 2021, our colleague Sean Klein had
instituted regular demos for the area that he works on (the Oxide control plane), and others around the
company — seeing the energy that came from it — asked if they, too, could start regular demos for their domain. But
instead of doing it group by group, we instituted it company-wide starting in the fall of 2021: an unstructured hour
once a week in which anyone can demo anything."
</blockquote>
<p>Currently, I'm transitioning to doing more work as factional CTO for startup companies, and I've become
increasingly frustrated
with the
endless stream of meetings that seem to define modern engineering management. I miss demo days. Demos feel easy and
fun, and they
produce
high quality feedback and questions that help with implementation and help engineers focus on the
important stuff.
</p>
<p>The benefits I observed went beyond just better code. Engineers started thinking differently about their work when
they knew they'd be showing it to their peers. They naturally prioritized getting to working software faster and
thought more carefully about their implementation choices.</p>
<p>However, I'm also aware of the potential challenges. Teams might over-optimize for demos at the expense of deeper
technical work. Some engineers might feel undue pressure to show progress every week. Without proper facilitation,
demo sessions could devolve into critique sessions that harm team morale.</p>
<p>As I prepare to implement this approach again with my consulting clients, I'm particularly interested in hearing from
other technical leaders who have experience with demo days. How have you scaled this practice in larger
organizations? What challenges did you encounter with distributed teams? How do you balance the demo cadence with
other engineering processes?</p>
<p>If you're using demo days in your organization, I'd love to hear about your experience. What's worked? What hasn't?
How have you adapted the practice for your specific context?</p>
<p><a href="proxy.php?url=mailto:[email protected]">[email protected]</a></p>[email protected] (gcollazo)Wed, 19 Feb 2025 00:00:00 -0000https://gcollazo.com/demo-daysValue Objects en Gohttps://gcollazo.com/go-value-objects<p>
Llevo años experimentando con Value Objects (VO) en varios lenguajes, desde
JavaScript y <a href="proxy.php?url=/debe-ser-imposible-representar-estados-ilegales/">TypeScript</a> hasta Python y, más
recientemente, Go.</p>
<p>
Llevo dos años sumergido en Go por lo que he tenido la necesidad de implementar Value Objects en este lenguaje.
Abajo
comparto el acercamiento que he estado usando recientemente con bastante éxito.
</p>
<p>Esta implementación cumple con todos mis requisitos:</p>
<ul>
<li>Es imposible instanciar un VO inválido</li>
<li>Métodos <code>New*</code> devuelven un <code>error</code></li>
<li>Métodos <code>MustNew*</code> hacen panic si hay un error</li>
<li>Inmutabilidad</li>
<li>Igualdad basada en valor</li>
<li>Encapsulación del comportamiento</li>
</ul>
<p>Para la mayoría de los casos uso el método <code>New*</code> que devuelve un <code>error</code> y me permite
decidir como manejar los casos en que la instanciación falla. El método <code>MustNew*</code> lo uso principalmente
en pruebas, dónde puedo estar seguro que estoy creando un VO válido manualmente y de esta forma evito tener que
manejar
un error que nunca sucederá.</p>
<p>En todo caso es imposible crear un VO usando uno de estos métodos sin que cumpla con todos los requisitos de mi
dominio. Y donde veo que se crea un VO sin usar uno de estos dos métodos puedo rechazar ese código sin pensarlo dos
veces. Aunque esto require un poco de disciplina, es una regla fácil de seguir y hacer cumplir.</p>
<p>Este es un ejemplo sintético de como implemento VO en Go. Es intencionalmente simple para que sea fácil de entender y
aplicar en otros codebases. Aunque los patrones y el diseño del VO está bien y correcto esto no es una
implementación válida de <code>Amount</code> así que no hagas copy y paste y lo uses en producción, te va a doler.
</p>
<pre><code>package amount
import (
"errors"
"fmt"
)
type Currency string
const (
USD Currency = "USD"
EUR Currency = "EUR"
)
func (c Currency) IsValid() bool {
validCurrencies := map[Currency]bool{
USD: true,
EUR: true,
}
return validCurrencies[c]
}
type Amount struct {
value int
currency Currency
}
func NewAmount(value int, currency Currency) (Amount, error) {
if value < 0 {
return Amount{}, errors.New("amount must be positive")
}
if value > 1_000_000 {
return Amount{}, errors.New("amount must be less than or equal to 1,000,000")
}
if !currency.IsValid() {
return Amount{}, errors.New("invalid currency")
}
return Amount{
value: value,
currency: currency,
}, nil
}
func MustNewAmount(value int, currency Currency) Amount {
a, err := NewAmount(value, currency)
if err != nil {
panic(err)
}
return a
}
func (a Amount) Value() int {
return a.value
}
func (a Amount) Currency() Currency {
return a.currency
}
func (a Amount) String() string {
switch a.currency {
case USD:
return fmt.Sprintf("$%d", a.value)
case EUR:
return fmt.Sprintf("%d€", a.value)
default:
return fmt.Sprintf("%d %s", a.value, a.currency)
}
}
func (a Amount) Equal(other Amount) bool {
return a.value == other.value && a.currency == other.currency
}
func (a Amount) ConvertTo(newCurrency Currency) (Amount, error) {
if a.currency == newCurrency {
return a, nil
}
// Assume 1 USD = 0.85 EUR
conversionRate := 0.85
if a.currency == EUR {
conversionRate = 1 / conversionRate
}
newValue := int(float64(a.value) * conversionRate)
return NewAmount(newValue, newCurrency)
}
func (a Amount) Add(other Amount) (Amount, error) {
if a.currency != other.currency {
return Amount{}, errors.New("cannot add amounts with different currencies")
}
return NewAmount(a.value+other.value, a.currency)
}
func (a Amount) Subtract(other Amount) (Amount, error) {
if a.currency != other.currency {
return Amount{}, errors.New("cannot subtract amounts with different currencies")
}
return NewAmount(a.value-other.value, a.currency)
}
</code></pre>
<p>Siento que si un veterano de Go mira este código no le encantaría. Pero para mí que llevo solo dos años en Go se
siente natural y creo que encaja bien con los otros APIs que he visto en el "standard library". Si tienes una mejor
forma de implementar VOs en Go me gustaría verla.</p>[email protected] (gcollazo)Tue, 25 Mar 2025 00:00:00 -0000https://gcollazo.com/go-value-objectsAQUI.PR: Un nuevo newsletter que está cambiando la forma de consumir noticias en Puerto Ricohttps://gcollazo.com/aquipr<p>Recientemente lanzamos <a
href="proxy.php?url=https://aqui.pr?utm_source=gcollazo.com&utm_medium=web&utm_campaign=aquipr">aqui.pr</a>,
un proyecto que me tiene super entusiasmado y
del que quería compartir un poco
más.</p>
<p>Todos lo hemos vivido, entras a un site de noticias para leer algo que te interesa y te bombardean con anuncios,
videos que se reproducen solos y popups que se meten en el medio. La experiencia es tan frustrante que muchos
terminamos
abandonando antes de leer lo que queríamos o nos vemos obligados a usar "ad blockers" o el "reader mode" que muchos
navegadores tienen disponible para lidiar con este problema.</p>
<p>Así nació aqui.pr, un newsletter diario que llega a tu correo a las 6:30 de la mañana con las 10 noticias más
importantes y comentadas de Puerto Rico. Sin fastasmeo, sin clickbait, sin publicidad invasiva.</p>
<h3>¿Cómo funciona?</h3>
<p>Lo que hace diferente a aqui.pr es su modelo híbrido que combina lo mejor del periodismo tradicional con las
herramientas de inteligencia artificial:</p>
<ol>
<li>Nuestros editores seleccionan diariamente las 10 noticias más relevantes.</li>
<li>Para cada noticia, recopilamos información de hasta tres fuentes periodísticas distintas.</li>
<li>Utilizamos IA para resumir ese contenido extenso en párrafos concisos y claros.</li>
<li>Los editores revisan, pulen y verifican cada resumen antes de publicarlo.</li>
</ol>
<p>Ese proceso nos permite ofrecerte en cinco minutos toda la información que necesitas para estar al día con lo que
ocurre en la isla.</p>
<h3>Respetando al lector</h3>
<p>No es ningún secreto que el modelo de negocio de los medios digitales está roto. La dependencia de clicks y
publicidad intrusiva ha creado una experiencia horrible para los lectores.</p>
<p>En aqui.pr apostamos por un enfoque diferente:</p>
<ol>
<li>Sin clickbait: No necesitamos titulares sensacionalistas porque no dependemos de que hagas click.</li>
<li>Respeto por tu tiempo: Resumimos lo importante para que estés informado en minutos.</li>
<li>Publicidad no intrusiva: Sí, tenemos anuncios (tres por newsletter), pero están diseñados para no interrumpir tu
lectura.
</li>
<li>Patrocinios voluntarios: Por solo $3 mensuales puedes apoyar este proyecto y ayudarnos a seguir creciendo.</li>
</ol>
<h3>Los resultados hasta ahora</h3>
<p>Aunque hoy es el lanzamiento oficial, llevamos varias semanas haciendo pruebas y puliendo el producto. Estas
primeras
semanas han sido increíbles. El feedback que hemos recibido confirma lo que sospechábamos: la gente
quiere estar informada, pero no quiere lidiar con lo difícil y fragmentado que se ha vuelto consumir noticias.</p>
<p>Muchos suscriptores nos comentan que ahora se sienten más preparados para sus conversaciones diarias y más conectados
con lo que pasa en Puerto Rico—especialmente aquellos boricuas que viven fuera de la isla.</p>
<p>Suscríbete
gratis visitando <a
href="proxy.php?url=https://aqui.pr?utm_source=gcollazo.com&utm_medium=web&utm_campaign=aquipr">aqui.pr</a>.</p>[email protected] (gcollazo)Mon, 07 Apr 2025 00:00:00 -0000https://gcollazo.com/aquiprTaking on New Development Workhttps://gcollazo.com/taking-on-new-development-work<p>It's been a while since I've shared any professional updates here.</p>
<p>As some of you might know, I stepped down as CTO of Gasolina Móvil (now Alias Payments) in 2022 after more than
seven years building what became one of Puerto Rico's largest mobile payment platforms. What started as a simple
idea (let people pay for gas with an app) turned into a complex fintech operation spanning multiple countries, with
custom hardware, payment processing, and some of the most stringent security requirements I've ever worked with.</p>
<p>Those years were an incredible learning experience. I went from knowing very little about cybersecurity to becoming
obsessed with it. Going through our first PCI compliance audit was almost traumatic, now it's just routine. I
learned to scale teams, handle customer service at enterprise level, and most importantly, how to build secure
systems when the stakes are real, when you're processing actual payments and handling real customer data.</p>
<p>Now I'm also in a transition period. After spending so many years focused on one
product, I'm exploring what comes next. I'm taking my time to figure it out, whatever I choose will likely be a
long-term commitment, so I don't want to rush.</p>
<p>In the meantime, I'm doing software development work and some consulting.</p>
<blockquote>Over the past two years, I've worked on custom software development for a large construction company,
product design
and development for a nonprofit, and SDLC consulting for a data analytics company. I've also helped a fintech
startup with PCI DSS compliance, an insurance startup with SOC 2, and a SaaS company with ISO 27001
implementation.
</blockquote>
<p>I currently have time available to take on new clients, so now is a great time to reach out if you need help
building software. <strong>My primary focus is hands-on software development, backend and frontend work in Python,
Go, and TypeScript.</strong> But I have some experience in a bunch of other technologies. I want to write code
and ship products alongside other teams, or help
entrepreneurs
create and launch their product idea.</p>
<p>I can also consult on DevSecOps, cloud architecture, and secure development lifecycle practices when needed. I've
been through the trenches of building payment systems, implementing security compliance frameworks, and scaling
teams under pressure.</p>
<p>We can talk about your architectural decisions, security posture, development processes, or dive deep into specific
technical challenges. Whether you're a startup trying to build secure-by-design systems or an established company
dealing with technical debt and compliance requirements, I enjoy sharing what I've learned from building systems
that handle real money and real security threats.</p>
<p>The format is flexible, we can do a single consultation call to start, or work together over several sessions if it
makes sense. This will probably work best for technical leaders, CTOs, or teams dealing with security and scaling
challenges.</p>
<p>If this sounds interesting, drop me an email at <a href="proxy.php?url=mailto:[email protected]">[email protected]</a>, and I'll
get back to you if I can help.</p>
<p>¡Gracias!</p>[email protected] (gcollazo)Wed, 11 Jun 2025 00:00:00 -0000https://gcollazo.com/taking-on-new-development-workTomando Nuevos Proyectos de Softwarehttps://gcollazo.com/tomando-nuevos-proyectos-de-software<p>Ha pasado tiempo desde que he compartido actualizaciones profesionales por aquí.</p>
<p>Como algunos ya saben, dejé mi posición como CTO de Gasolina Móvil (ahora Alias Payments) en el 2022 después de más
de siete años construyendo lo que se convirtió en una de las plataformas de pagos móviles más grandes de Puerto
Rico. Lo que empezó como una idea simple, que la gente pueda pagar la gasolina con un app, se convirtió en una
operación fintech compleja que abarca múltiples países, con hardware custom, procesamiento de pagos, y
algunos de los requisitos de seguridad más estrictos que he trabajado.</p>
<p>Esos años fueron una experiencia de aprendizaje increíble. Pasé de saber muy poco sobre ciberseguridad a
obsesionarme con el tema. Pasar por nuestra primera auditoría de cumplimiento PCI fue casi traumático, ahora se
siente rutinario. Aprendí a escalar equipos, manejar servicio al cliente, y lo
más importante, cómo construir sistemas seguros cuando los riesgos son reales, cuando estás procesando pagos de
verdad y manejando datos reales de clientes.</p>
<p>Ahora estoy en un período de transición. Después de tantos
años enfocado en un solo producto, estoy explorando qué viene después. Me estoy tomando mi tiempo para decidir, lo
que sea que escoja probablemente será un compromiso a largo plazo, así que no me quiero apurar.</p>
<p>Mientras tanto, estoy haciendo trabajo de desarrollo de software y algo de consultoría.</p>
<blockquote>Durante los últimos dos años, he trabajado en desarrollo de software "custom" para una compañía grande
de
construcción, diseño de productos y desarrollo para una nonprofit, y consultoría de SDLC para
una compañía de análisis de datos. También he ayudado a un startup fintech con cumplimiento PCI DSS, un startup de
seguros con SOC 2, y una compañía SaaS con implementación de ISO 27001.
</blockquote>
<p>Actualmente, tengo tiempo disponible para tomar nuevos clientes, así que ahora es un buen momento para contactarme si
necesitas ayuda construyendo software. <strong>Mi enfoque principal es desarrollo de software, trabajo de
backend y
frontend en Python, Go y TypeScript.</strong> Pero tengo experiencia en un montón de otras tecnologías. Quiero
escribir código y lanzar productos junto a
otros equipos, o
ayudar emprendedores a crear y lanzar su idea de producto.</p>
<p>También puedo hacer consultoría en DevSecOps, arquitectura de "cloud", y prácticas de "secure software development
lifecycle" cuando sea necesario. He pasado por las trincheras construyendo sistemas de pagos, implementando marcos
de
cumplimiento de seguridad, y escalando equipos bajo presión.</p>
<p>Podemos hablar sobre tus decisiones arquitectónicas, postura de seguridad, procesos de desarrollo, o profundizar en
desafíos técnicos específicos. Ya sea que tengas un startup tratando de construir sistemas seguros desde el diseño o
una compañía establecida lidiando con deuda técnica y requisitos de cumplimiento, disfruto compartir lo que he
aprendido construyendo sistemas que manejan dinero real y amenazas de seguridad reales.</p>
<p>El formato es flexible, podemos hacer una llamada de consultoría para empezar, o trabajar juntos durante
varias sesiones si tiene sentido. Esto probablemente funcionará mejor para líderes técnicos, CTOs, o equipos
lidiando con desafíos de seguridad y escalabilidad.</p>
<p>Si esto te suena interesante, envíame un email a <a href="proxy.php?url=mailto:[email protected]">[email protected]</a>, y te
respondo si puedo ayudar.</p>
<p>¡Gracias!</p>[email protected] (gcollazo)Wed, 11 Jun 2025 00:00:00 -0000https://gcollazo.com/tomando-nuevos-proyectos-de-software