Loading...

12-Hour Money-Back Guarantee

Design (LLD) Topmate - Machine Coding

Design (LLD) Topmate - Machine Coding

Design (LLD) Topmate - Machine Coding

23 Jun 202420 min read

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

Solution