Online food ordering service — a clean, extensible backend simulation built in C# with SOLID principles, OOP design patterns, and an in-memory data store.
- Overview
- Features
- Architecture
- Project Structure
- Design Principles
- Getting Started
- Usage / API Reference
- Sample Demo Output
- Edge Cases Handled
- Extending the System
Foodkart is a console-based food ordering platform where users can discover restaurants serviceable in their area, place orders, and rate their experience. Restaurant owners can register and manage their outlets. The system is built without any database — all data lives in-memory, making it easy to run and demo instantly.
| Feature | Details |
|---|---|
| 👤 User Registration & Login | Unique phone number, auto-logout on new login |
| 🏪 Restaurant Registration | One specialised dish per restaurant, multiple pincodes |
| 📦 Inventory Management | Owners can add stock; orders deduct quantity |
| 🗺️ Location Management | Owners can update serviceable pincodes |
| 🔍 Restaurant Discovery | Filter by user's pincode; sort by price or rating |
| 🛒 Order Placement | Validates serviceability and available quantity |
| ⭐ Reviews & Ratings | 1–5 stars, optional comment; average rating computed live |
| 📜 Order History (Bonus) | Full order history per user |
The solution follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────┐
│ Program.cs (Driver) │
└───────────────────┬─────────────────────────┘
│
┌───────────────────▼─────────────────────────┐
│ FoodkartFacade │ ← Single entry point
│ (Facade Pattern + Error Handling) │
└──────┬───────────┬──────────────┬───────────┘
│ │ │
┌──────▼──┐ ┌─────▼────┐ ┌─────▼──────┐
│ User │ │Restaurant│ │ Order │ ← Services
│ Service │ │ Service │ │ Service │
└──────┬──┘ └─────┬────┘ └─────┬──────┘
│ │ │
┌──────▼───────────▼──────────────▼──────────┐
│ Repository Interfaces │ ← Abstractions
│ IUserRepo IRestaurantRepo IOrderRepo │
└──────┬───────────┬──────────────┬──────────┘
│ │ │
┌──────▼───────────▼──────────────▼──────────┐
│ InMemory Repositories │ ← Data layer
└─────────────────────────────────────────────┘
Foodkart/
│
├── Models/
│ ├── User.cs
│ ├── FoodItem.cs
│ ├── Restaurant.cs
│ ├── Review.cs
│ └── Order.cs
│
├── Interfaces/
│ ├── IRepositories.cs # IUserRepository, IRestaurantRepository, IOrderRepository
│ ├── IServices.cs # IUserService, IRestaurantService, IOrderService, IReviewService
│ └── IRestaurantSortStrategy.cs
│
├── Repositories/
│ └── InMemoryRepositories.cs # In-memory implementations of all repos
│
├── Services/
│ ├── UserService.cs
│ ├── RestaurantService.cs
│ ├── OrderService.cs
│ └── ReviewService.cs
│
├── Strategies/
│ └── SortStrategies.cs # SortByRatingDescending, SortByPriceAscending
│
├── Session/
│ └── UserSession.cs # Manages currently logged-in user context
│
├── Facade/
│ └── FoodkartFacade.cs # Unified API surface with centralised error handling
│
├── Bootstrap/
│ └── AppBootstrapper.cs # Composition root — wires all dependencies
│
├── Program.cs # Driver / demo with all test cases
└── Foodkart.csproj
| Principle | Implementation |
|---|---|
| Single Responsibility | Each service class handles exactly one domain (users, restaurants, orders, reviews). UserSession only manages login state. |
| Open/Closed | New sort strategies (e.g. sort by name) implement IRestaurantSortStrategy — zero changes to existing code. |
| Liskov Substitution | All interface implementations are fully interchangeable; swapping InMemoryRestaurantRepository for a DB-backed one requires no other changes. |
| Interface Segregation | Four focused service interfaces (IUserService, IRestaurantService, IOrderService, IReviewService) — no fat interfaces. |
| Dependency Inversion | All services depend on abstractions. Concrete types are only instantiated in AppBootstrapper. |
| Pattern | Usage |
|---|---|
| Facade | FoodkartFacade is the sole entry point for the driver; centralises error handling so no try/catch leaks into Program.cs. |
| Strategy | IRestaurantSortStrategy — pluggable sorting algorithms. Adding a new sort is a new class, nothing more. |
| Repository | Data access is abstracted behind interfaces; swap to SQL/NoSQL with zero service-layer changes. |
| Composition Root | AppBootstrapper.Build() is the only place new is used for wiring — clean manual DI. |
git clone https://github.com/your-username/foodkart.git
cd foodkart/Foodkart
dotnet runThat's it — no database, no config, no environment variables.
All operations are available through FoodkartFacade. Below are the public methods:
// Register a new user
app.RegisterUser(name, gender, phoneNumber, pincode);
// Login (auto-logs out the previous user)
app.LoginUser(phoneNumber);// Register a restaurant (logged-in user becomes the owner)
// pincodes uses '/' as delimiter: "BTM/HSR"
app.RegisterRestaurant(name, pincodes, foodItemName, price, initialQty);
// Add inventory
app.UpdateQuantity(restaurantName, quantityToAdd);
// Update serviceable pincodes
app.UpdateLocation(restaurantName, pincodes);
// List serviceable restaurants for the logged-in user's pincode
// sortBy: "price" | "rating"
app.ShowRestaurants(sortBy);// Place an order (validates pincode + stock)
app.PlaceOrder(restaurantName, quantity);
// View order history for any phone number
app.ShowOrderHistory(phoneNumber);// Rate a restaurant (rating: 1–5, comment optional)
app.RateRestaurant(restaurantName, rating);
app.RateRestaurant(restaurantName, rating, "Great food!");══════════════════════════════════════════
SECTION 1: Register Users
══════════════════════════════════════════
[User] Registered: Pralove (phoneNumber-1) - HSR
[User] Registered: Nitesh (phoneNumber-2) - BTM
[User] Registered: Vatsal (phoneNumber-3) - BTM
[ERROR] User with phone 'phoneNumber-1' already exists.
══════════════════════════════════════════
SECTION 3: show_restaurant (Vatsal, BTM)
══════════════════════════════════════════
[Show] Restaurants sorted by price:
Food Court-1, NI Thali @ ₹100 | Rating: 0
Food Court-2, Burger @ ₹120 | Rating: 0
══════════════════════════════════════════
SECTION 4: Place Orders
══════════════════════════════════════════
[Order] Order Placed Successfully. Order#1 | Food Court-1 | NI Thali x2 | ₹200 | Placed
[Order] Cannot place order — insufficient quantity. Available: 3, Requested: 7.
══════════════════════════════════════════
SECTION 6: show_restaurant by rating (Vatsal, BTM)
══════════════════════════════════════════
[Show] Restaurants sorted by rating:
Food Court-1, NI Thali @ ₹100 | Rating: 5
Food Court-2, Burger @ ₹120 | Rating: 3
══════════════════════════════════════════
SECTION 7: update_quantity
══════════════════════════════════════════
[Restaurant] Updated: Food Court-2, BTM, Burger - 8
[ERROR] You do not own restaurant 'Food Court-3'.
══════════════════════════════════════════
SECTION 8: update_location
══════════════════════════════════════════
[Restaurant] Updated: Food Court-2, "BTM/HSR", Burger - 8
- ✅ Duplicate phone number on registration → clear error
- ✅ Duplicate restaurant name → clear error
- ✅ Order from restaurant not in user's pincode → rejected with message
- ✅ Order quantity exceeds available stock → rejected with message
- ✅ Invalid rating (outside 1–5) → exception thrown and caught
- ✅ Non-owner trying to update restaurant →
UnauthorizedAccessException - ✅ Operations without login →
InvalidOperationExceptioncaught gracefully - ✅ Login auto-logs out previous user
- ✅ Zero/negative quantity or price →
ArgumentException
// 1. Create the strategy — that's all
public class SortByNameAscending : IRestaurantSortStrategy
{
public string SortKey => "name";
public IOrderedEnumerable<Restaurant> Sort(IEnumerable<Restaurant> restaurants)
=> restaurants.OrderBy(r => r.Name);
}
// 2. Register it in AppBootstrapper
var sortStrategies = new List<IRestaurantSortStrategy>
{
new SortByRatingDescending(),
new SortByPriceAscending(),
new SortByNameAscending() // ← done
};Implement IRestaurantRepository (or any repository interface) against your database of choice and update AppBootstrapper.Build(). No service code changes required.
MIT