Skip to main content

Using NoSQL

Learn how to use NoSQL databases in the JAQ Stack ecosystem for flexible and scalable data storage.

Overview

NoSQL databases provide the data persistence layer for JAQ Stack, offering flexibility, scalability, and performance for modern applications.

Supported Databases

JAQ Stack supports multiple NoSQL databases:

  • MongoDB - Document database (Primary)
  • CouchDB - Document database
  • Redis - Key-value store
  • Cassandra - Wide-column store

Getting Started

Prerequisites

  • Docker (recommended)
  • Java 17+ (for Spring Boot integration)
  • Node.js 18+ (for JavaScript/TypeScript integration)

Installation

# MongoDB
docker run -d --name mongodb \
-p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=password \
mongo:latest

# Redis
docker run -d --name redis \
-p 6379:6379 \
redis:latest

# CouchDB
docker run -d --name couchdb \
-p 5984:5984 \
-e COUCHDB_USER=admin \
-e COUCHDB_PASSWORD=password \
couchdb:latest

Manual Installation

# MongoDB (Ubuntu/Debian)
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org

# Redis (Ubuntu/Debian)
sudo apt update
sudo apt install redis-server

Configuration

Spring Boot Configuration

# application.properties
# MongoDB Configuration
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=jaqstack
spring.data.mongodb.username=admin
spring.data.mongodb.password=password
spring.data.mongodb.authentication-database=admin

# Redis Configuration
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=2000ms
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0

Java Configuration

// MongoDB Configuration
@Configuration
@EnableMongoRepositories
public class MongoConfig {

@Value("${spring.data.mongodb.host}")
private String host;

@Value("${spring.data.mongodb.port}")
private int port;

@Value("${spring.data.mongodb.database}")
private String database;

@Bean
public MongoClient mongoClient() {
return MongoClients.create(
MongoClientSettings.builder()
.applyToClusterSettings(builder ->
builder.hosts(Arrays.asList(new ServerAddress(host, port))))
.build()
);
}

@Bean
public MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoClient(), database);
}
}

Data Models

Document Structure

// User Document
@Document(collection = "users")
public class User {
@Id
private String id;

@Field("name")
private String name;

@Field("email")
private String email;

@Field("profile")
private UserProfile profile;

@Field("created_at")
private LocalDateTime createdAt;

@Field("updated_at")
private LocalDateTime updatedAt;

// Constructors, getters, setters
}

// Embedded Document
@Document
public class UserProfile {
private String bio;
private String avatar;
private List<String> skills;
private Map<String, Object> preferences;
}

Repository Pattern

@Repository
public interface UserRepository extends MongoRepository<User, String> {

// Custom query methods
List<User> findByNameContainingIgnoreCase(String name);

List<User> findByEmail(String email);

@Query("{ 'profile.skills': { $in: ?0 } }")
List<User> findBySkillsIn(List<String> skills);

@Query("{ 'created_at': { $gte: ?0, $lte: ?1 } }")
List<User> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);

// Aggregation queries
@Aggregation("{ $group: { _id: '$role', count: { $sum: 1 } } }")
List<RoleCount> countUsersByRole();
}

Best Practices

Data Modeling

  1. Denormalization: Store related data together for better performance
  2. Embedding vs Referencing: Embed for 1:1 or 1:few relationships
  3. Indexing: Create indexes on frequently queried fields
  4. Schema Design: Design for your query patterns

Performance Optimization

// Index Configuration
@Document(collection = "users")
@CompoundIndex(def = "{'name': 1, 'email': 1}")
@CompoundIndex(def = "{'created_at': -1}")
public class User {
// Document fields
}

// Query Optimization
@Service
public class UserService {

@Autowired
private UserRepository userRepository;

public List<User> findUsersOptimized(String name) {
// Use projection to limit returned fields
Query query = new Query();
query.addCriteria(Criteria.where("name").regex(name, "i"));
query.fields().include("name", "email", "profile.avatar");

return mongoTemplate.find(query, User.class);
}
}

Caching with Redis

@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));

return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}

// Using Cache
@Service
public class UserService {

@Cacheable(value = "users", key = "#id")
public User findById(String id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}

@CacheEvict(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
}

Data Migration

@Component
public class DataMigrationService {

@Autowired
private MongoTemplate mongoTemplate;

@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
migrateUserData();
}

private void migrateUserData() {
// Example migration: Add new field to existing documents
Query query = new Query();
Update update = new Update().set("migrated", true);

UpdateResult result = mongoTemplate.updateMulti(query, update, User.class);
log.info("Migrated {} user documents", result.getModifiedCount());
}
}

Monitoring and Maintenance

Health Checks

@Component
public class DatabaseHealthIndicator implements HealthIndicator {

@Autowired
private MongoTemplate mongoTemplate;

@Override
public Health health() {
try {
mongoTemplate.getCollection("users").countDocuments();
return Health.up()
.withDetail("database", "MongoDB")
.withDetail("status", "Connected")
.build();
} catch (Exception e) {
return Health.down()
.withDetail("database", "MongoDB")
.withDetail("error", e.getMessage())
.build();
}
}
}

Backup and Restore

# MongoDB Backup
mongodump --host localhost:27017 --db jaqstack --out /backup/jaqstack

# MongoDB Restore
mongorestore --host localhost:27017 --db jaqstack /backup/jaqstack

# Redis Backup
redis-cli BGSAVE

Troubleshooting

Common Issues

  1. Connection Timeout: Check network connectivity and firewall settings
  2. Memory Usage: Monitor memory consumption and adjust cache settings
  3. Index Performance: Analyze slow queries and optimize indexes
  4. Data Consistency: Implement proper transaction handling

Performance Monitoring

@Component
public class DatabaseMetrics {

@Autowired
private MongoTemplate mongoTemplate;

@Scheduled(fixedRate = 60000) // Every minute
public void collectMetrics() {
// Collect database metrics
Document stats = mongoTemplate.getDb().runCommand(new Document("dbStats", 1));
log.info("Database stats: {}", stats);
}
}

Getting Help

Next Steps