avatarIsrael Josué Parra Rosales

Summary

The content provides a detailed guide on building a shopping cart microservice using Go, covering project structure, domain modeling, and service implementation following Domain-Driven Design principles.

Abstract

This chapter delves into the development of a comprehensive shopping cart microservice using the Go programming language. It begins with the creation of a root folder and the initialization of a Go module, followed by the establishment of a domain-driven design (DDD) structure with clearly defined packages and components. The guide emphasizes the importance of understanding the previous chapters to fully grasp the microservices architecture. It proceeds to outline the microservice's directory structure, detailing the purpose and interaction of each package, including the domain entities for shopping carts and products, their associated services, and repository interfaces. The code examples illustrate the implementation of domain entities with UUID identifiers for scalability and security, and the services that encapsulate business logic, error handling, and logging. The chapter concludes by hinting at the upcoming content, which will focus on database connectivity and further development of the microservice.

Opinions

  • The author advocates for a methodical approach to microservice development, starting with a well-organized project structure.
  • Domain-Driven Design is presented as a fundamental approach to model complex software systems, with a clear emphasis on separating concerns and encapsulating domain logic.
  • Using UUIDs for entity identification is recommended for improved security and scalability.
  • The use of repository interfaces is emphasized to define a contract for database access, allowing for clean separation between the application logic and data layer.
  • Logging is considered an essential aspect of service development for tracking service activities and enhancing observability.
  • The guide is structured to build upon previous knowledge, ensuring a step-by-step understanding of microservices architecture.
  • The chapter prepares the reader for the next phase of development, which will involve setting up database connections and further refining the microservice.

Chapter 11 - Coding our Microservice (Part 1)

Building a Microservice With Go

The following list is the previous chapters of this series:

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 packages
  • Best Practices
  • Defining Components
  • Coding the principal components used by the microservices according to their Domain.

Introduction

Our journey begins with the construction of a highly comprehensive microservice — the Shopping Cart. Throughout this meticulously planned process, we will methodically progress, step by step, by assembling each integral component of our final solution. Starting with the creation of the directory that houses our code, we will proceed to establish the essential DDD structure. We’ll then define the logic, conduct unit testing, and culminate with the deployment of the service encapsulated within a Docker container.

This endeavor promises not only a deep exploration of the “Shopping Cart” microservice but also an illustration of best practices and a comprehensive approach to microservices development. By the end, we’ll have not just a functional service but also a valuable knowledge resource for crafting robust microservices in the future used in this series or in the real world.

Starting with the code

Creating root folder

To start building this microservice let’s start creating the “root folder” named “shopping cart”:

mkdir shopping-cart

After having the root folder we have to start the golang module:

go mod init github.com/go-microservices/shopping-cart-service

Once we have the root folder and the go mod generated is time to start building the project structure, in that step each one of the packages and files will be defined for our code implementation.

Shopping-Cart packages structure

In this step, we will dive into the structure and fundamental components of the Shopping Cart microservice. We will examine in detail how this microservice has been designed and organized, along with the packages that play a crucial role in its functioning. This in-depth understanding lays the foundation for effective implementation and efficient management of the shopping cart within our microservices ecosystem.

shopping-cart/
├── cmd/
│   └── main.go
├── internal/
│   ├── app/
│   │   └── api
│   │       ├── dto/
│   │       │   ├── shopping_cart.go
│   │       │   └── product.go
│   │       ├── handler/
│   │       │   ├── product/
│   │       │   │   └── handler.go
│   │       │   └── shopping_cart/
│   │       │       └── handler.go
│   │       ├── api.go
│   │       └── routes.go
│   ├── domain/
│   │   ├── shopping_cart/
│   │   │   ├── shopping_cart.go
│   │   │   ├── shopping_cart_repository.go
│   │   │   └── shopping_cart_service.go
│   │   └── product/
│   │       ├── product.go
│   │       ├── product_repository.go
│   │       └── product_service.go
│   ├── infrastructure/
│   │   ├── persistence/
│   │   │   ├── shopping_cart_repository.go
│   │   │   └── product_repository.go
│   │   └── database/
│   │       ├── db_connection.go
│   │       └── database_config.go
│   └── shared/
│       ├── error/
│       │    └── error.go
│       └── logger/
│           └── logger.go
└── go.mod

Once the project structure is defined, and all the files and packages are placed it is time to start writing the needed code for each one of them. It’s worth noting that, at this stage, each of the “go” files can remain empty, solely defining the package with the same name as the containing directory.

Don’t worry if there is no description about what each file is, that description is part of the following steps where the content of each file will be developed.

Keep in mind, this exploration unveiled the critical packages and files that form the backbone of this microservice, this comprehensive understanding serves as the cornerstone for the seamless implementation and effective management of the shopping cart within our study case. With this foundation in place, we are well-equipped to embark on the journey of building and optimizing this vital application component.

Developing the domain components

After defining the code structure is time to start defining the domain components. In this case, it can be seen that two subdomains are evaluated as “shopping cart” and “products” (maybe there could be more but for this example, it is decided to keep it abstract and easy to develop). Each one of the subdomains is defined as a package with the name “shopping_cart” and “product” both as part of the domain package:

...
│   ├── domain/
│   │   ├── shopping_cart/
│   │   │   ├── shopping_cart.go
│   │   │   ├── shopping_cart_repository.go
│   │   │   └── shopping_cart_service.go
│   │   └── product/
│   │       ├── product.go
│   │       ├── product_repository.go
│   │       └── product_service.go
...

This step is focused on defining the content and the logic related to each one of the domains.

let’s start developing the shopping_cart:

/domain/shopping_cart/shopping_cart.go

package shoppingcart
import (
    uuid "github.com/satori/go.uuid"
)
type ShoppingCart struct {
    ID     *uuid.UUID `json:"id,omitempty"`
    UserID *uuid.UUID `json:"user_id"`
}
func NewShoppingCart(id *uuid.UUID, userID *uuid.UUID) ShoppingCart {
    return ShoppingCart{
        ID:     id,
        UserID: userID,
    }
}

Explaining the code: This code establishes the “ShoppingCart” entity within the realm of Domain-Driven Design (DDD). The presence of this entity, along with its constructor, plays a pivotal role in crafting a shopping cart component within a DDD-oriented system. It empowers the modeling and control of shopping cart-related data, fostering robust functionality and adherence to DDD principles.

This entity represents a shopping cart and has two main attributes:

The “ID” property is a unique identifier (UUID) that is used to uniquely identify each shopping cart. It’s optional in the JSON response, which means you don’t need to provide it when creating a new cart, as it will usually be automatically generated on the server.

“UserID” property is a unique identifier (UUID) that represents the user to whom the shopping cart belongs. This attribute is required and cannot be empty.

Identifier properties have been declared to be of type UUID, since using UUIDs instead of integers or strings as identifiers improves application scalability and security by avoiding ID collisions and reducing the chance of guessing valid identifiers.

The NewShoppingCart function is used to create a new instance of the “ShoppingCart” entity. It takes two parameters: a UUID for the ID and a UUID for the userID, and returns an instance of ShoppingCart with the supplied values.

/domain/shopping_cart/shopping_cart_service.go

package shoppingcart

import (
    uuid "github.com/satori/go.uuid"
    "github.com/sirupsen/logrus"
)

type shoppingCartService struct {
    logger           *logrus.Logger
    shoppingCartRepo ShoppingCartRepository
}

func NewShoppingCartService(scRepo ShoppingCartRepository, logger *logrus.Logger) shoppingCartService {
    return shoppingCartService{
        logger:           logger,
        shoppingCartRepo: scRepo,
    }
}

func (sc shoppingCartService) Create(spc ShoppingCart) error {
    sc.logger.Info("On Create Shopping")
    //more logic here ...
   
     return sc.shoppingCartRepo.Create(spc)
    
     //more logic here ...
}

func (sc shoppingCartService) GetByUserID(userId *uuid.UUID) (*ShoppingCart, error) {
    sc.logger.Info("On Get Shopping Cart for user")

    //more logic here ...

    shoppingCart, err := sc.shoppingCartRepo.GetByUserID(userId)
    if err != nil {
        sc.logger.Errorf("On Get Shopping Cart for user - error quering db: %d", err)
        return nil, err
    }

    //more logic here ...

    return shoppingCart, nil
}

Explaining the code: This code exemplifies a “shoppingCartService,” an integral part of a DDD system. Within a DDD architecture, services like these serve as intermediaries between the application and persistence layers, encapsulating domain and application-specific functionalities within reusable components.

Services play a pivotal role in DDD by ensuring a clear separation of responsibilities and enabling a more coherent and efficient modeling of the system based on core business domain concepts. They are indispensable components of a DDD-based software architecture.

In this specific instance, the shoppingCartService offers methods for creating shopping carts and retrieving them by user ID. These methods abstract the intricacies of business logic and database interactions. Additionally, a logging mechanism is utilized to track crucial service activities and events, enhancing system observability.

A detailed explanation of this service and its purpose is provided here:

Function NewShoppingCartService It receives two parameters: scRepo which represents a shopping cart repository and logger which references the singleton logger created for the application. This function allows initializing the service with the necessary dependencies.

Create function This function is used to create a new shopping cart. It takes an object of type ShoppingCart as an argument, which contains relevant information about the shopping cart to be created userID.

This function calls the Create method of the shopping cart repository “shoppingCartRepo” to perform the creation in the persistence layer. If the create operation is successful, no error is returned. Otherwise, an error is propagated.

GetByUserID function This function looks for a shopping cart based on the provided user ID. Receives a UUID representing the user as input. Similar to the “Create” function, it calls the “GetByUserID” method of the shopping cart repository to retrieve the cart related to the user.

If the operation is successful and a cart is found, the “ShoppingCart” object is returned. If any error occurs during the database lookup, an error message is logged and the error is propagated.

/domain/shopping_cart/shopping_cart_repository.go

package shoppingcart
import uuid "github.com/satori/go.uuid"

type ShoppingCartRepository interface {
    Create(spc ShoppingCart) error
    GetByUserID(userId *uuid.UUID) (*ShoppingCart, error)
}

Explaining the code: This interface serves as a contract that outlines the database access methods required for interacting with the shopping cart. What’s crucial to note is that this interface finds its concrete implementation in the “infrastructure” package, where all the underlying logic is meticulously defined. This implementation will then be seamlessly injected into the dependencies specified within the structure outlined in “shopping_cart_service.go,” namely, the “shoppingCartService.” This separation of concerns and implementation details ensures a clean and maintainable architecture for the shopping cart module.

Now is the time to develop product logic:

/domain/product/product.go

package product

import (
    uuid "github.com/satori/go.uuid"
)

type Product struct {
    ID             *uuid.UUID
    Name           string
    Quantity       int
    Description    string
    ShoppingCartID *uuid.UUID
}

func NewProduct(name string, quantity int, description string,
    shoppingCartID *uuid.UUID) Product {
    return Product{
        Name:           name,
        Quantity:       quantity,
        Description:    description,
        ShoppingCartID: shoppingCartID,
    }
}

Explaining the code: This code establishes the “Product” entity within the realm of Domain-Driven Design (DDD). The presence of this entity, along with its constructor, plays a pivotal role in crafting a shopping cart component within a DDD-oriented system. It empowers the modeling and control of product data, fostering robust functionality and adherence to DDD principles.

The provided code represents an entity within the context of DDD related to products in a shopping cart system. This entity, called “Product,” is essential for implementing a DDD-based shopping cart system as it allows modeling and managing information related to products in the cart.

The “Product” entity has the following characteristics:

  • ID: A unique identifier for each product, typically represented by a UUID (Universal Unique Identifier) to ensure its uniqueness.
  • Name: The name of the product, which provides a brief and meaningful description of the item.
  • Description: Provides a brief description and lists the product characteristics.
  • Quantity: The quantity of units of this product in the shopping cart.

The code defines the function NewProduct, which allows for the convenient creation of product instances by providing all the necessary attributes. This entity encapsulates data related to products in the shopping cart and can be used to perform calculations and operations related to product management in a shopping cart.

/domain/product/product_service.go

package product

import (
    uuid "github.com/satori/go.uuid"
    "github.com/sirupsen/logrus"
)

type productService struct {
    logger      *logrus.Logger
    productRepo ProductRepository
}

func NewProductService(productRepo ProductRepository, logger *logrus.Logger) productService {
    return productService{
        logger:      logger,
        productRepo: productRepo,
    }
}

func (ps productService) Create(product Product) error {
    ps.logger.Info("On Create product service")
    if err := ps.productRepo.Create(product); err != nil {
        return err
    }
    return nil
}

func (ps productService) GetAll(shoppingCartId uuid.UUID) ([]Product, error) {
    ps.logger.Info("On GetAll product service")
    return ps.productRepo.Get(shoppingCartId)
}

func (ps productService) Delete(productId uuid.UUID) error {
    ps.logger.Info("On Delete product service")
    err := ps.productRepo.Delete(productId)
    if err != nil {
        return err
    }
    return nil
}

func (ps productService) UpdateQuantity(productID uuid.UUID, quantity int) error {
    ps.logger.Info("On Update product service")
    err := ps.productRepo.UpdateQuantity(productID, quantity)
    if err != nil {
        return err
    }
    return nil
}

Explaining the code: This code exemplifies a “ProductService,” an integral part of a Domain-Driven Design (DDD) system. Within a DDD architecture, services like these serve as intermediaries between the application and persistence layers, encapsulating domain and application-specific functionalities within reusable components.

Services play a pivotal role in DDD by ensuring a clear separation of responsibilities and enabling a more coherent and efficient modeling of the system based on core business domain concepts. They are indispensable components of a DDD-based software architecture.

In this specific instance, the “ProductService” offers methods to manage the products related to a shopping cart. These methods abstract the intricacies of business logic and database interactions. Additionally, a logging mechanism is utilized to track crucial service activities and events, enhancing system observability.

A detailed explanation of this service and its purpose are provided below:

The structure “productService” represents the product management service. Its function, “NewProductService” accepts two parameters: “productRepo” which is of the product repository interface type, and “logger” which is of the logrus.Logger type.

The “NewProductService” function creates a new instance of the product management service. It receives a product repository and an event logger as parameters, which are then stored in the “productService” structure.

The “Create” method is used to create a new product. It logs an informative event in the logger and then calls the “Create” method of the product repository. If the creation process is successful, it doesn’t return errors; otherwise, it returns an error if any issues occur.

The “GetAll” method is used to retrieve all products associated with a specific shopping cart, identified by a “shoppingCartId”. It logs an informative event in the logger and then calls the “Get” method of the product repository to obtain the list of products. It returns the list of products and a possible error.

The “Delete” method is used to delete a product by its `productId`. It logs an informative event in the logger and then calls the “Delete” method of the product repository. It returns an error if the deletion is unsuccessful and no error if it succeeds.

The “UpdateQuantity” method is used to update the quantity of a product. It logs an informative event in the logger and then calls the “UpdateQuantity” method of the product repository. It returns an error if the update is unsuccessful and no error if it succeeds.

This service acts as an intermediate layer that encapsulates business logic related to products and serves as an interface between the application layer and the product repository, enabling a clear separation of responsibilities in Domain-Driven Design-based software design.

/domain/product/product_repository.go

package product

import uuid "github.com/satori/go.uuid"

type ProductRepository interface {
    Create(product Product) error
    Get(shoppingCartID uuid.UUID) ([]Product, error)
    Delete(productID uuid.UUID) error
    UpdateQuantity(productID uuid.UUID, quantity int) error
}

Explaining the code: This interface serves as a contract that outlines the database access methods required for interacting with the products related to a shopping cart. What’s crucial to note is that this interface finds its concrete implementation in the “infrastructure” package, where all the underlying logic is meticulously defined. This implementation will then be seamlessly injected into the dependencies specified within the structure outlined in “product_service.go,” namely, the “productService.” This separation of concerns and implementation details ensures a clean and maintainable architecture for the shopping cart module.

Next readings …

Wait for Chapter 12 “Coding our Microservice (Part 2) — Developing the Database Connection”.

https://readmedium.com/chapter-12-building-a-microservice-with-go-part-2-0a488f58ff57

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