π frameworks-common-java
A modular, extensible Java framework for building robust, scalable microservices and backend systems.
This repository provides shared libraries and utilities for database access, REST APIs, exception handling, caching, messaging, and moreβdesigned for rapid development and consistency across all OlannaTech Java projects.
π¦ Modules Overview
| Module Name | Description |
|---|---|
common-database |
Advanced JPA repositories, dynamic queries, tenant support, and projection utilities. |
common-rest |
REST controller base classes, global exception handling, and response DTOs. |
common-shared |
Shared DTOs, error response models, and utility classes. |
common-cache |
Centralized, two-level (local + Redis) caching with AOP support. |
common-auth |
JWT-based authentication, security config, and endpoint protection. |
common-rabbitmq |
RabbitMQ configuration, integration, and Vault support. |
βοΈ Module Use Cases & Configuration
1. common-database
Use Cases: - Dynamic field filtering and search - Tenant-aware repositories - DTO and interface-based projections - Integration with Testcontainers for DB testing
Configuration:
- Add as a dependency in your module's build.gradle.
- Use CustomJpaRepository as your repository base interface.
- Example:
public interface UserRepository extends CustomJpaRepository<User, Long> {}
filters.put("tenant.tenantId", tenantId);.
2. common-rest
Use Cases: - Base REST controllers with versioned paths - Global exception handling for clean error responses - Standardized DTOs for API responses
Configuration:
- Extend BaseController for all REST controllers:
@RestController
@RequestMapping("/api/v1")
public abstract class BaseController {}
3. common-shared
Use Cases: - Shared DTOs and utility classes across modules - Standard error response models - Common enums and constants
Configuration: - Import shared DTOs and utilities as needed in your modules. - Use error models for consistent error handling.
4. common-cache
Use Cases:
- Two-level caching (local + Redis)
- AOP-driven cache annotations: @CentralCacheable, @CentralCachePut, @CentralCacheEvict
- Distributed locking with Redisson (with fallback to local lock)
Configuration:
- Add to your dependencies.
- In application.properties or application.yml:
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.password=your_secure_password
@CentralCacheable(value = "userCache", key = "#userId")
public UserDto getUser(Long userId) { ... }
Lock Service Usage:
- The LockService bean provides distributed locking via Redis (Redisson) or local fallback.
- Inject and use it in your service:
@Autowired
private LockService lockService;
public void doWithLock(String lockKey, Runnable action) {
var lock = lockService.getLock(lockKey);
try {
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
try {
action.run();
} finally {
lock.unlock();
}
} else {
throw new IllegalStateException("Could not acquire lock");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Lock interrupted", e);
}
}
5. common-auth
Use Cases: - JWT-based authentication and authorization - Security configuration for endpoints - User context extraction
Configuration: - Add as a dependency. - Configure JWT secret and expiration in your properties:
security.jwt.secret=your_jwt_secret
security.jwt.expiration=3600
6. common-rabbitmq
Use Cases: - Centralized RabbitMQ configuration - Secure credential loading from Vault - Queue, exchange, and routing key management
Configuration:
- Add as a dependency.
- In application.yml:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
vault:
rabbitmq:
credentials-path: secret/data/rabbitmq
Producer Example:
@Autowired
private EventPublisher eventPublisher;
public void publishOrderCreated(OrderCreatedEvent event) {
// Publishes to the target service's exchange and routing key
eventPublisher.publishEvent(event, "order-service");
// Or to your own service's exchange:
eventPublisher.publishEvent(event);
}
Consumer Example:
@Component
public class OrderCreatedEventHandler implements EventHandler<OrderData> {
@Override
public void handleEvent(BaseEvent<?> event) {
OrderData data = (OrderData) event.getPayload();
// handle order created event
}
}
// Registration (e.g. in a @PostConstruct of a @Configuration class):
@Autowired
private EventConsumer eventConsumer;
@Autowired
private OrderCreatedEventHandler handler;
@PostConstruct
public void registerHandlers() {
eventConsumer.registerHandler("ORDER_CREATED", handler, OrderData.class);
}
- The consumer will automatically dispatch incoming events of type
ORDER_CREATEDto your handler. - Handlers can be registered for any event type and payload.
ποΈ Architecture Highlights
- Reusable JPA Repositories: Dynamic field and search queries, projection support, and tenant-aware methods.
- REST API Foundation: Base controllers, global exception handler, and standardized error responses.
- DTO & Projection Support: Easily map entities to DTOs or interfaces for efficient data transfer.
- Centralized Caching: Two-level cache (local + Redis) with AOP annotations for easy integration.
- Testcontainers Integration: Seamless database testing with isolated PostgreSQL containers.
- Nexus Publishing: Ready-to-use Gradle publishing configuration for internal Maven repositories.
- RabbitMQ & Vault: Secure, dynamic messaging configuration with Vault integration.
π Usage Guide
1. Database Access with CustomJpaRepository
Features:
- Dynamic field filtering (supports nested fields, e.g., "tenant.tenantId")
- Search with pagination
- Projection to DTOs or interfaces
Example Entity:
@Entity
public class User {
@Id private Long id;
private String username;
private String email;
@ManyToOne private Tenant tenant;
}
Example DTO:
public record UserSummaryDto(Long id, String username, String email, String tenantId) {}
Repository:
public interface UserRepository extends CustomJpaRepository<User, Long> {}
Service Usage:
@Autowired UserRepository userRepository;
public Page<UserSummaryDto> searchUsers(String search, String tenantId, Pageable pageable) {
Map<String, Object> filters = new HashMap<>();
filters.put("tenant.tenantId", tenantId);
return userRepository.findByFieldsAndSearch(filters, search, pageable, UserSummaryDto.class);
}
2. REST API Foundation
Base Controller:
@RestController
@RequestMapping("/api/v1")
public abstract class BaseController {}
All controllers extending
BaseControllerwill have/api/v1prepended to their paths.
3. Global Exception Handling
GlobalExceptionHandler provides: - User-friendly validation error messages - Clean SQL constraint violation messages (e.g., "A record with email 'foo@bar.com' already exists.") - Consistent error response structure
Example Error Response:
{
"timestamp": "2025-04-22T12:34:56Z",
"status": "BAD_REQUEST",
"message": "Validation failed",
"errorDetails": "[email: must not be blank (rejected value: null)]",
"path": "/api/v1/users"
}
4. Centralized Caching
- Use
@CentralCacheable,@CentralCachePut, and@CentralCacheEvictannotations for easy, AOP-driven caching. - Supports two-level cache (local + Redis) for high performance and resilience.
- Example annotation:
@CentralCacheable(value = "userCache", key = "#userId") public UserDto getUser(Long userId) { ... }
5. Testing with Testcontainers
DatabaseApplicationTests uses Testcontainers to spin up a PostgreSQL instance for integration tests:
static final PostgreSQLContainer<?> postgresContainer = new PostgreSQLContainer<>("postgres:15.3")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
Ensures your tests run in a real, isolated database environment.
6. Gradle Publishing & Dependency Management
Publishing to Nexus: - Configured for both release and snapshot repositories. - Credentials and URLs are parameterized for security and flexibility.
Reimporting/Cleaning Dependencies:
./gradlew clean build --refresh-dependencies
rm -rf ~/.gradle/caches
./gradlew clean build --refresh-dependencies
7. RabbitMQ & Vault Integration
- Securely load RabbitMQ credentials from Vault at runtime.
- Centralized configuration for queues, exchanges, and routing keys.
π‘οΈ Error Handling Patterns
- Validation errors: Clearly list all field errors with rejected values.
- SQL constraint violations: Parse and present a user-friendly message.
- Other exceptions: Return a generic, safe message with details for debugging.
π§© Extending the Framework
- Add new projections: Create DTOs with constructors matching selected fields.
- Add new modules: Follow the modular structure for easy integration.
- Customize error handling: Extend
GlobalExceptionHandleras needed.
π Contributing
- Fork the repo and create your feature branch.
- Write clear, tested code and update documentation.
- Submit a pull request!
π‘ Tips
- Use nested field names (e.g.,
"tenant.tenantId") in filters for deep queries. - Always match DTO constructor parameters to the fields you select in queries.
- For interface-based projections, use Spring Data repository methods.
π Example API Call
Search users by tenant and name:
GET /api/v1/users/search?tenantId=abc123&search=john&page=0&size=10
π₯ Maintainers
- OlannaTech DevOps Team
devops@olanna.ai
π·οΈ License
MIT License
frameworks-common-java: Your foundation for fast, reliable, and maintainable Java microservices.