Repository navigation
Project events; sync a project's name after create or re-key - #19
Merged
Merged
Conversation
ProjectService publishes ProjectCreated and ProjectUpdated (with the changed fields) inside the changing transaction. SyncOnProjectChange listens after commit and starts a background sync when a project is created or pointed at another SDLC project, so its name comes from the SDLC system without a separate sync request (resourceapi.sync.on-change, off in tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
Spring Modulith's JDBC event publication registry stores ProjectCreated and ProjectUpdated in event_publication within the changing transaction. ProjectNotifier sends them after commit to the resourceapi.projects topic exchange (project.created / project.updated, JSON, traceparent) on its own virtual-thread executor, and only counts a send once the broker confirmed it; the row is then deleted. UnsentNotifications resends what is left, one instance at a time (Redis lock). Delivery is at least once. Modulith's RabbitMQ externalization isn't used: in module-listener mode it doesn't wait for publisher confirms, so a refused message was counted as sent (caught by the test that deletes the exchange). Also: spring.task.execution.mode=force keeps Boot's application task executor next to the notification executor; failed fire-and-forget tasks are logged in one line. RabbitMQ in compose.yaml and in tests (Testcontainers, an exchange per test context). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
POST /sync now puts a ProjectSyncRequest on the durable queue resourceapi.project-sync (202 once the broker confirmed it, 503 if RabbitMQ is unreachable) instead of running it on the in-memory @async executor. Requests survive restarts and wait for a free consumer instead of being refused. ProjectSyncConsumer (4 consumers per instance, virtual threads, prefetch 1, trace continued from the request) runs the sync with Spring's RetryTemplate: SDLC failures that may pass (unreachable, 5xx, 429, our own rate limit) are retried after 1s and 2s; a sync that still fails, or fails otherwise, is dead-lettered to <queue>.dead and logged in one line, without the upstream body. The per-project lock is taken by the request and released by the consumer via the owner id carried in the message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
Events about one project can reach a listener or another system out of order (sent in parallel, or retried later), so each carries when the change was made and an event older than the last applied can be discarded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
With 16 sends each waiting for an unreachable broker, further sends made the committing request wait for a free slot, coupling API latency to the broker. A refused send leaves its event in the outbox, sent again later. README: consumers should order notifications by occurredAt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First of three event/messaging PRs: in-app Spring events, then outbox notifications to RabbitMQ, then durable sync jobs on RabbitMQ.
Events.
ProjectServicepublishes Spring application events (new packageevents) inside the transaction that changes a project:ProjectCreated: the project's id, SDLC system, external id and name.ProjectUpdated: the new state plus which fields changed (NAME,EXTERNAL_ID,SDLC_SYSTEM).occurredAton both, so a listener (or, after Project notifications to RabbitMQ through an outbox (Spring Modulith) #20, another system) that receives events about one project out of order can drop stale ones. Added in the review round.SyncOnProjectChange. A@TransactionalEventListener, so it only reacts to committed changes. It starts a background sync, the same asPOST …/sync, when a project is:Other details:
afterCompletion, so even an unexpected exception there can't fail the request (checked in Spring's source).ProjectUpdateddoesn't start another sync.resourceapi.sync.on-change(defaulttrue). The test config turns it off, so tests that create projects don't call SDLC systems unexpectedly.createProjectis now@Transactional, so its event belongs to the creating transaction.Known cost. The listener runs on the request thread after commit. With Redis unreachable, the lock attempt adds about a second to a create or re-key, as it already does for
POST …/sync. This is documented in the README.Tests
ProjectEventsTest(@RecordApplicationEvents):ProjectCreatedwithoccurredAt;ProjectUpdated{NAME}, later than the create;EXTERNAL_ID+SDLC_SYSTEM;SyncOnProjectChangeTest(WireMock SDLC system, real Redis):Full suite: 120 tests pass.
🤖 Generated with Claude Code
https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2