179 lines
6.3 KiB
Markdown
179 lines
6.3 KiB
Markdown
# NATS APP
|
|
|
|
Lectura de datos de sensores en un dispositivo IoT. Prueba técnica para optar
|
|
por el puesto de programador Go.
|
|
|
|
## 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`.
|
|
|
|

|
|
|
|
### 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. |