A full-stack notification delivery system built with Clojure/ClojureScript that handles message categorization, user subscriptions, and multi-channel notification delivery.
This system allows sending notifications to users based on their category subscriptions and preferred delivery channels. It implements a robust architecture with proper separation of concerns, comprehensive testing, and scalable design patterns.
- Multi-Channel Delivery: SMS, Email, and Push Notifications
- Category-Based Subscriptions: Sports, Finance, and Movies
- Real-time Interface: Live notification log updates
- Robust Architecture: Strategy pattern for channel selection
- Comprehensive Logging: Full audit trail of all notifications
- Database Persistence: PostgreSQL with proper migrations
- Full-Stack: ClojureScript frontend with Reagent/Re-frame
- Java 17+
- Clojure CLI tools
- Docker and Docker Compose
- Node.js (for frontend dependencies)
# Start PostgreSQL container
docker-compose up -d postgres
# Verify database is running
docker-compose ps# Install Clojure dependencies
clj -P -M:dev:test:build
# Install Node.js dependencies
npm install# Build optimized frontend
clj -M:build
# Start the server
clj -M:web
# Access the application at: http://localhost:3000# Terminal 1: Start backend server
clj -M:dev
# Terminal 2: Start frontend development server
clj -M:frontend
# Access at: http://localhost:3001 (with hot reload)- Strategy Pattern: For notification channel selection and delivery
- Repository Pattern: For data access abstraction
- Factory Pattern: For notification channel creation
- Observer Pattern: For real-time UI updates
src/
├── clj/notify_system/ # Backend (Clojure)
│ ├── main.clj # Application entry point
│ ├── web.clj # Web server and routing
│ ├── service.clj # Business logic layer
│ ├── channels.clj # Notification channels (Strategy pattern)
│ ├── db.clj # Database layer (Repository pattern)
│ ├── users.clj # User management
│ └── logging.clj # Logging system
└── cljs/notify_system/ # Frontend (ClojureScript)
├── frontend.cljs # Main frontend entry point
└── frontend/ # Frontend modules
├── core.cljs # Core app logic
├── components.cljs # UI components
├── events.cljs # Re-frame events
├── state.cljs # Application state
├── api.cljs # API client
├── websocket.cljs # WebSocket handling
├── theme.cljs # UI theming
└── utils.cljs # Utility functions
- users: User profiles with contact information
- user_subscriptions: Category subscriptions per user
- user_channels: Preferred notification channels per user
- notifications: Message storage
- notification_logs: Delivery audit trail
- Category: Select from Sports, Finance, or Movies
- Message: Enter your notification text (required)
- Click "Send Notification" to deliver to all subscribed users
- Real-time display of all sent notifications
- Shows timestamp, category, message, recipient, and delivery status
- Sorted from newest to oldest
- Updates automatically when new notifications are sent
The system includes mock users with different subscription preferences:
- Alice Johnson: Sports + Finance → SMS + Email
- Bob Smith: All categories → Email + Push
- Carol Davis: Movies + Finance → SMS + Push
- David Wilson: Sports only → All channels
clj -M:testclj -M:test-watch- Unit Tests: All service functions and business logic
- Integration Tests: Database operations and API endpoints
- System Tests: End-to-end notification delivery
- Regression Tests: Logging system reliability
# Start REPL
clj -M:repl
# In REPL, start the system
(require '[notify-system.main :as main])
(main/-main)# Reset database with fresh seed data
clj -M:dev -e "(require '[notify-system.db :as db]) (db/init-and-seed!)"
# Run specific migrations
clj -M:dev -e "(require '[migratus.core :as migratus]) (migratus/migrate {:store :database :migration-dir \"migrations/\" :db db-spec})"GET /api/users- List all users with subscriptionsPOST /api/notifications- Send new notificationGET /api/notifications/log- Retrieve notification historyGET /health- System health checkGET /metrics- Application metrics
- Single Responsibility: Each namespace has a clear, focused purpose
- Open/Closed: Easy to add new notification channels without modifying existing code
- Liskov Substitution: All notification channels implement the same interface
- Interface Segregation: Clean separation between data access, business logic, and presentation
- Dependency Inversion: High-level modules don't depend on low-level modules
- Pluggable Channels: Add new notification types by implementing the channel protocol
- Database Indexing: Optimized queries for user lookups and log retrieval
- Async Processing: Core.async for non-blocking notification delivery
- Stateless Design: Easy horizontal scaling
- Configuration-Driven: Environment-based configuration for different deployments
- Database connectivity
- System uptime
- Memory usage
- Active connections
- Structured logging with timestamps
- Request/response logging
- Error tracking and alerting
- Performance metrics
# Build and start production environment
docker-compose -f docker-compose.prod.yml up -d
# Access at configured domain with Nginx reverse proxyBuilt by Patrick Serrano with ❤️ using Clojure, ClojureScript, PostgreSQL, and modern web technologies
