{"id":833,"date":"2024-01-22T14:08:58","date_gmt":"2024-01-22T19:08:58","guid":{"rendered":"https:\/\/80bits.blog\/?p=833"},"modified":"2024-01-23T16:45:09","modified_gmt":"2024-01-23T21:45:09","slug":"mqtt-en-go-consumidor-que-almacena-mensajes-en-mongodb","status":"publish","type":"post","link":"https:\/\/80bits.blog\/index.php\/2024\/01\/22\/mqtt-en-go-consumidor-que-almacena-mensajes-en-mongodb\/","title":{"rendered":"MQTT en Go: Consumidor que Almacena Mensajes en MongoDB"},"content":{"rendered":"\n<p>En estos d\u00edas, he estado migrando algunos de mis scripts de Python a ejecutables escritos en Go. \u00bfLa raz\u00f3n? Para mantener mi habilidad en Go y, en \u00faltima instancia, porque Python nunca termin\u00f3 de convencerme por completo. <a href=\"https:\/\/80bits.blog\/index.php\/2023\/12\/13\/flashmq-como-instalar-un-servidor-mqtt-en-ubuntu-22-04\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Aprovechando que he configurado mi propio servidor MQTT<\/a>, he decidido trasladar un script que previamente recib\u00eda mensajes en varios t\u00f3picos y los reenviaba a otra ubicaci\u00f3n. Ahora, en lugar de reenviarlos, los recibo y los almaceno en una base de datos MongoDB.<\/p>\n\n\n\n<p>Uno de los requisitos que me impuse fue que el ejecutable se pudiera lanzar como un servicio utilizando <a href=\"https:\/\/es.wikipedia.org\/wiki\/Systemd\">systemd<\/a> de la siguiente manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl start mqttconsumertomongo.service\n<\/code><\/pre>\n\n\n\n<p>Para lograr esto, el ejecutable debe leer el archivo de configuraci\u00f3n pas\u00e1ndole la ruta como par\u00e1metro del comando, de la siguiente manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mqttconsumertomongo.exe \/path\/to\/settings.json\n<\/code><\/pre>\n\n\n\n<p>Esta configuraci\u00f3n permite una integraci\u00f3n fluida con systemd y asegura que el programa funcione seg\u00fan lo previsto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"configuraci\u00f3n-del-servicio-systemd\">Configuraci\u00f3n del servicio systemd<\/h2>\n\n\n\n<p>El archivo para lanzar el ejecutable como servicio es realmente muy simple, como pueden ver a continuaci\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Unit]\nDescription=Consumer mqtt to save mongodb\nAfter=network.target\n\n&#91;Service]\nExecStart=\/path\/to\/mqttconsumertomongo.exe \/path\/to\/settings.json\nType=simple\nRestart=on-failure\n\n&#91;Install]\nWantedBy=multi-user.target\n<\/code><\/pre>\n\n\n\n<p><a href=\"https:\/\/80bits.blog\/index.php\/2024\/01\/20\/systemd-crear-un-archivo-de-servicio-systemd\/\">Si no sabes c\u00f3mo configurar y lanzar un servicio systemd, puedes consultar esta publicaci\u00f3n que escrib\u00ed.<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"c\u00f3digo\">C\u00f3digo<\/h2>\n\n\n\n<p>El c\u00f3digo fuente es bastante sencillo, sin m\u00e1s pretensiones que realizar la tarea necesaria: recibir datos y guardarlos en una base de datos MongoDB.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"configuraci\u00f3n-settingsjson\">Configuraci\u00f3n settings.json<\/h3>\n\n\n\n<p>El c\u00f3digo est\u00e1 configurado para leer un archivo llamado &#8220;settings.json&#8221; en la misma ubicaci\u00f3n por defecto o para recibir la ruta del archivo como argumento al momento de ejecutarlo, como mencion\u00e9 anteriormente.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"ejemplo\">Ejemplo<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"mqttSettings\": {\n        \"mqttBrokerURL\": \"mqtt:\/\/localhost:1883\",\n        \"user\": \"testuser\",\n        \"password\": \"testpassword\",\n        \"clientId\": \"testclient\",\n        \"topics\": {\n            \"topic1\": 1,\n            \"topic2\": 2\n        }\n    },\n    \"mongodbSettings\": {\n        \"url\": \"mongodb:\/\/localhost:27017\",\n        \"authSource\": \"admin\",\n        \"username\": \"testuser\",\n        \"password\": \"testpassword\",\n        \"dbName\": \"testdb\",\n        \"collection\": \"testcollection\"\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Una vez configurado el archivo de conexiones y t\u00f3picos, este se lee desde <code>main.go<\/code> y se inician las configuraciones necesarias. La lectura y transformaci\u00f3n del archivo a una estructura se realiza en el archivo <code>settings\/read_settings.go<\/code><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"flujo-del-programa\">Flujo del programa<\/h3>\n\n\n\n<p>En el archivo <code>main.go<\/code>se leen las configuraciones para luego ejecutar tres funciones que se encuentran en el archivos <code>services\/srv.go<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>services.MqttConnection<\/code>: Funci\u00f3n donde se lleva acabo la conexi\u00f3n hacia un servidor MQTT. <\/li>\n\n\n\n<li><code>services.MongoConnection<\/code>: Funci\u00f3n para conectarse a una base de datos MongoDB.<\/li>\n\n\n\n<li><code>services.MqttSubscribe<\/code>: La funci\u00f3n que toma la lista de t\u00f3picos y se suscribe a ellos es capaz de recibir comodines del tipo <code>topic\/#<\/code>. El valor num\u00e9rico representa el nivel de calidad de servicio (QoS) para la suscripci\u00f3n.<\/li>\n<\/ul>\n\n\n\n<p>Dentro del archivo <code>srv.go<\/code>, existe una funci\u00f3n llamada <code>MessageHandler<\/code>. Esta funci\u00f3n se encarga de manejar los mensajes MQTT que se reciben en los t\u00f3picos suscritos y tambi\u00e9n gu\u00e1rdalos a la base de datos.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func MessageHandler(client mqtt.Client, msg mqtt.Message) {\n\n    var data = map&#91;string]interface{}{}\n    err := json.Unmarshal(msg.Payload(), &amp;data)\n\n    if err != nil {\n        data&#91;\"topic\"] = msg.Topic()\n        data&#91;\"payload\"] = string(msg.Payload())\n        mongoClient.Database(settingsValues.MongodbSettings.DbName).Collection(settingsValues.MongodbSettings.Collection).InsertOne(context.Background(), data)\n\n        return\n    }\n    data&#91;\"topic\"] = msg.Topic()\n    collectionValue, isCollection := data&#91;\"collection\"]\n    if isCollection {\n        collection := fmt.Sprintf(\"%v\", collectionValue)\n        mongoClient.Database(settingsValues.MongodbSettings.DbName).Collection(string(collection)).InsertOne(context.Background(), data)\n    } else {\n        mongoClient.Database(settingsValues.MongodbSettings.DbName).Collection(settingsValues.MongodbSettings.Collection).InsertOne(context.Background(), data)\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>La l\u00f3gica es sencilla: cuando llega un mensaje al t\u00f3pico suscrito, se verifica si es de tipo JSON. Si no lo es, se genera un mapa (JSON) con dos propiedades: <code>topic<\/code> y <code>payload<\/code>, y se guarda en la colecci\u00f3n configurada en <code>mongodbSettings.collection<\/code>.<\/p>\n\n\n\n<p>En caso de que sea un texto en formato JSON, se verifica la existencia de una propiedad llamada <code>collection<\/code>. Si esta propiedad existe, el mensaje se guarda en la colecci\u00f3n especificada; de lo contrario, se almacena en la colecci\u00f3n configurada por defecto.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"test-unitarios\">Test Unitarios<\/h3>\n\n\n\n<p>El c\u00f3digo incluye algunos test unitarios, aunque no son extensos. En realidad, los cre\u00e9 principalmente para realizar pruebas sencillas sobre c\u00f3mo hacer mocks para Mongodb y Mqtt, y no tienen la intenci\u00f3n de cubrir la mayor\u00eda del c\u00f3digo ni de ser exhaustivos. Fue m\u00e1s bien un experimento que podr\u00eda ser \u00fatil si necesitas ver c\u00f3mo se crearon los mocks para conexiones externas utilizando interfaces y extensiones de estructuras (similar a la herencia).<\/p>\n\n\n\n<p>Las interfaces para estos mocks se encuentran en la carpeta <code>interfaces<\/code>, y los \u00fanicos archivos de prueba son aquellos que tienen el prefijo <code>_test.go<\/code>, siguiendo las convenciones del lenguaje.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"makefile\">Makefile<\/h3>\n\n\n\n<p>En la mayor\u00eda de mis proyectos, suelo crear archivos <a href=\"https:\/\/es.wikipedia.org\/wiki\/Make\">Makefile<\/a> para simplificar el proceso de ejecuci\u00f3n de comandos y par\u00e1metros repetitivos. Esto facilita enormemente la gesti\u00f3n y automatizaci\u00f3n de tareas en el desarrollo y la construcci\u00f3n del proyecto.<\/p>\n\n\n\n<p>En el archivo Makefile solo tengo cuatro targets:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>build.linux<\/code><\/li>\n\n\n\n<li><code>build.windows<\/code><\/li>\n\n\n\n<li><code>build.darwin<\/code><\/li>\n\n\n\n<li><code>run.test<\/code><\/li>\n<\/ul>\n\n\n\n<p>Todos los comandos que dicen &#8220;Build&#8221; son utilizados para compilar el c\u00f3digo en el sistema operativo correspondiente, y el nombre del archivo resultante ser\u00e1 <code>mqttconsumertomongo.exe<\/code>.<\/p>\n\n\n\n<p>Nota: Aunque es posible compilar para Windows desde un sistema que no sea Windows, los archivos Makefile no funcionar\u00e1n en Windows a menos que se utilice Windows Subsystem for Linux (WSL).<\/p>\n\n\n\n<p>El \u00faltimo objetivo (target) se utiliza \u00fanicamente para ejecutar las pruebas unitarias, como mencion\u00e9 anteriormente.<\/p>\n\n\n\n<p>Todas las compilaciones incluyen ciertos flags para reducir el tama\u00f1o del archivo resultante:<\/p>\n\n\n\n<p><code>go build -ldflags=\"-s -w\" -o mqttconsumertomongo.exe main.go<\/code><\/p>\n\n\n\n<p><code>ldflags<\/code> se refiere a linker flags y los flags que se le pasan son <code>-s<\/code> y <code>-w<\/code>. El primero elimina la tabla de s\u00edmbolos y la informaci\u00f3n de depuraci\u00f3n, y el segundo elimina la informaci\u00f3n de depuraci\u00f3n <a href=\"https:\/\/dwarfstd.org\/\">DWARF<\/a>. Esto resulta en la reducci\u00f3n de un archivo de 12MB a aproximadamente 8MB.<\/p>\n\n\n\n<p> <code>-trimpath<\/code> durante la compilaci\u00f3n en Go se utiliza para eliminar las rutas de acceso absolutas de los archivos en la informaci\u00f3n de depuraci\u00f3n incluida en el binario final.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/genitalico\/mqttconsumertomongo\" target=\"_blank\" rel=\"noopener\" title=\"\">El c\u00f3digo pueden clonarlo desde este repositorio.<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>En estos d\u00edas, he estado migrando algunos de mis scripts de Python a ejecutables escritos en Go. \u00bfLa raz\u00f3n? Para mantener mi habilidad en Go y, en \u00faltima instancia, porque Python nunca termin\u00f3 de convencerme por completo. Aprovechando que he configurado mi propio servidor MQTT, he decidido trasladar un script que previamente recib\u00eda mensajes en [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":847,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[80,20,66,49,65,67],"tags":[81,82,73],"class_list":["post-833","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-golang","category-lenguajes-de-programacion","category-linux","category-macos","category-sistemas-operativos","category-windows","tag-go","tag-golang","tag-linux"],"jetpack_featured_media_url":"https:\/\/80bits.blog\/wp-content\/uploads\/2024\/01\/mqtt-mongodb-golang-animals.jpg","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/833","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/comments?post=833"}],"version-history":[{"count":11,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/833\/revisions"}],"predecessor-version":[{"id":854,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/833\/revisions\/854"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media\/847"}],"wp:attachment":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media?parent=833"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/categories?post=833"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/tags?post=833"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}