A modern, scalable chat application built with Domain-Driven Design (DDD) and Command Query Responsibility Segregation (CQRS) patterns using Ruby on Rails 8.
This application demonstrates clean architecture principles with a clear separation of concerns:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend Layer β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Controllers (Rails) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Application Layer (CQRS) β
β βββββββββββββββββββ βββββββββββββββββββ β
β β Commands β β Queries β β
β β (Write/Postgres)β β (Read/Redis) β β
β βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Domain Layer (DDD) β
β βββββββββββββββββββ βββββββββββββββββββ β
β β Aggregates β β Value Objects β β
β β Domain Events β β Repositories β β
β βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Infrastructure Layer β
β βββββββββββββββββββ βββββββββββββββββββ β
β β PostgreSQL β β Redis β β
β β RabbitMQ β β Gemini β β
β β Kafka β β Kafka Connect β β
β β (Redpanda) β β (JDBC sink) β β
β βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The analytics bounded context follows an event-driven Message State flow instead of in-memory projections:
MessageSent
domain event
β
βΌ
MessageStatePublisher
(Kafka producer)
β
βΌ
chat.message.state (compacted topic)
β
βΌ
Kafka Connect (JDBC sink β analytics DB)
β
βΌ
message_records (analytics Postgres DB)
β
βΌ
SQL Read Models (ChatActivity / UserEngagement)
Rails reads analytics from a dedicated analytics database managed via Rails multi-database, while the JDBC sink only writes β the Rails schema owns the table.
- π― Domain-Driven Design: Clean domain models with aggregates, value objects, and domain events
- β‘ CQRS Pattern: Separate command and query models for optimal performance
- ποΈ Clean Architecture: Clear separation between domain, application, and infrastructure layers
- π‘ Event-Driven Analytics: Message state published to a compacted Kafka topic and synced via Kafka Connect JDBC sink
- ποΈ Multi-Database: Dedicated analytics database integrated with Rails multi-database
- π³ Docker Support: Complete containerized development environment
- π± Real-time Chat: WebSocket support with Action Cable
- π§ͺ Comprehensive Testing: RSpec test suite with proper test isolation
- π Security: Brakeman security scanning and best practices
- Ruby 3.4.10 - Ruby programming language
- Rails 8.1.2 - Ruby on Rails framework
- PostgreSQL 15 - Robust relational database (primary + analytics)
- Redis 7.2.0 - In-memory data structure store
- RabbitMQ 3.12 - Message broker for event-driven architecture
- Redpanda (Kafka) - Kafka-compatible message streaming platform
- Kafka Connect - JDBC sink connector that streams message state into the analytics database
- Gemini - LLM / Embeddings service used for RAG (Retrieval-Augmented Generation)
- RSpec - Framework for testing
- RuboCop - Ruby code style checker
- Brakeman - Security vulnerability scanner
- Faker - Test data generation
- Docker Compose - Multi-container development environment
- Domain Events - Event-driven communication between aggregates
- Aggregate Roots - Consistency boundaries for domain objects
- Value Objects - Immutable domain concepts
- Repository Pattern - Domain-focused data access abstraction that acts as a collection of aggregates and hides infrastructure details.
- Command/Query Separation - Optimized read/write operations
- Ruby 3.4.10
- Docker & Docker Compose
- PostgreSQL client (optional)
git clone https://github.com/mapeveri/ruby-ddd-cqrs.git
cd ruby-ddd-cqrscp env.example .env
# Edit .env with your configurationdocker-compose up -dbundle installCreates and migrates both the primary and analytics databases:
bin/rails db:create db:migrate# Creates the compacted `chat.message.state` topic
bin/bootstrap_kafka.rb
# Registers the JDBC sink connector
bin/register_sink_connector.shbin/rails serverVisit http://localhost:3000 to see your application!
Republish the full state of every existing message to the Kafka topic (idempotent upsert):
bin/backfill_messages.rbKafka (Redpanda) - inspect the message state topic using rpk:
# Follow new messages as they arrive
docker exec -it redpanda rpk topic consume chat.message.state
# Read the topic from the beginning
docker exec -it redpanda rpk topic consume chat.message.state -o beginning
# List all topics
docker exec -it redpanda rpk topic listNote: The JDBC sink connector (
messages-jdbc-sink) consumes the topic automatically and persists every message into the analytics database (message_records). Therpkcommands above are only for inspecting/debugging raw messages β they are not part of the persistence flow.
RabbitMQ - consume domain events and dispatch them to their handlers:
bin/rabbit_mq_consumersrc/
βββ shared/ # Shared domain components
β βββ domain/
β β βββ aggregate_root.rb # Base aggregate root class
β β βββ domain_events/ # Domain event infrastructure
β β βββ bus/ # Event bus implementation
β β βββ value_objects/ # Shared value objects
β βββ infrastructure/ # Shared infrastructure
β βββ messaging/kafka/ # Kafka client wrapper
βββ chat/ # Chat bounded context
β βββ domain/ # Domain layer
β β βββ message/ # Message aggregate
β β βββ user/ # User aggregate
β βββ application/ # Application layer (CQRS)
β β βββ message/
β β βββ commands/ # Write operations
β β βββ queries/ # Read operations
β βββ infrastructure/ # Infrastructure layer
β βββ messaging/kafka/ # Message state publisher
β βββ subscribers/ # Domain event subscribers
βββ analytics/ # Analytics bounded context
βββ domain/ # Domain layer
βββ application/ # Queries (read models)
βββ infrastructure/
βββ persistence/
βββ redis/ # Caching
βββ analytics_db/ # Message state records + SQL read models
db/
βββ migrate/ # Primary database migrations
βββ analytics_migrate/ # Analytics database migrations
βββ analytics_schema.rb # Analytics database schema
docker/
βββ kafka-connect/ # Kafka Connect image + connector config
βββ analytics_db/ # Analytics DB init scripts
Run the complete test suite:
bundle exec rspecRun specific test files:
bundle exec rspec spec/chat/domain/message- PostgreSQL (primary):
localhost:5435 - PostgreSQL (analytics):
localhost:5436 - Redis:
localhost:6379 - Redis Test:
localhost:6380 - RabbitMQ:
localhost:5672 - RabbitMQ Management:
localhost:15672 - Redpanda (Kafka):
localhost:9092 - Kafka Connect REST API:
localhost:8083
# View logs
docker-compose logs -f
# Restart services
docker-compose restart
# Stop all services
docker-compose down
# Rebuild containers
docker-compose up --build
# Inspect the analytics connector status
curl localhost:8083/connectors/messages-jdbc-sink/status- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.