README ΒΆ
SpaceTrouble π
SpaceTrouble is a Go-based REST API for booking space travel tickets. It enables users to create and manage bookings for space trips while ensuring launchpad availability and avoiding conflicts with SpaceX launches.
Features π
- Create and view space travel bookings
- Real-time integration with SpaceX API to check launch conflicts
- Validation of booking requests (age, destination, launchpad availability)
- Support for multiple destinations (Mars, Moon, Pluto, etc.)
- PostgreSQL database for persistent storage
- JSON and XML response formats
- Cursor-based pagination for booking listings
- Health check endpoint with system metrics
- Docker containerization for easy deployment
Prerequisites π
- Docker and Docker Compose
- Go 1.22 or higher (for local development)
- Make (optional, but recommended)
- PostgreSQL 16 (handled by Docker)
Running the Project π
Local Development
- Clone the repository:
git clone https://github.com/chrisdamba/spacetrouble.git
cd spacetrouble
- Copy and configure environment variables:
cp .env.example .env
# Edit .env with your preferred settings
- Start the services:
# Build the Docker images
make docker-build
# Start all services
make docker-up
# Run database migrations
make migrate-up
- Verify the setup:
# Check if services are running
docker-compose ps
# Check application logs
docker-compose logs -f app
# Test the health endpoint
curl http://localhost:5000/v1/health
Stopping the Project
# Stop all services
make docker-down
# Stop and remove all containers, networks, and volumes
docker-compose down -v
Rebuilding After Changes
# Rebuild the application
make docker-build
# Restart services
make docker-down
make docker-up
The API will be available at http://localhost:5000
API Endpoints π οΈ
Create Booking
POST /v1/bookings
Content-Type: application/json
{
"first_name": "John",
"last_name": "Doe",
"gender": "male",
"birthday": "1990-01-01T00:00:00Z",
"launchpad_id": "5e9e4502f5090995de566f86",
"destination_id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"launch_date": "2025-01-01T00:00:00Z"
}
Response (201 Created):
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"user": {
"id": "123e4567-e89b-12d3-a456-426614174001",
"first_name": "John",
"last_name": "Doe",
"gender": "male",
"birthday": "1990-01-01T00:00:00Z"
},
"flight": {
"id": "123e4567-e89b-12d3-a456-426614174002",
"launchpad_id": "5e9e4502f5090995de566f86",
"destination": {
"id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"name": "Mars"
},
"launch_date": "2025-01-01T00:00:00Z"
},
"status": "ACTIVE",
"created_at": "2024-01-01T00:00:00Z"
}
List Bookings
GET /v1/bookings?limit=10&cursor=<cursor_token>
Accept: application/json
Response (200 OK):
{
"bookings": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"user": {
"id": "123e4567-e89b-12d3-a456-426614174001",
"first_name": "John",
"last_name": "Doe",
"gender": "male",
"birthday": "1990-01-01T00:00:00Z"
},
"flight": {
"id": "123e4567-e89b-12d3-a456-426614174002",
"launchpad_id": "5e9e4502f5090995de566f86",
"destination": {
"id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"name": "Mars"
},
"launch_date": "2025-01-01T00:00:00Z"
},
"status": "ACTIVE",
"created_at": "2024-01-01T00:00:00Z"
}
],
"limit": 10,
"cursor": "next_page_token"
}
Delete Booking
DELETE /v1/bookings?id=123e4567-e89b-12d3-a456-426614174000
Response (204 No Content)
Health Check
GET /v1/health
Response (200 OK):
{
"status": "healthy",
"timestamp": "2024-01-01T00:00:00Z",
"version": "1.0.0",
"uptime": "24h0m0s",
"go_version": "go1.22",
"memory": {
"alloc": 1234567,
"totalAlloc": 2345678,
"sys": 3456789,
"numGC": 10
}
}
Available Destinations
Destination | ID |
---|---|
Mars | a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 |
Moon | b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22 |
Pluto | c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a33 |
Asteroid Belt | d0eebc99-9c0b-4ef8-bb6d-6bb9bd380a44 |
Europa | e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a55 |
Titan | f0eebc99-9c0b-4ef8-bb6d-6bb9bd380a66 |
Ganymede | 70eebc99-9c0b-4ef8-bb6d-6bb9bd380a77 |
Error Responses
Status Code | Description |
---|---|
400 | Bad Request - Invalid input data |
404 | Not Found - Booking or destination not found |
409 | Conflict - Launchpad unavailable or SpaceX conflict |
500 | Internal Server Error |
Example error response:
{
"error": "launchpad is unavailable"
}
Request Validation Rules
first_name
,last_name
: Required, max 50 charactersgender
: Must be "male", "female", or "other"birthday
: Must be between 18-75 years oldlaunchpad_id
: Must be 24 charactersdestination_id
: Must be a valid UUID from available destinationslaunch_date
: Must be in the future
Environment Variables βοΈ
Variable | Description | Default |
---|---|---|
SERVER_ADDRESS | Server listening address | :5000 |
SERVER_READ_TIMEOUT | HTTP read timeout | 15s |
SERVER_WRITE_TIMEOUT | HTTP write timeout | 15s |
SERVER_IDLE_TIMEOUT | HTTP idle timeout | 30s |
POSTGRES_HOST | PostgreSQL host | localhost |
POSTGRES_PORT | PostgreSQL port | 5432 |
POSTGRES_DB | PostgreSQL database name | space |
POSTGRES_USER | PostgreSQL username | postgres |
POSTGRES_PASSWORD | PostgreSQL password | postgres |
MAX_CONNS | Max DB connections | 99 |
SPACEX_URL | SpaceX API base URL | https://api.spacexdata.com/v4 |
Project Structure π
spacetrouble/
βββ cmd/
β βββ api/
β βββββmain.go # Application entry point
βββ internal/
β βββ api/ # API handlers
β βββ models/ # Domain models
β βββ repository/ # Database operations
β βββ service/ # Business logic
β βββ validator/ # Request validation
βββ pkg/
β βββ config/ # Configuration management
β βββ health/ # Health check endpoint
β βββ spacex/ # SpaceX API client
βββ tests/ # Tests
β βββ api/
β βββ mocks/
β βββ pkg/
β βββ repository/
β βββ service/
β βββ utils/
β βββ validator/
βββ migrations/ # Database migrations
βββ Dockerfile # Docker build instructions
βββ docker-compose.yml # Docker compose configuration
βββ Makefile # Build and development commands
βββ README.md # Project documentation
Available Make Commands π οΈ
make build
: Build the applicationmake test
: Run testsmake vet
: Run Go vetmake docker-build
: Build Docker imagemake docker-up
: Start Docker containersmake docker-down
: Stop Docker containersmake migrate-up
: Run database migrationsmake migrate-down
: Revert database migrations
Testing π§ͺ
Run the test suite:
make test
Testing Structure π§ͺ
Test Organization
tests/
βββ api/ # API handler tests
β βββ api_test.go # Tests for API endpoints
βββ mocks/ # Mock implementations
β βββ booking_repository.go # Mock repository
β βββ spacex_client.go # Mock SpaceX client
βββ pkg/ # Package tests
β βββ config/ # Configuration tests
β βββ health/ # Health check tests
β βββ spacex/ # SpaceX client tests
βββ repository/ # Repository layer tests
β βββ booking_repository_test.go
βββ service/ # Service layer tests
β βββ booking_service_test.go
βββ utils/ # Test utilities
β βββ mock_data.go # Test data generators
βββ validator/ # Validation tests
βββ validator_test.go
Test Coverage
API Tests (tests/api/
)
- Tests HTTP endpoints
- Validates request/response formats
- Checks content type handling
- Verifies error responses
- Tests pagination
- Validates HTTP method restrictions
Repository Tests (tests/repository/
)
- Database CRUD operations
- Transaction handling
- Error scenarios
- Connection pooling
- Query building
- Cursor-based pagination
Service Tests (tests/service/
)
- Business logic validation
- SpaceX integration
- Booking workflow
- Error handling
- Data transformation
- Business rules enforcement
Mock Implementations (tests/mocks/
)
-
MockBookingRepository
: Repository layer mocking- Simulates database operations
- Provides predictable responses
- Enables error scenario testing
-
MockSpaceXClient
: SpaceX API mocking- Simulates API responses
- Tests network failures
- Validates integration points
Test Utilities (tests/utils/
)
mock_data.go
: Test data generation- Creates consistent test bookings
- Generates valid UUIDs
- Provides sample requests/responses
- Helps compare complex objects
Running Tests
All Tests
make test
Specific Package Tests
go test ./tests/service/...
go test ./tests/repository/...
go test ./tests/api/...
With Coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Test Categories
Unit Tests
- Individual component testing
- Mock external dependencies
- Fast execution
- High coverage
Integration Tests
- Database interaction
- API endpoint behavior
- External service integration
- Real-world scenarios
Mock Tests
Tests using mock implementations to verify:
- Error handling
- Edge cases
- Resource unavailability
- Invalid data scenarios
Test Scenarios
Booking Creation Tests
- Valid booking creation
- Invalid input validation
- Destination verification
- Launch date conflicts
- SpaceX availability
- Database errors
- Transaction rollback
Booking Listing Tests
- Pagination functionality
- Cursor handling
- Empty results
- Large result sets
- Filter application
- Sort ordering
Booking Deletion Tests
- Successful deletion
- Non-existent booking
- Invalid UUID
- Unauthorized deletion
- Status validation
Error Handling Tests
- Database connection failures
- SpaceX API unavailability
- Invalid input data
- Business rule violations
- Concurrent access
Test Design Principles
-
Isolation
- Tests run independently
- No shared state
- Clean setup/teardown
-
Reproducibility
- Consistent test data
- Deterministic results
- Clear failure messages
-
Coverage
- Happy path scenarios
- Error conditions
- Edge cases
- Business rules
-
Maintainability
- Clear test names
- Shared utilities
- Common patterns
- Documentation
Writing New Tests
- Create test file in appropriate directory
package repository_test
func TestNewFeature(t *testing.T) {
t.Run("successful case", func(t *testing.T) {
// Test setup
// Test execution
// Assertions
})
t.Run("error case", func(t *testing.T) {
// Test setup
// Test execution
// Assertions
})
}
- Use test utilities
booking := utils.CreateMockBooking(uuid.Nil)
bookings := utils.CreateMockBookings(5)
- Use mocks
mockRepo := new(mocks.MockBookingRepository)
mockSpaceX := new(mocks.MockSpaceXClient)
- Assert expectations
assert.NoError(t, err)
assert.Equal(t, expected, actual)
mockRepo.AssertExpectations(t)
Common Test Patterns
- Table-Driven Tests
tests := []struct {
name string
input string
expected string
wantErr bool
}{
{"valid case", "input", "expected", false},
{"error case", "bad input", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test logic
})
}
- Setup/Teardown
func setupTest(t *testing.T) (*mocks.MockBookingRepository, *service.BookingService) {
mockRepo := new(mocks.MockBookingRepository)
svc := service.NewBookingService(mockRepo)
return mockRepo, svc
}
- Helper Functions
func assertBookingsEqual(t *testing.T, expected, actual *models.Booking) {
t.Helper()
assert.Equal(t, expected.ID, actual.ID)
// Additional assertions
}
Deployment π’
The application is containerised and can be deployed using Docker:
- Build the image:
make docker-build
-
Configure environment variables for your deployment environment
-
Run the containers:
make docker-up
Troubleshooting π§
Common Docker Issues
- Port Conflicts
Error starting userland proxy: listen tcp 0.0.0.0:5000: bind: address already in use
Solution:
# Find the process using the port
sudo lsof -i :5000
# Kill the process
kill -9 <PID>
# Or change the port in docker-compose.yml
- Database Connection Issues
error: connect: connection refused
Solutions:
- Check if the database container is running:
docker-compose ps
- Verify database credentials in
.env
- Try restarting the services:
make docker-down
make docker-up
- Permission Issues
permission denied while trying to connect to the Docker daemon socket
Solution:
# Add your user to the docker group
sudo usermod -aG docker ${USER}
# Log out and back in or run:
newgrp docker
- Database Migration Failures
error: migration failed
Solutions:
- Check migration logs:
docker-compose logs app
- Reset migrations:
make migrate-down
make migrate-up
- Verify database connection:
docker-compose exec db psql -U postgres -d space -c "\l"
- Container Not Starting
Error response from daemon: Container is not running
Solutions:
- Check container logs:
docker-compose logs <service_name>
- Verify container status:
docker-compose ps
- Remove containers and volumes:
docker-compose down -v
docker-compose up -d
- Image Build Failures
failed to build: exit status 1
Solutions:
- Clean Docker cache:
docker system prune -a
- Rebuild with no cache:
docker-compose build --no-cache
Maintenance Commands
- Reset Everything
# Stop all containers and remove volumes
make docker-down
docker-compose down -v
# Remove all related images
docker rmi $(docker images | grep spacetrouble)
# Rebuild from scratch
make docker-build
make docker-up
make migrate-up
- View Logs
# All services
docker-compose logs -f
# Specific service
docker-compose logs -f app
docker-compose logs -f db
- Access Database
# Connect to PostgreSQL
docker-compose exec db psql -U postgres -d space
# Backup database
docker-compose exec db pg_dump -U postgres space > backup.sql
# Restore database
cat backup.sql | docker-compose exec -T db psql -U postgres -d space
- Check Container Status
# List containers
docker-compose ps
# Container details
docker inspect <container_id>
# Resource usage
docker stats
Performance Tuning
If you experience performance issues:
- Database Tuning
- Adjust
max_connections
in PostgreSQL - Modify connection pool size in
.env
- Monitor query performance with:
- Adjust
docker-compose exec db psql -U postgres -d space -c "SELECT * FROM pg_stat_activity;"
- Application Tuning
- Adjust timeouts in
.env
- Monitor memory usage:
- Adjust timeouts in
docker stats spacetrouble_app_1
- System Resources
- Increase Docker resources (CPU/Memory) in Docker Desktop settings
- Monitor resource usage:
docker-compose top
Technical Details π§
- Architecture: Clean Architecture pattern
- API Design: RESTful with JSON/XML support
- Database: PostgreSQL with migrations
- External Integration: SpaceX API for launch checks
- Validation: Custom validation rules for bookings
- Error Handling: Structured error responses
- Monitoring: Health check endpoint with metrics