Skip to content

Commit

Permalink
Merge pull request #52 from ermasavior/feat/docs
Browse files Browse the repository at this point in the history
Feat/docs
  • Loading branch information
ermasavior authored Nov 22, 2024
2 parents 429f875 + 30843e8 commit c477403
Show file tree
Hide file tree
Showing 24 changed files with 281 additions and 1,912 deletions.
108 changes: 98 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,80 @@
# Nebengjek
# NebengJek
![SonarQube Coverage](https://sonarcloud.io/api/project_badges/measure?project=nebengjek-demo_nebengjek&metric=coverage)
![CI](https://github.com/ermasavior/nebeng-jek/actions/workflows/ci.yml/badge.svg)

## Description
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. 🚀

## Main Features
1. __Ride Matching__

Riders can request a ride to nearest available drivers (in radius 1 km). Drivers can accept or ignore requests. Riders can also be selective on who offered the ride.
2. __Real-Time Location Tracking__:

Once the ride is matched and rider got picked up, the ride begins. Both users sending location updates every minute. The app tracks and calculates the distance traveled.
3. __Ride Commissions__:

The app takes 30% commission from each ride to support the service maintenance and growth. :)

## Architecture
![LLD](docs/pictures/Nebengjek-LLD.png)

This system contains of four internal services and one external service (mocked).

1. Riders, responsible for maintaining Riders' connection for real-time location update and ride update broadcast.
2. Drivers, responsible for maintaining Drivers' connection for real-time location update and ride update broadcast.
3. Rides, responsible for managing Ride data, including driver-rider assignments and ride status lifecycle.
4. Location, responsible for managing users' real time locations.
5. (External) Tsel-payment service mock, responsible for maintaining users' credits.

The communication between users and our services utilize __Websocket__ (for real-time bidirectional communication) and __REST API__ (for stateless data updates). To enable system High Availability, a Load Balancer sits in front of our services. The services are containerized using Docker. The services communicate with Event Driven Architecture using NATS JetStream (for ride update broadcasting and real time location tracking).

The database we are using are Relational Database (Postgres) for storing Rides data and Key-Value storage (Redis) for storing Location data (in Geolocation format). Redis provides geo-location operations, including queries based on indexed coordinates and searches within a specified radius.

## Data Schema
![DB](docs/pictures/ERD.png)

The data consists of four tables: Drivers, Riders, Rides, RideCommissions

### Enumerations
__Ride Status__
![State Diagram](docs/pictures/state-diagram.ride-status.png)

__Driver Status__
| Number | Status | Description |
|---- |-------- |------------- |
| 0 | OFF | Driver is not in the radar |
| 1 | AVAILABLE | Driver turns on the beacon |

## Prerequisites
1. Docker

## Data Migration
### Prerequisites
1. Postgres 16
2. [DBMate](https://github.com/amacneil/dbmate), data migration script to initialize tables and data seeds

### Steps
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
```

## How to Run
### Prerequisites
1. **Golang >=1.22**, serves web API
2. **Postgres 16**, for ride data store
3. **Redis**, for GeoLocation data store
4. **NATS JetStream**, message broker for event streaming and queue group

5. (Alternatively) **Docker**, for practical containerized environment

1. Initialize `.env` file. See `./configs/.env.example` for example.
2. Run each service independently:
### Steps

1. Ensure that all the service dependencies are running--Redis, Postgres, and NATS JetStream.
2. Initialize Postgres database (see Data Migration step)
3. Initialize NATS Jetstream using [create_stream script](deployments/nats/create_streams.sh)
2. Initialize `.env` files for each services. (See [`./configs/rides/.env.example`](configs/rides/.env.example) for example)
3. Run each service independently:
```sh
make run-drivers
make run-riders
Expand All @@ -15,15 +83,35 @@

### Using Docker

1. Initialize `.env` file into each `./configs/*` folders
2. Go to `/deployments`, then execute `docker-compose up -d` to run all services (including the dependencies)
1. Initialize `.env` file for each services. [(`./configs/rides/.env.example`)](configs/rides/.env.example)
2. In root path, execute `docker-compose up -d` to run all services (including the dependencies)

## API Contract
TBD

## Load Test

For load testing, we use `k6`.
The load test target is PATCH set driver availability endpoint. The test scenario spawned 50-250 concurrent users that run gradually on stages.
The test scenario will hit two selected APIs which spawned a number of concurrent users that was run gradually on multiple stages.

The load test target is GET ride data endpoint (with target of 50-200 users) PATCH driver availability endpoint (with target of 20-80 users).

## Prerequisites
1. [K6](https://github.com/grafana/k6)

## How to Run
To run the load test:
```sh
cd loadtest && k6 run load_stages.js
```
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
```

## Author
Erma Safira Nurmasyita

Telegram: @ermasavior
59 changes: 59 additions & 0 deletions docs/ERD.wsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@startuml Rides DB Schema
' hide the spot
' hide circle

' avoid problems with angled crows feet
skinparam linetype ortho

entity "drivers" as e01 {
*id : number <<generated>>
--
*name : varchar(255)
*phone_number : varchar(15)
*vehicle_type : int
*vehicle_plate : varchar(20)
*status : int
created_at : timestamp
updated_at : timestamp
}

entity "riders" as e02 {
*id : number <<generated>>
--
*name : varchar(255)
*phone_number : varchar(15)
created_at : timestamp
updated_at : timestamp
}

entity "rides" as e03 {
*id : number <<generated>>
--
*rider_id : number <<FK>>
*driver_id : number <<FK>>
*pickup_location : point
*destination : point
*status : int
distance : decimal
fare : decimal
final_price : decimal
start_time : timestamp
end_time : timestamp
created_at : timestamp
updated_at : timestamp
}

entity "ride_commissions" as e04 {
*id : number <<generated>>
--
*ride_id : number <<FK>>
platform_fee : decimal
driver_commission : decimal
created_at : timestamp
}

e01 }o..|| e03
e02 }o..|| e03
e03 |o..|| e04

@enduml
44 changes: 0 additions & 44 deletions docs/allocation-diagram.wsd

This file was deleted.

Binary file added docs/pictures/ERD.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pictures/Nebengjek-HLD.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pictures/Nebengjek-LLD.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pictures/sequence.create-ride.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pictures/sequence.match-driver-rider.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pictures/sequence.start-ride.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pictures/sequence.stop-ride.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pictures/state-diagram.ride-status.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions docs/sequence.create-ride.wsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@startuml Create New Ride Flow

actor Rider
actor Driver

== Create New Ride ==
Rider->Rides: Create new ride request
Rides->Location: Get nearest available drivers
Location->LocationDB: Query drivers
LocationDB-->Location: Return driver list
Location-->Rides: Return driver list
Rides->RidesDB: Store ride with status WAITING_FOR_DRIVER
Rides->Drivers: Publish ride request to driver list
Drivers-->Driver: Notify drivers


== Set Driver Availability ==
Driver->Rides: Set as active
Rides->RidesDB: Update as active driver
Rides->Location: Update driver's current location
Location->LocationDB: Store driver location


@enduml
26 changes: 26 additions & 0 deletions docs/sequence.match-driver-rider.wsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@startuml Driver-Rider Matching Flow

actor Rider
actor Driver

alt Driver accepted the request
Driver->Rides: Accept request
Rides->RidesDB: Update ride to MATCHED_DRIVER
Rides-->Riders: Publish update to rider
Riders->Rider: Notify rider

alt Rider accepted the match
Rider->Rides: Accept match
Rides->RidesDB: Update ride to WAITING_FOR_PICKUP
Rides->RidesDB: Set driver as inactive
Rides->Location: Remove driver's current location
Location->LocationDB: Delete driver's location
Rides->Riders: Publish update to rider
Riders-->Rider: Notify rider to wait for pickup
else Rider rejected the match
Rider->Rides: Reject match
Rides->RidesDB: Update ride to CANCELLED
end
end

@enduml
21 changes: 21 additions & 0 deletions docs/sequence.start-ride.wsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@startuml Start Ride Flow

actor Rider
actor Driver

== Start Ride ==
Driver->Rides: Start ride request
Rides->RidesDB: Update ride to RIDE_STARTED
Rides->Riders: Publish ride update
Riders-->Rider: Notify rider

== Live Tracking Location ==
Driver->Drivers: Send current location
Drivers->Location: Track driver location
Location->LocationDB: Store driver location

Rider->Riders: Send current location
Riders->Location: Track rider location
Location->LocationDB: Store rider location

@enduml
26 changes: 26 additions & 0 deletions docs/sequence.stop-ride.wsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@startuml Stop Ride Flow

actor Rider
actor Driver

== End Ride ==
Driver->Rides: Stop ride
Rides->RidesDB: Update ride to RIDE_ENDED
Rides->Riders: Publish ride update
Riders-->Rider: Notify rider

== Confirm Ride Payment ==
Driver->Rides: Confirm payment
note left of Rides
with custom price
(optional)
end note
Rides->Rides: Calculate commission (5%)
Rides->Payment: Deduct rider's credit
Rides->Payment: Add rider's credit
Rides->RidesDB: Store ride commission
Rides->RidesDB: Update ride to RIDE_PAID
Rides->Riders: Publish ride update
Riders-->Rider: Notify rider

@enduml
15 changes: 15 additions & 0 deletions docs/state-diagram.ride-status.wsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@startuml Ride Status Diagram
hide empty description

[*] --> NEW_RIDE_REQUEST : [Rider] creates ride request

NEW_RIDE_REQUEST --> MATCHED_DRIVER : [Driver] accepts ride request
MATCHED_DRIVER --> RIDE_CANCELLED : [Rider] rejects
MATCHED_DRIVER --> READY_TO_PICKUP : [Rider] accepts
READY_TO_PICKUP --> RIDE_STARTED : [Driver] starts ride in pickup location
RIDE_STARTED --> RIDE_ENDED : [Driver] ends ride
RIDE_ENDED --> RIDE_PAID : [Driver] confirms price

RIDE_PAID --> [*]
RIDE_CANCELLED --> [*]
@enduml
12 changes: 6 additions & 6 deletions loadtest/get_ride_data.load_stages.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";
//scenario 2 (load test based on stages)
export const options = {
stages: [
{ duration: "0.3m", target: 100 }, // simulate ramp-up of traffic from 1 to 100 users over 0.3 minute
{ duration: "0.4m", target: 100 }, // stay at 100 users for 0.4 minute
{ duration: "0.6m", target: 150 }, // simulate ramp-up of traffic from 100 to 200 users over 0.6 minute
{ duration: "1m", target: 250 }, // simulate ramp-up to 300 users over 1 minute
{ duration: "1.4m", target: 0 }, // simulate ramp-down to 0 users over 1.4 minute
{ duration: "0.3m", target: 50 },
{ duration: "0.4m", target: 100 },
{ duration: "0.6m", target: 150 },
{ duration: "1m", target: 200 },
{ duration: "1.4m", target: 0 },
],
thresholds: {
http_req_failed: ["rate<0.001"], // the error rate must be lower than 0.1%
Expand Down Expand Up @@ -41,7 +41,7 @@ export default function () {

export function handleSummary(data) {
return {
"result/load_stages/get_ride_data.result.html": htmlReport(data),
"result.get_ride_data.html": htmlReport(data),
stdout: textSummary(data, { indent: " ", enableColors: true }),
};
}
Loading

0 comments on commit c477403

Please sign in to comment.