Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
*.so
*.dylib

# Server binary
server

# Test binary, built with `go test -c`
*.test

Expand Down
90 changes: 89 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,106 @@ The implemented adapters are a full REST API and a web adapters, but only for th

The drivers are stored inside this layer.

The implemented drivers are an in-memory driver, but for the basket there is also a MongoDB driver.
The implemented drivers are an in-memory driver, MongoDB driver, and MySQL driver for the basket.

## Start application

### Start application using Go

#### In-Memory Driver (Default)
```shell
go run ./cmd/server
```

#### MongoDB Driver
```shell
DRIVER=mongodb go run ./cmd/server
```

#### MySQL Driver
```shell
# Set MySQL environment variables (optional - defaults shown)
export MYSQL_HOST=localhost
export MYSQL_PORT=3306
export MYSQL_USERNAME=root
export MYSQL_PASSWORD=password
export MYSQL_DATABASE=ecommerce

# Start with MySQL driver
DRIVER=mysql go run ./cmd/server
```

**Note:** For MySQL, make sure to create the database and tables first by running:
```shell
mysql -u root -p < internal/domain/basket/drivers/mysql/schema.sql
```

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `DRIVER` | `inmemory` | Database driver to use (`inmemory`, `mongodb`, `mysql`) |
| `HTTP_ADDR` | `localhost:8080` | HTTP server address |
| `MYSQL_HOST` | `localhost` | MySQL server host (when using MySQL driver) |
| `MYSQL_PORT` | `3306` | MySQL server port (when using MySQL driver) |
| `MYSQL_USERNAME` | `root` | MySQL username (when using MySQL driver) |
| `MYSQL_PASSWORD` | `password` | MySQL password (when using MySQL driver) |
| `MYSQL_DATABASE` | `ecommerce` | MySQL database name (when using MySQL driver) |

### Start application using Docker

#### Option 1: Using Docker Compose (Recommended)

**Quick Start Scripts:**

```shell
# Start with MySQL (includes database setup)
./start-with-mysql.sh

# Start with MongoDB
./start-with-mongodb.sh

# Start with in-memory (no database needed)
./start-with-inmemory.sh
```

**Manual Setup:**

Start the database services:

```shell
# Start MySQL and MongoDB services
docker-compose up -d

# Start the application with MySQL driver
DRIVER=mysql go run ./cmd/server

# Or start with MongoDB driver
DRIVER=mongodb go run ./cmd/server
```

Stop the services when done:

```shell
docker-compose down
```

#### Docker Services

The docker-compose.yaml file includes:

- **MySQL 8.0**: Available on port 3306
- Database: `clean_architecture_go`
- Root password: `password`
- Automatically loads the schema.sql on first startup
- Data persisted in `mysql_data` volume

- **MongoDB 8.0**: Available on port 27017
- Database: `ecommerce`
- No authentication required

#### Option 2: Using Docker Build Scripts

First build the docker image:

```shell
Expand Down
46 changes: 46 additions & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/arkadiusjonczek/clean-architecture-go/internal/domain/basket/business/usecases/helper"
"github.com/arkadiusjonczek/clean-architecture-go/internal/domain/basket/drivers/inmemory"
basketdrivermongodb "github.com/arkadiusjonczek/clean-architecture-go/internal/domain/basket/drivers/mongodb"
basketdrivermysql "github.com/arkadiusjonczek/clean-architecture-go/internal/domain/basket/drivers/mysql"
warehouse "github.com/arkadiusjonczek/clean-architecture-go/internal/domain/warehouse/business/entities"
warehousehelper "github.com/arkadiusjonczek/clean-architecture-go/internal/domain/warehouse/business/usecases/helper"
warehousedriverinmemory "github.com/arkadiusjonczek/clean-architecture-go/internal/domain/warehouse/drivers/inmemory"
Expand Down Expand Up @@ -57,6 +58,51 @@ func startHTTPServer() error {
basketsCollection := mongoClient.Database("ecommerce").Collection("baskets")

basketRepository = basketdrivermongodb.NewMongoBasketRepository(basketsCollection)
case "mysql":
fmt.Printf("Driver: MySQL\n")

// Get MySQL configuration from environment variables
mysqlHost := os.Getenv("MYSQL_HOST")
if mysqlHost == "" {
mysqlHost = "localhost"
}
mysqlPort := os.Getenv("MYSQL_PORT")
if mysqlPort == "" {
mysqlPort = "3306"
}
mysqlUsername := os.Getenv("MYSQL_USERNAME")
if mysqlUsername == "" {
mysqlUsername = "root"
}
mysqlPassword := os.Getenv("MYSQL_PASSWORD")
if mysqlPassword == "" {
mysqlPassword = "password"
}
mysqlDatabase := os.Getenv("MYSQL_DATABASE")
if mysqlDatabase == "" {
mysqlDatabase = "ecommerce"
}

// Create MySQL database connection
config := basketdrivermysql.DatabaseConfig{
Host: mysqlHost,
Port: mysqlPort,
Username: mysqlUsername,
Password: mysqlPassword,
Database: mysqlDatabase,
}

mysqlDB, mysqlErr := basketdrivermysql.NewConnection(config)
if mysqlErr != nil {
panic(fmt.Errorf("failed to connect to MySQL: %w", mysqlErr))
}
defer func() {
if closeErr := mysqlDB.Close(); closeErr != nil {
log.Printf("Failed to close MySQL connection: %v", closeErr)
}
}()

basketRepository = basketdrivermysql.NewMySQLBasketRepository(mysqlDB)
default:
fmt.Printf("Driver: InMemory\n")

Expand Down
15 changes: 15 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,18 @@ services:
- MONGO_INITDB_DATABASE=ecommerce
ports:
- "27017:27017"

mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=ecommerce
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./internal/domain/basket/drivers/mysql/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
command: --default-authentication-plugin=mysql_native_password

volumes:
mysql_data:
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
)

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
Expand All @@ -20,6 +21,7 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/golang/snappy v1.0.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
Expand All @@ -21,6 +23,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
Expand Down
132 changes: 132 additions & 0 deletions internal/domain/basket/drivers/mysql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# MySQL Basket Repository Adapter

This package provides a MySQL implementation of the `BasketRepository` interface for the clean architecture Go project.

## Setup

### 1. Database Setup

First, create the MySQL database and tables by running the provided schema:

```bash
mysql -u root -p < schema.sql
```

### 2. Dependencies

The MySQL driver is already included in the project dependencies:

```go
github.com/go-sql-driver/mysql v1.9.3
```

## Usage

### Basic Usage

```go
package main

import (
"log"

"github.com/arkadiusjonczek/clean-architecture-go/internal/domain/basket/business/entities"
"github.com/arkadiusjonczek/clean-architecture-go/internal/domain/basket/drivers/mysql"
)

func main() {
// Configure database connection
config := mysql.DatabaseConfig{
Host: "localhost",
Port: "3306",
Username: "root",
Password: "password",
Database: "ecommerce",
}

// Create database connection
db, err := mysql.NewConnection(config)
if err != nil {
log.Fatal(err)
}
defer db.Close()

// Create repository
repository := mysql.NewMySQLBasketRepository(db)

// Use the repository
basketFactory := entities.NewBasketFactory()
basket, err := basketFactory.NewBasket("user123")
if err != nil {
log.Fatal(err)
}

// Add items
basket.AddItem("product1", 2)
basket.AddItem("product2", 1)

// Save basket
basketID, err := repository.Save(basket)
if err != nil {
log.Fatal(err)
}

// Find basket
foundBasket, err := repository.Find(basketID)
if err != nil {
log.Fatal(err)
}

log.Printf("Found basket for user: %s", foundBasket.GetUserID())
}
```

### Using DSN

```go
// Alternative: use DSN string
dsn := "root:password@tcp(localhost:3306)/ecommerce?charset=utf8mb4&parseTime=True&loc=Local"
db, err := mysql.NewConnectionFromDSN(dsn)
if err != nil {
log.Fatal(err)
}
```

## Database Schema

The adapter uses two main tables:

### `baskets` table
- `id` (VARCHAR(36), PRIMARY KEY): Unique basket identifier
- `userid` (VARCHAR(255)): User identifier
- `items` (JSON): Serialized basket items
- `created_at` (TIMESTAMP): Creation timestamp
- `updated_at` (TIMESTAMP): Last update timestamp

### Items Storage
Basket items are stored as JSON in the `items` column with the following structure:

```json
{
"product1": {"product_id": "product1", "count": 2},
"product2": {"product_id": "product2", "count": 1}
}
```

## Features

- **Upsert Support**: The `Save` method uses `INSERT ... ON DUPLICATE KEY UPDATE` for efficient upsert operations
- **Connection Pooling**: Configured with appropriate connection pool settings
- **Error Handling**: Proper error handling with meaningful error messages
- **JSON Storage**: Efficient storage of basket items using MySQL's JSON data type
- **Indexing**: Optimized with indexes on frequently queried fields

## Testing

Run the example test to verify the setup:

```bash
go test ./internal/domain/basket/drivers/mysql/... -v
```

Note: The test requires a running MySQL database and will be skipped if not available.
Loading