From 9bdb93df61799bd0eeea01eb44dc5095dbba7439 Mon Sep 17 00:00:00 2001 From: Andres Contreras Date: Wed, 11 Feb 2026 09:47:42 +0100 Subject: [PATCH] docs: add professional README with unified template (#6) Standardize README.md with a consistent template including CI badge, features, installation, quick start, configuration, and documentation sections. Add paths-ignore to ci.yml to skip CI for docs-only changes. --- .github/workflows/ci.yml | 19 +- README.md | 1289 +++----------------------------------- 2 files changed, 89 insertions(+), 1219 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9093b6..9567aee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,22 @@ name: CI on: push: branches: [develop] + paths-ignore: + - '**.md' + - 'docs/**' + - 'tutorials/**' + - 'examples/**/README.md' + - 'LICENSE' + - '.gitignore' pull_request: branches: [develop, main] + paths-ignore: + - '**.md' + - 'docs/**' + - 'tutorials/**' + - 'examples/**/README.md' + - 'LICENSE' + - '.gitignore' workflow_dispatch: inputs: triggered-by: @@ -13,10 +27,5 @@ on: jobs: build: uses: fireflyframework/.github/.github/workflows/java-ci.yml@main - permissions: - packages: read - contents: read - actions: write with: java-version: '25' - secrets: inherit diff --git a/README.md b/README.md index aa8c0ac..9abe106 100644 --- a/README.md +++ b/README.md @@ -1,1267 +1,128 @@ -# Firefly CQRS Framework Library - +# Firefly Framework - CQRS + [![CI](https://github.com/fireflyframework/fireflyframework-cqrs/actions/workflows/ci.yml/badge.svg)](https://github.com/fireflyframework/fireflyframework-cqrs/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) +[![Java](https://img.shields.io/badge/Java-21%2B-orange.svg)](https://openjdk.org) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.x-green.svg)](https://spring.io/projects/spring-boot) -**Enterprise-grade CQRS implementation for Spring Boot applications** +> CQRS pattern implementation with reactive command/query buses, execution contexts, authorization, validation, and metrics. -*Zero-boilerplate โ€ข Reactive-first โ€ข Production-ready* +--- -[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.x-brightgreen.svg)](https://spring.io/projects/spring-boot) -[![Java](https://img.shields.io/badge/Java-21+-orange.svg)](https://openjdk.java.net/) -[![Project Reactor](https://img.shields.io/badge/Project%20Reactor-3.x-blue.svg)](https://projectreactor.io/) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +## Table of Contents ---- +- [Overview](#overview) +- [Features](#features) +- [Requirements](#requirements) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Configuration](#configuration) +- [Documentation](#documentation) +- [Contributing](#contributing) +- [License](#license) -## ๐ŸŽฏ **Why Firefly CQRS?** +## Overview -Building scalable, maintainable applications with clear separation between **commands** (write operations) and **queries** (read operations) shouldn't require extensive boilerplate code. The Firefly CQRS Framework Library eliminates complexity while providing enterprise-grade features out of the box. +Firefly Framework CQRS provides a production-ready implementation of the Command Query Responsibility Segregation pattern for reactive Spring Boot microservices. It separates read (query) and write (command) operations through dedicated buses, handlers, and execution contexts. -### โœจ **Key Benefits** +The library features annotation-driven handler registration (`@CommandHandlerComponent`, `@QueryHandlerComponent`), fluent command/query builders, pluggable authorization with `@CustomAuthorization`, automatic Bean Validation integration, and comprehensive metrics collection. It supports caching at the query handler level and includes Actuator endpoints for monitoring CQRS operations. -- **๐Ÿ”ฅ Zero Boilerplate**: Write only business logic - everything else is automatic -- **โšก Reactive-First**: Built on Project Reactor for high-performance async processing -- **๐Ÿ›ก๏ธ Enterprise Security**: Custom authorization logic with context-aware access control -- **๐Ÿ“Š Production-Ready Observability**: Built-in metrics, health checks, and actuator endpoints -- **๐ŸŽ›๏ธ Intelligent Caching**: Automatic cache key generation via fireflyframework-cache (Redis/Caffeine support) -- **๐Ÿ”ง Auto-Configuration**: Spring Boot auto-configuration with sensible defaults +The execution context propagates correlation IDs, tenant information, and security context across command and query processing pipelines, enabling full traceability in distributed environments. -### ๐Ÿ† **What Makes It Different** +## Features -| Feature | Traditional CQRS | Firefly CQRS | -|---------|------------------|---------------| -| Handler Setup | Manual registration, boilerplate | Annotation-based, automatic discovery | -| Validation | Manual setup | Jakarta Bean Validation + custom async validation | -| Caching | Manual cache management | Automatic cache key generation via fireflyframework-cache | -| Metrics | Custom metrics implementation | Built-in Micrometer integration with actuator endpoints | -| Authorization | Custom authorization logic | Custom business authorization with context-aware access control | -| Type Safety | Runtime type resolution errors | Compile-time generic type safety | -| Context Propagation | Manual context passing | ExecutionContext with automatic propagation | +- Reactive `CommandBus` and `QueryBus` with type-safe handler dispatch +- `@CommandHandlerComponent` and `@QueryHandlerComponent` annotations for auto-registration +- `Command` and `Query` marker interfaces with response types +- Context-aware handlers via `ContextAwareCommandHandler` and `ContextAwareQueryHandler` +- `ExecutionContext` with correlation ID, tenant, and security context propagation +- Fluent `CommandBuilder` and `QueryBuilder` APIs +- Authorization framework with `@CustomAuthorization` and `AuthorizationService` +- Automatic Bean Validation on commands and queries +- Query-level caching with `@Cacheable` and `@CacheEvict` +- CQRS health indicator and metrics endpoint for Actuator +- Configurable properties via `CqrsProperties` and `AuthorizationProperties` +- Generic type resolution for handler-to-command/query matching -## ๐Ÿš€ **Quick Start - From Zero to CQRS in 5 Minutes** +## Requirements -### ๐Ÿ“ฆ **Installation** +- Java 21+ +- Spring Boot 3.x +- Maven 3.9+ + +## Installation ```xml org.fireflyframework fireflyframework-cqrs - 1.0.0-SNAPSHOT + 26.01.01 - - - - org.fireflyframework - fireflyframework-cache - 1.0.0-SNAPSHOT - -``` - -### โš™๏ธ **Zero-Configuration Setup** - -The framework auto-configures when detected on the classpath - **no manual configuration required:** - -```java -@SpringBootApplication -public class BankingApplication { - public static void main(String[] args) { - SpringApplication.run(BankingApplication.class, args); - - // โœ… CommandBus and QueryBus beans automatically available - // โœ… CommandMetricsService configured for production monitoring - // โœ… Jakarta Bean Validation enabled with custom validation support - // โœ… Handler discovery and registration automatic - // โœ… Actuator endpoints exposed: /actuator/cqrs, /actuator/cqrs/commands, etc. - // โœ… Authorization system ready with custom authorization logic - } -} -``` - -### ๐ŸŽฏ **Enable Observability (Optional)** - -```yaml -# application.yml - Enable comprehensive monitoring -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus,cqrs - endpoint: - cqrs: - enabled: true - metrics: - export: - prometheus: - enabled: true - -firefly: - cqrs: - command: - metrics-enabled: true - query: - metrics-enabled: true ``` -### ๐Ÿ’ผ **Your First Command Handler** - -**Problem**: Traditional CQRS requires extensive setup, validation handling, metrics collection, and error management. - -**Solution**: Write only your business logic - everything else is automatic! +## Quick Start ```java -@CommandHandlerComponent( - timeout = 30000, // 30-second timeout - retries = 3, // 3 automatic retries with exponential backoff - metrics = true, // Automatic metrics collection via CommandMetricsService - tracing = true // Distributed tracing support -) -public class CreateAccountHandler extends CommandHandler { - - @Autowired - private AccountService accountService; - - @Override - protected Mono doHandle(CreateAccountCommand command) { - // ๐ŸŽฏ Only business logic required - everything else is handled! - return accountService.createAccount(command) - .map(account -> AccountResult.builder() - .accountId(account.getAccountId()) - .customerId(account.getCustomerId()) - .status("CREATED") - .createdAt(Instant.now()) - .build()); - } - - // โœ… ZERO BOILERPLATE - All handled automatically: - // โ€ข Command type detection via generics - // โ€ข Jakarta Bean Validation (@NotNull, @Valid, etc.) - // โ€ข Custom async validation (command.customValidate()) - // โ€ข Authorization (command.authorize() with custom logic) - // โ€ข Metrics collection (firefly.cqrs.command.* metrics) - // โ€ข Error handling with retry logic - // โ€ข ExecutionContext propagation - // โ€ข Distributed tracing -} -``` - -### ๐Ÿ“Š **Your First Query Handler with Smart Caching** +import org.fireflyframework.cqrs.command.*; +import org.fireflyframework.cqrs.annotations.CommandHandlerComponent; -**Problem**: Implementing efficient caching requires cache key management, TTL handling, and eviction strategies. - -**Solution**: Declarative caching with automatic key generation! - -```java -@QueryHandlerComponent( - cacheable = true, // Enable intelligent caching - cacheTtl = 300, // 5-minute cache TTL - cacheKeyFields = {"accountId", "currency"}, // Custom cache key fields - cacheKeyPrefix = "account_balance", // Custom prefix - metrics = true, // Cache hit/miss metrics - autoEvictCache = true, // Auto-evict on related commands - evictOnCommands = {"TransferMoneyCommand", "UpdateAccountCommand"} -) -public class GetAccountBalanceHandler extends QueryHandler { - - @Autowired - private AccountService accountService; - - @Override - protected Mono doHandle(GetAccountBalanceQuery query) { - // ๐ŸŽฏ Pure business logic - caching handled automatically! - return accountService.getBalance(query.getAccountId(), query.getCurrency()) - .map(balance -> AccountBalance.builder() - .accountId(query.getAccountId()) - .balance(balance) - .currency(query.getCurrency()) - .lastUpdated(Instant.now()) - .build()); - } - - // โœ… INTELLIGENT CACHING - All managed automatically via fireflyframework-cache: - // โ€ข Cache key: "account_balance_ACC123_USD" (auto-generated) - // โ€ข TTL management with configurable expiration - // โ€ข Cache hit/miss metrics (cache.gets with result tags) - // โ€ข Automatic eviction when TransferMoneyCommand executes - // โ€ข Redis/Caffeine support via fireflyframework-cache configuration - // โ€ข Cache metrics in actuator endpoint /actuator/cqrs/queries -} -``` +// Define a command +public record CreateAccountCommand(String name, String email) implements Command {} -## ๐ŸŽจ **Core Features & Architecture** - -### โœจ **Zero-Boilerplate CQRS** -- **๐ŸŽฏ Single Approach**: Only one way to do things - extend base classes with annotations -- **๐Ÿ”„ Automatic Type Detection**: Generic types resolved at compilation time from handler classes -- **โœ… Built-in Validation**: Jakarta Bean Validation annotations + async custom validation support -- **๐ŸŽ›๏ธ Smart Caching**: Automatic cache key generation, TTL management, and eviction strategies -- **๐Ÿ“Š Performance Metrics**: Built-in CommandMetricsService with per-type breakdown and timing - -### ๐Ÿ” **Enterprise Security & Authorization** -- **๐Ÿ” Custom Authorization**: Flexible custom business authorization logic -- **๐ŸŒ Context-Aware**: Rich ExecutionContext with tenant isolation, user context, and feature flags -- **โšก Reactive Security**: Non-blocking authorization with `Mono` return types -- **๐Ÿ”ง Flexible Configuration**: Fine-grained control through properties and environment variables - -### ๐Ÿ“Š **Production-Ready Observability** -- **๐Ÿ“ฑ Comprehensive Actuator Integration**: - - `/actuator/cqrs` - Complete framework metrics overview - - `/actuator/cqrs/commands` - Command processing metrics - - `/actuator/cqrs/queries` - Query metrics with cache hit/miss rates - - `/actuator/cqrs/handlers` - Handler registry information - - `/actuator/cqrs/health` - Framework health status -- **๐Ÿ“ Automatic Metrics Collection**: Real-time metrics via `CommandMetricsService`: - - `firefly.cqrs.command.processed` - Total successful commands - - `firefly.cqrs.command.failed` - Failed commands with error classification - - `firefly.cqrs.command.validation.failed` - Validation failures by phase - - `firefly.cqrs.command.processing.time` - Processing duration timers - - Per-command-type metrics with automatic tagging -- **๐Ÿ” Health Monitoring**: CQRS component health checks with detailed status -- **๐Ÿ”— Distributed Tracing**: Automatic trace ID and correlation ID propagation - -### โšก **High-Performance & Resilience** -- **๐ŸŒŠ Reactive Streams**: Built on Project Reactor for non-blocking, high-throughput processing -- **โฑ๏ธ Timeout Management**: Per-handler configurable timeouts with circuit breaker integration -- **๐Ÿ”„ Intelligent Retry Logic**: Exponential backoff with jitter for failure recovery -- **๐ŸŽ›๏ธ Multi-Level Caching via fireflyframework-cache**: - - Local caching (Caffeine) for low-latency access - - Distributed caching (Redis) for cluster-wide consistency - - Automatic cache key generation with field-based customization - - TTL management and automatic eviction on related commands - - Unified cache abstraction through FireflyCacheManager - -### ๐Ÿ”ง **Developer Experience** -- **๐Ÿ› ๏ธ Auto-Configuration**: Spring Boot auto-configuration with production-ready defaults -- **๐Ÿ“œ Comprehensive Documentation**: Real-world examples and patterns in [docs/](./docs/) -- **๐Ÿ“ Property-Based Configuration**: YAML configuration with environment variable overrides -- **๐Ÿ” Handler Discovery**: Automatic registration and type-safe handler discovery -- **๐Ÿงช Type Safety**: Compile-time generic type validation prevents runtime errors - -## ๐Ÿ“– Core Concepts - -### Commands -Commands represent **intentions to change state** and are processed by CommandHandlers: - -```java -import jakarta.validation.constraints.*; - -public class TransferMoneyCommand implements Command { - - @NotBlank(message = "Source account is required") - private final String sourceAccountId; - - @NotBlank(message = "Target account is required") - private final String targetAccountId; - - @NotNull(message = "Amount is required") - @Positive(message = "Amount must be positive") - private final BigDecimal amount; - - public TransferMoneyCommand(String sourceAccountId, String targetAccountId, BigDecimal amount) { - this.sourceAccountId = sourceAccountId; - this.targetAccountId = targetAccountId; - this.amount = amount; - } - - // Getters... - - @Override - public Mono customValidate() { - // Custom business validation beyond annotations - if (sourceAccountId.equals(targetAccountId)) { - return Mono.just(ValidationResult.failure("targetAccountId", - "Cannot transfer to the same account")); - } - return Mono.just(ValidationResult.success()); - } -} -``` - -### Queries -Queries represent **requests for data** and should be idempotent read operations: +// Implement the handler +@CommandHandlerComponent +public class CreateAccountHandler implements CommandHandler { -```java -public class GetTransactionHistoryQuery implements Query { - - @NotBlank - private final String accountId; - - private final LocalDate fromDate; - private final LocalDate toDate; - private final int limit; - - public GetTransactionHistoryQuery(String accountId, LocalDate fromDate, - LocalDate toDate, int limit) { - this.accountId = accountId; - this.fromDate = fromDate; - this.toDate = toDate; - this.limit = limit; - } - - // Getters... - @Override - public String getCacheKey() { - // Custom cache key for better cache utilization - // Note: The framework will automatically prefix this with ":cqrs:" - // Final key will be "firefly:cache:default::cqrs:txn_history:..." after - // fireflyframework-cache adds its "firefly:cache:{cacheName}:" prefix - return String.format("txn_history:%s:%s:%s:%d", - accountId, fromDate, toDate, limit); + public Mono handle(CreateAccountCommand command) { + return accountService.create(command.name(), command.email()); } } -``` -### Command/Query Buses -Central dispatching mechanism for routing commands and queries to their handlers: - -```java -@Service -public class BankingService { - - private final CommandBus commandBus; - private final QueryBus queryBus; - - public Mono transferMoney(String fromAccount, String toAccount, BigDecimal amount) { - TransferMoneyCommand command = new TransferMoneyCommand(fromAccount, toAccount, amount); - return commandBus.send(command); - } - - public Mono getBalance(String accountId) { - GetAccountBalanceQuery query = new GetAccountBalanceQuery(accountId); - return queryBus.query(query); - } -} -``` - -### ExecutionContext -ExecutionContext provides a powerful way to pass cross-cutting concerns and metadata that aren't part of the core command/query data. It enables tenant isolation, user context, feature flags, tracing correlation, and custom properties. - -#### Context Creation and Propagation - -```java +// Dispatch from a controller @RestController public class AccountController { - - private final CommandBus commandBus; - private final QueryBus queryBus; - - @PostMapping("/accounts") - public Mono> createAccount( - @RequestBody CreateAccountRequest request, - @RequestHeader("Authorization") String authToken, - @RequestHeader(value = "X-Tenant-ID", required = false) String tenantId, - @RequestHeader(value = "X-Correlation-ID", required = false) String correlationId, - ServerHttpRequest httpRequest) { - - ExecutionContext context = ExecutionContext.builder() - .userId(extractUserIdFromToken(authToken)) - .tenantId(tenantId != null ? tenantId : "default") - .sessionId(extractSessionId(httpRequest)) - .correlationId(correlationId != null ? correlationId : UUID.randomUUID().toString()) - .source("web-app") - .clientIp(getClientIp(httpRequest)) - .featureFlag("enhanced-validation", featureFlagService.isEnabled("enhanced-validation", tenantId)) - .featureFlag("premium-features", isPremiumTenant(tenantId)) - .customProperty("request-id", UUID.randomUUID().toString()) - .build(); - - CreateAccountCommand command = toCommand(request); - - return commandBus.send(command, context) - .map(result -> ResponseEntity.ok(result)) - .onErrorResume(this::handleError); - } - - @GetMapping("/accounts/{accountId}/balance") - public Mono> getAccountBalance( - @PathVariable String accountId, - @RequestHeader("Authorization") String authToken) { - - ExecutionContext context = buildExecutionContext(authToken); - GetAccountBalanceQuery query = new GetAccountBalanceQuery(accountId); - - return queryBus.query(query, context) - .map(balance -> ResponseEntity.ok(balance)); - } -} -``` - -#### Context-Aware Service Implementation - -```java -@Service -public class TenantAwareAccountService { - - public Mono createAccountInTenant(CreateAccountCommand command, ExecutionContext context) { - String tenantId = context.getTenantId(); - String userId = context.getUserId(); - - return validateTenantLimits(tenantId) - .flatMap(limits -> validateUserPermissions(userId, tenantId)) - .flatMap(permissions -> createAccount(command, tenantId, context)) - .flatMap(account -> applyTenantSpecificRules(account, context)); - } - - private Mono createAccount(CreateAccountCommand command, String tenantId, ExecutionContext context) { - Account account = Account.builder() - .tenantId(tenantId) - .customerId(command.getCustomerId()) - .type(command.getAccountType()) - .balance(command.getInitialDeposit()) - .createdBy(context.getUserId()) - .createdFrom(context.getSource()) - .build(); - - // Apply feature flags - if (context.getFeatureFlag("enhanced-security", false)) { - account = account.withEnhancedSecurityEnabled(true); - } - - if (context.getFeatureFlag("premium-features", false)) { - account = account.withPremiumFeaturesEnabled(true); - } - - return accountRepository.save(account); - } -} -``` - -#### ExecutionContext Builder Pattern -```java -@Service -public class ContextAwareService { - private final CommandBus commandBus; - - public Mono createAccountForTenant(CreateAccountCommand command, - String userId, String tenantId) { - ExecutionContext context = ExecutionContext.builder() - .userId(userId) - .tenantId(tenantId) - .sessionId(UUID.randomUUID().toString()) - .correlationId(UUID.randomUUID().toString()) - .source("internal-service") - .featureFlag("enhanced-validation", true) - .featureFlag("audit-logging", isAuditRequired(tenantId)) - .customProperty("service-version", "2.1.0") - .customProperty("operation-type", "account-creation") - .build(); - - return commandBus.send(command, context); - } -} -``` - -## โš™๏ธ Configuration - -### Basic Configuration - -```yaml -# application.yml -firefly: - cqrs: - enabled: true - command: - timeout: 30s # Default command timeout - retries: 3 # Default retry attempts - backoff-ms: 1000 # Retry backoff delay - metrics-enabled: true # Enable command metrics - validation-enabled: true # Enable automatic validation - query: - timeout: 15s # Default query timeout - caching-enabled: true # Enable query caching - cache-ttl: 5m # Default cache TTL - metrics-enabled: true # Enable query metrics - authorization: - enabled: true # Enable authorization - custom: - enabled: true # Enable custom authorization - timeout-ms: 5000 # Custom authorization timeout -``` - -### Environment Variables - -All configuration properties can be overridden with environment variables: - -```bash -# Global CQRS settings -FIREFLY_CQRS_ENABLED=true - -# Command settings -FIREFLY_CQRS_COMMAND_TIMEOUT=30s -FIREFLY_CQRS_COMMAND_RETRIES=3 -FIREFLY_CQRS_COMMAND_METRICS_ENABLED=true - -# Query settings -FIREFLY_CQRS_QUERY_CACHING_ENABLED=true -FIREFLY_CQRS_QUERY_CACHE_TTL=5m - -# Authorization settings -FIREFLY_CQRS_AUTHORIZATION_ENABLED=true -FIREFLY_CQRS_AUTHORIZATION_CUSTOM_ENABLED=true -``` - -### Cache Configuration via fireflyframework-cache - -The CQRS library uses **fireflyframework-cache** for unified cache management across all Firefly libraries. This provides: - -- โœ… **Unified cache abstraction** - Single cache configuration for all Firefly libraries -- โœ… **Better cache providers** - Support for Caffeine (local) and Redis (distributed) -- โœ… **Reactive API** - Full Project Reactor integration for non-blocking cache operations -- โœ… **Improved observability** - Better metrics and monitoring capabilities -- โœ… **Zero code changes** - Existing query handlers work without modification - -#### Basic Configuration - -```yaml -# Enable caching via fireflyframework-cache -firefly: - cache: - enabled: true - default-ttl: 300s # Default TTL for cache entries - provider: caffeine # Options: caffeine (local), redis (distributed) - - cqrs: - query: - caching-enabled: true # Enable query caching - -# Note: All CQRS cache keys are automatically prefixed with ":cqrs:" -# Combined with fireflyframework-cache's "firefly:cache:{cacheName}:" prefix, final keys are: -# - Caffeine: "firefly:cache:default::cqrs:QueryName" -# - Redis: "firefly:cache:default::cqrs:QueryName" -# The double colon (::) provides clear visual separation between cache infrastructure and CQRS namespace -``` - -#### Caffeine (Local Cache) Configuration - -Best for single-instance applications or when low latency is critical: - -```yaml -firefly: - cache: - enabled: true - provider: caffeine - default-ttl: 300s - - caffeine: - default: - maximum-size: 1000 # Max entries in cache - expire-after-write: PT5M # Expire 5 minutes after write - expire-after-access: PT10M # Expire 10 minutes after last access - initial-capacity: 100 # Initial cache capacity -``` - -#### Redis (Distributed Cache) Configuration - -Best for multi-instance applications requiring cache consistency: - -```yaml -firefly: - cache: - enabled: true - provider: redis - default-ttl: 300s - - redis: - host: localhost - port: 6379 - password: ${REDIS_PASSWORD:} # Optional password - timeout: 2000ms - database: 0 - ssl: false -``` - -#### Advanced Configuration - -```yaml -firefly: - cache: - enabled: true - provider: redis - default-ttl: 300s - ttl: PT15M # 15-minute TTL - maximum-size: 5000 # Larger cache size - - cqrs: - query: - caching-enabled: true - -# Note: CQRS uses namespace prefixing (:cqrs:) for all query cache keys -# Combined with fireflyframework-cache's "firefly:cache:{cacheName}:" prefix, final keys are: -# - Caffeine: "firefly:cache:default::cqrs:QueryName" -# - Redis: "firefly:cache:default::cqrs:QueryName" -# The double colon (::) provides clear visual separation -``` - -#### Migration from Old Cache Configuration - -If you're upgrading from an older version of fireflyframework-cqrs: - -**Old Configuration (Deprecated):** -```yaml -firefly: - cqrs: - query: - cache-type: REDIS # โŒ DEPRECATED - -spring: - cache: - type: redis # โŒ DEPRECATED - data: - redis: - host: localhost -``` - -**New Configuration:** -```yaml -firefly: - cache: - enabled: true - provider: redis # โœ… Use fireflyframework-cache - redis: - host: localhost - port: 6379 -``` - -**Migration Steps:** -1. Add `fireflyframework-cache` dependency to your `pom.xml` -2. Remove old `firefly.cqrs.query.cache-type` property -3. Remove `spring.cache.*` configuration -4. Add `firefly.cache.*` configuration as shown above -5. No code changes required - existing query handlers work automatically - -For detailed migration instructions, see [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md). - -## ๐Ÿ” Authorization - -### Built-in Authorization Patterns - -#### Custom Authorization -Implement custom business logic authorization: - -```java -public class TransferMoneyCommand implements Command { - - @Override - public Mono authorize(ExecutionContext context) { - String userId = context.getUserId(); - - return validateTransferLimits(amount, userId) - .flatMap(limitsValid -> { - if (!limitsValid) { - return Mono.just(AuthorizationResult.failure("limits", - "Transfer exceeds daily limit")); - } - return validateAccountOwnership(sourceAccountId, userId); - }) - .map(ownershipValid -> ownershipValid - ? AuthorizationResult.success() - : AuthorizationResult.failure("ownership", "Account not owned by user")); - } -} -``` - -#### Context-Aware Authorization -Use ExecutionContext for tenant-aware and feature-flag-based authorization: - -```java -@Override -public Mono authorize(ExecutionContext context) { - String tenantId = context.getTenantId(); - boolean highValueTransfersEnabled = context.getFeatureFlag("high-value-transfers", false); - - if (amount.compareTo(new BigDecimal("10000")) > 0 && !highValueTransfersEnabled) { - return Mono.just(AuthorizationResult.failure("amount", - "High-value transfers require premium features")); - } - - return tenantService.verifyAccountBelongsToTenant(sourceAccountId, tenantId) - .map(belongsToTenant -> belongsToTenant - ? AuthorizationResult.success() - : AuthorizationResult.failure("tenant", "Account does not belong to tenant")); -} -``` - -## ๐ŸŽฏ Handler Annotations - -### @CommandHandlerComponent - -```java -@CommandHandlerComponent( - timeout = 30000, // Handler timeout in milliseconds - retries = 3, // Number of retry attempts - backoffMs = 1000, // Backoff delay between retries - metrics = true, // Enable metrics collection - tracing = true, // Enable distributed tracing - validation = true, // Enable automatic validation - priority = 0, // Handler priority (higher = more priority) - tags = {"banking", "transfer"}, // Tags for categorization - description = "Handles money transfers between accounts" -) -public class TransferMoneyHandler extends CommandHandler { - // Handler implementation -} -``` - -### @QueryHandlerComponent - -```java -@QueryHandlerComponent( - cacheable = true, // Enable result caching - cacheTtl = 300, // Cache TTL in seconds - cacheKeyFields = {"accountId"}, // Fields to include in cache key - cacheKeyPrefix = "account_balance", // Custom cache key prefix - metrics = true, // Enable metrics collection - tracing = true, // Enable distributed tracing - timeout = 15000, // Query timeout in milliseconds - autoEvictCache = true, // Auto-evict cache on related commands - evictOnCommands = {"CreateAccountCommand", "TransferMoneyCommand"} -) -public class GetAccountBalanceHandler extends QueryHandler { - // Handler implementation -} -``` - -## ๐Ÿ” Observability & Monitoring - -### Spring Boot Actuator Integration - -```yaml -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus,cqrs - endpoint: - health: - show-details: always - cqrs: - enabled: true -``` - -### Available Endpoints -- `GET /actuator/health/cqrs` - CQRS system health -- `GET /actuator/metrics` - Comprehensive CQRS metrics -- `GET /actuator/cqrs` - Custom CQRS information endpoint - -### Metrics - -The library provides comprehensive metrics: - -#### Command Metrics -- `cqrs.command.processed.total` - Total commands processed -- `cqrs.command.processing.time` - Command processing duration -- `cqrs.command.success.total` - Successful command executions -- `cqrs.command.failure.total` - Failed command executions -- `cqrs.command.retry.total` - Command retry attempts - -#### Query Metrics -- `cqrs.query.processed.total` - Total queries processed -- `cqrs.query.processing.time` - Query processing duration -- `cqrs.query.cache.hits.total` - Query cache hits -- `cqrs.query.cache.misses.total` - Query cache misses -- `cqrs.query.cache.evictions.total` - Cache evictions - -#### Authorization Metrics -- `cqrs.authorization.attempts.total` - Authorization attempts -- `cqrs.authorization.success.total` - Successful authorizations -- `cqrs.authorization.failure.total` - Failed authorizations -- `cqrs.authorization.duration` - Authorization processing time - -### Health Indicators - -The library provides a comprehensive health indicator: - -```json -{ - "status": "UP", - "components": { - "cqrs": { - "status": "UP", - "details": { - "commandHandlers": 15, - "queryHandlers": 23, - "cacheHitRatio": 0.87, - "authorizationEnabled": true - } - } - } -} -``` - -## ๐Ÿ—๏ธ Advanced Features - -### Fluent Command/Query Builders - -```java -// Fluent command building -TransferResult result = CommandBuilder.command(TransferMoneyCommand.class) - .sourceAccount("ACC-001") - .targetAccount("ACC-002") - .amount(BigDecimal.valueOf(1000)) - .correlationId("TXN-12345") - .build() - .sendVia(commandBus) - .block(); - -// Fluent query building -AccountBalance balance = QueryBuilder.query(GetAccountBalanceQuery.class) - .accountId("ACC-001") - .currency("USD") - .build() - .queryVia(queryBus) - .block(); -``` - -### Cache Management Annotations - -```java -// Automatic cache eviction -@CacheEvict(cacheNames = "accountBalance", key = "#command.accountId") -@CommandHandlerComponent -public class UpdateAccountHandler extends CommandHandler { - // Handler implementation -} - -// Conditional caching -@Cacheable(condition = "#query.amount.compareTo(new BigDecimal('1000')) > 0") -@QueryHandlerComponent -public class ExpensiveCalculationHandler extends QueryHandler { - // Handler implementation -} -``` - -### Context-Aware Handlers - -For handlers that need ExecutionContext: - -```java -@CommandHandlerComponent -public class TenantAwareHandler extends CommandHandler { - - @Override - protected Mono doHandle(TenantCommand command, ExecutionContext context) { - String tenantId = context.getTenantId(); - String userId = context.getUserId(); - boolean featureEnabled = context.getFeatureFlag("new-feature", false); - - return processWithContext(command, tenantId, userId, featureEnabled); + @PostMapping("/accounts") + public Mono create(@RequestBody CreateAccountCommand command) { + return commandBus.dispatch(command); } } ``` -## ๐Ÿงช Testing - -### Test Configuration +## Configuration ```yaml -# application-test.yml firefly: cqrs: - enabled: true - command: - timeout: 5s # Shorter timeouts for tests - retries: 1 # Fewer retries for tests - query: - caching-enabled: false # Disable caching for predictable tests + validation: + enabled: true authorization: - enabled: false # Disable authorization for unit tests -``` - -### Handler Testing - -```java -@SpringBootTest -class TransferMoneyHandlerTest { - - @Autowired - private CommandBus commandBus; - - @Autowired - private QueryBus queryBus; - - @Test - void shouldTransferMoneySuccessfully() { - // Given - TransferMoneyCommand command = new TransferMoneyCommand("ACC-001", "ACC-002", - new BigDecimal("1000")); - - // When & Then - StepVerifier.create(commandBus.send(command)) - .assertNext(result -> { - assertThat(result.getTransferId()).isNotNull(); - assertThat(result.getStatus()).isEqualTo("COMPLETED"); - assertThat(result.getAmount()).isEqualTo(new BigDecimal("1000")); - }) - .verifyComplete(); - } -} -``` - -## ๐Ÿ”„ Integration with fireflyframework-domain - -This library integrates seamlessly with fireflyframework-domain for complete DDD support: - -```xml - - org.fireflyframework - fireflyframework-domain - 2.0.0-SNAPSHOT - - -``` - -**What you get from fireflyframework-domain:** -- Domain Events: Multi-messaging event publishing (Kafka, RabbitMQ, SQS, Kinesis) -- ServiceClient Framework: Reactive REST and gRPC service communication -- Resilience Patterns: Circuit breakers, retries, and fault tolerance -- Saga Integration: Integration with lib-transactional-engine -- ExecutionContext: Context propagation across distributed services -- Observability: Metrics, health checks, and distributed tracing - -## ๐Ÿฆ **Real-World Example - Enterprise Application** - -See how Firefly CQRS eliminates complexity in a production application: - -### ๐Ÿ’ณ **Command: Transfer Money Between Accounts** - -```java -// 1. Define your command with validation -public class TransferMoneyCommand implements Command { - - @NotNull(message = "Source account required") - @Pattern(regexp = "^ACC-\\d{6}$", message = "Invalid account format") - private final String sourceAccountId; - - @NotNull @Positive - private final BigDecimal amount; - - @NotNull - private final String currency; - - // Constructor, getters... - - @Override - public Mono customValidate() { - // Custom business validation - if (sourceAccountId.equals(targetAccountId)) { - return Mono.just(ValidationResult.failure( - "targetAccountId", "Cannot transfer to same account")); - } - return Mono.just(ValidationResult.success()); - } - - @Override - public Mono authorize(ExecutionContext context) { - // Custom authorization logic - return validateDailyLimits(context.getUserId(), amount) - .flatMap(withinLimits -> withinLimits - ? Mono.just(AuthorizationResult.success()) - : Mono.just(AuthorizationResult.failure("limits", - "Transfer exceeds daily limit"))); - } -} - -// 2. Implement your handler (only business logic!) -@CommandHandlerComponent( - timeout = 30000, - retries = 3, - metrics = true, - description = "Processes money transfers between accounts" -) -public class TransferMoneyHandler extends CommandHandler { - - @Autowired - private TransferService transferService; - - @Override - protected Mono doHandle(TransferMoneyCommand command) { - // ๐ŸŽฏ Pure business logic - everything else handled automatically! - return transferService.processTransfer( - command.getSourceAccountId(), - command.getTargetAccountId(), - command.getAmount(), - command.getCurrency() - ); - } - - // โœ… NO BOILERPLATE: - // - Validation: Handled by Jakarta Bean Validation + customValidate() - // - Authorization: Custom authorize() method with business logic - // - Metrics: Automatic via CommandMetricsService - // - Retry logic: 3 retries with exponential backoff - // - Error handling: Built-in with proper error classification - // - Tracing: Automatic correlation ID and trace propagation -} -``` - -### ๐Ÿ“ˆ **Query: Get Account Balance with Smart Caching** - -```java -// 1. Define your query -public class GetAccountBalanceQuery implements Query { - - @NotBlank - private final String accountId; - - private final String currency; - - // Constructor, getters... -} - -// 2. Implement your handler with intelligent caching -@QueryHandlerComponent( - cacheable = true, - cacheTtl = 300, // 5-minute cache - cacheKeyFields = {"accountId", "currency"}, // Custom cache key - cacheKeyPrefix = "account_balance", // Prefix for clarity - autoEvictCache = true, - evictOnCommands = {"TransferMoneyCommand", "DepositMoneyCommand"}, - metrics = true -) -public class GetAccountBalanceHandler extends QueryHandler { - - @Autowired - private AccountService accountService; - - @Override - protected Mono doHandle(GetAccountBalanceQuery query) { - // ๐ŸŽฏ Business logic only - caching automatic! - return accountService.getBalance(query.getAccountId(), query.getCurrency()) - .map(balance -> AccountBalance.builder() - .accountId(query.getAccountId()) - .balance(balance) - .currency(query.getCurrency()) - .lastUpdated(Instant.now()) - .build()); - } - - // โœ… INTELLIGENT CACHING via fireflyframework-cache: - // - Cache key: "account_balance_ACC123456_USD" - // - Automatic eviction when TransferMoneyCommand affects this account - // - Cache hit/miss metrics automatically collected - // - Redis/Caffeine support via fireflyframework-cache configuration -} -``` - -### ๐ŸŽ **Using Commands & Queries in Your Controller** - -```java -@RestController -@RequestMapping("/api/banking") -public class BankingController { - - private final CommandBus commandBus; - private final QueryBus queryBus; - - @PostMapping("/transfer") - public Mono> transferMoney( - @RequestBody TransferRequest request, - @RequestHeader("Authorization") String authToken) { - - // Build rich execution context - ExecutionContext context = ExecutionContext.builder() - .userId(extractUserId(authToken)) - .tenantId(extractTenantId(authToken)) - .source("web-api") - .featureFlag("high-value-transfers", - featureFlagService.isEnabled("high-value-transfers")) - .build(); - - TransferMoneyCommand command = new TransferMoneyCommand( - request.getSourceAccountId(), - request.getTargetAccountId(), - request.getAmount(), - request.getCurrency() - ); - - return commandBus.send(command, context) - .map(ResponseEntity::ok) - .onErrorResume(this::handleError); - } - - @GetMapping("/accounts/{accountId}/balance") - public Mono> getBalance( - @PathVariable String accountId, - @RequestParam(defaultValue = "USD") String currency) { - - GetAccountBalanceQuery query = new GetAccountBalanceQuery(accountId, currency); - - return queryBus.query(query) - .map(ResponseEntity::ok); - } -} -``` - -### ๐Ÿ“Š **Production Monitoring** - -```bash -# Real-time CQRS metrics -curl http://localhost:8080/actuator/cqrs - -# Command-specific metrics -curl http://localhost:8080/actuator/cqrs/commands - -# Query cache performance -curl http://localhost:8080/actuator/cqrs/queries -``` - -**Response includes comprehensive metrics:** -```json -{ - "commands": { - "total_processed": 15420, - "success_rate": 98.7, - "avg_processing_time_ms": 85.3, - "by_type": { - "TransferMoneyCommand": { - "processed": 8950, - "failed": 12, - "avg_processing_time_ms": 95.2 - } - } - }, - "queries": { - "cache": { - "hit_rate": 87.4 - } - } -} -``` - -## ๐Ÿ“š **Complete Documentation** - -For comprehensive documentation and examples, see our **[Documentation Hub](./docs/README.md)**: - -### ๐ŸŽ† **Getting Started** -- **[๐Ÿš€ Quick Start Guide](./docs/QUICKSTART.md)** - Complete setup in 5 minutes with working examples -- **[๐Ÿ”ง Configuration Guide](./docs/CONFIGURATION.md)** - Complete configuration reference with real metrics endpoints - -### ๐Ÿ—๏ธ **Architecture & Design** -- **[๐Ÿข Architecture Overview](./docs/ARCHITECTURE.md)** - Deep dive into CQRS patterns, CommandMetricsService, and actuator endpoints -- **[๐Ÿ“ Developer Guide](./docs/DEVELOPER_GUIDE.md)** - Comprehensive development patterns with production observability - -### ๐Ÿ“ฆ **What's Included** -- **โœ… Real Implementation Examples** - Code from the actual fireflyframework-cqrs codebase -- **โœ… Production Monitoring** - Complete CommandMetricsService integration and actuator endpoints -- **โœ… Enterprise Security** - Custom authorization patterns with context-aware access control -- **โœ… Performance Optimization** - Intelligent caching strategies and reactive patterns -- **โœ… Testing Methodologies** - Unit, integration, and end-to-end testing approaches -- **โœ… Real Metrics** - Actual `firefly.cqrs.*` metrics with Prometheus integration - -### ๐Ÿ“ˆ **Monitoring & Observability** - -The framework provides **production-ready observability** out of the box: - -```bash -# Framework overview with uptime, handler counts, and health -GET /actuator/cqrs - -# Command metrics: success rates, processing times, per-type breakdown -GET /actuator/cqrs/commands - -# Query metrics: cache hit rates, processing performance -GET /actuator/cqrs/queries - -# Handler registry: registered command/query handlers -GET /actuator/cqrs/handlers - -# Component health: CommandBus, QueryBus, metrics service status -GET /actuator/cqrs/health -``` - -**Automatic Metrics Collection:** -- `firefly.cqrs.command.processed` - Success counters -- `firefly.cqrs.command.failed` - Failure counters with error classification -- `firefly.cqrs.command.processing.time` - Processing duration timers -- Per-command-type metrics with automatic tagging -- Cache hit/miss rates for queries -- Validation failure tracking by phase - ---- - -## ๐Ÿš€ **Ready to Get Started?** - -### **1๏ธโƒฃ Add the Dependency** -```xml - - org.fireflyframework - fireflyframework-cqrs - 1.0.0-SNAPSHOT - -``` - -### **2๏ธโƒฃ Enable Monitoring** -```yaml -management: - endpoints: - web: - exposure: - include: cqrs -firefly: - cqrs: - command: - metrics-enabled: true -``` - -### **3๏ธโƒฃ Create Your First Handler** -```java -@CommandHandlerComponent(metrics = true) -public class MyHandler extends CommandHandler { - @Override - protected Mono doHandle(MyCommand command) { - return Mono.just(new MyResult("Success!")); - } -} -``` - -### **4๏ธโƒฃ Check Your Metrics** -```bash -curl http://localhost:8080/actuator/cqrs + enabled: true + metrics: + enabled: true + cache: + enabled: true ``` -**That's it!** ๐ŸŽ‰ You now have enterprise-grade CQRS with zero boilerplate. - ---- - -### ๐Ÿ’ฌ **Questions? Issues? Contributions?** - -- **๐Ÿ“† Documentation**: Complete guides in [`docs/`](./docs/) -- **๐Ÿ› Issues**: Report bugs or request features -- **๐Ÿ’ฌ Discussions**: Ask questions and share use cases -- **๐Ÿค Contributing**: We welcome contributions! - -### โœจ **Contributing Guidelines** - -1. **๐ŸŽจ Follow Existing Patterns**: Use annotation-based approach consistently -2. **๐Ÿงช Write Tests**: Comprehensive test coverage for new features -3. **๐Ÿ“ Update Documentation**: Keep docs current with API changes -4. **๐Ÿ”ฅ Zero-Boilerplate Philosophy**: Maintain the "business logic only" approach -5. **๐Ÿ“ˆ Include Metrics**: Ensure new features have appropriate observability +## Documentation ---- - -## ๐Ÿ“œ **License** - -**Copyright 2024-2026 Firefly Software Solutions Inc** +Additional documentation is available in the [docs/](docs/) directory: -Licensed under the Apache License, Version 2.0 (the "License"). -You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +- [Quickstart](docs/QUICKSTART.md) +- [Architecture](docs/ARCHITECTURE.md) +- [Configuration](docs/CONFIGURATION.md) +- [Developer Guide](docs/DEVELOPER_GUIDE.md) +- [Event Sourcing Integration](docs/EVENT_SOURCING_INTEGRATION.md) -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - ---- +## Contributing -
+Contributions are welcome. Please read the [CONTRIBUTING.md](CONTRIBUTING.md) guide for details on our code of conduct, development process, and how to submit pull requests. -**๐Ÿš€ Built with โค๏ธ by Firefly Software Solutions** +## License -*Enterprise-grade CQRS โ€ข Zero boilerplate โ€ข Production ready* +Copyright 2024-2026 Firefly Software Solutions Inc. -
\ No newline at end of file +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.