nats-app/README.md
2025-10-10 03:09:34 +02:00

213 lines
7.5 KiB
Markdown

# 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 registra cambios en
ambas partes. 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.
### El simulador
Basada en _gorutinas_ y canales, cuando se inicia el simulador, se crea un canal
para detener simuladores que están en ejecución para su actualización o
detención.
Cuando se registra un nuevo sensor, está la función SimulateSensor, que se
inicia como una _gorutina_ y usa el `SamplingInterval` para el canal `ticker`,
así llamar a `generateData` cada vez que toque.
Una vez que el dato está generado se hace una publicación al asunto _sensor.data_,
que al mismo tiempo, el _handler_ registerData lo captura al estar registrado
al mismo asunto _sensor.data_.
## Pruebas
La realización de pruebas unitarias de lo que son los controladores de NATS me
han sido imposible hacerlas en condiciones, podría haber usado Claude pero es
que no daba pie con bola y no entendía nada, así que por la máxima transparencia
he optado por no incorporarlas.
Las pruebas más interesantes son las de reglas de negocio y validación, lo que
viene a ser los servicios y dominio.
## 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 y otras librerías
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.
También se ha planteado incorporar la librería _testify_, descartado porque para
comprobar si existe el error y algunas comparaciones no era necesario meter una
dependencia más.