Uno de los casos de uso en los que mejor se desenvuelve Go es en una arquitectura de microservicios.
Para explotar lo mejor posible la arquitectura de microservicios lo mejor es utilizar contenedores (docker, k8s, etc…). Si hablamos de producción esto es prácticamente necesario si queremos escalar cada microservicio de forma asimétrica, etc.
Pero a la hora de desarrollar es también muy cómodo. Podemos por ejemplo orquestar estos contenedores con un sencillo archivo de docker-compose.yml y así gestionar los microservicios, las bases de datos que necesitan, etc.
Pre-requisitos
Antes de empezar necesitarás tener instalado en tu sistema:
- Docker
- Docker Compose
- Go
Si usas Windows o Mac lo más sencillo es que utilices Docker Desktop. Si usas linux, puedes revisar este tutorial sobre como instalar docker (community edition) y docker compose en linux
Si no tienes Go, puedes descubrir como instalarlo en este tutorial
Creando el proyecto en Go
Para demostrar como desplegar en Docker crearemos una aplicación sencilla que simplemente ejecute un servidor http en el puerto 8080.
mkdir go-docker
cd go-docker
go mod init yeraycat/go-docker
touch main.go
Dentro del archivo crearemos una función main
simple que ponga el servidor HTTP a la escucha y devuelva un mensaje cuando se le envíe una petición.
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Holi")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Ejecutando nuestro contenedor con un comando
Llegados a este punto podríamos simplemente ejecutar nuestra aplicación en un contenedor con docker run
y la imagen oficial de golang
.
docker run -v "$PWD":/app -w /app -p 8080:8080 golang go run .
Esto es una opción aceptable para ejecutarlo en un entorno local de desarrollo, al menos si nuestra aplicación es lo único que tenemos que ejecutar. Pero si dependemos de una base de datos o nuestra aplicación está compuesta de varios microservicios que se ejecutan en distintos contenedores, se vuelve más complicado gestionarlo con comandos de docker.
Docker compose para entornos de desarrollo multi-contenedor
Docker Compose permite gestionar de forma agrupada varios contenedores definidos por un archivo .yml
.
Primero crearemos un archivo docker-compose.yml
en el directorio del proyecto. Vamos a aprovechar también para añadir un contenedor para la base de datos que ejecute MySQL.
version: '3.1'
services:
db:
image: mysql
command: --default-authentication-plugin=mysql_native_password
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: rootexample
MYSQL_DATABASE: godocker
MYSQL_USER: godocker
MYSQL_PASSWORD: example
app:
image: golang
ports:
- 8080:8080
volumes:
- .:/app
depends_on:
- db
command: >
bash -c "sleep 2 && cd /app && go run ."
Voy a crear un archivo create-table.sql
para importar en la base de datos, para así crear la tabla y los datos iniciales:
DROP TABLE IF EXISTS message;
CREATE TABLE message
(
id INT AUTO_INCREMENT NOT NULL,
content VARCHAR(128) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO message (content) VALUES ('Holi');
Para importarlo, voy a levantar los contenedores primero, con el comando docker-compose up
y luego copio el archivo al contenedor de la base de datos. Después ejecuto bash en el contenedor de la base de datos para importar el archivo.
docker cp ./create-table.sql go-docker_db_1:/create-table.sql
docker-compose exec db bash
# En el contenedor
mysql -u root -p godocker < /create-table.sql
Ahora vamos a cambiar el código para que se conecte a la base de datos. Entender este código no es parte del tutorial realmente, pero lo publico para que podáis seguir el ejemplo en vuestra máquina.
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"github.com/go-sql-driver/mysql"
)
var db *sql.DB
type Message struct {
ID int64
Content string
}
func handler(w http.ResponseWriter, r *http.Request) {
msg, err := messageByID(1)
if err != nil {
log.Fatal(err)
fmt.Fprint(w, "Message not found")
} else {
fmt.Fprintf(w, "%s", msg.Content)
}
}
func messageByID(id int64) (Message, error) {
var msg Message
row := db.QueryRow("SELECT * FROM message WHERE id = ?", id)
if err := row.Scan(&msg.ID, &msg.Content); err != nil {
if err == sql.ErrNoRows {
return msg, fmt.Errorf("messageById %d: no such message", id)
}
return msg, fmt.Errorf("messageById %d: %v", id, err)
}
return msg, nil
}
func main() {
config := mysql.Config{
User: "godocker",
Passwd: "example",
Net: "tcp",
Addr: "db:3306",
DBName: "godocker",
AllowNativePasswords: true,
}
var err error
db, err = sql.Open("mysql", config.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connected to database!")
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Para terminar, añadiremos las dependencias que estamos usando en el nuevo código:
go mod tidy
Tras todo esto, ya podemos levantar nuestros contenedores y empezarán a funcionar.
docker-compose up
Si vamos a nuestro navegador, a http://localhost:8080
podremos ver el mensaje recuperado de base de datos.
Esto es solo el principio y nos servirá para desarrollar usando contenedores, pero estamos utilizando go run para ejecutar nuestra aplicación.
En un próximo tutorial veremos como preparar una imagen lista para desplegar nuestra aplicación en producción, que primero compile el código y luego lo ejecute.
Comments
No comments yet. Be the first to react!