Embedded Systems Backend Using Go

Project Structure

The project follows a clean architecture pattern with separated concerns:

├── es_website_gcek_backend
├── go.mod
├── go.sum
├── handlers
│   ├── register.go
│   └── test.go
├── initializers
│   ├── initDB.go
│   └── loadEnvVars.go
├── main.go
└── routes
    ├── routes.go
    └── test.go

4 directories, 11 files

🛢Database

Repositories

Without CTX (context)

package repositories

import (
	"github.com/aruncs31s/es_website_gcek_backend/database/model"
	"gorm.io/gorm"
)

type UserRepository interface {
	CreateUser(user *model.User) error
	GetUserByID(id uint) (*model.User, error)
	GetAllUsers() ([]*model.User, error)
}
	"github.com/aruncs31s/es_website_gcek_backend/database/model"
	"gorm.io/gorm"
)

type UserRepository interface {
	CreateUser(user *model.User) error
	GetUserByID(id uint) (*model.User, error)
	GetAllUsers() ([]*model.User, error)
}

type userRepository struct {
	db *gorm.DB
}

func NewUserRepository(db *gorm.DB) UserRepository {
	return &userRepository{db: db}
}

func (ur *userRepository) CreateUser(user *model.User) error {
	return ur.db.Create(user).Error
}

func (ur *userRepository) GetUserByID(id uint) (*model.User, error) {
	var user model.User
	err := ur.db.First(&user, id).Error
	if err != nil {
		return nil, err
	}
	return &user, nil
}

func (ur *userRepository) GetAllUsers() ([]*model.User, error) {
	var users []*model.User
	err := ur.db.Find(&users).Error
	if err != nil {
		return nil, err
	}
	return users, nil
}
```{ #repofullcode}




##### Flow 
1. Defines the `UserRepository` interface with methods for user operations.
>[!ABSTRACT]- **Why?**
> **Abstraction & Decoupling**: The interface abstracts the data layer from business logic. Your handlers and services depend on the interface, not the concrete implementation, making the code more modular and testable.
>
> **Testability**: You can easily create mock implementations of `UserRepository` for unit testing without needing a real database connection.
>
> **Flexibility**: Want to switch from GORM to raw SQL or a different ORM? Just implement the interface differently - no changes needed in your handlers or business logic.
>
> **Dependency Inversion**: High-level modules (handlers) don't depend on low-level modules (database). Both depend on abstractions (interfaces).
>
> **Example**: Your handler receives `UserRepository` interface, not `*gorm.DB` directly:
> 
> ```go
> type UserHandler struct {
>     userRepo UserRepository // Interface, not concrete type
> }
> ```

```go
type UserRepository interface {
	CreateUser(user *model.User) error
	GetUserById(id uint) (*model.User, error)
	GetAllUsers() ([]*model.User, error)
}
  1. Implements the userRepository struct that uses GORM for database operations.
  2. Provides concrete implementations of all interface methods using GORM's API.
Multiple Implementations Example

The beauty of the UserRepository interface is that we can have multiple implementations:

type UserRepository interface {
    CreateUser(user *model.User) error
    GetUserById(id uint) (*model.User, error)
    GetAllUsers() ([]*model.User, error)
}

type userRepository struct {
    db *gorm.DB
}

So the idea is that UserRepository is the contract and we can have multiple implementations of it:

// GORM implementation
type gormUserRepository struct {
    db *gorm.DB
}

// Redis implementation
type redisUserRepository struct {
    client *redis.Client
}

// Mock implementation for testing
type mockUserRepository struct {
    users []model.User
}

// API client implementation
type apiUserRepository struct {
    httpClient *http.Client
    baseURL    string
}
```{ #diffimplementations}


>[!ABSTRACT]- **How to use them individually?**
> All of these variables can hold ANY implementation of `UserRepository`:
>
> ```go
> var repo UserRepository
>
> repo = NewGormRepository(db)      // Uses database
> repo = NewMockRepository()        // Uses in-memory data
> repo = NewAPIRepository(client)   // Uses external API
>
> // Your handler code stays exactly the same!
> handler := NewUserHandler(repo)
> ```

**Benefits**: It is easier to test this way because you can swap implementations without changing your business logic! 

---

##### Creation 

```go

func NewUserRepository(db *gorm.DB) UserRepository {
	return &userRepository{db: db}
}
func NewRedisUserRepository(client *redis.Client) UserRepository {
  return &redisUserRepository{client: client}
}

if i want i can have an another one by using ![[#^diffimplementations]]
Like the following

func NewUserRepositoryTwo(db *gorm.DB) UserRepository {
	return &redisUserRepository{db: db}
}
func NewUserRepositoryThree(db *gorm.DB) UserRepository {
	return &mockUserRepository{db: db}
}

abcd = NewUserRepository(db)
abcd.CreateUser(&model.User{Name: "John Doe", Email: "john@example.com"})
newDb := NewRedisUserRepository(client)
client.CreateUser(&model.User{Name: "Jane Doe", Email: "

With ctx(Context)

Key Benefits:

Advantages of Context

  1. Request Cancellation: If an HTTP request is cancelled (user closes browser, network timeout), the database query gets cancelled too 2. Timeout Control: Prevents long-running queries from blocking indefinitely
  2. Request Tracing: Allows tracing requests across different services/layers
  3. Graceful Shutdown: During server shutdown, ongoing requests can be cancelled properly
✅ Correct Implementation
package repositories

import (
	"context"
	"github.com/aruncs31s/es_website_gcek_backend/database/model"
	"gorm.io/gorm"
)

type UserRepository interface {
	CreateUser(ctx context.Context, user *model.User) error
	GetUserByID(ctx context.Context, id uint) (*model.User, error)
	GetAllUsers(ctx context.Context) ([]*model.User, error)
}

type userRepository struct {
	db *gorm.DB  // ✅ Only store db, NOT context
}

func NewUserRepository(db *gorm.DB) UserRepository {
	return &userRepository{db: db}
}

func (ur *userRepository) CreateUser(ctx context.Context, user *model.User) error {
	return ur.db.WithContext(ctx).Create(user).Error
}

func (ur *userRepository) GetUserByID(ctx context.Context, id uint) (*model.User, error) {
	var user model.User
	err := ur.db.WithContext(ctx).First(&user, id).Error
	if err != nil {
		return nil, err
	}
	return &user, nil
}

func (ur *userRepository) GetAllUsers(ctx context.Context) ([]*model.User, error) {
	var users []*model.User
	err := ur.db.WithContext(ctx).Find(&users).Error
	if err != nil {
		return nil, err
	}
	return users, nil
}
❌ Wrong Implementation
// DON'T DO THIS
type userRepository struct {
	db  *gorm.DB
	ctx context.Context  // ❌ WRONG: Never store context in structs
}

func NewUserRepository(db *gorm.DB, ctx context.Context) UserRepository {
	return &userRepository{db: db, ctx: ctx}  // ❌ WRONG
}
Why storing context is wrong:
  1. Request-scoped: Each HTTP request needs its own context
  2. Concurrency issues: Multiple requests would share the same context
  3. Lifetime mismatch: Repository lives longer than individual requests
  4. Go best practices: Context should flow through call chain, not be stored