# NATS APP Lectura de datos de sensores en un dispositivo IoT. Prueba técnica para optar por el puesto de programador Go. ## TL:DR - make lazy-start - abre terminal y escribe: `nats sub sensors.data.*` ## Requisitos previos - Docker - NATS CLI - Make, si prefieres la comodidad de usar Makefile ## Comandos ### Registrar un sensor - Campos obligatorios: `sensor_id` y `sensor_type`. - Campos opcionales: `sampling`, `thresholdabove` y `thresholdbelow`. `nats req sensors.register '{ "sensor_id": "sensor-001", "sensor_type": "temperature", "sampling": 3600, "thresoldabove": 50.0, "thresoldbelow": -10.0 }'` ### Actualizar configuración sensor - Campo obligatorio: `sensor_id` y la presencia de al menos un parámetro. `nats req sensors.update '{ "sensor_id": "sensor-001", "sensor_type": "temperature", "sampling": 10, "thresoldabove": 100.0, "thresoldbelow": -15.0 }'` ### Obtener información de un sensor - Campo obligatorio: `sensor_id`. `nats req sensors.get '{ "sensor_id": "sensor-001" }'` ### Obtener valores de un sensor - Campo obligatorio: `sensor_id`. - Campos opcionales: `from` y `to` en formato RFC3339. Si no se especifican, se toman los últimos 7 días. `nats req sensors.values.get '{ "sensor_id": "sensor-001", "from": "2025-10-03T00:00:00Z", "to": "2025-10-10T23:59:59Z" }'` ### Obtener listado de sensores No hay _payload_, pero hay que poner comillas dobles o si no se queda esperando una entrada de datos. `nats req sensors.list ""` ### Suscribirse a un sensor `nats sub sensors.data.sensor-001` ### Suscribirse a todos los sensores `nats sub sensors.data.*` ## Consideraciones Hay partes de códigos que son _snippets_ extraídos de una librería de autoría propia. [Repositorio GitHub](https://github.com/zepyrshut/gopher-toolbox). De las cuales son: - El _logger_ usando la _stdlib log/slog_. - La conexión con la base de datos, usando el controlador [pgx](https://github.com/jackc/pgx). ## Bitácora ### Quickstart y toma de contacto con NATS Lo primero que he hecho es un _quickstart_ del proyecto, con lo que siempre o casi siempre pongo en mis experimentos. Y lo siguiente, en lugar de empezar a construir el proyecto como loco he tratado de entender cómo funciona NATS. Al final ha sido muy sencillo, siguiendo esos pasos: 1. Levantar el servidor NATS en Docker 2. Instalar el CLI de NATS 2. Abrir un puñado de terminales, y en un par de ellas escribir: `nats sub "hello"` lo cual significa que se está suscribiendo al canal `hello`. Y en otra escribir: `nats pub "hello" "Hola mundo!"`, lo cual significa que está escribiendo el mensaje `Hola mundo!` en el canal `hello`. ![demo de NATS](./assets/nats-demo-1.png) ### Creación de la aplicación La parte fácil es hacer todo el _boilerplate_, hecho en solo _commit_, que no es lo ideal pero lo dicho, es puro relleno, mucho código ya viene escrito de la librería `gopher-toolbox`. En este punto el proyecto compila pero hay error en tiempo de ejecución por la falta de implementaciones en el repositorio. ### Empezando por el repositorio El almacenamiento de los datos se ha optado por el uso de `TimescaleDB`, una extensión de `PostgreSQL`, el motivo es porque ya he trabajado mucho con ese motor y tengo los _drivers_ ya escritos. Tengo entendido que está optimizado para trabajar con grandes cantidades de datos de series temporales, lo que viene siendo valores de sensores por ejemplo. Por otro lado también hay un sistema de caché muy rudimentario, en memoria que es un mapa de valores. Para el registro de valores y mantener ambos se ha usado el patrón decorador que bajo un mismo _struct_ se incluye las dos implementaciones y se llama a ambas funciones. Desde la capa servicios sólo tiene que llamar al decorador sin saber los detalles de la implementación. ### Continuamos con los servicios Con el repositorio sin implementar, se puede realizar los servicios. En ese proyecto ha quedado muy básico, sirviendo solamente de enlace entre los controladores y el repositorio. Cuando se creó el _broker_ de NATS, inicialmente fue para crear una interfaz de mensajería donde se pudiese manejar _websockets_, _SSE_ y otras mensajerías pero se consideró que había otras prioridades. Así se quedó. ### Y finalmente los controladores Ahí tuve muchas dudas con el entendimiento de NATS, estaba muy arraigado en el patrón REST, y cambiar la mentalidad costó un poco, al final mirando un poco la documentación me quedé con los conceptos clave: 1. Está basado en asuntos, los canales se crean de forma jerárquica. 2. Cuando se hace un _subscribe_, se suma a un canal del asunto dado. 3. Para escribir en el canal, hay que hacer el _publish_. 4. Finalmente para solicitar un recurso, está el _request_. Esto es todo, entonces los controladores de la entidad _sensors_ están constituidos por una serie de _endpoints_ haciendo las acciones que se solicita. ## LLMS He usado Claude para la toma de decisiones y ayuda con el _boilerplate_, que no es poca cosa, además también se ha usado para la generación de las pruebas unitarias, además de resolución de algunos problemas complejos. ## Generadores de código Existen generadores de código para Golang, de hecho, se fomenta su desarrollo, hay un artículo interesante de Rob Pike [hablando sobre ello](https://go.dev/blog/generate). Muchas de las herramientas son muy interesantes usarlas ya que acelera mucho la generación de código repetitivo. Da mas confianza usar esas herramientas que la IA. Para las consultas SQL con seguridad de tipos, existe la herramienta [sqlc](https://sqlc.dev/). Para ese proyecto sólo se ha usado [GoMock](https://github.com/uber-go/mock), mantenida por Uber y sirve para usar la interfaz `Repository` sin usar una base de datos real. Por otro lado, hubiese sido interesante incorporar [ginkgo](https://onsi.github.io/ginkgo/), es un marco de trabajo de pruebas unitarias usando un lenguaje de dominio específico (DSL). > En lugar de escribir una prueba así: `Test_Validate(t *testing.T) { ... }` > se puede hacer de la siguiente manera: `var _ Describe("Models", func () { ... })`, > y dentro del cuerpo se describen las pruebas a realizar. No se ha incorporado porque hay que instalar la herramienta que ejecutan las pruebas, y no quería correr el riesgo de que no funcionase en otro equipo o no diesen los resultados esperados. Que se podría haber usado un contenedor Docker, sí, pero la prueba no consiste en eso.