Cortex is a scalable, event-driven architecture pattern inspired by brain-like processing. This guide covers implementing Cortex architecture in Java for building highly scalable, resilient distributed systems.
Cortex Architecture Overview
Core Principles:
- Event-Driven: Asynchronous message processing
- Microservices: Decoupled, specialized services
- CQRS: Command Query Responsibility Segregation
- Event Sourcing: State changes as event sequences
- Resilience: Circuit breakers, retries, fallbacks
Key Components:
- Cortex Core: Event bus and message routing
- Neurons: Specialized processing units
- Synapses: Inter-neuron communication channels
- Axons: Long-distance communication
- Dendrites: Input receivers
Dependencies and Setup
Maven Dependencies
<properties> <spring-boot.version>3.1.0</spring-boot.version> <axon.version>4.8.0</axon.version> <resilience4j.version>2.1.0</resilience4j.version> <kafka.version>3.4.0</kafka.version> </properties> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- Axon Framework (Event Sourcing & CQRS) --> <dependency> <groupId>org.axonframework</groupId> <artifactId>axon-spring-boot-starter</artifactId> <version>${axon.version}</version> </dependency> <!-- Resilience4j --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot3</artifactId> <version>${resilience4j.version}</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> <version>${resilience4j.version}</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-retry</artifactId> <version>${resilience4j.version}</version> </dependency> <!-- Messaging --> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>${kafka.version}</version> </dependency> <!-- Database --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency> <!-- Caching --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- Monitoring --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring-boot.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.axonframework</groupId> <artifactId>axon-test</artifactId> <version>${axon.version}</version> <scope>test</scope> </dependency> </dependencies> Application Configuration
# application.yml server: port: 8080 spring: application: name: cortex-system datasource: url: jdbc:postgresql://localhost:5432/cortex username: postgres password: password driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: false kafka: bootstrap-servers: localhost:9092 consumer: group-id: cortex-group auto-offset-reset: earliest producer: acks: all retries: 3 data: redis: host: localhost port: 6379 axon: axonserver: servers: localhost:8124 eventhandling: processors: cortex-processor: mode: tracking source: kafkaMessageSource resilience4j: circuitbreaker: instances: cortex-service: register-health-indicator: true sliding-window-size: 10 minimum-number-of-calls: 5 permitted-number-of-calls-in-half-open-state: 3 automatic-transition-from-open-to-half-open-enabled: true wait-duration-in-open-state: 10s failure-rate-threshold: 50 event-consumer-buffer-size: 10 retry: instances: cortex-service: max-attempts: 3 wait-duration: 2s management: endpoints: web: exposure: include: health,metrics,info,prometheus endpoint: health: show-details: always
Core Cortex Architecture
1. Event Base Classes
public abstract class CortexEvent { protected final String eventId; protected final String correlationId; protected final String neuronId; protected final LocalDateTime timestamp; protected final Map<String, Object> metadata; protected CortexEvent(String correlationId, String neuronId) { this.eventId = UUID.randomUUID().toString(); this.correlationId = correlationId; this.neuronId = neuronId; this.timestamp = LocalDateTime.now(); this.metadata = new HashMap<>(); } // Getters public String getEventId() { return eventId; } public String getCorrelationId() { return correlationId; } public String getNeuronId() { return neuronId; } public LocalDateTime getTimestamp() { return timestamp; } public Map<String, Object> getMetadata() { return Collections.unmodifiableMap(metadata); } public void addMetadata(String key, Object value) { metadata.put(key, value); } public abstract String getEventType(); } public abstract class DomainEvent extends CortexEvent { protected final String aggregateId; protected DomainEvent(String aggregateId, String correlationId, String neuronId) { super(correlationId, neuronId); this.aggregateId = aggregateId; } public String getAggregateId() { return aggregateId; } } public abstract class IntegrationEvent extends CortexEvent { protected final String sourceService; protected final String destinationService; protected IntegrationEvent(String sourceService, String destinationService, String correlationId, String neuronId) { super(correlationId, neuronId); this.sourceService = sourceService; this.destinationService = destinationService; } // Getters public String getSourceService() { return sourceService; } public String getDestinationService() { return destinationService; } } 2. Command Base Classes
public abstract class CortexCommand { protected final String commandId; protected final String correlationId; protected final String neuronId; protected final LocalDateTime timestamp; protected CortexCommand(String correlationId, String neuronId) { this.commandId = UUID.randomUUID().toString(); this.correlationId = correlationId; this.neuronId = neuronId; this.timestamp = LocalDateTime.now(); } // Getters public String getCommandId() { return commandId; } public String getCorrelationId() { return correlationId; } public String getNeuronId() { return neuronId; } public LocalDateTime getTimestamp() { return timestamp; } public abstract String getCommandType(); } public abstract class DomainCommand extends CortexCommand { protected final String aggregateId; protected DomainCommand(String aggregateId, String correlationId, String neuronId) { super(correlationId, neuronId); this.aggregateId = aggregateId; } public String getAggregateId() { return aggregateId; } } 3. Query Base Classes
public abstract class CortexQuery { protected final String queryId; protected final String correlationId; protected final String neuronId; protected final LocalDateTime timestamp; protected CortexQuery(String correlationId, String neuronId) { this.queryId = UUID.randomUUID().toString(); this.correlationId = correlationId; this.neuronId = neuronId; this.timestamp = LocalDateTime.now(); } // Getters public String getQueryId() { return queryId; } public String getCorrelationId() { return correlationId; } public String getNeuronId() { return neuronId; } public LocalDateTime getTimestamp() { return timestamp; } public abstract String getQueryType(); public abstract Class<?> getResponseType(); } public class QueryResult<T> { private final String queryId; private final String correlationId; private final T data; private final boolean success; private final String errorMessage; private QueryResult(String queryId, String correlationId, T data, boolean success, String errorMessage) { this.queryId = queryId; this.correlationId = correlationId; this.data = data; this.success = success; this.errorMessage = errorMessage; } public static <T> QueryResult<T> success(String queryId, String correlationId, T data) { return new QueryResult<>(queryId, correlationId, data, true, null); } public static <T> QueryResult<T> failure(String queryId, String correlationId, String error) { return new QueryResult<>(queryId, correlationId, null, false, error); } // Getters public String getQueryId() { return queryId; } public String getCorrelationId() { return correlationId; } public T getData() { return data; } public boolean isSuccess() { return success; } public String getErrorMessage() { return errorMessage; } } Cortex Core Infrastructure
1. Event Bus Implementation
@Component @Slf4j public class CortexEventBus { private final ApplicationEventPublisher applicationEventPublisher; private final Map<String, List<EventHandler>> eventHandlers = new ConcurrentHashMap<>(); private final EventProcessorRegistry eventProcessorRegistry; public CortexEventBus(ApplicationEventPublisher applicationEventPublisher, EventProcessorRegistry eventProcessorRegistry) { this.applicationEventPublisher = applicationEventPublisher; this.eventProcessorRegistry = eventProcessorRegistry; } public void publish(CortexEvent event) { log.debug("Publishing event: {} [ID: {}]", event.getEventType(), event.getEventId()); // Publish to local Spring application context applicationEventPublisher.publishEvent(event); // Notify registered handlers notifyHandlers(event); // Process through Axon framework if it's a domain event if (event instanceof DomainEvent) { processDomainEvent((DomainEvent) event); } } public void subscribe(String eventType, EventHandler handler) { eventHandlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) .add(handler); log.debug("Subscribed handler for event type: {}", eventType); } public void unsubscribe(String eventType, EventHandler handler) { List<EventHandler> handlers = eventHandlers.get(eventType); if (handlers != null) { handlers.remove(handler); log.debug("Unsubscribed handler for event type: {}", eventType); } } private void notifyHandlers(CortexEvent event) { String eventType = event.getEventType(); List<EventHandler> handlers = eventHandlers.get(eventType); if (handlers != null) { handlers.forEach(handler -> { try { handler.handle(event); } catch (Exception e) { log.error("Error handling event {} by handler {}", eventType, handler.getClass().getSimpleName(), e); } }); } } private void processDomainEvent(DomainEvent event) { // This would integrate with Axon EventBus in a real implementation log.debug("Processing domain event through Axon: {}", event.getEventType()); } @FunctionalInterface public interface EventHandler { void handle(CortexEvent event); } } 2. Neuron Base Class
public abstract class Neuron { protected final String neuronId; protected final String neuronType; protected final CortexEventBus eventBus; protected final NeuronHealth health; protected final Map<String, Object> state; protected Neuron(String neuronId, String neuronType, CortexEventBus eventBus) { this.neuronId = neuronId; this.neuronType = neuronType; this.eventBus = eventBus; this.health = new NeuronHealth(neuronId); this.state = new ConcurrentHashMap<>(); initialize(); } protected abstract void initialize(); protected abstract void processEvent(CortexEvent event); protected abstract void processCommand(CortexCommand command); protected void publishEvent(CortexEvent event) { eventBus.publish(event); health.recordEventPublished(); } protected void updateHealth(HealthStatus status, String message) { health.updateStatus(status, message); } protected void updateState(String key, Object value) { state.put(key, value); } protected <T> T getState(String key, Class<T> type) { return type.cast(state.get(key)); } // Getters public String getNeuronId() { return neuronId; } public String getNeuronType() { return neuronType; } public NeuronHealth getHealth() { return health; } public Map<String, Object> getState() { return Collections.unmodifiableMap(state); } public void handleEvent(CortexEvent event) { try { health.recordEventProcessed(); processEvent(event); health.recordSuccess(); } catch (Exception e) { health.recordError(e.getMessage()); log.error("Error processing event in neuron {}: {}", neuronId, e.getMessage(), e); handleError(event, e); } } public void handleCommand(CortexCommand command) { try { health.recordCommandProcessed(); processCommand(command); health.recordSuccess(); } catch (Exception e) { health.recordError(e.getMessage()); log.error("Error processing command in neuron {}: {}", neuronId, e.getMessage(), e); handleError(command, e); } } protected void handleError(Object message, Exception error) { // Default error handling - can be overridden by specific neurons ErrorEvent errorEvent = new ErrorEvent( neuronId, message.getClass().getSimpleName(), error.getMessage(), health.getCorrelationId() ); publishEvent(errorEvent); } } public class NeuronHealth { private final String neuronId; private HealthStatus status; private String message; private long eventsProcessed; private long commandsProcessed; private long eventsPublished; private long errors; private long successes; private LocalDateTime lastActivity; private final String correlationId; public NeuronHealth(String neuronId) { this.neuronId = neuronId; this.status = HealthStatus.HEALTHY; this.message = "Neuron initialized"; this.correlationId = UUID.randomUUID().toString(); this.lastActivity = LocalDateTime.now(); } public void updateStatus(HealthStatus status, String message) { this.status = status; this.message = message; this.lastActivity = LocalDateTime.now(); } public void recordEventProcessed() { eventsProcessed++; lastActivity = LocalDateTime.now(); } public void recordCommandProcessed() { commandsProcessed++; lastActivity = LocalDateTime.now(); } public void recordEventPublished() { eventsPublished++; lastActivity = LocalDateTime.now(); } public void recordError(String errorMessage) { errors++; updateStatus(HealthStatus.UNHEALTHY, errorMessage); } public void recordSuccess() { successes++; if (status == HealthStatus.UNHEALTHY) { updateStatus(HealthStatus.HEALTHY, "Recovered from previous errors"); } } // Getters public String getNeuronId() { return neuronId; } public HealthStatus getStatus() { return status; } public String getMessage() { return message; } public long getEventsProcessed() { return eventsProcessed; } public long getCommandsProcessed() { return commandsProcessed; } public long getEventsPublished() { return eventsPublished; } public long getErrors() { return errors; } public long getSuccesses() { return successes; } public LocalDateTime getLastActivity() { return lastActivity; } public String getCorrelationId() { return correlationId; } public double getErrorRate() { long totalOperations = eventsProcessed + commandsProcessed; return totalOperations > 0 ? (double) errors / totalOperations : 0.0; } public boolean isHealthy() { return status == HealthStatus.HEALTHY; } } public enum HealthStatus { HEALTHY, DEGRADED, UNHEALTHY, OFFLINE } 3. Synapse Communication
@Component @Slf4j public class SynapseRouter { private final Map<String, List<Neuron>> routingTable = new ConcurrentHashMap<>(); private final CortexEventBus eventBus; private final CircuitBreakerRegistry circuitBreakerRegistry; private final RetryRegistry retryRegistry; public SynapseRouter(CortexEventBus eventBus, CircuitBreakerRegistry circuitBreakerRegistry, RetryRegistry retryRegistry) { this.eventBus = eventBus; this.circuitBreakerRegistry = circuitBreakerRegistry; this.retryRegistry = retryRegistry; setupEventForwarding(); } public void registerNeuron(String eventType, Neuron neuron) { routingTable.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) .add(neuron); log.info("Registered neuron {} for event type: {}", neuron.getNeuronId(), eventType); } public void unregisterNeuron(String eventType, Neuron neuron) { List<Neuron> neurons = routingTable.get(eventType); if (neurons != null) { neurons.remove(neuron); log.info("Unregistered neuron {} from event type: {}", neuron.getNeuronId(), eventType); } } public void routeEvent(CortexEvent event) { String eventType = event.getEventType(); List<Neuron> targetNeurons = routingTable.get(eventType); if (targetNeurons == null || targetNeurons.isEmpty()) { log.warn("No neurons registered for event type: {}", eventType); return; } targetNeurons.forEach(neuron -> forwardEventToNeuron(event, neuron)); } public void routeCommand(CortexCommand command, String targetNeuronId) { // Direct command routing to specific neuron // Implementation would find neuron by ID and forward command log.debug("Routing command to neuron: {}", targetNeuronId); } private void forwardEventToNeuron(CortexEvent event, Neuron neuron) { CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("cortex-service"); Retry retry = retryRegistry.retry("cortex-service"); Supplier<Void> eventForwarding = () -> { if (neuron.getHealth().isHealthy()) { neuron.handleEvent(event); } else { log.warn("Skipping unhealthy neuron: {}", neuron.getNeuronId()); // Could implement fallback strategies here } return null; }; // Apply resilience patterns Supplier<Void> decoratedSupplier = CircuitBreaker.decorateSupplier(circuitBreaker, eventForwarding); decoratedSupplier = Retry.decorateSupplier(retry, decoratedSupplier); try { decoratedSupplier.get(); } catch (Exception e) { log.error("Failed to forward event to neuron {}: {}", neuron.getNeuronId(), e.getMessage()); handleRoutingFailure(event, neuron, e); } } private void handleRoutingFailure(CortexEvent event, Neuron neuron, Exception error) { RoutingFailureEvent failureEvent = new RoutingFailureEvent( event.getEventId(), neuron.getNeuronId(), error.getMessage(), event.getCorrelationId() ); eventBus.publish(failureEvent); } private void setupEventForwarding() { // Subscribe to all events and route them appropriately eventBus.subscribe(".*", this::routeEvent); } public Map<String, List<String>> getRoutingTable() { return routingTable.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> entry.getValue().stream() .map(Neuron::getNeuronId) .collect(Collectors.toList()) )); } } 4. Axon Integration for Event Sourcing
@Configuration @EnableAxon public class AxonConfiguration { @Bean public EventStorageEngine eventStorageEngine(DataSource dataSource) { return JpaEventStorageEngine.builder() .dataSource(dataSource) .build(); } @Bean public TokenStore tokenStore(DataSource dataSource) { return JpaTokenStore.builder() .dataSource(dataSource) .serializer(JacksonSerializer.defaultSerializer()) .build(); } @Bean public SagaStore sagaStore(DataSource dataSource) { return JpaSagaStore.builder() .dataSource(dataSource) .build(); } } @Component @Slf4j public class AxonEventProcessor { private final EventBus eventBus; private final EventStore eventStore; private final CommandBus commandBus; private final QueryBus queryBus; public AxonEventProcessor(EventBus eventBus, EventStore eventStore, CommandBus commandBus, QueryBus queryBus) { this.eventBus = eventBus; this.eventStore = eventStore; this.commandBus = commandBus; this.queryBus = queryBus; } public void processDomainEvent(DomainEvent event) { try { // Convert to Axon event and publish org.axonframework.eventhandling.GenericDomainEventMessage.asEventMessage(event); eventBus.publish(org.axonframework.eventhandling.GenericDomainEventMessage.asEventMessage(event)); log.debug("Processed domain event through Axon: {}", event.getEventType()); } catch (Exception e) { log.error("Failed to process domain event through Axon: {}", e.getMessage(), e); throw new CortexRuntimeException("Axon event processing failed", e); } } public <R> CompletableFuture<R> sendCommand(Object command) { return commandBus.send(command); } public <R> CompletableFuture<R> sendQuery(Object query) { return queryBus.query(query, ResponseTypes.instanceOf((Class<R>) query.getClass())); } } Example: E-commerce Cortex Implementation
1. Domain Events
// Product Events public class ProductCreatedEvent extends DomainEvent { private final String productId; private final String name; private final String description; private final BigDecimal price; private final int initialStock; public ProductCreatedEvent(String productId, String name, String description, BigDecimal price, int initialStock, String correlationId) { super(productId, correlationId, "product-neuron"); this.productId = productId; this.name = name; this.description = description; this.price = price; this.initialStock = initialStock; } @Override public String getEventType() { return "PRODUCT_CREATED"; } // Getters public String getProductId() { return productId; } public String getName() { return name; } public String getDescription() { return description; } public BigDecimal getPrice() { return price; } public int getInitialStock() { return initialStock; } } public class ProductStockUpdatedEvent extends DomainEvent { private final String productId; private final int newStock; private final int oldStock; private final String reason; public ProductStockUpdatedEvent(String productId, int newStock, int oldStock, String reason, String correlationId) { super(productId, correlationId, "inventory-neuron"); this.productId = productId; this.newStock = newStock; this.oldStock = oldStock; this.reason = reason; } @Override public String getEventType() { return "PRODUCT_STOCK_UPDATED"; } // Getters public String getProductId() { return productId; } public int getNewStock() { return newStock; } public int getOldStock() { return oldStock; } public String getReason() { return reason; } } // Order Events public class OrderCreatedEvent extends DomainEvent { private final String orderId; private final String customerId; private final List<OrderItem> items; private final BigDecimal totalAmount; private final OrderStatus status; public OrderCreatedEvent(String orderId, String customerId, List<OrderItem> items, BigDecimal totalAmount, String correlationId) { super(orderId, correlationId, "order-neuron"); this.orderId = orderId; this.customerId = customerId; this.items = items; this.totalAmount = totalAmount; this.status = OrderStatus.CREATED; } @Override public String getEventType() { return "ORDER_CREATED"; } // Getters public String getOrderId() { return orderId; } public String getCustomerId() { return customerId; } public List<OrderItem> getItems() { return items; } public BigDecimal getTotalAmount() { return totalAmount; } public OrderStatus getStatus() { return status; } } public class OrderStatusUpdatedEvent extends DomainEvent { private final String orderId; private final OrderStatus oldStatus; private final OrderStatus newStatus; private final String reason; public OrderStatusUpdatedEvent(String orderId, OrderStatus oldStatus, OrderStatus newStatus, String reason, String correlationId) { super(orderId, correlationId, "order-neuron"); this.orderId = orderId; this.oldStatus = oldStatus; this.newStatus = newStatus; this.reason = reason; } @Override public String getEventType() { return "ORDER_STATUS_UPDATED"; } // Getters public String getOrderId() { return orderId; } public OrderStatus getOldStatus() { return oldStatus; } public OrderStatus getNewStatus() { return newStatus; } public String getReason() { return reason; } } 2. Neurons Implementation
@Component @Slf4j public class ProductNeuron extends Neuron { private final ProductRepository productRepository; private final AxonEventProcessor axonProcessor; public ProductNeuron(CortexEventBus eventBus, ProductRepository productRepository, AxonEventProcessor axonProcessor) { super("product-neuron", "PRODUCT_MANAGEMENT", eventBus); this.productRepository = productRepository; this.axonProcessor = axonProcessor; } @Override protected void initialize() { // Subscribe to relevant events eventBus.subscribe("PRODUCT_CREATED", this::handleEvent); eventBus.subscribe("PRODUCT_UPDATED", this::handleEvent); log.info("Product neuron initialized: {}", neuronId); updateHealth(HealthStatus.HEALTHY, "Product neuron ready"); } @Override protected void processEvent(CortexEvent event) { if (event instanceof ProductCreatedEvent) { handleProductCreated((ProductCreatedEvent) event); } else if (event instanceof ProductStockUpdatedEvent) { handleStockUpdated((ProductStockUpdatedEvent) event); } } @Override protected void processCommand(CortexCommand command) { // Handle product-related commands log.debug("Processing command in product neuron: {}", command.getCommandType()); } private void handleProductCreated(ProductCreatedEvent event) { try { // Create product in read model Product product = new Product( event.getProductId(), event.getName(), event.getDescription(), event.getPrice(), event.getInitialStock() ); productRepository.save(product); updateState("productsCreated", getState("productsCreated", Integer.class) + 1); log.info("Product created: {} - {}", event.getProductId(), event.getName()); } catch (Exception e) { log.error("Failed to handle product creation: {}", e.getMessage(), e); updateHealth(HealthStatus.DEGRADED, "Product creation handling failed"); throw new CortexRuntimeException("Product creation failed", e); } } private void handleStockUpdated(ProductStockUpdatedEvent event) { try { // Update product stock in read model Product product = productRepository.findById(event.getProductId()) .orElseThrow(() -> new EntityNotFoundException("Product not found: " + event.getProductId())); product.setStockQuantity(event.getNewStock()); productRepository.save(product); updateState("stockUpdates", getState("stockUpdates", Integer.class) + 1); log.debug("Product stock updated: {} -> {}", event.getProductId(), event.getNewStock()); } catch (Exception e) { log.error("Failed to handle stock update: {}", e.getMessage(), e); throw new CortexRuntimeException("Stock update failed", e); } } } @Component @Slf4j public class OrderNeuron extends Neuron { private final OrderRepository orderRepository; private final ProductRepository productRepository; private final SynapseRouter synapseRouter; public OrderNeuron(CortexEventBus eventBus, OrderRepository orderRepository, ProductRepository productRepository, SynapseRouter synapseRouter) { super("order-neuron", "ORDER_MANAGEMENT", eventBus); this.orderRepository = orderRepository; this.productRepository = productRepository; this.synapseRouter = synapseRouter; } @Override protected void initialize() { // Subscribe to order-related events eventBus.subscribe("ORDER_CREATED", this::handleEvent); eventBus.subscribe("ORDER_STATUS_UPDATED", this::handleEvent); log.info("Order neuron initialized: {}", neuronId); updateHealth(HealthStatus.HEALTHY, "Order neuron ready"); } @Override protected void processEvent(CortexEvent event) { if (event instanceof OrderCreatedEvent) { handleOrderCreated((OrderCreatedEvent) event); } else if (event instanceof OrderStatusUpdatedEvent) { handleOrderStatusUpdated((OrderStatusUpdatedEvent) event); } } @Override protected void processCommand(CortexCommand command) { // Handle order-related commands log.debug("Processing command in order neuron: {}", command.getCommandType()); } private void handleOrderCreated(OrderCreatedEvent event) { try { // Validate product availability for (OrderItem item : event.getItems()) { Product product = productRepository.findById(item.getProductId()) .orElseThrow(() -> new EntityNotFoundException("Product not found: " + item.getProductId())); if (product.getStockQuantity() < item.getQuantity()) { throw new InsufficientStockException( "Insufficient stock for product: " + item.getProductId()); } } // Create order in read model Order order = new Order( event.getOrderId(), event.getCustomerId(), event.getItems(), event.getTotalAmount(), event.getStatus() ); orderRepository.save(order); updateState("ordersCreated", getState("ordersCreated", Integer.class) + 1); // Publish event for inventory update OrderValidatedEvent validatedEvent = new OrderValidatedEvent( event.getOrderId(), event.getCustomerId(), event.getItems(), event.getCorrelationId() ); publishEvent(validatedEvent); log.info("Order created and validated: {}", event.getOrderId()); } catch (InsufficientStockException e) { // Handle insufficient stock OrderRejectedEvent rejectedEvent = new OrderRejectedEvent( event.getOrderId(), "INSUFFICIENT_STOCK", e.getMessage(), event.getCorrelationId() ); publishEvent(rejectedEvent); log.warn("Order rejected due to insufficient stock: {}", event.getOrderId()); } catch (Exception e) { log.error("Failed to handle order creation: {}", e.getMessage(), e); updateHealth(HealthStatus.DEGRADED, "Order creation handling failed"); throw new CortexRuntimeException("Order creation failed", e); } } private void handleOrderStatusUpdated(OrderStatusUpdatedEvent event) { try { Order order = orderRepository.findById(event.getOrderId()) .orElseThrow(() -> new EntityNotFoundException("Order not found: " + event.getOrderId())); order.setStatus(event.getNewStatus()); orderRepository.save(order); updateState("statusUpdates", getState("statusUpdates", Integer.class) + 1); log.debug("Order status updated: {} -> {}", event.getOrderId(), event.getNewStatus()); } catch (Exception e) { log.error("Failed to handle order status update: {}", e.getMessage(), e); throw new CortexRuntimeException("Order status update failed", e); } } } @Component @Slf4j public class InventoryNeuron extends Neuron { private final ProductRepository productRepository; public InventoryNeuron(CortexEventBus eventBus, ProductRepository productRepository) { super("inventory-neuron", "INVENTORY_MANAGEMENT", eventBus); this.productRepository = productRepository; } @Override protected void initialize() { // Subscribe to inventory-related events eventBus.subscribe("ORDER_VALIDATED", this::handleEvent); eventBus.subscribe("PRODUCT_STOCK_UPDATED", this::handleEvent); log.info("Inventory neuron initialized: {}", neuronId); updateHealth(HealthStatus.HEALTHY, "Inventory neuron ready"); } @Override protected void processEvent(CortexEvent event) { if (event instanceof OrderValidatedEvent) { handleOrderValidated((OrderValidatedEvent) event); } } @Override protected void processCommand(CortexCommand command) { // Handle inventory-related commands log.debug("Processing command in inventory neuron: {}", command.getCommandType()); } private void handleOrderValidated(OrderValidatedEvent event) { try { // Update inventory for each product in the order for (OrderItem item : event.getItems()) { Product product = productRepository.findById(item.getProductId()) .orElseThrow(() -> new EntityNotFoundException("Product not found: " + item.getProductId())); int oldStock = product.getStockQuantity(); int newStock = oldStock - item.getQuantity(); if (newStock < 0) { throw new IllegalStateException("Stock would go negative for product: " + item.getProductId()); } product.setStockQuantity(newStock); productRepository.save(product); // Publish stock update event ProductStockUpdatedEvent stockEvent = new ProductStockUpdatedEvent( item.getProductId(), newStock, oldStock, "ORDER_FULFILLMENT", event.getCorrelationId() ); publishEvent(stockEvent); } // Publish order fulfilled event OrderFulfilledEvent fulfilledEvent = new OrderFulfilledEvent( event.getOrderId(), event.getCorrelationId() ); publishEvent(fulfilledEvent); updateState("inventoryUpdates", getState("inventoryUpdates", Integer.class) + 1); log.info("Inventory updated for order: {}", event.getOrderId()); } catch (Exception e) { log.error("Failed to handle order validation: {}", e.getMessage(), e); updateHealth(HealthStatus.DEGRADED, "Inventory update failed"); throw new CortexRuntimeException("Inventory update failed", e); } } } 3. Repository Interfaces
@Repository public interface ProductRepository extends JpaRepository<Product, String> { @Query("SELECT p FROM Product p WHERE p.stockQuantity < :threshold") List<Product> findLowStockProducts(@Param("threshold") int threshold); @Modifying @Query("UPDATE Product p SET p.stockQuantity = p.stockQuantity - :quantity WHERE p.id = :productId AND p.stockQuantity >= :quantity") int decrementStock(@Param("productId") String productId, @Param("quantity") int quantity); } @Repository public interface OrderRepository extends JpaRepository<Order, String> { List<Order> findByCustomerId(String customerId); List<Order> findByStatus(OrderStatus status); @Query("SELECT o FROM Order o WHERE o.createdAt BETWEEN :startDate AND :endDate") List<Order> findOrdersInPeriod(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); } Cortex Management API
1. REST Controllers
@RestController @RequestMapping("/api/cortex") @Slf4j public class CortexManagementController { private final CortexEventBus eventBus; private final SynapseRouter synapseRouter; private final List<Neuron> neurons; private final AxonEventProcessor axonProcessor; public CortexManagementController(CortexEventBus eventBus, SynapseRouter synapseRouter, List<Neuron> neurons, AxonEventProcessor axonProcessor) { this.eventBus = eventBus; this.synapseRouter = synapseRouter; this.neurons = neurons; this.axonProcessor = axonProcessor; } @GetMapping("/health") public ResponseEntity<Map<String, Object>> getSystemHealth() { Map<String, Object> health = new HashMap<>(); // Neuron health Map<String, Object> neuronHealth = neurons.stream() .collect(Collectors.toMap( Neuron::getNeuronId, neuron -> Map.of( "status", neuron.getHealth().getStatus(), "message", neuron.getHealth().getMessage(), "errorRate", neuron.getHealth().getErrorRate(), "lastActivity", neuron.getHealth().getLastActivity() ) )); health.put("neurons", neuronHealth); health.put("systemStatus", calculateSystemStatus(neuronHealth)); health.put("timestamp", LocalDateTime.now()); return ResponseEntity.ok(health); } @GetMapping("/routing") public ResponseEntity<Map<String, List<String>>> getRoutingTable() { return ResponseEntity.ok(synapseRouter.getRoutingTable()); } @PostMapping("/events/publish") public ResponseEntity<Map<String, Object>> publishEvent(@RequestBody Map<String, Object> eventData) { try { String eventType = (String) eventData.get("type"); String correlationId = UUID.randomUUID().toString(); CortexEvent event = createEventFromData(eventType, eventData, correlationId); eventBus.publish(event); return ResponseEntity.ok(Map.of( "status", "success", "eventId", event.getEventId(), "correlationId", correlationId )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(Map.of( "status", "error", "message", e.getMessage() )); } } @GetMapping("/neurons/{neuronId}/state") public ResponseEntity<Map<String, Object>> getNeuronState(@PathVariable String neuronId) { Optional<Neuron> neuron = neurons.stream() .filter(n -> n.getNeuronId().equals(neuronId)) .findFirst(); if (neuron.isEmpty()) { return ResponseEntity.notFound().build(); } Map<String, Object> state = new HashMap<>(); state.put("neuronId", neuronId); state.put("state", neuron.get().getState()); state.put("health", Map.of( "status", neuron.get().getHealth().getStatus(), "eventsProcessed", neuron.get().getHealth().getEventsProcessed(), "errorRate", neuron.get().getHealth().getErrorRate() )); return ResponseEntity.ok(state); } @PostMapping("/neurons/{neuronId}/reset") public ResponseEntity<Map<String, Object>> resetNeuron(@PathVariable String neuronId) { Optional<Neuron> neuron = neurons.stream() .filter(n -> n.getNeuronId().equals(neuronId)) .findFirst(); if (neuron.isEmpty()) { return ResponseEntity.notFound().build(); } // In a real implementation, this would reset the neuron's state neuron.get().getHealth().updateStatus(HealthStatus.HEALTHY, "Manually reset"); return ResponseEntity.ok(Map.of( "status", "success", "message", "Neuron reset successfully" )); } private CortexEvent createEventFromData(String eventType, Map<String, Object> data, String correlationId) { // This would create appropriate event based on type // Simplified implementation return new CortexEvent(correlationId, "api-neuron") { @Override public String getEventType() { return eventType; } }; } private String calculateSystemStatus(Map<String, Object> neuronHealth) { long unhealthyNeurons = neuronHealth.values().stream() .filter(health -> health instanceof Map) .map(health -> (Map<?, ?>) health) .filter(health -> health.get("status") != HealthStatus.HEALTHY) .count(); if (unhealthyNeurons == 0) { return "HEALTHY"; } else if (unhealthyNeurons < neuronHealth.size() / 2) { return "DEGRADED"; } else { return "UNHEALTHY"; } } } @RestController @RequestMapping("/api/products") @Slf4j public class ProductController { private final ProductRepository productRepository; private final CortexEventBus eventBus; public ProductController(ProductRepository productRepository, CortexEventBus eventBus) { this.productRepository = productRepository; this.eventBus = eventBus; } @PostMapping public ResponseEntity<Map<String, Object>> createProduct(@RequestBody CreateProductRequest request) { try { String productId = UUID.randomUUID().toString(); String correlationId = UUID.randomUUID().toString(); ProductCreatedEvent event = new ProductCreatedEvent( productId, request.getName(), request.getDescription(), request.getPrice(), request.getInitialStock(), correlationId ); eventBus.publish(event); return ResponseEntity.accepted().body(Map.of( "status", "accepted", "productId", productId, "correlationId", correlationId )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of( "status", "error", "message", e.getMessage() )); } } @GetMapping("/{productId}") public ResponseEntity<Product> getProduct(@PathVariable String productId) { return productRepository.findById(productId) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @GetMapping public ResponseEntity<List<Product>> getProducts() { return ResponseEntity.ok(productRepository.findAll()); } } @RestController @RequestMapping("/api/orders") @Slf4j public class OrderController { private final OrderRepository orderRepository; private final CortexEventBus eventBus; public OrderController(OrderRepository orderRepository, CortexEventBus eventBus) { this.orderRepository = orderRepository; this.eventBus = eventBus; } @PostMapping public ResponseEntity<Map<String, Object>> createOrder(@RequestBody CreateOrderRequest request) { try { String orderId = UUID.randomUUID().toString(); String correlationId = UUID.randomUUID().toString(); // Calculate total amount BigDecimal totalAmount = request.getItems().stream() .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))) .reduce(BigDecimal.ZERO, BigDecimal::add); OrderCreatedEvent event = new OrderCreatedEvent( orderId, request.getCustomerId(), request.getItems(), totalAmount, correlationId ); eventBus.publish(event); return ResponseEntity.accepted().body(Map.of( "status", "accepted", "orderId", orderId, "correlationId", correlationId, "totalAmount", totalAmount )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of( "status", "error", "message", e.getMessage() )); } } @GetMapping("/{orderId}") public ResponseEntity<Order> getOrder(@PathVariable String orderId) { return orderRepository.findById(orderId) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @GetMapping("/customer/{customerId}") public ResponseEntity<List<Order>> getCustomerOrders(@PathVariable String customerId) { return ResponseEntity.ok(orderRepository.findByCustomerId(customerId)); } } 2. Monitoring Endpoints
@RestController @RequestMapping("/api/monitoring") @Slf4j public class MonitoringController { private final MeterRegistry meterRegistry; private final List<Neuron> neurons; public MonitoringController(MeterRegistry meterRegistry, List<Neuron> neurons) { this.meterRegistry = meterRegistry; this.neurons = neurons; } @GetMapping("/metrics") public ResponseEntity<Map<String, Object>> getSystemMetrics() { Map<String, Object> metrics = new HashMap<>(); // Neuron metrics Map<String, Object> neuronMetrics = neurons.stream() .collect(Collectors.toMap( Neuron::getNeuronId, neuron -> { NeuronHealth health = neuron.getHealth(); return Map.of( "eventsProcessed", health.getEventsProcessed(), "commandsProcessed", health.getCommandsProcessed(), "eventsPublished", health.getEventsPublished(), "errors", health.getErrors(), "successes", health.getSuccesses(), "errorRate", health.getErrorRate(), "uptime", health.getUptime().toMinutes() ); } )); metrics.put("neurons", neuronMetrics); // System metrics from Micrometer metrics.put("jvm", getJvmMetrics()); metrics.put("system", getSystemMetrics()); return ResponseEntity.ok(metrics); } @GetMapping("/prometheus") public String getPrometheusMetrics() { // Expose metrics in Prometheus format StringBuilder prometheus = new StringBuilder(); for (Neuron neuron : neurons) { NeuronHealth health = neuron.getHealth(); String neuronName = neuron.getNeuronId().replace("-", "_"); prometheus.append(String.format("cortex_neuron_events_processed{neuron=\"%s\"} %d\n", neuron.getNeuronId(), health.getEventsProcessed())); prometheus.append(String.format("cortex_neuron_errors_total{neuron=\"%s\"} %d\n", neuron.getNeuronId(), health.getErrors())); prometheus.append(String.format("cortex_neuron_error_rate{neuron=\"%s\"} %.4f\n", neuron.getNeuronId(), health.getErrorRate())); } return prometheus.toString(); } private Map<String, Object> getJvmMetrics() { return Map.of( "memory", Map.of( "used", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(), "max", Runtime.getRuntime().maxMemory(), "total", Runtime.getRuntime().totalMemory() ), "threads", Thread.activeCount() ); } private Map<String, Object> getSystemMetrics() { return Map.of( "timestamp", LocalDateTime.now(), "uptime", ManagementFactory.getRuntimeMXBean().getUptime() ); } } Resilience Configuration
@Configuration @Slf4j public class ResilienceConfiguration { @Bean public CircuitBreakerRegistry circuitBreakerRegistry() { return CircuitBreakerRegistry.ofDefaults(); } @Bean public RetryRegistry retryRegistry() { return RetryRegistry.ofDefaults(); } @Bean public BulkheadRegistry bulkheadRegistry() { return BulkheadRegistry.ofDefaults(); } @Bean @EventListener public void onCircuitBreakerEvent(CircuitBreakerOnStateTransitionEvent event) { log.warn("Circuit Breaker {} transitioned to {}", event.getCircuitBreakerName(), event.getStateTransition()); } @Bean @EventListener public void onRetryEvent(RetryOnRetryEvent event) { log.info("Retry attempt {} for {}", event.getNumberOfRetryAttempts(), event.getName()); } } @Component @Slf4j public class CortexResilienceService { private final CircuitBreaker circuitBreaker; private final Retry retry; private final Bulkhead bulkhead; public CortexResilienceService(CircuitBreakerRegistry circuitBreakerRegistry, RetryRegistry retryRegistry, BulkheadRegistry bulkheadRegistry) { this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("cortex-service"); this.retry = retryRegistry.retry("cortex-service"); this.bulkhead = bulkheadRegistry.bulkhead("cortex-service"); } public <T> T executeResilient(Supplier<T> supplier, Function<Throwable, T> fallback) { Supplier<T> decoratedSupplier = Decorators.ofSupplier(supplier) .withCircuitBreaker(circuitBreaker) .withRetry(retry) .withBulkhead(bulkhead) .withFallback(fallback) .decorate(); return Try.ofSupplier(decoratedSupplier) .onSuccess(result -> log.debug("Resilient execution succeeded")) .onFailure(throwable -> log.error("Resilient execution failed", throwable)) .get(); } public void executeResilient(Runnable runnable, Runnable fallback) { CheckedRunnable decoratedRunnable = Decorators.ofCheckedRunnable(runnable::run) .withCircuitBreaker(circuitBreaker) .withRetry(retry) .withBulkhead(bulkhead) .withFallback(throwable -> { log.warn("Executing fallback due to: {}", throwable.getMessage()); fallback.run(); }) .decorate(); Try.run(decoratedRunnable) .onSuccess(result -> log.debug("Resilient execution succeeded")) .onFailure(throwable -> log.error("Resilient execution failed", throwable)); } } Testing
1. Unit Tests
@ExtendWith(MockitoExtension.class) class ProductNeuronTest { @Mock private CortexEventBus eventBus; @Mock private ProductRepository productRepository; @Mock private AxonEventProcessor axonProcessor; private ProductNeuron productNeuron; @BeforeEach void setUp() { productNeuron = new ProductNeuron(eventBus, productRepository, axonProcessor); } @Test void testHandleProductCreated() { // Given ProductCreatedEvent event = new ProductCreatedEvent( "prod-123", "Test Product", "Description", BigDecimal.valueOf(99.99), 100, "corr-123" ); // When productNeuron.handleEvent(event); // Then verify(productRepository).save(any(Product.class)); verify(eventBus).publish(any(CortexEvent.class)); } } @SpringBootTest class CortexEventBusTest { @Autowired private CortexEventBus eventBus; @Test void testEventPublishing() { // Given AtomicInteger handlerCallCount = new AtomicInteger(0); CortexEventBus.EventHandler handler = event -> handlerCallCount.incrementAndGet(); eventBus.subscribe("TEST_EVENT", handler); // When CortexEvent event = new CortexEvent("corr-123", "test-neuron") { @Override public String getEventType() { return "TEST_EVENT"; } }; eventBus.publish(event); // Then assertThat(handlerCallCount.get()).isEqualTo(1); } } 2. Integration Test
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(properties = { "spring.datasource.url=jdbc:h2:mem:testdb", "spring.jpa.hibernate.ddl-auto=create-drop" }) class CortexSystemIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Autowired private ProductRepository productRepository; @Test void testProductCreationFlow() { // Given Map<String, Object> productRequest = Map.of( "name", "Test Product", "description", "Test Description", "price", 99.99, "initialStock", 100 ); // When ResponseEntity<Map> response = restTemplate.postForEntity( "/api/products", productRequest, Map.class); // Then assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); assertThat(response.getBody()).containsKeys("productId", "correlationId"); // Verify product was created in read model String productId = (String) response.getBody().get("productId"); await().atMost(5, TimeUnit.SECONDS).until(() -> productRepository.findById(productId).isPresent()); Product product = productRepository.findById(productId).orElseThrow(); assertThat(product.getName()).isEqualTo("Test Product"); assertThat(product.getStockQuantity()).isEqualTo(100); } } Deployment Configuration
# docker-compose.yml version: '3.8' services: cortex-app: build: . ports: - "8080:8080" environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/cortex - SPRING_KAFKA_BOOTSTRAP_SERVERS=kafka:9092 - SPRING_REDIS_HOST=redis depends_on: - postgres - kafka - redis postgres: image: postgres:15 environment: - POSTGRES_DB=cortex - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password ports: - "5432:5432" kafka: image: confluentinc/cp-kafka:7.3.0 environment: - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 ports: - "9092:9092" depends_on: - zookeeper zookeeper: image: confluentinc/cp-zookeeper:7.3.0 environment: - ZOOKEEPER_CLIENT_PORT=2181 redis: image: redis:7-alpine ports: - "6379:6379" axon-server: image: axoniq/axonserver:latest ports: - "8024:8024" - "8124:8124" environment: - AXONIQ_AXONSERVER_DEVMODE_ENABLED=true prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml grafana: image: grafana/grafana:latest ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin
Best Practices
- Event Design: Keep events small, focused, and immutable
- Error Handling: Implement comprehensive error handling and dead letter queues
- Monitoring: Extensive monitoring of all neurons and system components
- Testing: Comprehensive testing including unit, integration, and resilience tests
- Documentation: Document event schemas and neuron responsibilities
- Versioning: Implement event versioning for schema evolution
// Example of event versioning public class ProductCreatedEventV2 extends DomainEvent { private final String productId; private final String name; private final String description; private final BigDecimal price; private final int initialStock; private final String category; // New field in V2 public ProductCreatedEventV2(String productId, String name, String description, BigDecimal price, int initialStock, String category, String correlationId) { super(productId, correlationId, "product-neuron"); this.productId = productId; this.name = name; this.description = description; this.price = price; this.initialStock = initialStock; this.category = category; } @Override public String getEventType() { return "PRODUCT_CREATED_V2"; } // Getters including new category field } Conclusion
The Cortex architecture provides:
- High Scalability through event-driven, asynchronous processing
- Resilience with circuit breakers, retries, and fallbacks
- Maintainability through clear separation of concerns
- Observability with comprehensive monitoring and metrics
- Flexibility through modular neuron design
- Event Sourcing for complete audit trails and temporal queries
This implementation demonstrates how to build a scalable, resilient system using Cortex architecture principles in Java, suitable for complex enterprise applications requiring high availability and performance.
Java Observability, Logging Intelligence & AI-Driven Monitoring (APM, Tracing, Logs & Anomaly Detection)
https://macronepal.com/blog/beyond-metrics-observing-serverless-and-traditional-java-applications-with-thundra-apm/
Explains using Thundra APM to observe both serverless and traditional Java applications by combining tracing, metrics, and logs into a unified observability platform for faster debugging and performance insights.
https://macronepal.com/blog/dynatrace-oneagent-in-java-2/
Explains Dynatrace OneAgent for Java, which automatically instruments JVM applications to capture metrics, traces, and logs, enabling full-stack monitoring and root-cause analysis with minimal configuration.
https://macronepal.com/blog/lightstep-java-sdk-distributed-tracing-and-observability-implementation/
Explains Lightstep Java SDK for distributed tracing, helping developers track requests across microservices and identify latency issues using OpenTelemetry-based observability.
https://macronepal.com/blog/honeycomb-io-beeline-for-java-complete-guide-2/
Explains Honeycomb Beeline for Java, which provides high-cardinality observability and deep query capabilities to understand complex system behavior and debug distributed systems efficiently.
https://macronepal.com/blog/lumigo-for-serverless-in-java-complete-distributed-tracing-guide-2/
Explains Lumigo for Java serverless applications, offering automatic distributed tracing, log correlation, and error tracking to simplify debugging in cloud-native environments. (Lumigo Docs)
https://macronepal.com/blog/from-noise-to-signals-implementing-log-anomaly-detection-in-java-applications/
Explains how to detect anomalies in Java logs using behavioral patterns and machine learning techniques to separate meaningful incidents from noisy log data and improve incident response.
https://macronepal.com/blog/ai-powered-log-analysis-in-java-from-reactive-debugging-to-proactive-insights/
Explains AI-driven log analysis for Java applications, shifting from manual debugging to predictive insights that identify issues early and improve system reliability using intelligent log processing.
https://macronepal.com/blog/titliel-java-logging-best-practices/
Explains best practices for Java logging, focusing on structured logs, proper log levels, performance optimization, and ensuring logs are useful for debugging and observability systems.
https://macronepal.com/blog/seeking-a-loguru-for-java-the-quest-for-elegant-and-simple-logging/
Explains the search for simpler, more elegant logging frameworks in Java, comparing modern logging approaches that aim to reduce complexity while improving readability and developer experience.