|
21 | 21 | - [Synchronously updating projections](#4-6)
|
22 | 22 | - [Asynchronously sending integration events to a message broker](#4-7)
|
23 | 23 | - [Reliable transactional outbox with PostgreSQL](#4-7-1)
|
24 |
| - - [Database polling alternatives](#4-7-2) |
| 24 | + - [Database polling](#4-7-2) |
| 25 | + - [Database polling alternative](#4-7-3) |
25 | 26 | - [Adding new asynchronous event handlers](#4-8)
|
26 | 27 | - [Class diagrams](#4-9)
|
27 | 28 | - [Class diagram of the domain model](#4-9-1)
|
@@ -272,7 +273,7 @@ INSERT INTO ES_AGGREGATE_SNAPSHOT (AGGREGATE_ID, VERSION, JSON_DATA)
|
272 | 273 | VALUES (:aggregateId, :version, :jsonObj::json)
|
273 | 274 | ```
|
274 | 275 |
|
275 |
| -Snapshotting for an aggregate type can be disabled and configured in the `application.yml` |
| 276 | +Snapshotting for an aggregate type can be disabled and configured in the [`application.yml`](src/main/resources/application.yml) |
276 | 277 |
|
277 | 278 | ```yaml
|
278 | 279 | event-sourcing:
|
@@ -417,33 +418,63 @@ the events it created won't be read by the event subscription processor until tr
|
417 | 418 |
|
418 | 419 | 
|
419 | 420 |
|
420 |
| -Event subscription processing can be disabled and configured in the `application.yml` |
| 421 | +#### <a id="4-7-2"></a>Database polling |
| 422 | + |
| 423 | +To get new events from the `ES_EVENT` table, the application has to poll the database. |
| 424 | +The shorter the polling period, the shorter the delay between persisting a new event and processing it by the subscription. |
| 425 | +But the lag is inevitable. If the polling period is 1 second, then the lag is at most 1 second. |
| 426 | + |
| 427 | +The polling mechanism implementation [ScheduledEventSubscriptionProcessor](src/main/java/com/example/eventsourcing/service/ScheduledEventSubscriptionProcessor.java) |
| 428 | +uses a Spring annotation [@Scheduled](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html) to poll database with a fixed period. |
| 429 | + |
| 430 | +The polling event subscription processing can be enabled and configured in the [`application.yml`](src/main/resources/application.yml) |
421 | 431 |
|
422 | 432 | ```yaml
|
423 | 433 | event-sourcing:
|
424 |
| - subscriptions: |
425 |
| - enabled: true |
| 434 | + subscriptions: polling # Enable database polling subscription processing |
| 435 | + polling-subscriptions: |
426 | 436 | polling-initial-delay: PT1S
|
427 | 437 | polling-interval: PT1S
|
428 | 438 | ```
|
429 | 439 |
|
430 |
| -#### <a id="4-7-2"></a>Database polling alternatives |
| 440 | +#### <a id="4-7-3"></a>Database polling alternative |
431 | 441 |
|
432 |
| -PostgreSQL `LISTEN`/`NOTIFY` functionality can be used instead of polling. |
| 442 | +To reduce the lag associated with database polling, the polling period can be set to a very low value, |
| 443 | +such as 1 second. |
| 444 | +But this means that there will be 3600 database queries per hour and 86400 per day, even if there are no new events. |
433 | 445 |
|
434 |
| -A key limitation of the PostgreSQL JDBC driver is that it cannot receive asynchronous notifications |
| 446 | +PostgreSQL `LISTEN` and `NOTIFY` feature can be used instead of polling. |
| 447 | +This mechanism allows for sending asynchronous notifications across database connections. |
| 448 | +Notifications are not sent directly from the application, |
| 449 | +but via the database [trigger](src/main/resources/db/migration/V2__notify_trigger.sql) on a table. |
| 450 | + |
| 451 | +To use this functionality an unshared [PgConnection](https://jdbc.postgresql.org/documentation/publicapi/org/postgresql/jdbc/PgConnection.html) |
| 452 | +which remains open is required. |
| 453 | +The long-lived dedicated JDBC `Connection` for receiving notifications has to be created using the `DriverManager` API, |
| 454 | +instead of getting from a pooled `DataSource`. |
| 455 | + |
| 456 | +PostgreSQL JDBC driver can't receive asynchronous notifications |
435 | 457 | and must poll the backend to check if any notifications were issued.
|
436 |
| -A timeout can be given to the poll function, |
| 458 | +A timeout can be given to the poll function `getNotifications(int timeoutMillis)`, |
437 | 459 | but then the execution of statements from other threads will block.
|
| 460 | +When `timeoutMillis` = 0, blocks forever or until at least one notification has been received. |
| 461 | +It means that notification is delivered almost immediately, without a lag. |
| 462 | +If more than one notification is about to be received, these will be returned in one batch. |
| 463 | +
|
| 464 | +This solution significantly reduces the number of issued queries |
| 465 | +and also solves the lag problem that the polling solution suffers from. |
438 | 466 |
|
439 |
| -Thus, the creation of a long-lived dedicated JDBC `Connection` for receiving notifications is required. |
440 |
| -This connection should not be obtained from a pooled DataSources. |
441 |
| -Instead, a dedicated `Connection` has to be created using the `DriverManager` API. |
| 467 | +The Listen/Notify mechanism implementation [PostgresChannelEventSubscriptionProcessor](src/main/java/com/example/eventsourcing/service/PostgresChannelEventSubscriptionProcessor.java) |
| 468 | +is inspired by the Spring Integration class [PostgresChannelMessageTableSubscriber](https://github.com/spring-projects/spring-integration/blob/v6.0.0/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/channel/PostgresChannelMessageTableSubscriber.java). |
| 469 | +
|
| 470 | +The Listen/Notify event subscription processing can be enabled in the [`application.yml`](src/main/resources/application.yml) |
| 471 | +
|
| 472 | +```yaml |
| 473 | +event-sourcing: |
| 474 | + subscriptions: postgres-channel # Enable Listen/Notify event subscription processing |
| 475 | +``` |
442 | 476 |
|
443 |
| -In practice, implementations based on PostgreSQL `LISTEN`/`NOTIFY` are quite complex. |
444 |
| -For example, |
445 |
| -[PostgresChannelMessageTableSubscriber](https://github.com/spring-projects/spring-integration/blob/v6.0.0/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/channel/PostgresChannelMessageTableSubscriber.java) |
446 |
| -from the Spring Integration. |
| 477 | +This mechanism is used by default as more efficient. |
447 | 478 |
|
448 | 479 | ### <a id="4-8"></a>Adding new asynchronous event handlers
|
449 | 480 |
|
@@ -520,7 +551,7 @@ Using PostgreSQL as an event store has a lot of advantages, but there are also d
|
520 | 551 |
|
521 | 552 | 7. Run E2E tests and see the output
|
522 | 553 | ```bash
|
523 |
| - E2E_TESTING=true; ./gradlew clean test -i |
| 554 | + E2E_TESTING=true ./gradlew clean test -i |
524 | 555 | ```
|
525 | 556 |
|
526 | 557 | 8. Explore the database using the Adminer database management tool at http://localhost:8181.
|
|
0 commit comments