avatarIsrael Josué Parra Rosales

Summary

The provided content outlines the process of deploying a "Shopping Cart" microservice using Docker, including setting up the database, creating Dockerfiles, and utilizing Docker Compose, as well as demonstrating how to interact with the API using OpenAPI.

Abstract

The text is a comprehensive guide for deploying a "Shopping Cart" microservice within a Dockerized environment. It begins by detailing the creation of the necessary database tables, "shopping_cart" and "products," and the use of a Dockerfile to configure the database service. It then proceeds to explain the steps for building a Docker image for the Go application, including dependency management and execution commands. The use of Docker Compose is introduced to define and manage the multi-container application setup, specifying the services for both the database and the application with their respective configurations and networks. The document also highlights the advantages of using OpenAPI for real-time API interaction, tool-free testing, and living documentation, emphasizing the "Try it out" feature for testing API endpoints. Finally, it concludes by summarizing the development process and previewing the next chapter on microservice communication.

Opinions

  • The author emphasizes the importance of Docker in modern software development for packaging applications and their dependencies into portable containers.
  • The database setup is considered separate from the microservice code, with a dedicated folder for database initialization scripts.
  • The Dockerfile for the Go application is designed to streamline both building and running the application within a Docker container, ensuring efficient dependency management and execution.
  • Docker Compose is praised for its ability to simplify the management of multi-container applications and for providing a clear and reproducible development environment.
  • OpenAPI's "Try it out" feature is highly recommended for its practicality in testing and validating API endpoints without additional tools, enhancing the development and documentation process.
  • The conclusion reflects on the value of Domain-Driven Design (DDD) in building microservices, noting its benefits in creating well-defined contexts and fostering low coupling with high cohesion.
  • The upcoming chapter is anticipated to delve into microservice communication techniques, including both synchronous and asynchronous methods, and will introduce a new microservice to illustrate these concepts.

Chapter 17 Coding our Microservice (Part 7)

Deploying using Docker

The following list is the previous chapters of this series:

Important note: The example here is just a implementation to run our code, in a real project the Database won’t depend on the dockerfiles and the docker-compose use to be replaced for another technology.

Deploying on Docker

Docker has become an indispensable tool in modern software development, allowing us to package our applications and their dependencies into portable containers.

In this step, we’ll explore the process of deploying our “Shopping Cart” microservice using Docker. By the end of this section, you’ll have a clear understanding of how to create Docker containers for your Go applications, making deployment more efficient and reliable.

Preparing the DataBase

Let’s start by preparing the database that our “Shopping Cart” microservice will use. For our test case, we need to define two tables: “shopping_cart” and “products.” We need to add a new folder in the root of the project named “DeployDB”, it is important to mention that that folder is not part of the microservice code, it is just to keep the database configuration for our use case.

Inside the new folder, we need to define the following files:

database.sql

USE shopping_cart;

CREATE TABLE shopping_cart (
    id CHAR(36) NOT NULL,
    user_id CHAR(36) NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE product (
    id CHAR(36) NOT NULL,
    name VARCHAR(255) NOT NULL,
    quantity VARCHAR(50) NOT NULL,
    product_description VARCHAR(50) NOT NULL,
    shopping_cart_id CHAR(36) NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (shopping_cart_id) REFERENCES shopping_cart(id)
);

Explaining the code:

The SQL code is used to create two tables in a database called “shopping_cart”. It is explained in more detail below:

USE shopping_cart This line sets the current database on which the following operations will be performed. In this case, the database is called “shopping_cart”. This means that all subsequent SQL operations will be applied to this specific database.

CREATE TABLE shopping_cart (…) This code block creates a table called “shopping_cart” in the database “shopping_cart”. The “shopping_cart” table has the following columns:

  • “id”: It is a column of type CHAR(36) that uses PRIMARY KEY and NOT NULL.
  • “user_id”: It is a column of type CHAR(36) that stores a user identifier and NOT NULL.

CREATE TABLE product (…) This code block creates another table named “product” in the same “shopping_cart” database. The “product” table has the following columns:

  • “id”: It is a column of type CHAR(36) that is used as PRIMARY KEY and NOT NULL.
  • “name”: It is a column of type VARCHAR(255) that stores the name of the product and cannot be null.
  • “quantity”: It is a column of type VARCHAR(50) that stores the quantity of the product and cannot be null.
  • “product_description”: It is a column of type VARCHAR(50) that stores a description of the product and cannot be null.
  • “hopping_cart_id”: It is a CHAR(36) type column that stores a shopping cart identifier and cannot be null.

Dockerfile

FROM mysql:latest

ENV MYSQL_ROOT_PASSWORD=root
ENV MYSQL_DATABASE=shopping_cart
ENV MYSQL_USER=user123
ENV MYSQL_PASSWORD=pass123

COPY database.sql /docker-entrypoint-initdb.d/database.sql

Explaining the code: The Dockerfile configuration code defined above will be used to create our database in a docker container. We have included a file for database initialization that automatically creates the necessary tables.

Shopping Cart Microservice Docker Configurations

After configuring the necessary Dockerfile for our database, it’s time to build the Dockerfile responsible for creating the “shopping cart” image. In this scenario, we must define the Dockerfile at the root of our source code and include the following instructions:

Dockerfile

FROM golang:latest
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download

COPY . .
RUN go build -o /cmd/main ./cmd
CMD ["/cmd/main"]

Explaining the code: This Dockerfile establishes an environment tailored for both building and running a Go application within a Docker container. It guarantees proper management of dependencies, orchestrates the application’s compilation, and automatically initiates its execution upon container startup. Let’s delve into each of these essential steps:

  • FROM golang:latest This line indicates that we are basing our Docker image on the latest Golang (Go) image available on the Docker Hub.
  • WORKDIR /app Sets the current working directory within the container to “/app”. This means that all subsequent commands and operations will be executed in the “/app” directory inside the container.
  • COPY go.mod Copy the “go.mod” file.
  • COPY go.sum Similar to the previous line, copy the “go.sum” file.
  • RUN go mod download Run the “go mod download” command inside the container. This will download all project dependencies that are specified in the “go.mod” file and store them in the Go cache directory inside the container. This is necessary before building the application to ensure that all dependencies are available.
  • COPY src dest Copies all the contents of the local directory to the working directory “/app” in the container.
  • RUN go build -o /cmd/main ./cmd Run the “go build” command to build the Go application. The build output is saved to an executable file named “main” in the “/cmd” directory inside the container. This is done to make sure the app is ready to run.
  • CMD[“/cmd/main”] Sets the default command to run when a container based on this image is started. In this case, it will run the “main” executable file found in the “/cmd” directory. This will start the Go application when the container starts.

Finally, to run our containers related to the “Shopping Cart” microservices we are going to use docker-compose, this will allow us to build the containers easily.

Before starting with the necessary configuration code, let’s see what “docker-compose” is. When incorporating ‘docker-compose’ into any project, it becomes imperative to craft a YAML file that serves as the blueprint for defining and configuring multiple Docker container services as a unified application. This approach streamlines the establishment of coherent and easily reproducible development environments, ultimately enhancing both the productivity and the quality of software development. These files are a fundamental part of the Docker Compose tool, which makes it easier to define and manage applications composed of multiple Docker containers.

Let’s see the configurations needed for our use case:

docker-compose.yml

version: '3.8'
services:
  mysql:
    image: mysql:latest
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: shopping_cart
      MYSQL_USER: user123
      MYSQL_PASSWORD: pass123
    ports:
      - 3306:3306
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10
    volumes:
      - ./DeployDB/database.sql:/docker-entrypoint-initdb.d/database.sql
  app:
    build:
      context: .
      dockerfile: Dockerfile
    depends_on:
      - mysql
    ports:
      - 8080:8080
    networks:
      - app-network
    environment:
      - DB_HOST=mysql
      - DB_PORT=3306
      - DB_USER=user123
      - DB_PASSWORD=pass123
      - DB_DATABASE=shopping_cart
      - DB_TYPE=mysql
    command: ["sh", "-c", "sleep 20 && /cmd/main"]
networks:
  app-network:

Explaining the code: This “docker-compose” file defines two main services, “mysql” and “app”. Its key parts are explained below:

version: ‘3.8’. Indicates the version of the Docker Compose configuration format being used.

services. Define the container services that make up the application. Inside of that property are defined:

MySQL. Configures a MySQL database service. This service defines the following properties:

  • image: mysql:latest”: Uses the latest official MySQL image available on Docker Hub.
  • environment”: Set environment variables for MySQL, including root password, database user, and password.
  • ports”: Maps port 3306 of the MySQL container to port 3306 of the local host.
  • networks”: Connects the MySQL container to the network called “app-network”.
  • healthcheck”: Sets up a health check to make sure MySQL is working properly.
  • volumes”: Mounts a local SQL file in the MySQL container to initialize the database.

app: Configures a service for the main application. This defines the following properties:

  • build”: Specifies that the application container image should be built from a Dockerfile in the current directory.
  • depends_on”: Make sure the application service waits for the MySQL service to become available before starting.
  • ports”: Maps port 8080 of the application container to port 8080 of the local host.
  • networks”: Connects the application container to the same “app-network” network.
  • environment”: Set environment variables for the application, including connection information to the MySQL database.
  • command”: Execute a command to start the application, including an initial pause.
  • networks”: Define a network called “app-network” that connects both services, allowing them to communicate with each other.

To run the containers we can run the following command

docker-compose up

When the containers are up we can start interacting with our microservice up and running.

Consuming the REST APIs

In this step, we will demonstrate how to interact with the API using the OpenAPI documentation. As you may recall, OpenAPI allows us to configure it and utilize it for making requests and testing the different endpoints of the API.

The “Try it out” feature of OpenAPI provides an extremely useful capability to directly test API endpoints from within the API documentation. Here are some key advantages of using:

Real-time Interaction

It allows developers to send real HTTP requests to API endpoints and see real-time responses. This makes it easier to understand how the API functions and how to construct requests.

Tool-Free Testing

No additional tools or clients are required to test the API. Developers can experiment and debug easily without setting up third-party tools.

Learning and Documentation

It makes it easy for developers to learn about the API and how to use it directly from the documentation. This is especially valuable for new team members or those working with the API for the first time.

Debugging and Validation

Developers can test specific scenarios, debug issues, and verify that the API responds correctly to expected requests.

Living Documentation

This functionality keeps API documentation up to-date and dynamic, as any changes in the API are immediately reflected in the testing tool.

In summary, OpenAPI’s “Try it out” feature is an essential tool for enhancing understanding, development, and the quality of an API, while simplifying the testing and validation process.

Next will show some of our endpoints consumed from OpenAPI.

Manage “Shopping cart” endpoints

Create new shopping cart for user cURL request:

curl -X 'POST' \
  'http://localhost:8080/api/v1/shopping-cart' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "user_id": "560a8451-a29c-41d4-a716-544676554499"
}'

The Figure 4.2 shows an example of the request to create a new shopping cart.

Figure 4.2: POST /shopping-cart request

Figure 4.3 shows an example of the successful response of creating a shopping cart

Figure 4.3: POST /shopping-cart response

Get shopping cart for user cURL request:

curl -X 'GET' \
'http://localhost:8080/api/v1/shopping-cart/560a8451-a29c-41d4-a716-544676554499' \
-H 'accept: application/json'

Figure 4.4 shows an example of the request to get the user’s shopping cart.

Figure 4.4 shows an example of the request to get the user’s shopping cart.

Figure 4.4: GET /shopping-cart request

Figure 4.5 shows an example of the successful response of get shopping-cart.

Figure 4.5: GET /shopping-cart response

Manage “Products” endpoints

Add products to shopping cart cURL request:

curl -X 'POST' \
  'http://localhost:8080/api/v1/product' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "description": "smart tv full HD",
  "name": "TV",
  "quantity": 10,
  "shoppingCartId": "1f2ab666-d9e7-4928-9f5f-c9463935e2a5"
}'

Figure 4.6 shows an example of the request to add products in a user shopping cart.

Figure 4.6: POST /product request

Figure 4.7 shows an example of the response to adding products to a user’s shopping cart.

Figure 4.7: POST /product response

List shopping cart products cURL request:

curl -X 'GET' \
  'http://localhost:8080/api/v1/product/1f2ab666-d9e7-4928-9f5f-c9463935e2a5' \
  -H 'accept: application/json'

Figure 4.8 shows an example of the request to list the products in a user shopping cart.

Figure 4.8: GET/product request

Figure 4.9 shows an example of the response to listing the products in a user’s shopping cart.

Figure 4.9: GET /product response

Conclusion

This chapter is entirely dedicated to the development of a microservice in Golang, using the ‘shopping cart’ domain from our e-commerce system as a practical example.

After carefully walking through the creation of this microservice, we now possess a comprehensive understanding that spans from defining our API, setting up routing, and performing unit testing, to adding a foundational architecture to our project. Armed with this knowledge, we are well-prepared to embark on building real-life microservices.

By delving into each of the topics discussed here, you’ve laid a solid foundation to venture further into the realm of microservices with Golang. As demonstrated, Domain-Driven Design (DDD) yields significant advantages in building microservices by offering well-defined contexts and responsibilities, fostering low coupling, and ensuring high cohesion.

We also explored specific frameworks that streamline the creation of microservices, simplifying the development of individual modules through their valuable contributions and standardized approaches.

In the upcoming chapter, we will delve deeper into microservices, specifically focusing on communication between each one. As part of the next chapter will be analyzed techniques like Asynchronous and Synchronous communication and communication protocols. To facilitate our exploration of communication techniques, we will introduce a second, smaller microservice. This addition will serve as a practical example to enhance our understanding of effective communication strategies and pave the way for more advanced topics in microservices.

Next readings …

Wait for Chapter 18 “Microservice Communication”.

Golang
Software Development
Software Architecture
Software Engineering
Computer Science
Recommended from ReadMedium