Chapter 12 — Coding our Microservice (Part 2)
Developing the Database Connection

The following list is the previous chapters of this series:
- Chapter 1 — Introduction to Microservices
- Chapter 2 — Introduction to Microservices (Part 2)
- Chapter-3 Domain-driven design and microservices
- Chapter -4 Advantages of using Go for web development
- Chapter — 5 Understanding HTTP Protocols and REST APIs
- Chapter-6 HTTP Package in Golang
- Chapter 7 — Step-by-step guide on building a simple web service using Go
- Chapter 8 “ Documenting our APIs — Introduction to OpenAPI”
- Chapter 9 -Building a Microservice with Go (Defining the technologies )
- Chapter 10-Structuring our project
- Chapter 11- Coding our Microservice (Part 1)
I recommend you take a look at the previous chapters if you have not read them yet. That will help you to get more knowledge in this wonderful world of “Microservices architecture”.
Structure
In this chapter, the following topics will be covered:
- Developing the database connection
- Adding database interaction to our microservices
- Implementing MyQL
- Consuming the database by repositories
Developing the database connection
The infrastructure package, as mentioned before this package, is defined to declare all the logic related to the persistence process. For this example, two sub-packages are defined as “persistence” and “database”.
Persistence will contain the concrete implementation of the repository, In both cases we are implementing a MySQL repository, and then the functionality related to products and the shopping carts will be defined there each one on its respective properties “shopping_cart_repository” and “product_repository” Those functions are the same defined by the interface declared on the domain package that was analyzed in the previous step.
On the other hand the subpackage “database” defines the needed code to load the DB configuration values “database_config.go” and defines the code used to establish a database connection “db_connection”.
Next is described and analyzed each one of the files.
│ ├── infrastructure/
│ │ ├── persistence/
│ │ │ ├── shopping_cart_repository.go
│ │ │ └── product_repository.go
│ │ └── database/
│ │ ├── db_connection.go
│ │ └── database_config.go/infrastructure/persistence/shopping_cart_repository.go
package persistence
import (
"database/sql"
shoppingcart "github.com/go-microservices/shopping-cart-service/internal/domain/shopping_cart"
uuid "github.com/satori/go.uuid"
"github.com/sirupsen/logrus"
template_errors "github.com/go-microservices/shopping-cart-service/internal/shared/error"
_ "github.com/go-sql-driver/mysql"
)
type shoppingCartMySQLRepo struct {
logger *logrus.Logger
db DataBaseConn
}
func NewshoppingCartMySQLRepo(db DataBaseConn, logger *logrus.Logger) shoppingCartMySQLRepo {
return shoppingCartMySQLRepo{
logger: logger,
db: db,
}
}
func (sc shoppingCartMySQLRepo) Create(spc shoppingcart.ShoppingCart) error {
sc.logger.Info("On ShoppingCart repository - creating shopping cart")
query := "INSERT INTO shopping_cart (id, user_id) VALUES (?, ?)"
randomUUID := uuid.NewV4()
_, err := sc.db.Exec(query, randomUUID.String(), spc.UserID)
if err != nil {
sc.logger.Errorf("error saving shopping cart: %v", err)
return err
}
return nil
}
func (sc shoppingCartMySQLRepo) GetByUserID(userId *uuid.UUID) (*shoppingcart.ShoppingCart, error) {
sc.logger.Info("On ShoppingCart repository - get shopping cart info")
query := "SELECT * FROM shopping_cart WHERE user_id = ?"
row := sc.db.QueryRow(query, userId.String())
if row.Err() != nil {
sc.logger.Errorf("error getting shopping cart: %v", row)
return nil, row.Err()
}
var cart shoppingcart.ShoppingCart
err := row.Scan(&cart.ID, &cart.UserID)
if err == sql.ErrNoRows {
return nil, template_errors.NewRecordNotFoundError("shopping cart not found", template_errors.RecordNotFound)
}
if err != nil {
sc.logger.Errorf("error getting shopping cart values: %v", err)
return nil, err
}
return &cart, nil
}Explaining the code:
This Go code serves as the repository implementation for seamless interaction with a MySQL database within the scope of a shopping cart microservice. Essentially, it provides the concrete implementation of the repository required by the “shopping cart domain.” Within this code, we define and implement functions that fulfill the interface specified in “/domain/shopping_cart/shopping_cart_repository.go”.
Let’s break down the key components and functionalities of this code:
The code imports various packages, including:
- “database/sql” for working with SQL databases
- “github.com/sirupsen/logrus” for logging
- “github.com/satori/go.uuid” for UUID generation
- “github.com/go-sql-driver/mysql” for the MySQL driver
- It also imports custom error handling and domain packages
The “shoppingCartMySQLRepo” struct holds a reference to a logger “logrus.Logger ‘’ and a database connection “DataBaseConn”. This struct represents the repository for interacting with the shopping cart data in the database.
The “NewshoppingCartMySQLRepo” function is a constructor for creating a new instance of the “shoppingCartMySQLRepo”. It takes a database connection and a logger as parameters and returns an instance of the repository.
The “Create” method is used to create a new shopping cart entry in the database. For this example, It generates a random UUID as the cart’s ID and executes an SQL “INSERT” query. If the insertion is successful, it returns `nil`. If an error occurs, it logs the error and returns it.
The “GetByUserID” method retrieves a shopping cart by user ID from the database. It executes a SQL “SELECT” query and maps the result to a “shoppingcart.ShoppingCart” struct. If the cart is not found, it returns a custom “RecordNotFoundError”. If any other error occurs, it logs the error and returns it.
This code represents the data access layer of a microservice responsible for creating and retrieving shopping cart data in a MySQL database. It abstracts the database operations and provides error handling for these operations.
/infrastructure/persistence/product_repository.go
package persistence
import (
"database/sql"
"log"
"github.com/go-microservices/shopping-cart-service/internal/domain/product"
template_errors "github.com/go-microservices/shopping-cart-service/internal/shared/error"
uuid "github.com/satori/go.uuid"
"github.com/sirupsen/logrus"
)
type ProductMySQLRepo struct {
logger *logrus.Logger
db *sql.DB
}
func NewProductMySQLRepo(db *sql.DB, logger *logrus.Logger) ProductMySQLRepo {
return ProductMySQLRepo{
logger: logger,
db: db,
}
}
func (pr ProductMySQLRepo) Create(product product.Product) error {
log.Println("On Create product repository")
query := "INSERT INTO product (id,name,quantity,product_description, shopping_cart_id) VALUES (?, ?,?,?,?)"
randomUUID := uuid.NewV4()
_, err := pr.db.Exec(query, randomUUID.String(),
product.Name, product.Quantity, product.Description, product.ShoppingCartID)
if err != nil {
log.Println("error saving product: ", err.Error())
return err
}
return nil
}
func (pr ProductMySQLRepo) Get(shoppingCartID uuid.UUID) ([]product.Product, error) {
pr.logger.Info("On List products repository")
products := []product.Product{}
query := "SELECT * FROM product WHERE shopping_cart_id = ?"
rows, err := pr.db.Query(query, shoppingCartID.String())
if err != nil {
pr.logger.Errorf("error getting products cart: %v", err)
return products, err
}
defer rows.Close()
for rows.Next() {
var product product.Product
err := rows.Scan(&product.ID, &product.Name, &product.Quantity,
&product.Description, &product.ShoppingCartID)
if err != nil {
pr.logger.Errorf("error scanning row: %v", err)
continue
}
products = append(products, product)
}
return products, nil
}
func (pr ProductMySQLRepo) Delete(productID uuid.UUID) error {
pr.logger.Info("On Delete product repository")
if err := pr.GetByID(productID); err != nil {
return err
}
query := "DELETE FROM product WHERE id = ?"
_, err := pr.db.Exec(query, productID)
if err != nil {
pr.logger.Errorf("On Delete product repository - error deleting product: %v", err)
return err
}
return nil
}
func (pr ProductMySQLRepo) UpdateQuantity(productID uuid.UUID, quantity int) error {
pr.logger.Info("On Update product quantity repository")
if err := pr.GetByID(productID); err != nil {
return err
}
query := "UPDATE product SET quantity = ? WHERE id = ?"
_, err := pr.db.Exec(query, quantity, productID)
if err != nil {
pr.logger.Errorf("error updating product: %v", err)
return err
}
return nil
}
func (pr ProductMySQLRepo) GetByID(productID uuid.UUID) error {
pr.logger.Info("On get product by ID quantity repository")
querySelect := "SELECT id FROM product WHERE id = ?"
var existingID uuid.UUID
err := pr.db.QueryRow(querySelect, productID).Scan(&existingID)
if err == sql.ErrNoRows {
pr.logger.Infof("On Get product quantity repository - product not found %d", productID)
return template_errors.NewRecordNotFoundError("product not found", template_errors.RecordNotFound)
} else if err != nil {
pr.logger.Errorf("error quering product: %v", err)
return err
}
return nil
}Explaining the code:
This Go code serves as the repository implementation for seamless interaction with a MySQL database within the scope of a shopping cart microservice. Essentially, it provides the concrete implementation of the repository required by the “product domain.” Within this code, we define and implement functions that fulfill the interface specified in /domain/product/product_repository.go.
Similar to the “shopping cart repository” studied previously, this code represents the data access layer of a microservice responsible for creating and retrieving shopping cart data in a MySQL database. It abstracts the database operations and provides error handling for these operations.
The logic is the same but implemented to handle product records, offering functions for creating, listing, deleting, and updating products. These functions adhere to the same specifications and standards applied in the shopping cart repository.
/infrastructure/database/database_config.go
package database
import (
"log"
"github.com/jinzhu/configor"
)
type DBConfig struct {
Host string `env:"DB_HOST" default:"localhost"`
Port string `env:"DB_PORT" default:"3306"`
User string `env:"DB_USER" default:"user123"`
Password string `env:"DB_PASSWORD" default:"pass123"`
Database string `env:"DB_DATABASE" default:"shopping_cart"`
Type string `env:"DB_TYPE" default:"mysql"`
}
func NewConfig() (conf DBConfig, err error) {
log.Println("On loading database configuration")
err = configor.New(&configor.Config{Environment: "development"}).Load(&conf)
log.Println("configuration loaded: ")
log.Println(conf)
return
}Explaining the code:
It’s essential to mention the required packages for this code. It includes the core “log” package for logging purposes and relies on a third-party library called github.com/jinzhu/configor” for effective configuration management.
An essential aspect to highlight is the DBConfig struct, responsible for storing configuration parameters for the database. These parameters encompass the host, port, user, password, database name, and database type. Each field in the struct is tagged with specific environment variables, facilitating value mapping. Default values are also provided for each field, serving as fallbacks in cases where the corresponding environment variable is not defined.
The NewConfig function is responsible for loading the database configuration. It uses the configor package to load the configuration. The configor.New function is called with a configuration object that specifies the environment as “development.” This means it’s loading the configuration for the development environment.
The configor.New function’s “Load” method is used to load the configuration values into the conf variable. This function returns any errors encountered during the loading process.
Finally, the function returns the loaded configuration conf and any error that occurred during the loading process.
In summary, this code sets up a structure DBConfig to hold database configuration values, loads these values using the configor package, and logs the loaded configuration.
/infrastructure/database/db_connection.go
package database
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
func NewDatabseConnection(conf DBConfig) (*sql.DB, error) {
dsn := conf.User + ":" + conf.Password + "@tcp(" + conf.Host + ":" + conf.Port + ")/" + conf.Database
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Println("Error connecting to database")
return nil, err
}
err = db.Ping()
if err != nil {
log.Println("Error connecting to database")
return nil, err
}
log.Println("database connected")
return db, nil
}Explaining the code:
This Go function is responsible for establishing a connection to a MySQL database using the provided “DBConfig” configuration. Let’s break down its functionality:
The function takes a single input parameter, “conf”, which is an instance of the “DBConfig” struct. This struct holds the database configuration details, such as host, port, user, password, database name, and database type.
The function constructs a Data Source Name (var dns) string using the configuration details from “conf”. This DSN string contains information about how to connect to the MySQL database, including the user, password, host, and port.
It then uses the sql.Open() function from the github.com/go-sql-driver/mysql package to establish a connection to the MySQL database. If an error occurs during this process, it logs an error message and returns both `nil` and the encountered error.
After successfully opening the database connection, it immediately checks the connection’s validity using the “Ping()” method. If the ping fails, it logs an error and returns an error indicating the connection failure.
If both the database connection and ping are successful, it logs a confirmation message that the database is connected and returns a reference to the open database connection “*sql.DB” along with a “nil” error, indicating a successful connection.
This function encapsulates the process of connecting to a MySQL database using the provided configuration, handling potential errors along the way.
Next readings …
Wait for Chapter 13 “Building a Microservice With Go (Part 3)— Defining the REST API”.





