Los programadores tenemos la suerte de tener infinidad de herramientas a nuestro alcance para poder resolver diversos problemas con los que nos vamos encontrando.
En el mundo de las bases de datos, podemos encontrar infinitas posibilidades, como son, mysql, postgresql, sqlite, mongodb, entre otras. Pero hoy os queremos hablar de CockroachDB, CockroachDB ha sido creado por los chicos de Cockroach Labs y la presentan como una evolución a las bases de datos, creada especialmente para el cloud.
Las características principales que posee esta base de datos son:
- Es open source.
- Tienes soporte nativo para JSON.
- Alta disponibilidad.
- Fuerte consitencia de replicación.
- Fácil de usar.
Nosotros no hemos podido usar de momento esta base de datos en un proyecto real, pero eso no quiere decir que no podamos experimentar juntos sus bondades.
Para ello vamos a utilizar nuestro mítico proyecto GopherApi, y modificaremos lo necesario para comenzar a usar CockroachDB.
Instalar CockroachDB
No vamos a entrar en detalles como instalar la base de datos ya que no es la parte que nos incumbe en nuestro artículo para ello os dejamos el link de la documentación oficial sobre la instalación.
Una vez tengamos instalado nuestra base de datos la ejecutaremos.
$ cockroach start --insecure
Si quieres más información de como arrancar * CockroachDB* en tu sistema operativo, podéis ver la documentación oficial.
CockroachDB node starting at 2020-03-07 17:30:10.099783 +0000 UTC (took 1.3s)
...
Ya tenemos todo listo para empezar.
Creando nuestra base de datos
Ahora necesitaremos una base de datos, para ello en otra terminal
nos conectaremos a nuestro servidor recién levantado.
cockroach sql --insecure
Una vez conectados lanzaremos el comando:
create database gopherapi;
Y tendremos un output similar al siguiente:
root@:26257/defaultdb> create database gopherapi;
CREATE DATABASE
Time: 5.063ms
A continuación indicaremos al sistema que queremos utilizar dicha base de datos ya que debemos de crear nuestra tabla.
set database = gopherapi;
Además CockroachDB nos ofrece una webgui donde podemos monitorizar nuestra base de datos, y podremos ir viendo los cambios que hemos ido creando, por defecto estará levantada en localhost:8080
.
Una vez hemos creado nuestra base de datos, si accedemos al panel de Cockroach veremos nuestra base de datos ahí.
Así que ahora vamos a crear nuestra tabla, para ello vamos a recordar la entidad que habíamos creado previamente.
type Gopher struct {
ID string `json:"ID"`
Name string `json:"name,omitempty"`
Image string `json:"image,omitempty"`
Age int `json:"age,omitempty"`
}
A partir de nuestra entidad Gopher
podemos deducir que tendremos la siguiente tabla:
CREATE TABLE gophers (
id STRING(32),
name STRING(100) NOT NULL,
image STRING NULL,
age INT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ,
PRIMARY KEY ("id")
);
Ya tenemos todo lo necesario para empezar a picar, que seguro que ya hay ganas de meternos en faena.
Instalar el driver para CockroachDB
Como cada vez que utilizamos una base de datos en Go necesitamos instalar su driver para poder trabajar con dicha base de datos.
Pero esta vez nos va a pillar por sorpresa, porque el driver que vamos a utilizar es el de Postgres, ya que utiliza el mismo protocolo.
go get -u github.com/lib/pq
Conectando con CockroachDB
Primeramente añadiremos a nuestro fichero de .env
todo lo necesario para realizar nuestra conexión.
COCKROACH_ADDR="postgresql://root@localhost:26257"
COCKROACH_DB=gopherapi
Acto seguido vamos a crear dentro de nuestro directorio storage
un nuevo directorio llamado cockroach
y dentro crearemos un nuevo fichero llamado conn.go
en donde meteremos nuestro helper para inicializar nuestra conexión con la base de datos.
package cockroach
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
func NewConn(addr, db string) (*sql.DB, error) {
conn := fmt.Sprintf("postgresql://%s/%s?sslmode=disable", addr, db)
return sql.Open("postgres", conn)
}
Simplemente hemos creado como deciamos un método helper para poder encapsular toda la lógica que lleva inicializar la conexión con CockroachDB. Si vuestra conexión necesita de ssl, podéis consultar la documentación oficial para revisar como pasar el certificado pertinente.
Lo siguiente que haremos es crear nuestro repositorio, es decir storage/crockoach/repository.go
package cockroach
import (
"context"
"database/sql"
"errors"
_ "github.com/lib/pq"
gopher "github.com/friendsofgo/gopherapi/pkg"
"github.com/openzipkin/zipkin-go"
)
type gopherRepository struct {
db *sql.DB
tracer *zipkin.Tracer
}
// NewRepository creates a crockoach repository with the necessary dependencies
func NewRepository(db *sql.DB, tracer *zipkin.Tracer) gopher.Repository {
return gopherRepository{db: db, tracer: tracer}
}
func (r gopherRepository) CreateGopher(ctx context.Context, g *gopher.Gopher) error {
return errors.New("method not implemented")
}
func (r gopherRepository) FetchGophers(ctx context.Context) ([]gopher.Gopher, error) {
return nil, errors.New("method not implemented")
}
func (r gopherRepository) DeleteGopher(ctx context.Context, ID string) error {
return errors.New("method not implemented")
}
func (r gopherRepository) UpdateGopher(ctx context.Context, ID string, g gopher.Gopher) error {
return errors.New("method not implemented")
}
func (r gopherRepository) FetchGopherByID(ctx context.Context, ID string) (*gopher.Gopher, error) {
return nil, errors.New("method not implemented")
}
De momento no hemos implementado nuestros métodos, eso lo veremos a continuación, primero queremos dejar montado todo el esqueleto para empezar a funcionar con nuestra nueva base de datos.
Para ello hemos creado un constructor
func NewRepository(db *sql.DB, tracer *zipkin.Tracer) gopher.Repository {
return gopherRepository{db: db, tracer: tracer}
}
Este constructor espera la conexión con la base de datos que estableceremos en el main.go
y además el tracer que ya vimos en artículos anteriores.
Ahora deberemos modificar nuestro main.go
para poder empezar a utilizar dicho repositorio en lugar del inmemory
que venimos usando desde el principio.
Dado que por defecto nuestro servidor se inicializa con el repositorio inmemory
, vamos a añadir un nuevo flag para informar de que levantaremos el servidor con CockroachDB.
...
crockoachDB := flag.Bool("crockoach", false, "initialize the api using CrockoachDB as db engine")
...
repo := inmem.NewRepository(gophers, trc)
if *cockroachDB {
repo = newCockroachRepository(trc)
}
func newCockroachRepository(trc *zipkin.Tracer) gopher.Repository {
cockroachAddr := os.Getenv("COCKROACH_ADDR")
cockroachDBName := os.Getenv("COCKROACH_DB")
cockroachConn, err := cockroach.NewConn(cockroachAddr, cockroachDBName)
if err != nil {
log.Fatal(err)
}
return cockroach.NewRepository(cockroachConn, trc)
}
De esta manera tendremos controlado a la hora de levantar nuestro servidor, si utilizará la base de datos inmemory
o bien empezará a utilizar CockroachDB.
Si ahora levantamos nuestro proyecto y hemos hecho todo bien deberíamos de poder empezar a recibir los mensajes de method not implemented
que hicimos que devolvieran nuestros métodos.
go run cmd/gopherapi/main.go --cockroach
Usando SQL
Recordemos que tenemos una interfaz que cumplir con una serie de métodos que hemos dejado devolviendo siempre el error de que el método no está implementado:
type Repository interface {
// CreateGopher saves a given gopher
CreateGopher(ctx context.Context, g *Gopher) error
// FetchGophers return all gophers saved in storage
FetchGophers(ctx context.Context) ([]Gopher, error)
// DeleteGopher remove gopher with given ID
DeleteGopher(ctx context.Context, ID string) error
// UpdateGopher modify gopher with given ID and given new data
UpdateGopher(ctx context.Context, ID string, g Gopher) error
// FetchGopherByID returns the gopher with given ID
FetchGopherByID(ctx context.Context, ID string) (*Gopher, error)
}
Si queréis información más completa de cómo funcionan el lenguaje SQL
de CockroachDB, podéis seguir el enlace a su documentación oficial.
Vamos a implementar los métodos de create
y de fetch all
para poder ver que todo funciona correctamente.
func (r gopherRepository) CreateGopher(_ context.Context, g *gopher.Gopher) error {
sqlStm := `INSERT INTO gophers (id, name, age, image, created_at)
VALUES ($1, $2, $3, $4, NOW())`
_, err := r.db.Exec(sqlStm, g.ID, g.Name, g.Age, g.Image)
if err != nil {
return err
}
return nil
}
Como veis nada nuevo se nos presenta aquí, simplemente hemos realizado un Insert
utilizando la librería estándar que nos ofrece Go.
Ahora para comprobar que nuestros gophers
se están almacenando correctamente, podríamos hacer la query
en nuestra consola o bien implementar nuestro método FetchGophers
, vamos a optar por esta segunda opción.
func (r gopherRepository) FetchGophers(ctx context.Context) ([]gopher.Gopher, error) {
sqlStm := `SELECT id, name, age, image, created_at, updated_at FROM gophers`
rows, err := r.db.Query(sqlStm)
if err != nil {
return nil, err
}
defer rows.Close()
var gophers []gopher.Gopher
for rows.Next() {
var g gopher.Gopher
if err := rows.Scan(&g.ID, &g.Name, &g.Age, &g.Image, &g.CreatedAt, &g.UpdatedAt); err != nil {
log.Println(err)
continue
}
gophers = append(gophers, g)
}
return gophers, nil
}
Como podemos observar este método tiene algo más de complicación pero nada más lejos de la realidad, primero que nada lo que hacemos es preparar nuestra query
, SELECT id, name, age, image, created_at, updated_at FROM gophers
, simple, queremos todos los resultados.
Luego utilizando la librería estándar utilizaremos el método Query()
el cual nos devolverá un cursor sobre el que podremos iterar.
Una de las particularidades más feas es que la librería estándar no es capaz de convertir directamente a struct
y tendremos que pasar exactamente el mismo número de elementos que devolvemos en la query
, en otro artículo veremos como podemos refactorizar esto.
Una vez tenemos cada elemento dentro de la iteración, lo que hacemos es añadirlo a nuestro array y por último devolverlo.
Conclusión
Hemos visto como utilizar un nuevo motor de bases de datos como es CockroachDB, lo hemos instalado, y hemos creado nuestra base de datos y nuestra primera tabla. No sólo eso sino que hemos podido apreciar que al haber creado nuestro proyecto utilizando la arquitectura hexagonal nos ha sido muy sencillo implementar y empezar a usar nuestro nuevo repositorio.
Ahora es vuestro turno, ¿seréis capaces de implementar los métodos restantes y hacernos una Pull Request? Lo esperaremos con ansías.
// DeleteGopher remove gopher with given ID
DeleteGopher(ctx context.Context, ID string) error
// UpdateGopher modify gopher with given ID and given new data
UpdateGopher(ctx context.Context, ID string, g Gopher) error
// FetchGopherByID returns the gopher with given ID
FetchGopherByID(ctx context.Context, ID string) (*Gopher, error)
Recuerda que puedes dejar tus dudas en los comentarios o en nuestro twitter oficial, @FriendsOfGoTech.