This I have written as part of my LLD Practice. Please let me know further improvements that can be done.
Also current design is Strong/Average/Lean Hire
I have a strategy to assign nearest warehouse to user, but when user opens his app, will he see genric inventory? Also if nearest warehouse dont have certain product, but slight far warehouse has that product, can we assign that?
/*
Flow->
Domain ===> Reposoitory(DB) ==> Service (BuisLogic) ==> Controller (Client Code)
/* ======= Domain ======= */
User
Cart
Product
ProductCategory
Address
Warehouse
Order
Invoice
/* ======= Repository (DB Logic) ======= */
UserRepository
ProductRepository
WarehouseRepository
OrderRepository
/* ======= Service (Buisness Logic) ======= */
CartService
SearchService
WarehouseService
OrderService
/* ======= Controller (Client Interaction) ======= */
BlinkitController (Client Interaction)
Client Code
*//*
Functional Requirements
- User Management (Register, Login, Address)
- Inventory Management (Track product per warehouse)
- Warehouse Selection (Choose nearest one)
- Cart Management (Add/remove items, validate stock before checkout)
- Payment System (UPI,Card, COD)
- Delivery System (Assign Delivery patners)
- Notification (Out of scope)
*/
/* ======== Domain ======== */
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
enum PaymentMode{
UPI, Cash
}
enum OrderStatus{
Created, Payment_Pending, Paid, Failed, Cancelled, ASSIGNED_FOR_DELIVERY, Delivered
}
class User{
String id;
String name;
String email;
Cart cart;
List<String> orderIds;
Address address;
}
class Cart{
String id;
Map<String,Integer> productQuantityMap; // (productId -> qty)
void add(String productId, int qty){
}
void remove(String productId, int qty){
}
void clear(){
productQuantityMap.clear();
}
}
class Product{
String id;
String productName;
ProductCategory productCategory;
double price;
}
class ProductCategory{
String id;
String productCategoryName;
}
class Address{
int pincode;
String city;
String state;
double latitude;
double longitude;
}
class Warehouse{
String storeId;
String storeName;
Address address;
Map<String,Integer> productInventory;
}
class Order{
String id;
String userId;
Map<String,Integer> items;
String wareHouseId;
double amount;
OrderStatus orderStatus;
LocalDateTime createdAt;
}
class Invoice{
String id;
String orderId;
double amount;
}
interface WareHouseSelectionStrategy{
Warehouse findNearestWarehouse(Address customerAddress, List<Warehouse> warehouses, Map<String,Integer> reqItems);
}
class NearestWareHouseSelectionStrategy implements WareHouseSelectionStrategy{
@Override
public Warehouse findNearestWarehouse(Address customerAddress, List<Warehouse> warehouses, Map<String,Integer> reqItems){
}
}
interface Payment{
boolean pay(double amount);
}
class UPIPayment implements Payment{
@Override
public boolean pay(double amount){
}
}
class PaymentService{
static Payment getPaymentMethod(PaymentMode mode){
switch (mode){
case UPI: return new UPIPayment();
case Cash: return new CashPayment();
default: throw new RuntimeException("Invalid Payment Mode");
}
}
}
interface SearchStrategy{
List<Product> search(List<Product> products, String query);
}
class SearchByName implements SearchStrategy{
@Override
public List<Product> search(List<Product> products, String query){
}
}
class SearchByCategory implements SearchStrategy{
@Override
public List<Product> search(List<Product> products, String query){
}
}
/* ======== Repository ======== */
class UserRepository{
Map<String,User> usersDb;
void addUser(User user){
}
User getUser(String id){
}
}
class ProductRepository{
Map<String,Product> productDb;
void addProduct(Product product){
}
Product getProduct(String id){
}
List<Product> getAllProducts(){
return new ArrayList<>(productDb.values());
}
}
class WareHouseRepository{
Map<String,Warehouse> warehouseDb;
void addWarehouse(Warehouse warehouse){
}
Warehouse getWarehouse(String id){
}
List<Warehouse> getAllWarehouses(){
}
synchronized boolean reserve(String wareHouseId, Map<String,Integer> items){
Warehouse w=warehouseDb.get(wareHouseId);
if(w==null) return false;
for(Map.Entry<String,Integer> e: items.entrySet()){
if(w.productInventory.getOrDefault(e.getKey(),0)<e.getValue()) return false;
}
for(Map.Entry<String,Integer> e: items.entrySet()){
String prodId=e.getKey();
int qty=e.getValue();
w.productInventory.put(prodId,w.productInventory.get(prodId)-qty);
}
return true;
}
synchronized void release(String wareHouseId, Map<String,Integer> items){
Warehouse w=warehouseDb.get(wareHouseId);
if(w==null) return;
for(Map.Entry<String,Integer> e:items.entrySet()){
String prodId=e.getKey();
int qty=e.getValue();
w.productInventory.put(prodId,w.productInventory.get(prodId)+qty);
}
}
}
class OrderRepository{
Map<String,Order> orderDb;
void saveOrder(Order order){
}
Order getOrderById(String orderId){
}
}
/* ======== Service ======== */
class CartService{
ProductRepository productRepository;
void addToCart(User user, String productId, int qty){
Product product= productRepository.getProduct(productId);
if(product==null) throw new RuntimeException("Product not found");
user.cart.add(productId,qty);
}
void removeFromCart(User user, String productId, int qty){
user.cart.remove(productId,qty);
}
}
class SearchService{
ProductRepository productRepository;
SearchStrategy strategy;
List<Product> search(String query){
return strategy.search(productRepository.getAllProducts(),query);
}
}
class WareHouseService{
WareHouseRepository wareHouseRepository;
WareHouseSelectionStrategy strategy;
Warehouse choose(Address add, Map<String,Integer> items){
return strategy.findNearestWarehouse(add,wareHouseRepository.getAllWarehouses(),items);
}
}
class OrderService{
UserRepository userRepository;
WareHouseRepository wareHouseRepository;
WareHouseSelectionStrategy strategy;
WareHouseService wareHouseService;
OrderRepository orderRepository;
ProductRepository productRepository;
Order checkOut(String userId, PaymentMode paymentMode){
User user=userRepository.getUser(userId);
if(user==null) throw new RuntimeException("User not found");
Map<String,Integer> cartItems=user.cart.productQuantityMap;
if(cartItems.isEmpty()) throw new RuntimeException("Cart Empty");
double amount=0;
for(Map.Entry<String,Integer> e:cartItems.entrySet()){
Product p=productRepository.getProduct(e.getKey());
int qty=e.getValue();
if(p==null) throw new RuntimeException("Product Not Found");
amount+=(p.price*qty);
}
Warehouse warehouse=strategy.findNearestWarehouse(user.address,wareHouseRepository.getAllWarehouses(),cartItems);
if(warehouse==null) throw new RuntimeException("No Warehouse can fullfill the order");
boolean reserved=wareHouseRepository.reserve(warehouse.storeId,cartItems);
if(!reserved) throw new RuntimeException("Failed to reserve inventory");
Order order=new Order();
order.userId=userId;
order.items=cartItems;
order.wareHouseId=warehouse.storeId;
order.amount=amount;
order.orderStatus=OrderStatus.Payment_Pending;
orderRepository.saveOrder(order);
Payment payment=PaymentFactory.getPaymentMethod(paymentMode);
boolean paid=false;
try {
paid= payment.pay(amount);
} catch (Exception e){
paid=false;
}
if(paid){
order.orderStatus=OrderStatus.Paid;
orderRepository.saveOrder(order);
user.orderIds.add(order.id);
user.cart.clear();
assignDeliveryPartner(order);
return order;
}
else{
wareHouseRepository.release(warehouse.storeId, cartItems);
order.orderStatus=OrderStatus.Failed;
orderRepository.saveOrder(order);
throw new RuntimeException("Payment Failed");
}
}
void assignDeliveryPartner(Order order){
order.orderStatus=OrderStatus.ASSIGNED_FOR_DELIVERY;
orderRepository.saveOrder(order);
}
}
/* ======== Controller ======== */
class BlinkitController{
UserRepository userRepository;
CartService cartService;
SearchService searchService;
WareHouseService wareHouseService;
OrderService orderService;
List<Product> searchProduct(String query){
return searchService.search(query);
}
void addToCart(String userId, String prodId, int qty){
User user=userRepository.getUser(userId);
cartService.addToCart(user,prodId,qty);
}
void removeFromCart(String userId, String prodId, int qty){
User user=userRepository.getUser(userId);
cartService.removeFromCart(user,prodId,qty);
}
Order checkOut(String userId, PaymentMode paymentMode){
return orderService.checkOut(userId,paymentMode);
}
}
/* ======== Client Code ======== */
public class Blinkit {
public static void main(String[] args) {
BlinkitController controller=new BlinkitController();
User u1=new User();
controller.addToCart("U1","P1",2);
controller.addToCart("U1","P3",1);
controller.checkOut("U1",PaymentMode.UPI);
}
}