Chapter 13 - Coding our Microservice (Part 3)
Defining the REST API

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)
- Chapter 12 — Coding our Microservice (Part 2)
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:
- Defining REST API
- Implementing gin-gonic in our project.
- Coding our needed endpoints
- Coding the request handlers
Introduction
In this chapter will be defined the code related to the application. The “api” package is primarily responsible for exposing the microservice’s functionality through an HTTP API. It encompasses Data Transfer Objects (DTOs) for efficient data transfer, controllers for handling HTTP requests, and route definitions specified in “api.go” and “routes.go” files.
This package seamlessly interfaces with the “domain” package, which serves as the domain layer. Here, the fundamental structures and business logic associated with the “Shopping Cart” and “Product” entities are meticulously defined and orchestrated.
Next will describe and analyze each one of the files defined under this package.
...
│ ├── app/
│ │ └── api
│ │ ├── dto/
│ │ │ ├── shopping_cart.go
│ │ │ └── product.go
│ │ ├── handler/
│ │ │ ├── product/
│ │ │ │ └── handler.go
│ │ │ └── shopping_cart/
│ │ │ └── handler.go
│ │ ├── api.go
│ │ └── routes.go
.../app/api/api.go
package api
import (
"github.com/gin-gonic/gin"
product_handler "github.com/go-microservices/shopping-cart-service/internal/app/api/handler/product"
scHandler "github.com/go-microservices/shopping-cart-service/internal/app/api/handler/shopping_cart"
"github.com/go-microservices/shopping-cart-service/internal/domain/product"
shoppingcart "github.com/go-microservices/shopping-cart-service/internal/domain/shopping_cart"
"github.com/go-microservices/shopping-cart-service/internal/infrastructure/database"
"github.com/go-microservices/shopping-cart-service/internal/infrastructure/persistence"
"github.com/go-microservices/shopping-cart-service/internal/shared/logger"
)
type ShoppingCartHandler interface {
Create(c *gin.Context)
Get(c *gin.Context)
}
type ProductHandler interface {
Create(c *gin.Context)
Get(c *gin.Context)
Delete(c *gin.Context)
Update(c *gin.Context)
}
// Description 1
type apiV1 struct {
shoppingCartHandler ShoppingCartHandler
productHandler ProductHandler
}
func NewApiV1(shoppingCartHandler ShoppingCartHandler, productHandler ProductHandler) apiV1 {
return apiV1{
shoppingCartHandler: shoppingCartHandler,
productHandler: productHandler,
}
}
// Description 2
func LoadApiV1() {
logger := logger.NewLogger()
logger.Info("loading shopping-cart API")
dbConfig, err := database.NewConfig()
if err != nil {
logger.Errorf("error loading configuration: %v", err)
}
dbConnection, err := database.NewDatabseConnection(dbConfig)
if err != nil {
logger.Errorf("error connecting to database: %v", err)
}
defer dbConnection.Close()
concreteShoppinCartRepoImpl := persistence.NewshoppingCartMySQLRepo(dbConnection, logger)
shoppingCartSvc := shoppingcart.NewShoppingCartService(concreteShoppinCartRepoImpl, logger)
spHandler := scHandler.NewShoppingCartHandler(shoppingCartSvc, logger)
concreteProductRepoImp := persistence.NewProductMySQLRepo(dbConnection, logger)
producScv := product.NewProductService(concreteProductRepoImp, logger)
pHandler := product_handler.NewProductHandler(producScv, logger)
apiV1 := NewApiV1(spHandler, pHandler)
// defined in a separate file
apiV1.loadRoutes()
}Explaining the code: This code will integrate the different components of our microservice, Injecting the dependencies for each one of the components.
Description 1: Here is defined a structure to group the Handlers defined for our microservice, as can be seen, those handlers are wrapped by using the Interface.
Description 2 (LoadApiV1): The function LoadApiV1 will be called by main.go to build our REST API. This function defines the configuration of the database, the creation of concrete repositories and services, and the assignment of handlers to the “apiV1” instance. Finally, load the routes for the API.
The approach used to integrate each of the ApiV1 components is the Inversion of Control (IoC), since it is beneficial in software development for several reasons:
Decoupling By injecting dependencies instead of creating them internally, software components and modules become less dependent on concrete implementations of their dependencies. This reduces coupling and makes the code more flexible and maintainable.
Testability The IoC makes unit testing and automated testing easy. You can replace concrete implementations with test implementations (mocks) during testing, allowing you to isolate and test each component independently.
Component Replacement With the IoC, it’s easier to replace a particular implementation with another without changing a lot of code. This is useful when you need to change the data source, for example from a database to an external API.
Extensibility The IoC makes your code more extensible. You can add new implementations or functionality without modifying existing code, as long as they adhere to the same interface.
Clarity and Maintainability By defining dependencies explicitly, your code becomes clearer and easier to understand. Developers can quickly see what dependencies a component has by looking at its constructor or configure method.
Code Reuse The IoC promotes code reuse. You can use a specific implementation in multiple places without duplicating code.
/app/api/routes.go
package api
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
func (api apiV1) loadRoutes() {
log.Println("loading Routes")
r := gin.Default()
v1 := r.Group("/api/v1")
{
shoppingCart := v1.Group("shopping-cart")
{
shoppingCart.POST("", api.shoppingCartHandler.Create)
shoppingCart.GET(":userID", api.shoppingCartHandler.Get)
}
product := v1.Group("/product")
{
product.POST("", api.productHandler.Create)
product.PATCH(":productId", api.productHandler.Update)
product.GET(":shoppingCartId", api.productHandler.Get)
product.DELETE(":productId", api.productHandler.Delete)
}
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
err := r.Run(":8080")
if err != nil {
log.Println("can not start service")
}
}Explaining the code: This code defines the routes and routing for a REST API of our microservice. It contains all the necessary configurations to start up our microservice and start up a new service.
It uses the Gin-Gonic framework to configure routes and handle HTTP requests. Exposing endpoints related to shopping carts and products. Each of those endpoints helped us understand how different HTTP methods are implemented.
Two endpoints are defined as part of the “shopping cart” routes:
- POST /shopping-cart This endpoint is defined to create new shopping carts in the database.
- GET /shopping-cart/{userId} This endpoint is defined to retrieve a “shopping cart” assigned to a specific user.
On the other hand, to handle the “products” are defined the following endpoints:
- POST /product This endpoint is defined to add products to a specific shopping cart
- PATCH /product/{productId} This endpoint is defined to update the quantity of the product added to the shopping cart.
- GET /product/{shoppingCartId} This endpoint is defined to list the products from a shopping cart.
- DELETE /product/{productId} This endpoint is defined to remove a product from the shopping cart.
These routes facilitate the smooth operation of our microservice, making it a robust solution for managing shopping carts and product data.
Now let’s analyze the “dto” package. Inside the “api” package is defined the “dto” package, and inside will be defined the needed Request and Response objects used by “products” and “Shopping Cart” endpoints.
DTOs are data structures designed to transport information between different parts of an application. These objects encapsulate specific data required for specific operations and can include validations and transformations. Their main purpose is to enable a clear separation of responsibilities and enhance efficiency in communication between different layers of an application.
/app/api/dto/shopping_cart.go
package dto
import (
validation "github.com/go-ozzo/ozzo-validation/v4"
uuid "github.com/satori/go.uuid"
)
type ShoppingCartResponse struct {
ID *uuid.UUID `json:"id,omitempty"`
UserID *uuid.UUID `json:"user_id"`
}
func NewShoppingCartResponse(id *uuid.UUID, userID *uuid.UUID) ShoppingCartResponse {
return ShoppingCartResponse{
ID: id,
UserID: userID,
}
}
type ShoppingCartRequest struct {
UserID *uuid.UUID `json:"user_id"`
}
func (sc ShoppingCartRequest) Validate() error {
return validation.ValidateStruct(&sc,
validation.Field(&sc.UserID, validation.Required))
}Explaining the code: In this file will be defined two structs ShoppingCartRequest and ShoppingCartRespose, those structs define the DTOs used to map the API request and response.
The ShoppingCartRequest defines a method Validate(). That method will help us to verify the data values in order to ensure the data integrity before being stored in the database.
That method implements the ozzoo-validation library that provides a very good functionality to validate structs.
On the other hand, the ShoppingCartResponse is implemented to map the object returned from the domain to the API response.
/app/api/dto/product.go
package dto
import (
validation "github.com/go-ozzo/ozzo-validation/v4"
uuid "github.com/satori/go.uuid"
)
type ProductReq struct {
Name string `json:"name"`
Quantity int `json:"quantity"`
Description string `json:"description"`
ShoppingCartID *uuid.UUID `json:"shoppingCartId"`
}
func (p ProductReq) Validate() error {
return validation.ValidateStruct(&p,
validation.Field(&p.Name, validation.Required),
validation.Field(&p.Quantity, validation.Required, validation.Min(1)),
validation.Field(&p.Description, validation.Required),
validation.Field(&p.ShoppingCartID, validation.Required),
)
}
type ProductResp struct {
ID *uuid.UUID `json:"id,omitempty"`
Name string `json:"name"`
Quantity int `json:"quantity"`
Description string `json:"description"`
ShoppingCartID *uuid.UUID `json:"shoppingCartId"`
}
func NewProductResponse(
id *uuid.UUID,
name string,
quantity int,
description string,
shoppingCartID *uuid.UUID,
) ProductResp {
return ProductResp{
ID: id,
Name: name,
Quantity: quantity,
Description: description,
ShoppingCartID: shoppingCartID,
}
}
type ProductQuantity struct {
Quantity int `json:"quantity"`
}
func (pq ProductQuantity) Validate() error {
return validation.ValidateStruct(&pq,
validation.Field(&pq.Quantity, validation.Required, validation.Min(1)),
)
}Explaining the code: This is the code required to define the Request and Response structures used by the product endpoints.
In this file, we have defined three structs (in your applications each one could be defined in a separate file) ProductQuantity, ProductReq, ProductResp.
As similar done for the shopping cart, the structures related to the request objects define their own validation function.
After defining the DTOs, the next step in the context of the “api” package is to implement the handler functionality. To achieve this, we will create a sub-package named “handler.” This sub-package aids in maintaining a structured and organized codebase.
The handler functions defined in this step are called by the/app/api/routes.go, which means that we going to have one function for each one of the defined routes.
To define the handlers we are implementing gin-gonic, each handler functions must follow a specific signature that includes a parameter of type *gin.Context, representing the context of the current request:
func myHandler(c *gin.Context) {
...
}Let’s analyze each of the handles for “shopping Cart” and “products”.
/app/api/handler/product/handler.go
package product_handler
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-microservices/shopping-cart-service/internal/app/api/dto"
"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 ProductService interface {
Create(product product.Product) error
GetAll(shoppingCartId uuid.UUID) ([]product.Product, error)
Delete(productId uuid.UUID) error
UpdateQuantity(productID uuid.UUID, quantity int) error
}
type productHandler struct {
logger *logrus.Logger
productService ProductService
}
func NewProductHandler(productService ProductService, logger *logrus.Logger) productHandler {
return productHandler{
logger: logger,
productService: productService,
}
}
// Create godoc
//
// @Summary Adds products to shopping cart
// @Description Add product to shopping cart
// @Tags Products
// @Accept json
// @Param request body dto.ProductReq true "shopping cart info"
// @Success 201
// @Failure 400 {object} template_errors.TemplateError
// @Failure 500 {object} template_errors.TemplateError
// @Router /product [post]
func (p productHandler) Create(c *gin.Context) {
p.logger.Info("On Create product handler")
var input dto.ProductReq
if err := c.BindJSON(&input); err != nil {
p.logger.Errorf("On Create product handler - error: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewBadRequestError("bad request", err))
return
}
if err := input.Validate(); err != nil {
p.logger.Errorf("On Create product handler - invalid request: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewValidationError("invalid params", err))
return
}
newProduct := product.NewProduct(input.Name, input.Quantity, input.Description, input.ShoppingCartID)
err := p.productService.Create(newProduct)
var errWraping template_errors.TemplateError
if errors.As(err, &errWraping) {
p.logger.Errorf("On Create product handler - error: %v", errWraping.Message)
c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
return
}
if err != nil {
p.logger.Errorf("On Create product handler - error creating new record: %v", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
return
}
c.JSON(http.StatusCreated, nil)
}
// Get godoc
//
// @Summary Get shopping cart products
// @Description List products in shopping cart
// @Tags Products
// @Produce json
// @Param shoppingCartId path string true "shopping cart ID"
// @Success 202 {array} dto.ProductResp
// @Failure 400 {object} template_errors.TemplateError
// @Failure 404 {object} template_errors.TemplateError
// @Failure 500 {object} template_errors.TemplateError
// @Router /product/{shoppingCartId} [get]
func (p productHandler) Get(c *gin.Context) {
p.logger.Info("On List products handler")
id, err := uuid.FromString(c.Param("shoppingCartId"))
if err != nil {
p.logger.Errorf("On List products handler: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewErrorResponse(err.Error()))
return
}
products, err := p.productService.GetAll(id)
var errWraping template_errors.TemplateError
if errors.As(err, &errWraping) {
p.logger.Errorf("On List products handler - error: %v", errWraping.Message)
c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
return
}
if err != nil {
p.logger.Errorf("On List products handler - error: %v", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}
productsResp := []dto.ProductResp{}
for _, p := range products {
productsResp = append(productsResp, dto.NewProductResponse(p.ID, p.Name, p.Quantity, p.Description, p.ShoppingCartID))
}
c.JSON(http.StatusOK, productsResp)
}
// Delete godoc
//
// @Summary Delete shopping cart products by ID
// @Description Delete products in shopping cart
// @Tags Products
// @Param productId path string true "product ID"
// @Success 202
// @Failure 400 {object} template_errors.TemplateError
// @Failure 404 {object} template_errors.TemplateError
// @Failure 500 {object} template_errors.TemplateError
// @Router /product/{productId} [delete]
func (p productHandler) Delete(c *gin.Context) {
p.logger.Info("On Delete product handler")
id, err := uuid.FromString(c.Param("productId"))
if err != nil {
p.logger.Errorf("On Delete product handler: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewErrorResponse(err.Error()))
return
}
err = p.productService.Delete(id)
var errWraping template_errors.TemplateError
if errors.As(err, &errWraping) {
p.logger.Errorf("On Delete product handler - error: %v", err)
c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
return
}
if err != nil {
p.logger.Errorf("On Delete product quantity handler - error: %v", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
return
}
c.JSON(http.StatusOK, nil)
}
// Update godoc
//
// @Summary Update shopping cart products
// @Description Update product quatity in shopping cart
// @Tags Products
// @Accept json
// @Param productId path string true "Product ID"
// @Param request body dto.ProductQuantity true "Product quantity request"
// @Success 202
// @Failure 400 {object} template_errors.TemplateError
// @Failure 404 {object} template_errors.TemplateError
// @Failure 500 {object} template_errors.TemplateError
// @Router /product/{productId} [patch]
func (p productHandler) Update(c *gin.Context) {
p.logger.Info("On Update product quantity handler")
id, err := uuid.FromString(c.Param("productId"))
if err != nil {
p.logger.Errorf("On Update product quantity handler error: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewErrorResponse(err.Error()))
return
}
var input dto.ProductQuantity
if err := c.BindJSON(&input); err != nil {
p.logger.Errorf("On Update product quantity handler - error: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewBadRequestError("bad request", err))
return
}
if err := input.Validate(); err != nil {
p.logger.Errorf("On Update product quantity handler - invalid request: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewValidationError("invalid params", err))
return
}
err = p.productService.UpdateQuantity(id, input.Quantity)
var errWraping template_errors.TemplateError
if errors.As(err, &errWraping) {
p.logger.Errorf("On Update product quantity - error: %v", errWraping.Message)
c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
return
}
if err != nil {
p.logger.Errorf("On Update product quantity handler - error: %v", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
return
}
c.JSON(http.StatusAccepted, nil)
}Explaining the code: In this file, we will define all the logic associated with the product handlers. Each function corresponds to an endpoint, and it’s worth noting that we declare an interface that defines the service methods. This interface is utilized because the productHandler structure includes a property used to access these service methods.
We’ll skip the comments preceding each function for now, as we’ll find their descriptions in the OpenAPI documentation section.
When working with the gin-gonic framework to define handler functions, we have access to a variety of functions that make easy request management. Let’s explore the generalities of these functions:
The handler function.
The logic defined in the function will be executed by each request to the related endpoint, for example: Request to POST /create, will execute the logic defined on func create(..){…}.
Those functions just need to define the logic related to the API handler layer, validations, object serializations, and responses. If there are more complex logic it should be split into a layer named service. That layer is defined as part of the application layer.
Taking the example of our use case, there isn’t a lot of logic, then we can work the validations in the handler function and then make the call to the DOMAIN service, and process the returned values.
Taking as an example the create, we are defining the domain logic inside of its own package and then the handler calls the domain.service function, for example: err := p.productService.Create(newProduct).
When the handler functions are related to the request that sent data, after getting the json and parsing it in a golang struct, the struct properties should be validated. Here is where the DTOs are implemented in our use case.
By using the defined DTOs we are able to execute the function defined Validate, which ensures that all the values read from the request are the expected, and then continue with the logic. If some of the values are not correct the handler will respond with a Validation error.
Another important point to mention in the handler is the use of the error wrapper. This allows us to map and propagate the correct errors from other layers. The error wrapping will be analyzed further in the section related to the error package.
The handler functions should validate and handle all the errors propagated in the other layers, by doing we ensure that the code returns the correct response and HTTP status.
It is crucial to not only validate incoming data but also effectively handle errors propagated from underlying layers. This robust error handling ensures that your code consistently returns the appropriate HTTP status codes and meaningful responses, contributing to the overall reliability and user-friendliness of your application.
/app/api/handler/shopping_cart/handler.go
Similar to products, the handler defined as the shopping cart is under the same specifications.
package shopping_cart_handler
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-microservices/shopping-cart-service/internal/app/api/dto"
shoppingcart "github.com/go-microservices/shopping-cart-service/internal/domain/shopping_cart"
template_errors "github.com/go-microservices/shopping-cart-service/internal/shared/error"
uuid "github.com/satori/go.uuid"
"github.com/sirupsen/logrus"
_ "github.com/swaggo/swag/example/celler/httputil"
)
type ShoppingCartService interface {
Create(spc shoppingcart.ShoppingCart) error
GetByUserID(userId *uuid.UUID) (*shoppingcart.ShoppingCart, error)
}
type shoppingCartHandler struct {
logger *logrus.Logger
shoppingCartService ShoppingCartService
}
func NewShoppingCartHandler(
shoppingCartService ShoppingCartService, logger *logrus.Logger) shoppingCartHandler {
return shoppingCartHandler{
logger: logger,
shoppingCartService: shoppingCartService,
}
}
// Create godoc
//
// @Summary Creates Shopping Cart
// @Description Create a new Shopping Cart
// @Tags Shopping Cart
// @Accept json
// @Param request body dto.ShoppingCartRequest true "shopping cart info"
// @Success 201
// @Failure 400 {object} template_errors.TemplateError
// @Failure 500 {object} template_errors.TemplateError
// @Router /shopping-cart [post]
func (sc shoppingCartHandler) Create(c *gin.Context) {
sc.logger.Info("On create sopping cart")
var input dto.ShoppingCartRequest
if err := c.BindJSON(&input); err != nil {
sc.logger.Errorf("On create shopping cart handler - error: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewBadRequestError("bad request", err))
return
}
if err := input.Validate(); err != nil {
sc.logger.Errorf("On create shopping cart handler - invalid request: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewValidationError("invalid values", err))
return
}
err := sc.shoppingCartService.Create(shoppingcart.NewShoppingCart(nil, input.UserID))
var errWraping template_errors.TemplateError
if errors.As(err, &errWraping) {
sc.logger.Errorf("On create shopping cart handler - error: %v", errWraping.Message)
c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
return
}
if err != nil {
sc.logger.Errorf("On create shopping cart handler - error creating new record: %v", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
return
}
c.JSON(http.StatusCreated, nil)
}
// Get godoc
//
// @Summary Get User Shopping Cart
// @Description Returns a Shopping Cart
// @Tags Shopping Cart
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} dto.ShoppingCartResponse
// @Failure 400 {object} template_errors.TemplateError
// @Failure 404 {object} template_errors.TemplateError
// @Failure 500 {object} template_errors.TemplateError
// @Router /shopping-cart/{id} [get]
func (sc shoppingCartHandler) Get(c *gin.Context) {
sc.logger.Info("On get shorpping cart")
id, err := uuid.FromString(c.Param("userID"))
if err != nil {
sc.logger.Errorf("On get shorpping cart handler - error: %v", err)
c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewErrorResponse(err.Error()))
return
}
resp, err := sc.shoppingCartService.GetByUserID(&id)
var errWraping template_errors.TemplateError
if errors.As(err, &errWraping) {
sc.logger.Errorf("On get shorpping cart handler - error: %v", errWraping.Message)
c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
return
}
if err != nil {
sc.logger.Errorf("On get shorpping cart handler: %v", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
return
}
c.JSON(http.StatusOK, dto.NewShoppingCartResponse(resp.ID, resp.UserID))
}The main.go
/cmd/main.go
package main
import (
"log"
api "github.com/go-microservices/shopping-cart-service/internal/app/api"
_ "github.com/go-microservices/shopping-cart-service/docs"
_ "github.com/swaggo/files"
_ "github.com/swaggo/gin-swagger"
)
// @title Shopping Cart API
// @version 1.0
// @description APIs to manage sopping cart.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email [email protected]
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
func main() {
log.Println("starting shopping-cart services")
api.LoadApiV1()
}Explaining the code: Let us focus on the main function content, we going to explain the comments related to OpenAPI in the next step.
The code here is simple, here it has just started up the web service by calling “api.LoadApiV1”, that function was defined in the previous steps when the API package was defined.
Next readings …
Wait for Chapter 14 “Coding our Microservice (Part 4) — Explaining the shared package”.






