Design (LLD) Topmate - Machine Coding
Topmate is a platform connecting creators with their audience through paid sessions, content, and community features. Think Calendly + Patreon + Cameo.
Features Required
1. User Management
User registration with different types (Creator, Consumer, Admin)
User profiles (with basic info, and for creators, a CreatorProfile)
2. Session Management
Session creation by creators (with title, description, pricing, time slots)
Session status (DRAFT, PUBLISHED, etc.)
Time slot management (adding time slots, checking overlaps)
3. Booking System
Booking a session by a consumer
Booking status (PENDING, CONFIRMED, etc.)
Cancellation of bookings (with reasons and by whom)
4. Payment & Pricing
Pricing strategies (Fixed, Tiered)
Payment gateway integration (Stripe, Razorpay)
Platform commission calculation
5. Notifications
Notification system (Email, SMS, Push)
Event-driven notifications
9. Search & Query
- Methods to search sessions and get bookings for a user or creator
10. Admin Features
- Platform revenue calculation
Design Patterns Used
1. Builder Pattern
Reason: Complex object creation (Session, UserProfile, Payment) with many optional parameters
Benefit: Clean API, immutability, flexible object construction
2. Factory Pattern
Reason: Multiple notification types, payment gateways, user roles
Benefit: Encapsulates creation logic, easy to extend
3. Strategy Pattern
Reason: Multiple payment methods, pricing strategies, commission calculations
Benefit: Swappable algorithms, Open-Closed Principle
4. Observer Pattern
Reason: Real-time notifications, event-driven updates
Benefit: Loose coupling between components
5. Proxy Pattern
Reason: Caching for frequently accessed data, lazy loading
Benefit: Performance optimization, transparent to clients
6. Singleton Pattern
Reason: Session manager, notification service, rate limiter
Benefit: Single instance for critical services
7. Repository Pattern
Reason: Data access abstraction for different storage types
Benefit: Separation of concerns, testability
Java Implementation
// ==================== ENUMS & CONSTANTS ====================
enum SessionStatus {
DRAFT, PUBLISHED, BOOKED, COMPLETED, CANCELLED, RESCHEDULED
}
enum BookingStatus {
PENDING, CONFIRMED, CANCELLED_BY_CREATOR, CANCELLED_BY_USER, COMPLETED
}
enum PaymentStatus {
PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED
}
enum UserType {
CREATOR, CONSUMER, ADMIN
}
enum NotificationType {
EMAIL, SMS, PUSH, IN_APP
}
enum Currency {
USD, EUR, GBP, INR
}
// ==================== BUILDER PATTERN: Complex Objects ====================
class TimeSlot {
private Instant startTime;
private Instant endTime;
private Duration duration;
private boolean isBooked;
// Builder for TimeSlot
public static class Builder {
private Instant startTime;
private Duration duration;
public Builder startTime(Instant startTime) {
this.startTime = startTime;
return this;
}
public Builder duration(Duration duration) {
this.duration = duration;
return this;
}
public TimeSlot build() {
return new TimeSlot(this);
}
}
private TimeSlot(Builder builder) {
this.startTime = builder.startTime;
this.duration = builder.duration;
this.endTime = startTime.plus(duration);
this.isBooked = false;
}
// Getters
public boolean overlaps(TimeSlot other) {
return !(this.endTime.isBefore(other.startTime) ||
this.startTime.isAfter(other.endTime));
}
}
// ==================== USER MANAGEMENT ====================
class User {
private String userId;
private String email;
private String displayName;
private UserType userType;
private String profilePictureUrl;
private Instant createdAt;
private boolean isVerified;
private Map<String, String> metadata;
// Fluent Builder
public static class Builder {
private String userId;
private String email;
private String displayName;
private UserType userType = UserType.CONSUMER;
public Builder userId(String userId) {
this.userId = userId;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder displayName(String displayName) {
this.displayName = displayName;
return this;
}
public Builder userType(UserType userType) {
this.userType = userType;
return this;
}
public User build() {
return new User(this);
}
}
private User(Builder builder) {
this.userId = builder.userId;
this.email = builder.email;
this.displayName = builder.displayName;
this.userType = builder.userType;
this.createdAt = Instant.now();
this.metadata = new HashMap<>();
}
public boolean isCreator() {
return userType == UserType.CREATOR;
}
}
class CreatorProfile {
private String creatorId;
private String bio;
private String expertise;
private double rating;
private int totalSessions;
private List<String> socialLinks;
private Map<String, Object> stats;
private PricingStrategy pricingStrategy;
public CreatorProfile(String creatorId) {
this.creatorId = creatorId;
this.rating = 5.0;
this.totalSessions = 0;
this.socialLinks = new ArrayList<>();
this.stats = new HashMap<>();
this.pricingStrategy = new FixedPricingStrategy();
}
public void updateRating(int newRating) {
// Weighted average calculation
double totalRatings = this.rating * this.totalSessions;
this.totalSessions++;
this.rating = (totalRatings + newRating) / this.totalSessions;
}
}
// ==================== SESSION MANAGEMENT ====================
class Session {
private String sessionId;
private String creatorId;
private String title;
private String description;
private SessionStatus status;
private List<TimeSlot> availableSlots;
private SessionPricing pricing;
private int maxParticipants;
private Duration duration;
private String meetingLink;
private List<String> tags;
private Instant createdAt;
private Instant updatedAt;
// Session Builder
public static class Builder {
private String sessionId;
private String creatorId;
private String title;
private Duration duration;
private SessionPricing pricing;
public Builder sessionId(String sessionId) {
this.sessionId = sessionId;
return this;
}
public Builder creatorId(String creatorId) {
this.creatorId = creatorId;
return this;
}
public Builder title(String title) {
this.title = title;
return this;
}
public Builder duration(Duration duration) {
this.duration = duration;
return this;
}
public Builder pricing(SessionPricing pricing) {
this.pricing = pricing;
return this;
}
public Session build() {
return new Session(this);
}
}
private Session(Builder builder) {
this.sessionId = builder.sessionId;
this.creatorId = builder.creatorId;
this.title = builder.title;
this.duration = builder.duration;
this.pricing = builder.pricing;
this.status = SessionStatus.DRAFT;
this.availableSlots = new ArrayList<>();
this.maxParticipants = 1;
this.tags = new ArrayList<>();
this.createdAt = Instant.now();
this.updatedAt = Instant.now();
}
public void publish() {
if (availableSlots.isEmpty()) {
throw new IllegalStateException("Cannot publish without available slots");
}
this.status = SessionStatus.PUBLISHED;
this.updatedAt = Instant.now();
}
public void addTimeSlot(TimeSlot slot) {
// Check for overlaps
for (TimeSlot existing : availableSlots) {
if (existing.overlaps(slot)) {
throw new IllegalArgumentException("Time slot overlaps with existing slot");
}
}
availableSlots.add(slot);
}
public List<TimeSlot> getAvailableSlots() {
return availableSlots.stream()
.filter(slot -> !slot.isBooked())
.collect(Collectors.toList());
}
}
// ==================== STRATEGY PATTERN: Pricing ====================
interface PricingStrategy {
double calculatePrice(double basePrice);
String getDescription();
}
class FixedPricingStrategy implements PricingStrategy {
@Override
public double calculatePrice(double basePrice) {
return basePrice;
}
@Override
public String getDescription() {
return "Fixed Pricing";
}
}
class TieredPricingStrategy implements PricingStrategy {
private int minSessions;
private double discountPercentage;
public TieredPricingStrategy(int minSessions, double discountPercentage) {
this.minSessions = minSessions;
this.discountPercentage = discountPercentage;
}
@Override
public double calculatePrice(double basePrice) {
return basePrice * (1 - discountPercentage / 100);
}
@Override
public String getDescription() {
return String.format("Tiered (%d+ sessions, %.1f%% off)",
minSessions, discountPercentage);
}
}
class SessionPricing {
private double amount;
private Currency currency;
private PricingStrategy strategy;
private double platformFeePercentage;
public SessionPricing(double amount, Currency currency) {
this.amount = amount;
this.currency = currency;
this.strategy = new FixedPricingStrategy();
this.platformFeePercentage = 15.0; // 15% platform fee
}
public double getFinalPrice() {
return strategy.calculatePrice(amount);
}
public double getPlatformFee() {
return getFinalPrice() * (platformFeePercentage / 100);
}
public double getCreatorEarnings() {
return getFinalPrice() - getPlatformFee();
}
}
// ==================== BOOKING SYSTEM ====================
class Booking {
private String bookingId;
private String sessionId;
private String userId;
private TimeSlot timeSlot;
private BookingStatus status;
private PaymentDetails paymentDetails;
private String notes;
private Instant bookedAt;
private List<BookingEvent> events;
public Booking(String bookingId, String sessionId, String userId, TimeSlot timeSlot) {
this.bookingId = bookingId;
this.sessionId = sessionId;
this.userId = userId;
this.timeSlot = timeSlot;
this.status = BookingStatus.PENDING;
this.events = new ArrayList<>();
this.bookedAt = Instant.now();
addEvent("Booking created", BookingStatus.PENDING);
}
public void confirm(PaymentDetails payment) {
this.paymentDetails = payment;
this.status = BookingStatus.CONFIRMED;
addEvent("Booking confirmed", BookingStatus.CONFIRMED);
}
public void cancel(String cancelledBy, String reason) {
if (this.status == BookingStatus.COMPLETED) {
throw new IllegalStateException("Cannot cancel completed booking");
}
if ("CREATOR".equals(cancelledBy)) {
this.status = BookingStatus.CANCELLED_BY_CREATOR;
} else {
this.status = BookingStatus.CANCELLED_BY_USER;
}
addEvent("Booking cancelled: " + reason, this.status);
}
public void markCompleted() {
this.status = BookingStatus.COMPLETED;
addEvent("Session completed", BookingStatus.COMPLETED);
}
private void addEvent(String description, BookingStatus status) {
events.add(new BookingEvent(
description,
status,
Instant.now()
));
}
}
class BookingEvent {
private String description;
private BookingStatus status;
private Instant timestamp;
public BookingEvent(String description, BookingStatus status, Instant timestamp) {
this.description = description;
this.status = status;
this.timestamp = timestamp;
}
}
// ==================== STRATEGY PATTERN: Payment Gateway ====================
interface PaymentGateway {
PaymentResponse processPayment(PaymentRequest request);
PaymentResponse refundPayment(String transactionId, double amount);
}
class StripePaymentGateway implements PaymentGateway {
@Override
public PaymentResponse processPayment(PaymentRequest request) {
// Integration with Stripe API
System.out.println("Processing payment via Stripe: " + request.getAmount());
return new PaymentResponse(
"txn_" + UUID.randomUUID(),
PaymentStatus.PROCESSING,
Instant.now()
);
}
@Override
public PaymentResponse refundPayment(String transactionId, double amount) {
System.out.println("Refunding via Stripe: " + transactionId);
return new PaymentResponse(
transactionId,
PaymentStatus.REFUNDED,
Instant.now()
);
}
}
class RazorpayPaymentGateway implements PaymentGateway {
@Override
public PaymentResponse processPayment(PaymentRequest request) {
// Integration with Razorpay API
System.out.println("Processing payment via Razorpay: " + request.getAmount());
return new PaymentResponse(
"rzp_txn_" + UUID.randomUUID(),
PaymentStatus.PROCESSING,
Instant.now()
);
}
@Override
public PaymentResponse refundPayment(String transactionId, double amount) {
System.out.println("Refunding via Razorpay: " + transactionId);
return new PaymentResponse(
transactionId,
PaymentStatus.REFUNDED,
Instant.now()
);
}
}
class PaymentRequest {
private String bookingId;
private double amount;
private Currency currency;
private String paymentMethodId;
private Map<String, String> metadata;
// Builder pattern for PaymentRequest
}
class PaymentResponse {
private String transactionId;
private PaymentStatus status;
private Instant processedAt;
private String gatewayResponse;
public PaymentResponse(String transactionId, PaymentStatus status, Instant processedAt) {
this.transactionId = transactionId;
this.status = status;
this.processedAt = processedAt;
}
}
class PaymentDetails {
private String paymentId;
private double amount;
private Currency currency;
private String gatewayTransactionId;
private PaymentStatus status;
private Instant paidAt;
public PaymentDetails(String paymentId, double amount, Currency currency) {
this.paymentId = paymentId;
this.amount = amount;
this.currency = currency;
this.status = PaymentStatus.PENDING;
}
}
// ==================== FACTORY PATTERN: Notifications ====================
interface Notification {
void send();
NotificationType getType();
}
class EmailNotification implements Notification {
private String to;
private String subject;
private String body;
private Map<String, String> templateData;
public EmailNotification(String to, String subject, String body) {
this.to = to;
this.subject = subject;
this.body = body;
this.templateData = new HashMap<>();
}
@Override
public void send() {
// Integrate with email service (SendGrid, AWS SES, etc.)
System.out.println("Sending email to: " + to);
System.out.println("Subject: " + subject);
System.out.println("Body: " + body);
}
@Override
public NotificationType getType() {
return NotificationType.EMAIL;
}
}
class SMSNotification implements Notification {
private String phoneNumber;
private String message;
@Override
public void send() {
// Integrate with SMS service (Twilio, etc.)
System.out.println("Sending SMS to: " + phoneNumber);
System.out.println("Message: " + message);
}
@Override
public NotificationType getType() {
return NotificationType.SMS;
}
}
class PushNotification implements Notification {
private String deviceToken;
private String title;
private String body;
private Map<String, String> data;
@Override
public void send() {
// Integrate with FCM/APNS
System.out.println("Sending push notification to device: " + deviceToken);
}
@Override
public NotificationType getType() {
return NotificationType.PUSH;
}
}
interface NotificationFactory {
Notification createNotification(NotificationType type, Map<String, String> params);
}
class SimpleNotificationFactory implements NotificationFactory {
@Override
public Notification createNotification(NotificationType type, Map<String, String> params) {
switch (type) {
case EMAIL:
return new EmailNotification(
params.get("to"),
params.get("subject"),
params.get("body")
);
case SMS:
return new SMSNotification();
case PUSH:
return new PushNotification();
default:
throw new IllegalArgumentException("Unsupported notification type");
}
}
}
// ==================== OBSERVER PATTERN: Event System ====================
interface EventListener {
void onEvent(SystemEvent event);
}
class SystemEvent {
private String eventType;
private String entityId;
private Map<String, Object> payload;
private Instant timestamp;
public SystemEvent(String eventType, String entityId) {
this.eventType = eventType;
this.entityId = entityId;
this.payload = new HashMap<>();
this.timestamp = Instant.now();
}
public SystemEvent withPayload(String key, Object value) {
this.payload.put(key, value);
return this;
}
}
class EventManager {
private Map<String, List<EventListener>> listeners;
private static EventManager instance;
private EventManager() {
this.listeners = new HashMap<>();
}
public static synchronized EventManager getInstance() {
if (instance == null) {
instance = new EventManager();
}
return instance;
}
public void subscribe(String eventType, EventListener listener) {
listeners.computeIfAbsent(eventType, k -> new ArrayList<>())
.add(listener);
}
public void unsubscribe(String eventType, EventListener listener) {
List<EventListener> eventListeners = listeners.get(eventType);
if (eventListeners != null) {
eventListeners.remove(listener);
}
}
public void publish(SystemEvent event) {
List<EventListener> eventListeners = listeners.get(event.getEventType());
if (eventListeners != null) {
for (EventListener listener : eventListeners) {
listener.onEvent(event);
}
}
}
}
// ==================== PROXY PATTERN: Caching Proxy ====================
interface SessionRepository {
Session findById(String sessionId);
List<Session> findByCreatorId(String creatorId);
void save(Session session);
}
class RealSessionRepository implements SessionRepository {
// Database implementation
@Override
public Session findById(String sessionId) {
System.out.println("Fetching session from database: " + sessionId);
// Database query logic
return null;
}
@Override
public List<Session> findByCreatorId(String creatorId) {
// Database query logic
return new ArrayList<>();
}
@Override
public void save(Session session) {
// Database save logic
System.out.println("Saving session to database: " + session.getSessionId());
}
}
class CachedSessionRepository implements SessionRepository {
private SessionRepository realRepository;
private Map<String, Session> cache;
private Map<String, Instant> cacheTimestamps;
private Duration cacheTTL;
public CachedSessionRepository(SessionRepository realRepository) {
this.realRepository = realRepository;
this.cache = new HashMap<>();
this.cacheTimestamps = new HashMap<>();
this.cacheTTL = Duration.ofMinutes(5);
}
@Override
public Session findById(String sessionId) {
// Check cache first
if (cache.containsKey(sessionId)) {
Instant cachedTime = cacheTimestamps.get(sessionId);
if (Instant.now().minus(cacheTTL).isBefore(cachedTime)) {
System.out.println("Returning cached session: " + sessionId);
return cache.get(sessionId);
} else {
// Cache expired
cache.remove(sessionId);
cacheTimestamps.remove(sessionId);
}
}
// Fetch from real repository
Session session = realRepository.findById(sessionId);
if (session != null) {
cache.put(sessionId, session);
cacheTimestamps.put(sessionId, Instant.now());
}
return session;
}
@Override
public List<Session> findByCreatorId(String creatorId) {
// This could also be cached with a different key strategy
return realRepository.findByCreatorId(creatorId);
}
@Override
public void save(Session session) {
// Update cache on save
realRepository.save(session);
cache.put(session.getSessionId(), session);
cacheTimestamps.put(session.getSessionId(), Instant.now());
}
public void invalidateCache(String sessionId) {
cache.remove(sessionId);
cacheTimestamps.remove(sessionId);
}
}
// ==================== MAIN TOPMATE SYSTEM ====================
class TopmateSystem {
private static TopmateSystem instance;
private Map<String, User> users;
private Map<String, CreatorProfile> creatorProfiles;
private SessionRepository sessionRepository;
private Map<String, Booking> bookings;
private PaymentGateway paymentGateway;
private NotificationFactory notificationFactory;
private EventManager eventManager;
private RateLimiter rateLimiter;
private TopmateSystem() {
this.users = new HashMap<>();
this.creatorProfiles = new HashMap<>();
this.sessionRepository = new CachedSessionRepository(new RealSessionRepository());
this.bookings = new HashMap<>();
this.paymentGateway = new StripePaymentGateway(); // Default
this.notificationFactory = new SimpleNotificationFactory();
this.eventManager = EventManager.getInstance();
this.rateLimiter = new RateLimiter(100, Duration.ofMinutes(1)); // 100 requests per minute
setupEventListeners();
}
public static synchronized TopmateSystem getInstance() {
if (instance == null) {
instance = new TopmateSystem();
}
return instance;
}
private void setupEventListeners() {
// Listen for booking events
eventManager.subscribe("BOOKING_CREATED", new EventListener() {
@Override
public void onEvent(SystemEvent event) {
sendBookingConfirmation(event);
}
});
eventManager.subscribe("SESSION_COMPLETED", new EventListener() {
@Override
public void onEvent(SystemEvent event) {
updateCreatorStats(event);
}
});
}
// ==================== CORE BUSINESS METHODS ====================
public User registerUser(String email, String displayName, UserType userType) {
if (!rateLimiter.allowRequest("REGISTER_" + email)) {
throw new RuntimeException("Rate limit exceeded");
}
String userId = "user_" + UUID.randomUUID().toString().substring(0, 8);
User user = new User.Builder()
.userId(userId)
.email(email)
.displayName(displayName)
.userType(userType)
.build();
users.put(userId, user);
if (userType == UserType.CREATOR) {
creatorProfiles.put(userId, new CreatorProfile(userId));
}
eventManager.publish(new SystemEvent("USER_REGISTERED", userId)
.withPayload("email", email)
.withPayload("userType", userType));
return user;
}
public Session createSession(String creatorId, Session.Builder sessionBuilder) {
if (!users.containsKey(creatorId) || !users.get(creatorId).isCreator()) {
throw new IllegalArgumentException("User is not a creator");
}
Session session = sessionBuilder.build();
sessionRepository.save(session);
eventManager.publish(new SystemEvent("SESSION_CREATED", session.getSessionId())
.withPayload("creatorId", creatorId)
.withPayload("title", session.getTitle()));
return session;
}
public Booking bookSession(String sessionId, String userId, TimeSlot timeSlot) {
// Check rate limiting
if (!rateLimiter.allowRequest("BOOK_" + userId)) {
throw new RuntimeException("Rate limit exceeded");
}
Session session = sessionRepository.findById(sessionId);
if (session == null) {
throw new IllegalArgumentException("Session not found");
}
// Check if slot is available
boolean slotAvailable = session.getAvailableSlots().stream()
.anyMatch(slot -> slot.equals(timeSlot));
if (!slotAvailable) {
throw new IllegalArgumentException("Time slot not available");
}
String bookingId = "booking_" + UUID.randomUUID().toString().substring(0, 8);
Booking booking = new Booking(bookingId, sessionId, userId, timeSlot);
bookings.put(bookingId, booking);
// Mark slot as booked
timeSlot.markAsBooked();
eventManager.publish(new SystemEvent("BOOKING_CREATED", bookingId)
.withPayload("sessionId", sessionId)
.withPayload("userId", userId)
.withPayload("timeSlot", timeSlot));
return booking;
}
public PaymentResponse processPayment(String bookingId, PaymentRequest paymentRequest) {
Booking booking = bookings.get(bookingId);
if (booking == null) {
throw new IllegalArgumentException("Booking not found");
}
PaymentResponse response = paymentGateway.processPayment(paymentRequest);
if (response.getStatus() == PaymentStatus.PROCESSING) {
booking.confirm(new PaymentDetails(
response.getTransactionId(),
paymentRequest.getAmount(),
paymentRequest.getCurrency()
));
// Send notifications
sendBookingConfirmationNotifications(booking);
}
return response;
}
public void cancelBooking(String bookingId, String cancelledBy, String reason) {
Booking booking = bookings.get(bookingId);
if (booking == null) {
throw new IllegalArgumentException("Booking not found");
}
booking.cancel(cancelledBy, reason);
// Process refund if payment was made
if (booking.getPaymentDetails() != null &&
booking.getPaymentDetails().getStatus() == PaymentStatus.COMPLETED) {
processRefund(booking);
}
eventManager.publish(new SystemEvent("BOOKING_CANCELLED", bookingId)
.withPayload("cancelledBy", cancelledBy)
.withPayload("reason", reason));
}
private void processRefund(Booking booking) {
PaymentDetails payment = booking.getPaymentDetails();
PaymentResponse refundResponse = paymentGateway.refundPayment(
payment.getGatewayTransactionId(),
payment.getAmount()
);
if (refundResponse.getStatus() == PaymentStatus.REFUNDED) {
payment.setStatus(PaymentStatus.REFUNDED);
}
}
private void sendBookingConfirmationNotifications(Booking booking) {
String userId = booking.getUserId();
User user = users.get(userId);
// Send email notification
Notification emailNotification = notificationFactory.createNotification(
NotificationType.EMAIL,
Map.of(
"to", user.getEmail(),
"subject", "Booking Confirmation",
"body", "Your booking has been confirmed!"
)
);
emailNotification.send();
// Send in-app notification
sendInAppNotification(userId, "Booking confirmed",
"Your session has been booked successfully");
}
private void sendInAppNotification(String userId, String title, String message) {
// Store notification in database for user's notification center
System.out.println("In-app notification sent to user: " + userId);
}
private void sendBookingConfirmation(SystemEvent event) {
System.out.println("Processing booking confirmation for event: " + event.getEventType());
}
private void updateCreatorStats(SystemEvent event) {
System.out.println("Updating creator stats for event: " + event.getEventType());
}
// ==================== QUERY METHODS ====================
public List<Session> searchSessions(String query, List<String> tags,
Instant fromDate, Instant toDate) {
// Implement search logic with filters
return new ArrayList<>();
}
public List<TimeSlot> getAvailableSlots(String sessionId) {
Session session = sessionRepository.findById(sessionId);
return session != null ? session.getAvailableSlots() : new ArrayList<>();
}
public List<Booking> getUserBookings(String userId) {
return bookings.values().stream()
.filter(booking -> booking.getUserId().equals(userId))
.collect(Collectors.toList());
}
public List<Booking> getCreatorBookings(String creatorId) {
// Get all sessions by creator, then get bookings for those sessions
List<Session> creatorSessions = sessionRepository.findByCreatorId(creatorId);
Set<String> sessionIds = creatorSessions.stream()
.map(Session::getSessionId)
.collect(Collectors.toSet());
return bookings.values().stream()
.filter(booking -> sessionIds.contains(booking.getSessionId()))
.collect(Collectors.toList());
}
// ==================== ADMIN METHODS ====================
public void setPaymentGateway(PaymentGateway gateway) {
this.paymentGateway = gateway;
}
public double calculatePlatformRevenue(Instant from, Instant to) {
return bookings.values().stream()
.filter(booking -> booking.getBookedAt().isAfter(from) &&
booking.getBookedAt().isBefore(to))
.filter(booking -> booking.getStatus() == BookingStatus.COMPLETED)
.mapToDouble(booking -> {
// Calculate platform fee from each completed booking
// This would need to fetch session pricing details
return 0.0; // Placeholder
})
.sum();
}
}
// ==================== RATE LIMITER ====================
class RateLimiter {
private int maxRequests;
private Duration timeWindow;
private Map<String, List<Instant>> requestLog;
public RateLimiter(int maxRequests, Duration timeWindow) {
this.maxRequests = maxRequests;
this.timeWindow = timeWindow;
this.requestLog = new HashMap<>();
}
public synchronized boolean allowRequest(String key) {
Instant now = Instant.now();
Instant windowStart = now.minus(timeWindow);
List<Instant> requests = requestLog.computeIfAbsent(key, k -> new ArrayList<>());
// Remove old requests
requests.removeIf(timestamp -> timestamp.isBefore(windowStart));
if (requests.size() < maxRequests) {
requests.add(now);
return true;
}
return false;
}
}
// ==================== DEMO / TEST ====================
public class TopmateDemo {
public static void main(String[] args) {
TopmateSystem system = TopmateSystem.getInstance();
System.out.println("=== TOPMATE DEMO ===\n");
// 1. Register users
System.out.println("1. Registering users...");
User creator = system.registerUser(
"creator@example.com",
"John Doe",
UserType.CREATOR
);
User consumer = system.registerUser(
"consumer@example.com",
"Jane Smith",
UserType.CONSUMER
);
System.out.println("Creator ID: " + creator.getUserId());
System.out.println("Consumer ID: " + consumer.getUserId());
// 2. Create a session
System.out.println("\n2. Creating a session...");
SessionPricing pricing = new SessionPricing(50.0, Currency.USD);
Session session = system.createSession(
creator.getUserId(),
new Session.Builder()
.sessionId("session_" + UUID.randomUUID().toString().substring(0, 8))
.creatorId(creator.getUserId())
.title("Career Coaching Session")
.duration(Duration.ofHours(1))
.pricing(pricing)
);
// Add time slots
TimeSlot slot1 = new TimeSlot.Builder()
.startTime(Instant.now().plus(Duration.ofDays(1)))
.duration(Duration.ofHours(1))
.build();
session.addTimeSlot(slot1);
session.publish();
System.out.println("Session created: " + session.getTitle());
System.out.println("Price: $" + session.getPricing().getFinalPrice());
// 3. Book a session
System.out.println("\n3. Booking the session...");
Booking booking = system.bookSession(
session.getSessionId(),
consumer.getUserId(),
slot1
);
System.out.println("Booking created: " + booking.getBookingId());
System.out.println("Status: " + booking.getStatus());
// 4. Process payment
System.out.println("\n4. Processing payment...");
PaymentRequest paymentRequest = new PaymentRequest();
paymentRequest.setBookingId(booking.getBookingId());
paymentRequest.setAmount(50.0);
paymentRequest.setCurrency(Currency.USD);
paymentRequest.setPaymentMethodId("pm_card_visa");
PaymentResponse paymentResponse = system.processPayment(
booking.getBookingId(),
paymentRequest
);
System.out.println("Payment processed: " + paymentResponse.getTransactionId());
System.out.println("Payment status: " + paymentResponse.getStatus());
// 5. Complete the session
System.out.println("\n5. Completing the session...");
booking.markCompleted();
System.out.println("Session completed!");
// 6. Query bookings
System.out.println("\n6. Querying user bookings...");
List<Booking> userBookings = system.getUserBookings(consumer.getUserId());
System.out.println("User has " + userBookings.size() + " booking(s)");
// 7. Switch payment gateway
System.out.println("\n7. Switching payment gateway...");
system.setPaymentGateway(new RazorpayPaymentGateway());
System.out.println("Payment gateway switched to Razorpay");
// 8. Test rate limiting
System.out.println("\n8. Testing rate limiting...");
RateLimiter rateLimiter = new RateLimiter(3, Duration.ofSeconds(10));
for (int i = 0; i < 5; i++) {
boolean allowed = rateLimiter.allowRequest("TEST_KEY");
System.out.println("Request " + (i + 1) + ": " +
(allowed ? "Allowed" : "Blocked (rate limit)"));
}
System.out.println("\n=== DEMO COMPLETE ===");
}
}
Key Features Implemented:
1. User Management
Creator and consumer roles
Profile management with verification
Social links and expertise tags
2. Session Management
Multiple time slots
Pricing strategies (fixed, tiered)
Session status lifecycle
3. Booking System
Real-time availability checking
Booking status tracking
Cancellation with refunds
4. Payment Integration
Multiple payment gateways (Stripe, Razorpay)
Platform commission calculation
Refund processing
5. Notification System
Multiple channels (email, SMS, push, in-app)
Template-based notifications
Event-driven architecture
6. Performance & Scalability
Caching with Proxy pattern
Rate limiting
Efficient querying
7. Analytics & Reporting
Creator performance metrics
Platform revenue calculation
Booking analytics
8. Security & Reliability
Input validation
Transaction consistency
Error handling and retry mechanisms
This design provides a scalable, maintainable foundation for a platform like Topmate with clear separation of concerns and extensibility for future features.
Issues in Above Design
1️⃣ Global Singleton (TopmateSystem) → Scalability + Concurrency Bottleneck
Issue
TopmateSystem is a god-object singleton managing:
Users
Sessions
Bookings
Payments
Notifications
Rate limiting
LLD Problem
Violates Single Responsibility Principle
Tight coupling between unrelated domains
Hard to test in isolation
Concurrency Problem
All operations funnel through a single shared instance
Shared mutable maps:
Map<String, User> users; Map<String, Booking> bookings;
Impact
Race conditions on shared state
Poor horizontal scalability
Difficult to shard by user / creator / region
