NebengJek is a ride-sharing app that connects users with shared rides. Users can either be Riders, requesting a ride, or Drivers, offering their vehicle. Both can choose whom to ride with, as long as they're within a specific area. 🚀
-
Ride Matching
Riders can request a ride to nearest available drivers (in radius 1 km). Drivers can accept or ignore requests. Riders can also choose to accept or reject the matched driver, and re-request the ride.
-
Real-Time Location Tracking
Once the ride is matched and rider got picked up, driver could start the ride. Both users sending location updates every minute. The app tracks and calculates the distance traveled.
-
Ride Fare Calculation and Commission
At the end of the ride, the app calculates the fare at 3000 IDR per km distance (rounded up) and deducts a 5% platform fee for service maintenance and growth. 😊 Driver can also set their own preferred price in a ride.
Database and Message Broker
- Postgres, relational database for storing rides detail, drivers, riders, and ride commissions .
- Redis, key-value storage for location data, with built-in geo-location operations for location indexing and nearest distance searching.
- NATS message broker, event-driven communication for asynchronous processes.
High Availability Tools
- Load Balancer, distributes user traffic to ensure no single server is overloaded.
- Multi-Availability Zone (Multi-AZ) cloud service, where cloud resources are distributed to more than one area in a cloud region, for data backup and recovery.
Monitoring Tools
- New Relic cloud for telemetry data monitoring (traces, metrics, and logs).
Other Surrounding
- Tsel-payment service, responsible for maintaining users' credits (mocked service).
This system contains of four internal services.
- Riders, responsible for maintaining Riders' connection for real-time location update and ride update broadcast
- Drivers, responsible for maintaining Drivers' connection for real-time location update and new ride broadcast
- Rides, responsible for managing Ride data, including driver-rider assignments and ride status lifecycle
- Location, responsible for managing users' real time locations
Communication Protocols
- Websocket, for real-time communication between users and our services, to broadcast ride event changes and real-time location tracking.
- REST API, for stateless ride data updates.
- NATS protocol, for event-driven communication between services. We are using NATS JetStream message broker.
The data consists of four tables
- Drivers: Store driver identity and availability status
- Riders: Store rider identity
- Rides: Store ride data (rider id and driver id matches, ride details, status)
- RideCommissions: Store platform fee and driver commissions at the end of the ride
Ride Status
Driver Status
Number | Status | Description |
---|---|---|
0 | OFF | Driver is not accepting ride request |
1 | AVAILABLE | Driver turns on the beacon |
- Postgres 16
- DBMate, data migration script to initialize tables and data seeds
Ensure that your database is running. To Initialize schema and add migration:
dbmate --url 'postgres://YOUR_USERNAME:YOUR_PASSWORD@DB_HOST:5436/rides_db?sslmode=disable' up
- Golang >=1.22, serves web API
- Postgres 16, for ride data store
- Redis, GeoSpatial data store for real time location
- NATS JetStream, message broker for event streaming and queue group
- Docker, to encapsulate applications and their dependencies in isolated environment
- Ensure that all service dependencies are running--Redis, Postgres, and NATS JetStream.
- Initialize Postgres database (see Data Migration step)
- Initialize NATS Jetstream using create_stream script
- Initialize
.env
files for each services. (See./configs/rides/.env.example
for example) - Run each service independently:
make run-drivers make run-riders make run-rides
- Initialize
.env
file for each services. (./configs/rides/.env.example
) - In root path, execute
docker-compose up -d
to run all services (including the dependencies)
The API contract is in openapi
format for REST API and asyncapi
for websocket APIs. To render the contracts, utilize Swagger Viewer (VSCode extension), Redoc-cli, or asyncapi-preview (VSCode extension).
No | Service | Contract Link |
---|---|---|
1 | Rides | docs/contracts/nebengjek-rides.openapi.yml |
2 | Location | docs/contracts/nebengjek-location.openapi.yml |
3 | Drivers | docs/contracts/nebengjek-websockets.asyncapi.yml |
4 | Riders | docs/contracts/nebengjek-websockets.asyncapi.yml |
Postman version: https://www.postman.com/ermasavior/nebengjek-public/overview
The load test scenario will spawn a number of concurrent users that send requests on multiple stages. The load test target is GET ride data endpoint (with target of 400 Transaction per Second) and PATCH driver availability endpoint (with target of 200 Transaction per Second).
To run the load test:
cd loadtest
## To run load test for GET ride data
k6 run get_ride_data.load_stages.js
## To run load test for PATCH driver availability
k6 run patch_driver_availability.load_stages.js
The load test for GET endpoint reached 491 Transaction per Second (TPS) average, with 176 ms average latency. All test thresholds were passed, with 99% success rate. 1% error (121 request) occured because maximum database connections has reached. For future scaling, we could utilize in-memory storage (Redis) to reduce database connection.
PATCH endpoint load test reached 243 TPS average, with 175 ms average latency. All test thresholds were passed as well (with 100% success rate).
For further information, see result and test evidence in loadtest/result
.
Erma Safira Nurmasyita
Telegram: @ermasavior