A real-time distributed ride-hailing system built with Go, implementing Service-Oriented Architecture (SOA) principles. This project demonstrates advanced microservices patterns including message queues, WebSocket communication, geospatial data processing, and distributed state management.
This platform simulates the backend infrastructure of modern transportation services like Uber. It handles real-time ride requests, intelligent driver matching, live location tracking, and complex ride coordination across multiple microservices.
- Advanced Message Queue Patterns - RabbitMQ topic/fanout exchanges
- Real-Time Communication - WebSocket bidirectional streaming
- Geospatial Processing - PostGIS distance calculations and matching
- Microservices Orchestration - Event-driven architecture
- High-Concurrency Programming - Goroutines and channels
- Distributed State Management - Database transactions and consistency
- 🚕 Real-time Ride Matching - Intelligent algorithm matches passengers with nearby drivers
- 📍 Live Location Tracking - Real-time GPS updates with WebSocket streaming
- 💰 Dynamic Pricing - Distance and duration-based fare calculation
- 🔔 Push Notifications - Instant updates for ride status changes
- 📊 Admin Dashboard - System metrics and active ride monitoring
- 🔐 JWT Authentication - Secure role-based access control
- 📈 Event Sourcing - Complete audit trail for all ride events
- Multiple Vehicle Types: Economy, Premium, XL
- Smart Driver Selection: Distance + rating based matching
- Timeout Management: Automatic fallback if drivers don't respond
- Session Tracking: Driver earnings and ride statistics
- Cancellation Handling: Refund logic and reason tracking
The system consists of four independent microservices:
┌─────────────┐ ┌──────────────────┐ ┌──────────┐
│ Passenger │◄───────►│ Ride Service │◄───────►│ Admin │
│ (WebSocket) │ │ (Orchestrator) │ │Dashboard │
└─────────────┘ └──────────────────┘ └──────────┘
▲
│
▼
┌────────────────────────────────────┐
│ RabbitMQ Message Broker │
│ │
│ Exchanges: │
│ • ride_topic (topic) │
│ • driver_topic (topic) │
│ • location_fanout (fanout) │
└────────────────────────────────────┘
▲
│
▼
┌─────────────┐ ┌──────────────────┐
│ Driver │◄───────►│ Driver Location │
│ (WebSocket) │ │ Service │
└─────────────┘ └──────────────────┘
▲
│
▼
┌────────────────┐
│ PostgreSQL │
│ + PostGIS │
└────────────────┘
- User registration and authentication
- JWT token generation and validation
- Role-based access control (Passenger, Driver, Admin)
- Ride lifecycle orchestration
- Fare calculation and estimation
- Passenger WebSocket connections
- Ride status management
- Cancellation handling
- Driver registration and availability
- Intelligent matching algorithm
- Real-time location tracking
- Driver WebSocket connections
- Session management
- System metrics and analytics
- Active ride monitoring
- Driver distribution tracking
- Revenue reporting
- Go 1.21+ - High-performance concurrent programming
- PostgreSQL 15+ - Relational database with PostGIS extension
- RabbitMQ 3.12+ - Message broker for service communication
- WebSocket - Real-time bidirectional communication
github.com/jackc/pgx/v5- PostgreSQL drivergithub.com/rabbitmq/amqp091-go- AMQP clientgithub.com/gorilla/websocket- WebSocket implementationgithub.com/golang-jwt/jwt/v5- JWT authenticationgopkg.in/yaml.v3- Configuration management
- Docker & Docker Compose - Containerization
- Make - Build automation
- golang-migrate - Database migrations
Ensure you have the following installed:
- Go 1.21 or higher
- Docker & Docker Compose
- Make (optional, for convenience)
- golang-migrate (for database migrations)
Install golang-migrate:
# macOS
brew install golang-migrate
# Linux
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/local/bin/
# Windows (using Scoop)
scoop install migrategit clone https://github.com/yourusername/ride-hailing-platform.git
cd ride-hailing-platformCreate a config.yaml file in the project root:
database:
host: localhost
port: 5432
user: ridehail_user
password: ridehail_pass
database: ridehail_db
rabbitmq:
host: localhost
port: 5672
user: guest
password: guest
websocket:
port: 8080
services:
ride_service: 3000
driver_location_service: 3001
admin_service: 3004
auth_service: 3005Start PostgreSQL and RabbitMQ using Docker Compose:
make up
# or
docker-compose up -dThis will start:
- PostgreSQL on port
5432 - RabbitMQ on port
5672(Management UI: http://localhost:15672)
make migrate-upThis will create all necessary tables and seed initial data.
make build-go
# or
go build -o ride-hail-system .Each service must be started in a separate terminal:
make run-auth
# or
go run main.go --mode=auth-servicemake run-ride
# or
go run main.go --mode=ride-servicemake run-driver
# or
go run main.go --mode=driver-servicemake run-admin
# or
go run main.go --mode=admin-serviceCheck that all services are running:
# Auth Service
curl http://localhost:3005/health
# Ride Service
curl http://localhost:3000/health
# Driver Service
curl http://localhost:3001/health
# Admin Service
curl http://localhost:3004/healthAll API requests (except registration/login) require JWT authentication:
Authorization: Bearer <your_jwt_token>POST /auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure_password",
"role": "PASSENGER"
}Response (201):
{
"user_id": "550e8400-e29b-41d4-a716-446655440001",
"email": "user@example.com",
"role": "PASSENGER"
}POST /auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure_password"
}Response (200):
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"email": "user@example.com",
"role": "PASSENGER"
}
}POST /rides
Content-Type: application/json
Authorization: Bearer {passenger_token}
{
"passenger_id": "550e8400-e29b-41d4-a716-446655440001",
"pickup_latitude": 43.238949,
"pickup_longitude": 76.889709,
"pickup_address": "Almaty Central Park",
"destination_latitude": 43.222015,
"destination_longitude": 76.851511,
"destination_address": "Kok-Tobe Hill",
"ride_type": "ECONOMY"
}Response (201):
{
"ride_id": "550e8400-e29b-41d4-a716-446655440000",
"ride_number": "RIDE_20241216_001",
"status": "REQUESTED",
"estimated_fare": 1450.0,
"estimated_duration_minutes": 15,
"estimated_distance_km": 5.2
}POST /rides/{ride_id}/cancel
Content-Type: application/json
Authorization: Bearer {passenger_token}
{
"reason": "Changed my mind"
}POST /drivers/{driver_id}/online
Content-Type: application/json
Authorization: Bearer {driver_token}
{
"latitude": 43.238949,
"longitude": 76.889709
}POST /drivers/{driver_id}/location
Content-Type: application/json
Authorization: Bearer {driver_token}
{
"latitude": 43.238949,
"longitude": 76.889709,
"accuracy_meters": 5.0,
"speed_kmh": 45.0,
"heading_degrees": 180.0
}POST /drivers/{driver_id}/start
Content-Type: application/json
Authorization: Bearer {driver_token}
{
"ride_id": "550e8400-e29b-41d4-a716-446655440000",
"driver_location": {
"latitude": 43.238949,
"longitude": 76.889709
}
}POST /drivers/{driver_id}/complete
Content-Type: application/json
Authorization: Bearer {driver_token}
{
"ride_id": "550e8400-e29b-41d4-a716-446655440000",
"final_location": {
"latitude": 43.222015,
"longitude": 76.851511
},
"actual_distance_km": 5.5,
"actual_duration_minutes": 16
}GET /admin/overview
Authorization: Bearer {admin_token}Response (200):
{
"timestamp": "2024-12-16T10:30:00Z",
"metrics": {
"active_rides": 45,
"available_drivers": 123,
"busy_drivers": 45,
"total_rides_today": 892,
"total_revenue_today": 1234567.5,
"average_wait_time_minutes": 4.2,
"average_ride_duration_minutes": 18.5,
"cancellation_rate": 0.05
}
}GET /admin/rides/active?page=1&page_size=20
Authorization: Bearer {admin_token}Connect:
const ws = new WebSocket('ws://localhost:3000/ws/passengers/{passenger_id}');Authenticate:
{
"type": "auth",
"token": "Bearer eyJhbGciOiJIUzI1NiIs..."
}Receive Events:
{
"type": "ride_status_update",
"ride_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "MATCHED",
"driver_info": {
"driver_id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Aidar Nurlan",
"rating": 4.8,
"vehicle": {
"make": "Toyota",
"model": "Camry",
"color": "White",
"plate": "KZ 123 ABC"
}
}
}Connect:
const ws = new WebSocket('ws://localhost:3001/ws/drivers/{driver_id}');Receive Ride Offers:
{
"type": "ride_offer",
"offer_id": "offer_123456",
"ride_id": "550e8400-e29b-41d4-a716-446655440000",
"pickup_location": {
"latitude": 43.238949,
"longitude": 76.889709,
"address": "Almaty Central Park"
},
"estimated_fare": 1500.0,
"driver_earnings": 1200.0,
"expires_at": "2024-12-16T10:32:00Z"
}Accept/Reject Ride:
{
"type": "ride_response",
"offer_id": "offer_123456",
"ride_id": "550e8400-e29b-41d4-a716-446655440000",
"accepted": true,
"current_location": {
"latitude": 43.235,
"longitude": 76.885
}
}What happens:
- Passenger opens the app and enters pickup and destination locations
- Ride Service receives the ride request via REST API
- Fare calculation is performed based on distance, duration, and vehicle type
- Ride record is created in the database with status
REQUESTED - Request is published to RabbitMQ
ride_topicexchange with routing keyride.request.{ride_type} - Passenger WebSocket connection receives confirmation of request submission
Key Components:
- REST API endpoint:
POST /rides - Database: Insert into
ridesandcoordinatestables - Message Queue: Publish to
ride_topicexchange - Response: Estimated fare and ride details
What happens:
- Driver Service consumes the ride request from
driver_matchingqueue - Geospatial query finds available drivers within 5km radius using PostGIS:
SELECT d.id, ST_Distance(...) as distance_km
FROM drivers d
JOIN coordinates c ON c.entity_id = d.id
WHERE d.status = 'AVAILABLE'
AND d.vehicle_type = 'ECONOMY'
AND ST_DWithin(geography_point, pickup_point, 5000)
ORDER BY distance_km, d.rating DESC
LIMIT 10- Ride offers sent to selected drivers via WebSocket
- 30-second timeout starts for each driver to respond
- First driver to accept wins the ride match
Key Components:
- Queue:
driver_matchingbound toride.request.* - Database: PostGIS geospatial queries on
coordinatestable - WebSocket: Push ride offers to drivers
- Logic: Timeout management and offer expiration
What happens:
- Driver accepts the ride offer via WebSocket
- Driver Service publishes acceptance to
driver_topicwith routing keydriver.response.{ride_id} - Ride Service consumes the driver response and updates ride status to
MATCHED - Driver status updated to
BUSYin the database - Passenger notified via WebSocket with driver details (name, rating, vehicle info, ETA)
- Other pending offers are automatically cancelled
- Ride event logged to
ride_eventstable for audit trail
Key Components:
- WebSocket: Driver acceptance message
- Message Queue:
driver.response.{ride_id}routing - Database: Update
rides.statustoMATCHED,rides.driver_id,drivers.statustoBUSY - Notification: Passenger receives driver information and estimated arrival
What happens:
- Driver updates location every 3-5 seconds via
POST /drivers/{driver_id}/location - Location stored in
coordinatestable (previous location marked asis_current=false) - Location broadcast to
location_fanoutexchange (fanout type - all subscribers receive) - Ride Service consumes location updates and forwards to passenger via WebSocket
- ETA recalculated based on current distance and speed
- Status transitions:
MATCHED→EN_ROUTE(driver heading to pickup)EN_ROUTE→ARRIVED(driver at pickup location)ARRIVED→IN_PROGRESS(ride started)
Key Components:
- REST API:
POST /drivers/{driver_id}/location - Database: Real-time updates to
coordinatesandlocation_history - Message Queue: Fanout exchange broadcasts to all interested services
- WebSocket: Continuous location stream to passenger
- Rate Limiting: Max 1 update per 3 seconds
What happens:
- Driver starts ride via
POST /drivers/{driver_id}/start- Ride status:
IN_PROGRESS started_attimestamp recorded
- Ride status:
- Continuous location tracking during the ride
- Driver completes ride via
POST /drivers/{driver_id}/complete- Final location, distance, and duration submitted
- Final fare calculated:
final_fare = base_fare + (actual_distance_km × rate_per_km) + (actual_duration_min × rate_per_min)
- Database updates:
rides.status→COMPLETEDrides.final_farecalculatedrides.completed_attimestampdrivers.status→AVAILABLEdrivers.total_ridesincrementeddrivers.total_earningsupdated
- Ride event logged with completion details
- Both parties notified via WebSocket
- Driver session updated with earnings
Key Components:
- REST API: Start and complete endpoints
- Database: Transaction ensuring ride completion and driver availability
- Fare Logic: Distance and duration-based calculation
- WebSocket: Completion notifications
- Analytics: Session tracking and driver statistics
Fare Rates:
| Vehicle Type | Base Fare | Per KM | Per Minute |
|---|---|---|---|
| ECONOMY | 500₸ | 100₸ | 50₸ |
| PREMIUM | 800₸ | 120₸ | 60₸ |
| XL | 1000₸ | 150₸ | 75₸ |
At any phase, either party can cancel:
Passenger Cancellation:
POST /rides/{ride_id}/cancel
{
"reason": "Changed my mind"
}What happens:
- Ride status →
CANCELLED - If driver matched → driver status →
AVAILABLE - Cancellation event logged with reason
- Both parties notified via WebSocket
- Refund logic applied based on cancellation timing
Driver Rejection:
- If driver rejects offer → next driver in queue gets the offer
- After 2 minutes with no acceptance → ride request expires
- Passenger notified to try again or adjust pickup location
| Exchange | Type | Purpose |
|---|---|---|
ride_topic |
Topic | Ride-related messages with routing |
driver_topic |
Topic | Driver-related messages with routing |
location_fanout |
Fanout | Broadcast location updates |
Ride Topic:
ride.request.ECONOMYride.request.PREMIUMride.request.XLride.status.MATCHEDride.status.COMPLETED
Driver Topic:
driver.response.{ride_id}driver.status.{driver_id}
- Passenger requests ride → Ride Service publishes to
ride_topicwith keyride.request.ECONOMY - Driver Service consumes from
driver_matchingqueue - Finds nearby drivers using PostGIS
- Sends offers via WebSocket to selected drivers
- Driver accepts → Publishes to
driver_topicwith keydriver.response.{ride_id} - Ride Service updates ride status to
MATCHED - Notifies passenger via WebSocket
users - Passenger, driver, and admin accounts drivers - Driver-specific information rides - Core ride records coordinates - Location tracking ride_events - Event sourcing audit trail location_history - GPS history for analytics
users (1) ──── (N) rides
users (1) ──── (1) drivers
rides (1) ──── (N) ride_events
rides (1) ──── (2) coordinates (pickup & destination)
drivers (1) ──── (N) location_history
This project uses gofumpt for code formatting:
make format
# or
gofumpt -l -w .Create a new migration:
make migrate-create name=add_ratings_tableApply migrations:
make migrate-upRollback last migration:
make migrate-down1Check migration version:
make migrate-versionAll services use structured JSON logging:
{
"timestamp": "2024-12-16T10:30:00Z",
"level": "INFO",
"service": "ride-service",
"action": "ride_requested",
"message": "New ride request created",
"hostname": "ride-service-1",
"request_id": "req_123456",
"ride_id": "550e8400-e29b-41d4-a716-446655440000"
}- Register users:
# Register passenger
curl -X POST http://localhost:3005/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"passenger@test.com","password":"pass123","role":"PASSENGER"}'
# Register driver
curl -X POST http://localhost:3005/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"driver@test.com","password":"pass123","role":"DRIVER"}'-
Login and get tokens
-
Driver goes online:
curl -X POST http://localhost:3001/drivers/{driver_id}/online \
-H "Authorization: Bearer {driver_token}" \
-H "Content-Type: application/json" \
-d '{"latitude":43.238949,"longitude":76.889709}'- Passenger requests ride:
curl -X POST http://localhost:3000/rides \
-H "Authorization: Bearer {passenger_token}" \
-H "Content-Type: application/json" \
-d '{
"passenger_id":"550e8400-e29b-41d4-a716-446655440001",
"pickup_latitude":43.238949,
"pickup_longitude":76.889709,
"pickup_address":"Almaty Central Park",
"destination_latitude":43.222015,
"destination_longitude":76.851511,
"destination_address":"Kok-Tobe Hill",
"ride_type":"ECONOMY"
}'- Monitor WebSocket connections for real-time updates
Check if ports are available:
lsof -i :3000 # Ride Service
lsof -i :3001 # Driver Service
lsof -i :3004 # Admin Service
lsof -i :3005 # Auth ServiceVerify RabbitMQ is running:
docker ps | grep rabbitmqCheck Management UI:
- URL: http://localhost:15672
- Username:
guest - Password:
guest
Verify PostgreSQL is running:
docker ps | grep postgresTest connection:
psql -h localhost -p 5432 -U ridehail_user -d ridehail_db- Ensure token is prefixed with
Bearer - Check token expiration
- Verify user role matches endpoint
- Check RabbitMQ Management UI for queue depths
- Verify exchange bindings are correct
- Check service logs for correlation IDs
- Ensure routing keys match expected patterns
# Stop infrastructure only
make down
# Stop and remove volumes (clean slate)
make nuke# Stop Docker containers
docker-compose down -v
# Kill running Go processes
pkill -f "ride-hail-system"ride-hailing-platform/
├── cmd/
│ ├── auth/ # Auth service
│ ├── ride/ # Ride service
│ ├── driver/ # Driver & location service
│ └── admin/ # Admin service
├── internal/
│ ├── config/ # Configuration
│ ├── database/ # Database utilities
│ ├── messaging/ # RabbitMQ helpers
│ ├── models/ # Data models
│ └── websocket/ # WebSocket handlers
├── migrations/ # Database migrations
├── docker-compose.yml # Infrastructure setup
├── Makefile # Build automation
├── config.yaml # Configuration file
└── main.go # Entry point
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Format code with gofumpt (
make format) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ using Go, PostgreSQL, and RabbitMQ




