Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
<description>A comprehensive Spring Boot library that provides Event Sourcing pattern implementation with reactive programming support, featuring event stores, aggregates, snapshots, and integration with EDA messaging.</description>

<dependencies>
<!-- Firefly Kernel (exception hierarchy, shared abstractions) -->
<dependency>
<groupId>org.fireflyframework</groupId>
<artifactId>fireflyframework-kernel</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Spring Boot WebFlux for reactive programming -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.fireflyframework.eventsourcing.aggregate;

import org.fireflyframework.kernel.exception.FireflyException;

/**
* Exception thrown when there's an issue with event handler methods in aggregates.
* <p>
Expand All @@ -25,7 +27,7 @@
* - Event handler method has invalid signature
* - Reflection issues when accessing event handlers
*/
public class EventHandlerException extends RuntimeException {
public class EventHandlerException extends FireflyException {

public EventHandlerException(String message) {
super(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,29 @@
package org.fireflyframework.eventsourcing.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.MeterRegistry;
import org.fireflyframework.eda.publisher.EventPublisherFactory;
import org.fireflyframework.eventsourcing.monitoring.EventStoreMetrics;
import org.fireflyframework.eventsourcing.outbox.EventOutboxProcessor;
import org.fireflyframework.eventsourcing.outbox.EventOutboxRepository;
import org.fireflyframework.eventsourcing.outbox.EventOutboxService;
import org.fireflyframework.eventsourcing.publisher.EventSourcingPublisher;
import org.fireflyframework.eventsourcing.transaction.EventSourcingTransactionalAspect;
import org.fireflyframework.eventsourcing.upcasting.EventUpcaster;
import org.fireflyframework.eventsourcing.upcasting.EventUpcastingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.ReactiveTransactionManager;

import java.util.List;

/**
* Auto-configuration for the Event Sourcing library.
Expand Down Expand Up @@ -59,23 +68,8 @@
@AutoConfiguration
@ConditionalOnProperty(prefix = "firefly.eventsourcing", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(EventSourcingProperties.class)
@ComponentScan(
basePackages = "org.fireflyframework.eventsourcing",
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "com\\.firefly\\.common\\.eventsourcing\\.examples\\..*"
)
)
@EnableAsync
@EnableScheduling
@Import({
EventStoreAutoConfiguration.class,
SnapshotAutoConfiguration.class,
EventSourcingHealthConfiguration.class,
EventSourcingMetricsConfiguration.class,
org.fireflyframework.core.config.R2dbcConfig.class,
org.fireflyframework.core.config.R2dbcTransactionConfig.class
})
@Slf4j
public class EventSourcingAutoConfiguration {

Expand Down Expand Up @@ -111,4 +105,76 @@ public ObjectMapper eventSourcingObjectMapper() {
mapper.findAndRegisterModules();
return mapper;
}

/**
* Creates the EventStoreMetrics bean for monitoring event store operations.
*/
@Bean
@ConditionalOnMissingBean
public EventStoreMetrics eventStoreMetrics(MeterRegistry meterRegistry) {
log.debug("Creating EventStoreMetrics bean");
return new EventStoreMetrics(meterRegistry);
}

/**
* Creates the EventTypeRegistry bean for automatic event type discovery and registration.
*/
@Bean
@ConditionalOnMissingBean
public EventTypeRegistry eventTypeRegistry(ApplicationContext applicationContext, ObjectMapper objectMapper) {
log.debug("Creating EventTypeRegistry bean");
return new EventTypeRegistry(applicationContext, objectMapper);
}

/**
* Creates the EventSourcingTransactionalAspect bean for transactional event sourcing operations.
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(ReactiveTransactionManager.class)
public EventSourcingTransactionalAspect eventSourcingTransactionalAspect(
ReactiveTransactionManager transactionManager,
EventSourcingPublisher eventPublisher) {
log.debug("Creating EventSourcingTransactionalAspect bean");
return new EventSourcingTransactionalAspect(transactionManager, eventPublisher);
}

/**
* Creates the EventUpcastingService bean for managing event upcasting.
*/
@Bean
@ConditionalOnMissingBean
public EventUpcastingService eventUpcastingService(List<EventUpcaster> upcasters) {
log.debug("Creating EventUpcastingService bean");
return new EventUpcastingService(upcasters);
}

/**
* Creates the EventOutboxService bean for managing Event Outbox operations.
*/
@Bean
@ConditionalOnMissingBean
public EventOutboxService eventOutboxService(
EventOutboxRepository outboxRepository,
EventSourcingPublisher eventPublisher,
ObjectMapper objectMapper) {
log.debug("Creating EventOutboxService bean");
return new EventOutboxService(outboxRepository, eventPublisher, objectMapper);
}

/**
* Creates the EventOutboxProcessor bean for background processing of outbox entries.
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(
prefix = "firefly.eventsourcing.outbox.processor",
name = "enabled",
havingValue = "true",
matchIfMissing = false
)
public EventOutboxProcessor eventOutboxProcessor(EventOutboxService outboxService) {
log.debug("Creating EventOutboxProcessor bean");
return new EventOutboxProcessor(outboxService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,28 @@
package org.fireflyframework.eventsourcing.config;

import lombok.extern.slf4j.Slf4j;
import org.fireflyframework.eventsourcing.health.EventStoreHealthIndicator;
import org.fireflyframework.eventsourcing.health.OutboxHealthIndicator;
import org.fireflyframework.eventsourcing.health.ProjectionHealthIndicator;
import org.fireflyframework.eventsourcing.health.SnapshotStoreHealthIndicator;
import org.fireflyframework.eventsourcing.outbox.EventOutboxService;
import org.fireflyframework.eventsourcing.projection.ProjectionService;
import org.fireflyframework.eventsourcing.snapshot.SnapshotStore;
import org.fireflyframework.eventsourcing.store.EventStore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
* Auto-configuration for event sourcing health indicators.
* <p>
* This configuration sets up health checks for event stores,
* snapshot stores, and other event sourcing components.
* Registers Spring Boot Actuator health checks for the event store,
* outbox, snapshot store, and projections when Actuator is on the classpath.
*/
@Configuration
@ConditionalOnClass(name = "org.springframework.boot.actuator.health.HealthIndicator")
Expand All @@ -37,7 +50,39 @@ public EventSourcingHealthConfiguration() {
log.debug("Event Sourcing Health Configuration initialized");
}

// TODO: Add health indicator bean configurations
// @Bean
// public EventStoreHealthIndicator eventStoreHealthIndicator(EventStore eventStore) { ... }
}
@Bean
@ConditionalOnBean(EventStore.class)
@ConditionalOnMissingBean(EventStoreHealthIndicator.class)
public EventStoreHealthIndicator eventStoreHealthIndicator(EventStore eventStore) {
log.debug("Creating EventStoreHealthIndicator bean");
return new EventStoreHealthIndicator(eventStore);
}

@Bean
@ConditionalOnBean(EventOutboxService.class)
@ConditionalOnMissingBean(OutboxHealthIndicator.class)
public OutboxHealthIndicator outboxHealthIndicator(EventOutboxService outboxService) {
log.debug("Creating OutboxHealthIndicator bean");
return new OutboxHealthIndicator(outboxService);
}

@Bean
@ConditionalOnBean(SnapshotStore.class)
@ConditionalOnMissingBean(SnapshotStoreHealthIndicator.class)
public SnapshotStoreHealthIndicator snapshotStoreHealthIndicator(SnapshotStore snapshotStore) {
log.debug("Creating SnapshotStoreHealthIndicator bean");
return new SnapshotStoreHealthIndicator(snapshotStore);
}

@Bean
@ConditionalOnMissingBean(ProjectionHealthIndicator.class)
public ProjectionHealthIndicator projectionHealthIndicator(List<ProjectionService<?>> projectionServices,
EventSourcingProjectionProperties projectionProperties) {
log.debug("Creating ProjectionHealthIndicator bean with {} projections", projectionServices.size());
return new ProjectionHealthIndicator(
projectionServices,
projectionProperties.getHealthCheck().getTimeout(),
projectionProperties.getHealthCheck().getMaxAcceptableLag()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,26 @@

package org.fireflyframework.eventsourcing.config;

import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.fireflyframework.eventsourcing.monitoring.EventStoreMetrics;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Auto-configuration for event sourcing metrics collection.
* <p>
* This configuration sets up Micrometer metrics for event sourcing
* operations like event appends, reads, snapshot operations, etc.
* Wires {@link EventStoreMetrics} to the Micrometer {@link MeterRegistry} when
* Micrometer is on the classpath and metrics are enabled.
*
* <p>Note: The {@link EventStoreMetrics} bean is also defined in
* {@link EventSourcingAutoConfiguration}. This configuration class ensures
* the metrics bean is available even when loaded through component scanning
* independently of the main auto-configuration.</p>
*/
@Configuration
@ConditionalOnClass(name = "io.micrometer.core.instrument.MeterRegistry")
Expand All @@ -37,7 +47,11 @@ public EventSourcingMetricsConfiguration() {
log.debug("Event Sourcing Metrics Configuration initialized");
}

// TODO: Add metrics bean configurations
// @Bean
// public EventStoreMetrics eventStoreMetrics(MeterRegistry meterRegistry) { ... }
}
@Bean
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnMissingBean
public EventStoreMetrics eventStoreMetrics(MeterRegistry meterRegistry) {
log.info("Creating EventStoreMetrics bean via metrics configuration");
return new EventStoreMetrics(meterRegistry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Set;
Expand All @@ -47,7 +46,6 @@
* <p>
* The event will be automatically discovered and registered when the application starts.
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class EventTypeRegistry {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@

package org.fireflyframework.eventsourcing.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.fireflyframework.eventsourcing.snapshot.SnapshotStore;
import org.fireflyframework.eventsourcing.snapshot.SnapshotTrigger;
import org.fireflyframework.eventsourcing.snapshot.r2dbc.R2dbcSnapshotStore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.r2dbc.core.DatabaseClient;

/**
* Auto-configuration for snapshot stores.
* <p>
* This configuration class sets up snapshot store implementations and
* related components like caching and cleanup schedulers.
* This configuration class sets up the R2DBC-backed snapshot store and a
* configurable snapshot trigger that creates snapshots after every N events.
*/
@Configuration
@ConditionalOnProperty(prefix = "firefly.eventsourcing.snapshot", name = "enabled", havingValue = "true", matchIfMissing = true)
Expand All @@ -35,8 +42,18 @@ public SnapshotAutoConfiguration() {
log.debug("Snapshot Auto-Configuration initialized");
}

// TODO: Add snapshot store bean configurations
// @Bean
// @ConditionalOnMissingBean
// public SnapshotStore snapshotStore(...) { ... }
}
@Bean
@ConditionalOnMissingBean
public SnapshotStore snapshotStore(DatabaseClient databaseClient, ObjectMapper objectMapper) {
log.info("Creating R2dbcSnapshotStore bean");
return new R2dbcSnapshotStore(databaseClient, objectMapper);
}

@Bean
@ConditionalOnMissingBean
public SnapshotTrigger snapshotTrigger(SnapshotStore snapshotStore, EventSourcingProperties properties) {
int frequency = properties.getSnapshot().getThreshold();
log.info("Creating SnapshotTrigger bean with frequency: {} events", frequency);
return new SnapshotTrigger(snapshotStore, frequency);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package org.fireflyframework.eventsourcing.examples.ledger.exceptions;

import org.fireflyframework.kernel.exception.FireflyException;

/**
* Exception thrown when attempting to perform operations on a closed account.
*/
public class AccountClosedException extends RuntimeException {
public class AccountClosedException extends FireflyException {

public AccountClosedException(String message) {
super(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package org.fireflyframework.eventsourcing.examples.ledger.exceptions;

import org.fireflyframework.kernel.exception.FireflyException;

/**
* Exception thrown when attempting to withdraw from a frozen account.
*/
public class AccountFrozenException extends RuntimeException {
public class AccountFrozenException extends FireflyException {

public AccountFrozenException(String message) {
super(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package org.fireflyframework.eventsourcing.examples.ledger.exceptions;

import org.fireflyframework.kernel.exception.FireflyException;

/**
* Exception thrown when attempting to withdraw more than the available balance.
*/
public class InsufficientFundsException extends RuntimeException {
public class InsufficientFundsException extends FireflyException {

public InsufficientFundsException(String message) {
super(message);
Expand Down
Loading