Skip to content
Draft
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
10 changes: 10 additions & 0 deletions go/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Example configuration file for PokeServer
# Copy this to .env and update with your values

database:
url: "postgres://postgres:postgres@localhost:5432/pokemon?sslmode=disable"
server:
port: "8080"
pokeapi:
url: "https://pokeapi.co/api/v2/pokemon?limit="
max: 1025
198 changes: 198 additions & 0 deletions go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# PokeServer Go Backend - Clean Architecture

This is a complete rewrite of the PokeServer Go backend following clean architecture principles, with improved error handling, proper dependency injection, and modern Go practices.

## Architecture Overview

The application now follows a clean, layered architecture:

```
┌─────────────────┐
│ HTTP Layer │ - Handlers, Middleware, Validation
├─────────────────┤
│ Business Layer │ - Services, Domain Logic
├─────────────────┤
│ Data Layer │ - Repository, Database
├─────────────────┤
│ External APIs │ - PokeAPI Client
└─────────────────┘
```

## Key Components

### Configuration (`config.go`)
- Centralized configuration management using Viper
- Environment variable support
- Configuration validation
- Default values for all settings

### Logging (`logger.go`)
- Structured JSON logging using Go's `slog` package
- Context-aware logging
- Request-specific fields
- Error tracking

### Middleware (`middleware.go`)
- **CORS**: Cross-origin resource sharing
- **Logging**: Request/response logging with timing
- **Recovery**: Panic recovery with logging
- Chainable middleware pattern

### Repository Layer (`repository.go`)
- Interface-based design for testability
- Proper error handling with context
- Database connection management
- Structured logging integration

### Service Layer (`service.go`)
- Business logic separation
- Pokemon operations (get, vote, list)
- Error handling and validation
- Database initialization

### Handler Layer (`handlers.go`)
- HTTP request/response handling
- Input validation
- Proper HTTP status codes
- JSON error responses
- Template rendering for web pages

### Validation (`validation.go`)
- Input parameter validation
- Structured error responses
- Type-safe validation functions

### External Client (`pokeclient.go`)
- PokeAPI integration with proper error handling
- No more `log.Fatal` calls
- Structured logging
- HTTP client best practices

## Key Improvements

### 1. Error Handling
- **Before**: `log.Fatal()` everywhere, causing application crashes
- **After**: Proper error propagation with context and structured responses

### 2. Dependency Injection
- **Before**: Global variables and tight coupling
- **After**: Interface-based design with constructor injection

### 3. Configuration Management
- **Before**: Scattered configuration with Viper calls throughout
- **After**: Centralized config struct with validation

### 4. Logging
- **Before**: Basic `log.Print()` statements
- **After**: Structured JSON logging with context and fields

### 5. HTTP Handling
- **Before**: Manual CORS, no validation, poor error responses
- **After**: Middleware-based CORS, validation, proper status codes

### 6. Graceful Shutdown
- **Before**: No signal handling
- **After**: Proper signal handling with graceful shutdown

## API Endpoints

All endpoints now support proper HTTP methods and return structured JSON responses:

### GET /health
Health check endpoint
```json
{"status": "healthy"}
```

### GET /getpokemon
Get a random Pokemon
```json
{
"id": 25,
"name": "pikachu",
"sprites": {
"front_default": "https://..."
}
}
```

### GET /getall
Get all Pokemon with vote counts
```json
{
"pokemon": [...],
"count": 5
}
```

### GET /vote?id=25&vote=up
Vote for a Pokemon (up/down)
```json
{
"id": 25,
"name": "pikachu",
"vote": 10,
"url": "https://..."
}
```

## Error Responses

All errors now return structured JSON:
```json
{
"error": "validation_error",
"message": "Invalid request parameters",
"details": [
{
"field": "vote",
"message": "vote is required"
}
]
}
```

## Configuration

The application can be configured via environment variables or a `.env` file:

```yaml
database:
url: "postgres://user:password@localhost:5432/pokemon?sslmode=disable"
server:
port: "8080"
pokeapi:
url: "https://pokeapi.co/api/v2/pokemon?limit="
max: 1025
```

Environment variables:
- `DATABASE_URL`
- `PORT`
- `POKEAPI_URL`
- `POKEAPI_MAX`

## Testing

The test suite has been updated to work with the new architecture:
- Fixed Pokemon struct inconsistencies
- Added proper dependency injection in tests
- Maintained all existing test functionality

## Running the Application

1. Set up your database URL in `.env` or environment variables
2. Run: `go run .`
3. The server will start with graceful shutdown support
4. Health check available at `/health`

## Migration Notes

The rewrite maintains **full backward compatibility** with the existing API while providing:
- Better error handling
- Improved performance
- Enhanced maintainability
- Proper testing infrastructure
- Production-ready features

All existing endpoints work exactly as before, but now with proper error handling and improved reliability.
96 changes: 96 additions & 0 deletions go/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package main

import (
"fmt"
"os"
"strconv"

"github.com/spf13/viper"
)

// Config holds all configuration for the application
type Config struct {
Database DatabaseConfig `mapstructure:"database"`
Server ServerConfig `mapstructure:"server"`
PokeAPI PokeAPIConfig `mapstructure:"pokeapi"`
}

type DatabaseConfig struct {
URL string `mapstructure:"url"`
}

type ServerConfig struct {
Port string `mapstructure:"port"`
}

type PokeAPIConfig struct {
URL string `mapstructure:"url"`
Max int `mapstructure:"max"`
}

// LoadConfig reads configuration from environment variables and config file
func LoadConfig() (*Config, error) {
// Set default values
viper.SetDefault("server.port", "8080")
viper.SetDefault("pokeapi.url", "https://pokeapi.co/api/v2/pokemon?limit=")
viper.SetDefault("pokeapi.max", 1025)

// Environment variable support
viper.SetEnvPrefix("POKE")
viper.AutomaticEnv()

// Try to read config file
viper.SetConfigFile(".env")
viper.SetConfigType("yaml")
if err := viper.ReadInConfig(); err != nil {
// Config file is optional, only log if it exists but can't be read
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf("error reading config file: %w", err)
}
}

// Override with environment variables if they exist
if dbURL := os.Getenv("DATABASE_URL"); dbURL != "" {
viper.Set("database.url", dbURL)
}
if port := os.Getenv("PORT"); port != "" {
viper.Set("server.port", port)
}
if pokeAPIURL := os.Getenv("POKEAPI_URL"); pokeAPIURL != "" {
viper.Set("pokeapi.url", pokeAPIURL)
}
if pokeAPIMaxStr := os.Getenv("POKEAPI_MAX"); pokeAPIMaxStr != "" {
if max, err := strconv.Atoi(pokeAPIMaxStr); err == nil {
viper.Set("pokeapi.max", max)
}
}

var config Config
if err := viper.Unmarshal(&config); err != nil {
return nil, fmt.Errorf("unable to decode config: %w", err)
}

// Validate required configuration
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}

return &config, nil
}

// Validate checks if the configuration is valid
func (c *Config) Validate() error {
if c.Database.URL == "" {
return fmt.Errorf("database URL is required")
}
if c.Server.Port == "" {
return fmt.Errorf("server port is required")
}
if c.PokeAPI.URL == "" {
return fmt.Errorf("PokeAPI URL is required")
}
if c.PokeAPI.Max <= 0 {
return fmt.Errorf("PokeAPI max must be greater than 0")
}
return nil
}
Loading