https://github.com/mohammadne/shopping-cart-manager
Shopping cart manager
https://github.com/mohammadne/shopping-cart-manager
Last synced: 6 months ago
JSON representation
Shopping cart manager
- Host: GitHub
- URL: https://github.com/mohammadne/shopping-cart-manager
- Owner: mohammadne
- Created: 2025-01-30T14:46:25.000Z (over 1 year ago)
- Default Branch: master
- Last Pushed: 2025-02-13T05:49:53.000Z (over 1 year ago)
- Last Synced: 2025-10-19T20:58:30.559Z (9 months ago)
- Language: Go
- Homepage:
- Size: 174 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Shopping Cart Manager




## Introduction
`Shopping Cart Manager` is a simple Go program that allows users to manage cart items.

## Architecture Overview
The project is organized as follows:
```bash
> tree
.
├── cmd
│ ├── migration
│ │ └── schemas
│ ├── version.go
│ └── web-api
├── deployments
│ ├── docker
│ └── k8s
├── internal
│ ├── api
│ │ └── http
│ ├── config
│ ├── entities
│ ├── repositories
│ │ ├── cache
│ │ └── storage
│ └── services
└── pkg
├── mysql
└── redis
```
### Continuous Integration (CI)
CI for this project is managed using `GitHub Actions`. The process builds the Docker image and uploads it to the `ghcr.io` registry.
### Deployments
The application can be deployed using `docker-compose`:
- **Local Development:** Use the `.local` compose file to resolve dependencies locally.
- **Production:** The `.prod` compose file is designed for deploying the application to a server.
Additionally, a Kubernetes setup (`k8s/`) is provided for deploying the application with `helmsman` and `helm` charts. The Kubernetes cluster is set up using `Kind`.
### API
The API layer currently only supports HTTP handlers. Template files are embedded using the `embed` package, replacing the previous design that used absolute paths, which caused deployment issues.
Although `Gin` was chosen as the HTTP engine, other high-throughput frameworks like `Fiber` (based on `fastHTTP`) could be used for more demanding systems. However, `Gin` is sufficient for this application’s needs.
A custom middleware was developed to handle cookie checking for users, improving code organization and maintainability.
> **Note:** The term `session` was incorrectly used for cookies in some parts of the code. This term was updated, but in the database, `session_id` remains unchanged for backward compatibility.
### Configuration
Configuration is managed using the `envconfig` package to avoid hardcoded values. For more complex use cases, tools like `koanf` could be used, but `envconfig` is sufficient for this project.
### Services
The `services` directory is designed to handle the business logic in the application, following Clean Architecture principles. The HTTP layer (e.g., `Gin`) is isolated from business logic, only handling queries, user input validation, and other presentation-layer concerns.
### Storage
MySQL is used as the primary database. Changes include:
1. Switching from `gorm` to raw SQL queries for improved performance and readability.
2. Developing migration logic to handle schema changes.
Benefits of using raw SQL:
- **Readability:** Queries are clearer and more intuitive.
- **Performance:** Raw SQL allows for better optimization, especially with complex queries.
- **Flexibility:** You can optimize SQL queries based on specific attributes.
- **DBA Friendly:** DBAs can easily understand and modify raw SQL.
### Cache
Redis is used for caching cart items, reducing the need to access the primary database on every request. Since Redis is an in-memory store, read and write operations are much faster compared to traditional databases like MySQL.
### Project Structure
In this project, the following structure is used:
- **`/pkg`**: Contains general-purpose packages like `mysql` and `redis`.
- **`/internal`**: Contains business logic and project-specific code.
This structure is recommended by the Go community, separating general utility code from domain-specific logic.
### Data Migration
To migrate the database schema, run the following command:
```sh
go run cmd/migration/main.go --direction up
```
This will apply the necessary schema changes.
Migration steps include:
1. Adding a relationship constraint between `cart_items.cart_id` and `cart_entities.cart_id`.
2. Creating a new `items` table for better organization and to avoid hardcoded values in the code.
3. Ensuring `session_id` is unique across tables.
4. Replacing the `total` column in `cart_entities` with a more normalized design.
5. Using `TIMESTAMP` instead of `DATETIME` for better tracking of changes over time.
### Why Migrations Were Necessary
Several issues in the original schema required migration:
1. The `cart_id` in the `cart_items` table had no foreign key constraint.
2. The `items` table was added to streamline the `cart_items` structure.
3. The `session_id` column should be unique across all related tables.
4. The `total` column in `cart_entities` made the table denormalized; this information can be derived from other related tables.
5. Using `TIMESTAMP` instead of `DATETIME` offers better tracking of changes over time.
### Tests
I have written unit tests for different modules, for mocking redis I have used `miniredis` and for mysql database I have used the package `sqlmock`, also for mocking api calls to this repositories I have used the `testify` package to mock the behavior of this repositories.
For the service layer and the item's service I have added some sample `benchmark` tests to measure the performance, altough this method uses the mocks and mocks do not represent real performance.
There is a functional test and an integration test in the tests directory which are very basic test cases spinned up by docker-compose (the one in deployments/docker/compose.local.yml). in the functional part we test the cache module and in the integration test we test the behavior of the whole application (set cookie and send the request to the applicaion, for more advanced integration tests we can examine the expectations like the value of items in redis and mysql by an actual redis and mysql driver and assert if something goes wrong).
I have also added `k6` for the `load` testing.
So In my project, I have the following tests:
- unit tests
- benchmark tests
- load test
- integration tests
- functional tests