Implementation

savant aakash
7 min readJul 25, 2024

--

Let’s discuss the implementation of these layers in the context of a food delivery application like Zomato. This layered architecture helps in organizing the code, separating concerns, and making the system more maintainable and scalable.

1. Presentation Layer (API endpoints, user interface)

This layer is responsible for handling user interactions and presenting data to the user.

```python
from flask import Flask, request, jsonify
from business_logic_layer import OrderService, RestaurantService

app = Flask(__name__)

@app.route(‘/restaurants’, methods=[‘GET’])
def get_restaurants():
service = RestaurantService()
restaurants = service.get_all_restaurants()
return jsonify([restaurant.to_dict() for restaurant in restaurants])

@app.route(‘/orders’, methods=[‘POST’])
def place_order():
order_data = request.json
service = OrderService()
order = service.place_order(order_data)
return jsonify(order.to_dict()), 201

@app.route(‘/orders/<order_id>’, methods=[‘GET’])
def get_order(order_id):
service = OrderService()
order = service.get_order(order_id)
if order:
return jsonify(order.to_dict())
return jsonify({“error”: “Order not found”}), 404

if __name__ == ‘__main__’:
app.run(debug=True)
```

This layer defines API endpoints for operations like getting restaurant lists, placing orders, and retrieving order details. It uses Flask in this example, but could be implemented with any web framework.

2. Business Logic Layer (service layer, use cases)

This layer contains the core business logic and orchestrates the use of domain objects and data access layers.

```python
from data_access_layer import RestaurantRepository, OrderRepository
from domain_model_layer import Order, Restaurant

class RestaurantService:
def __init__(self):
self.repository = RestaurantRepository()

def get_all_restaurants(self):
return self.repository.get_all()

def get_restaurant(self, restaurant_id):
return self.repository.get_by_id(restaurant_id)

class OrderService:
def __init__(self):
self.order_repository = OrderRepository()
self.restaurant_repository = RestaurantRepository()

def place_order(self, order_data):
restaurant = self.restaurant_repository.get_by_id(order_data[‘restaurant_id’])
if not restaurant:
raise ValueError(“Restaurant not found”)

order = Order(
user_id=order_data[‘user_id’],
restaurant=restaurant,
items=order_data[‘items’]
)
order.calculate_total()
return self.order_repository.save(order)

def get_order(self, order_id):
return self.order_repository.get_by_id(order_id)
```

This layer implements the business logic for operations like placing orders and retrieving restaurant information. It uses the data access layer to interact with the database and the domain model layer for business entities and logic.

3. Data Access Layer (repositories, database interactions)

This layer is responsible for all database operations and abstracts the database implementation details from the rest of the application.

```python
from sqlalchemy.orm import Session
from domain_model_layer import Restaurant, Order

class RestaurantRepository:
def __init__(self):
self.session = Session()

def get_all(self):
return self.session.query(Restaurant).all()

def get_by_id(self, restaurant_id):
return self.session.query(Restaurant).filter(Restaurant.id == restaurant_id).first()

class OrderRepository:
def __init__(self):
self.session = Session()

def save(self, order):
self.session.add(order)
self.session.commit()
return order

def get_by_id(self, order_id):
return self.session.query(Order).filter(Order.id == order_id).first()
```

This layer uses SQLAlchemy ORM for database operations in this example. It provides methods to fetch, save, and retrieve data from the database.

4. Domain Model Layer (core business entities and logic)

This layer contains the core business entities and their associated logic.

```python
from sqlalchemy import Column, Integer, String, Float, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Restaurant(Base):
__tablename__ = ‘restaurants’

id = Column(Integer, primary_key=True)
name = Column(String)
cuisine = Column(String)
rating = Column(Float)

def to_dict(self):
return {
“id”: self.id,
“name”: self.name,
“cuisine”: self.cuisine,
“rating”: self.rating
}

class Order(Base):
__tablename__ = ‘orders’

id = Column(Integer, primary_key=True)
user_id = Column(Integer)
restaurant_id = Column(Integer, ForeignKey(‘restaurants.id’))
total = Column(Float)

restaurant = relationship(“Restaurant”)
items = relationship(“OrderItem”)

def calculate_total(self):
self.total = sum(item.price * item.quantity for item in self.items)

def to_dict(self):
return {
“id”: self.id,
“user_id”: self.user_id,
“restaurant”: self.restaurant.to_dict(),
“items”: [item.to_dict() for item in self.items],
“total”: self.total
}

class OrderItem(Base):
__tablename__ = ‘order_items’

id = Column(Integer, primary_key=True)
order_id = Column(Integer, ForeignKey(‘orders.id’))
item_name = Column(String)
quantity = Column(Integer)
price = Column(Float)

def to_dict(self):
return {
“id”: self.id,
“item_name”: self.item_name,
“quantity”: self.quantity,
“price”: self.price
}
```

This layer defines the core entities like Restaurant, Order, and OrderItem. It includes business logic methods like `calculate_total()` for orders.

By organizing the application into these layers:

1. The presentation layer can focus on handling HTTP requests and responses without worrying about business logic or data access.
2. The business logic layer can implement complex operations using domain models and repositories, without being concerned with how data is presented or stored.
3. The data access layer can be optimized for database performance and can be easily swapped out for different database systems if needed.
4. The domain model layer encapsulates the core business concepts and rules, which can be reused across different parts of the application.

better separation of concerns, easier testing, and more flexibility in evolving different parts of the system independently. For a complex application like Zomato, this layered architecture would provide a solid foundation for handling the various aspects of food ordering, delivery, and restaurant management.

Let’s discuss the implementation of these layers in the context of a ride-hailing application like Ola. We’ll structure the application using these layers to create a clean, maintainable, and scalable architecture.

1. Presentation Layer (API endpoints, user interface)

This layer handles user interactions and presents data to the user. For a ride-hailing app, we’ll focus on API endpoints.

```python
from flask import Flask, request, jsonify
from business_logic_layer import RideService, DriverService, UserService

app = Flask(__name__)

@app.route(‘/rides’, methods=[‘POST’])
def request_ride():
ride_data = request.json
service = RideService()
ride = service.request_ride(ride_data)
return jsonify(ride.to_dict()), 201

@app.route(‘/rides/<ride_id>’, methods=[‘GET’])
def get_ride(ride_id):
service = RideService()
ride = service.get_ride(ride_id)
if ride:
return jsonify(ride.to_dict())
return jsonify({“error”: “Ride not found”}), 404

@app.route(‘/drivers/nearby’, methods=[‘GET’])
def get_nearby_drivers():
latitude = request.args.get(‘lat’)
longitude = request.args.get(‘lon’)
service = DriverService()
drivers = service.get_nearby_drivers(float(latitude), float(longitude))
return jsonify([driver.to_dict() for driver in drivers])

@app.route(‘/users/<user_id>/rides’, methods=[‘GET’])
def get_user_rides(user_id):
service = UserService()
rides = service.get_user_rides(user_id)
return jsonify([ride.to_dict() for ride in rides])

if __name__ == ‘__main__’:
app.run(debug=True)
```

This layer defines API endpoints for operations like requesting a ride, getting ride details, finding nearby drivers, and retrieving a user’s ride history.

2. Business Logic Layer (service layer, use cases)

This layer contains the core business logic and orchestrates the use of domain objects and data access layers.

```python
from data_access_layer import RideRepository, DriverRepository, UserRepository
from domain_model_layer import Ride, Driver, User

class RideService:
def __init__(self):
self.ride_repository = RideRepository()
self.driver_repository = DriverRepository()

def request_ride(self, ride_data):
nearest_driver = self.driver_repository.get_nearest_available_driver(
ride_data[‘pickup_latitude’],
ride_data[‘pickup_longitude’]
)
if not nearest_driver:
raise ValueError(“No available drivers nearby”)

ride = Ride(
user_id=ride_data[‘user_id’],
driver=nearest_driver,
pickup_location=(ride_data[‘pickup_latitude’], ride_data[‘pickup_longitude’]),
dropoff_location=(ride_data[‘dropoff_latitude’], ride_data[‘dropoff_longitude’])
)
ride.calculate_estimated_fare()
return self.ride_repository.save(ride)

def get_ride(self, ride_id):
return self.ride_repository.get_by_id(ride_id)

class DriverService:
def __init__(self):
self.driver_repository = DriverRepository()

def get_nearby_drivers(self, latitude, longitude):
return self.driver_repository.get_nearby_drivers(latitude, longitude)

class UserService:
def __init__(self):
self.user_repository = UserRepository()

def get_user_rides(self, user_id):
user = self.user_repository.get_by_id(user_id)
if not user:
raise ValueError(“User not found”)
return user.rides
```

This layer implements the business logic for operations like requesting rides, finding nearby drivers, and retrieving user ride history.

3. Data Access Layer (repositories, database interactions)

This layer is responsible for all database operations and abstracts the database implementation details.

```python
from sqlalchemy.orm import Session
from domain_model_layer import Ride, Driver, User
from geoalchemy2 import func

class RideRepository:
def __init__(self):
self.session = Session()

def save(self, ride):
self.session.add(ride)
self.session.commit()
return ride

def get_by_id(self, ride_id):
return self.session.query(Ride).filter(Ride.id == ride_id).first()

class DriverRepository:
def __init__(self):
self.session = Session()

def get_nearest_available_driver(self, latitude, longitude):
return self.session.query(Driver).filter(Driver.is_available == True)\
.order_by(func.ST_Distance(Driver.location, f’POINT({longitude} {latitude})’))\
.first()

def get_nearby_drivers(self, latitude, longitude, radius=5000): # radius in meters
return self.session.query(Driver).filter(
func.ST_DWithin(Driver.location, f’POINT({longitude} {latitude})’, radius)
).all()

class UserRepository:
def __init__(self):
self.session = Session()

def get_by_id(self, user_id):
return self.session.query(User).filter(User.id == user_id).first()
```

This layer uses SQLAlchemy ORM for database operations. It includes methods for saving rides, finding nearby drivers, and retrieving user information.

4. Domain Model Layer (core business entities and logic)

This layer contains the core business entities and their associated logic.

```python
from sqlalchemy import Column, Integer, String, Float, Boolean, ForeignKey, DateTime
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from geoalchemy2 import Geography
import datetime

Base = declarative_base()

class User(Base):
__tablename__ = ‘users’

id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String, unique=True)
phone = Column(String)

rides = relationship(“Ride”, back_populates=”user”)

def to_dict(self):
return {
“id”: self.id,
“name”: self.name,
“email”: self.email,
“phone”: self.phone
}

class Driver(Base):
__tablename__ = ‘drivers’

id = Column(Integer, primary_key=True)
name = Column(String)
vehicle_number = Column(String)
is_available = Column(Boolean, default=True)
location = Column(Geography(geometry_type=’POINT’, srid=4326))

rides = relationship(“Ride”, back_populates=”driver”)

def to_dict(self):
return {
“id”: self.id,
“name”: self.name,
“vehicle_number”: self.vehicle_number,
“is_available”: self.is_available
}

class Ride(Base):
__tablename__ = ‘rides’

id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey(‘users.id’))
driver_id = Column(Integer, ForeignKey(‘drivers.id’))
pickup_location = Column(Geography(geometry_type=’POINT’, srid=4326))
dropoff_location = Column(Geography(geometry_type=’POINT’, srid=4326))
start_time = Column(DateTime, default=datetime.datetime.utcnow)
end_time = Column(DateTime)
fare = Column(Float)
status = Column(String) # ‘requested’, ‘in_progress’, ‘completed’, ‘cancelled’

user = relationship(“User”, back_populates=”rides”)
driver = relationship(“Driver”, back_populates=”rides”)

def calculate_estimated_fare(self):
# Simplified fare calculation
distance = self.calculate_distance()
base_fare = 50 # Base fare in rupees
per_km_rate = 10 # Rate per km in rupees
self.fare = base_fare + (distance * per_km_rate)

def calculate_distance(self):
# This would use a more sophisticated distance calculation in a real application
return 5 # Assuming a fixed 5 km distance for simplicity

def to_dict(self):
return {
“id”: self.id,
“user”: self.user.to_dict(),
“driver”: self.driver.to_dict() if self.driver else None,
“pickup_location”: str(self.pickup_location),
“dropoff_location”: str(self.dropoff_location),
“start_time”: self.start_time.isoformat() if self.start_time else None,
“end_time”: self.end_time.isoformat() if self.end_time else None,
“fare”: self.fare,
“status”: self.status
}
```

This layer defines the core entities like User, Driver, and Ride. It includes business logic methods like `calculate_estimated_fare()` for rides.

By organizing the Ola-like application into these layers:

1. The presentation layer handles API requests and responses, making it easy to add new endpoints or modify existing ones without affecting the core business logic.

2. The business logic layer implements complex operations like ride requests and driver matching, using domain models and repositories. This separation allows for easy unit testing of business rules.

3. The data access layer abstracts database operations, making it possible to optimize database queries or even switch to a different database system without affecting the rest of the application.

4. The domain model layer encapsulates the core concepts of the ride-hailing business, such as users, drivers, and rides, along with their relationships and basic behaviors.

This layered architecture provides several benefits for an Ola-like application:

- Scalability: Each layer can be scaled independently based on the load.
- Maintainability: Changes in one layer (e.g., switching from SQL to NoSQL database) don’t affect other layers.
- Testability: Each layer can be unit tested in isolation.
- Flexibility: New features (e.g., ride sharing, scheduled rides) can be added by extending the existing layers without major refactoring.

This provides a solid foundation for handling the complexities of a ride-hailing application, including real-time driver tracking, fare calculation, ride matching, and user management.

--

--

savant aakash
savant aakash

Written by savant aakash

0 Followers

Introduction od oops in python

No responses yet