LLD for a Real-Time Order Tracking System
Low Level Design

LLD for a Real-Time Order Tracking System

S

Shivam Chauhan

14 days ago

Alright, let’s dive into how to build a real-time order tracking system, like what you see when you're waiting for that pizza. I'll walk you through the low-level design (LLD) and show you how to piece it together.

Why Does Order Tracking Matter?

Think about it: when you order food, you want to know where it is, right? Is it still being prepped? Has it left the restaurant? Is the delivery guy stuck in traffic? This visibility keeps customers happy and reduces support calls. Plus, it helps the restaurant and delivery service run more efficiently.

Core Components

Before we get into the nitty-gritty, let’s lay out the key components we'll need:

  • Order Service: Manages order creation, updates, and storage.
  • Location Service: Handles location data for drivers and restaurants.
  • Notification Service: Sends updates to the customer (e.g., via push notifications).
  • Real-Time Updates Service: Manages the real-time flow of data to the customer's app.

Diving into Low-Level Design

Let’s zoom in on each component and figure out how they’ll work together.

1. Order Service

This is the heart of the system. It needs to:

  • Store order details (items, customer info, delivery address).
  • Track the order status (pending, preparing, out for delivery, delivered).
  • Handle updates to the order (e.g., changing the delivery address).

Here’s a basic Java class:

java
public class Order {
    private String orderId;
    private String customerId;
    private String restaurantId;
    private String deliveryAddress;
    private OrderStatus status;

    // Getters and setters
}

enum OrderStatus {
    PENDING,
    PREPARING,
    OUT_FOR_DELIVERY,
    DELIVERED
}

2. Location Service

This service is all about tracking the location of the delivery driver. It needs to:

  • Receive location updates from the driver's app.
  • Store the location data.
  • Provide a way to query the current location of a driver.

Here's a simplified version:

java
public class LocationService {
    private Map<String, Location> driverLocations = new ConcurrentHashMap<>();

    public void updateLocation(String driverId, Location location) {
        driverLocations.put(driverId, location);
    }

    public Location getLocation(String driverId) {
        return driverLocations.get(driverId);
    }
}

class Location {
    double latitude;
    double longitude;
}

3. Notification Service

This service is responsible for sending updates to the customer. It needs to:

  • Support multiple notification channels (push, SMS, email).
  • Format and send notifications based on order status.

Using the Factory Design Pattern could be beneficial here to handle different notification types:

java
public interface NotificationSender {
    void sendNotification(String message, String userId);
}

public class PushNotificationSender implements NotificationSender {
    public void sendNotification(String message, String userId) {
        // Code to send push notification
        System.out.println("Sending push notification to user " + userId + ": " + message);
    }
}

// Factory
public class NotificationSenderFactory {
    public NotificationSender getSender(String type) {
        if ("push".equalsIgnoreCase(type)) {
            return new PushNotificationSender();
        }
        // Add other types like SMS, Email
        return null;
    }
}

4. Real-Time Updates Service

This is where the magic happens. We need a way to push updates to the customer's app in real-time. Options include:

  • WebSockets: Maintains a persistent connection between the server and the client.
  • Server-Sent Events (SSE): Allows the server to push updates to the client over HTTP.
  • Message Queues (e.g., RabbitMQ, Amazon MQ): Decouples the services and allows for asynchronous communication.

For this example, let’s use WebSockets. Here’s a basic setup:

java
@ServerEndpoint("/orderUpdates/{orderId}")
public class OrderUpdateWebSocket {

    private static Map < String, Session > sessions = new ConcurrentHashMap < > ();

    @OnOpen
    public void onOpen(Session session, @PathParam("orderId") String orderId) {
        sessions.put(orderId, session);
        System.out.println("Session opened for orderId: " + orderId);
    }

    @OnClose
    public void onClose(Session session, @PathParam("orderId") String orderId) {
        sessions.remove(orderId);
        System.out.println("Session closed for orderId: " + orderId);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        System.err.println("Error in session: " + error.getMessage());
    }

    public static void sendUpdate(String orderId, String message) {
        Session session = sessions.get(orderId);
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                System.err.println("Error sending message: " + e.getMessage());
            }
        }
    }
}

Putting It All Together

Here’s how the components interact:

  1. The driver updates their location via the Location Service.
  2. The Location Service updates the order with the driver's location.
  3. The Order Service determines if the order status has changed.
  4. If the status has changed, the Order Service sends a message to the Real-Time Updates Service.
  5. The Real-Time Updates Service pushes the update to the customer's app via WebSockets.
  6. The Notification Service sends a push notification to the customer.

Scaling Considerations

To handle a large number of orders and drivers, consider:

  • Database Sharding: Distribute the order data across multiple databases.
  • Load Balancing: Distribute the traffic across multiple servers.
  • Caching: Cache frequently accessed data to reduce database load.
  • Message Queues: Use message queues (like Amazon MQ or RabbitMQ) to decouple services and handle asynchronous communication. This improves resilience and scalability.

UML Diagram (React Flow)

Here’s a simplified UML diagram:

Drag: Pan canvas

FAQs

Q: How often should the driver's location be updated?

That depends on the desired accuracy and battery life. A good starting point is every 5-10 seconds.

Q: What if the WebSocket connection drops?

Implement a fallback mechanism, such as polling the server for updates or using Server-Sent Events (SSE).

Q: How can I test this system?

Use a combination of unit tests, integration tests, and end-to-end tests. Mock the external services to isolate the components.

Where Coudo AI Comes In

Want to test your skills building systems like this? Coudo AI has problems that challenge you to design and code real-world features. Try out the movie ticket API challenge for a taste of similar system design complexities.

Closing Thoughts

Building a real-time order tracking system involves a lot of moving parts, but breaking it down into smaller components makes it manageable. By focusing on the LLD of each component and how they interact, you can create a scalable and reliable system. And remember, practice makes perfect, so keep coding and keep learning!

By mastering the low-level design (LLD) of each component, you're one step closer to building a scalable and reliable real-time order tracking system.\n\n

About the Author

S

Shivam Chauhan

Sharing insights about system design and coding practices.