feat: declarative rollback rules on #[transactional] (26.6.27)#29
Merged
Conversation
A Spring Boot parity increment, chosen from a 16-area parity-gap analysis (~84% overall) as the best value-to-effort gap — the transaction runtime already supported per-error rollback decisions; only the macro surface was missing. #[transactional(no_rollback_for = "<pat>", rollback_only_for = "<pat>")] Spring names exception *types*; because Rust's Result already separates failure from success, the Firefly analog names an error *pattern*. By default every Err rolls back; then: - no_rollback_for = "P" — Spring's @transactional(noRollbackFor = …): an Err matching pattern P commits instead of rolling back; - rollback_only_for = "P": roll back only for errors matching P, committing the rest; - with both, no_rollback_for wins on overlap. The macro lowers to the already-present transactional_with / transactional_with_on runtime entry points (which take a should_rollback(&E) -> bool predicate), composes with manager = "…", and the generated predicate is matches!-based, so a pattern that does not fit the error type is a compile error. Patterns allow `A | B` alternatives (no `if` guard). Deliberately NOT named rollback_for: Spring's rollbackFor is *additive* (it widens the always-rollback set), but Rust has no checked/unchecked split — every Err already rolls back — so the faithful rule is *restrictive*. Writing rollback_for is a friendly compile error pointing at the two rules above, so a Spring port can't be silently inverted. Adversarially reviewed (caught and fixed the rollback_for naming footgun, the no-guard constraint, and an untested process-global path before merge). Tests: a spy TransactionManager records the per-call commit-vs-rollback decision across all four cases on both the explicit-manager and process-global paths. Docs: 07-persistence.md + CHANGELOG. make ci green: 316 suites, 4489 tests, 0 failed.
This was referenced Jun 16, 2026
ancongui
added a commit
that referenced
this pull request
Jun 16, 2026
#31) The published dist (firefly-rust-by-example.{pdf,epub}) was stale — built before the README/book audits (#26/#27) and the new framework features (declarative rollback rules #29, #[http_client] #30). Regenerated against current content and completed the manifest. - book.yaml: added the three chapters that existed in src/ but were omitted from the curated PDF manifest, renumbered the book contiguously 1–26: - ch6 "Application Bootstrap" (04b-bootstrap.md, Part I) - ch9 "OpenAPI & API Documentation" (06a-openapi.md, Part II) - ch18 "Layered Microservices" (22-layered-microservices.md, Part IV) (introduction.md stays excluded — the PDF has its own front matter.) - gen_openers.py: three new on-brand 720x300 chapter openers (s_bootstrap, s_openapi, s_layered) in the established style; switched the kicker numbers to an explicit reading-order list so every "CHAPTER N" matches book.yaml. The 18 ch06–ch23 openers changed only because their kicker number shifted; ch01–ch05 are byte-identical. - dist: regenerated PDF (485 -> 519 pages, +3 chapters) and EPUB. The new #[http_client] (ch13) and #[transactional] rollback-rules (ch8) sections now render in the published book. Built with the WeasyPrint pipeline (docs/book/build-book.sh; venv + weasyprint 69). Docs-only — no crate or version change. Co-authored-by: Andrés Contreras Guillén <ancongui@Andress-MacBook-Pro-2.local>
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.
What
A Spring Boot parity increment (
26.6.27): declarative transaction rollback rules on#[transactional].Picked from a 16-area parity-gap analysis (the framework sits at ~84% overall Spring parity) as the best value-to-effort gap — the transaction runtime already supported per-error rollback decisions (
transactional_with/transactional_with_ontake ashould_rollback(&E) -> bool), so only the macro surface was missing.The feature
Spring names exception types; because Rust's
Resultalready separates failure from success, the Firefly analog names an error pattern. By default everyErrrolls back; then:no_rollback_for = "P"— Spring's@Transactional(noRollbackFor = …): anErrmatchingPcommits instead of rolling back;rollback_only_for = "P": roll back only for errors matchingP, committing the rest;no_rollback_forwins on overlap.Patterns allow
A | Balternatives (noifguard). The generated predicate ismatches!-based, so a pattern that doesn't fit the error type is a compile error. Composes withmanager = "…"(the 2×2 routing across explicit-manager / process-global × rule / no-rule).Fidelity note (why it's
rollback_only_for, notrollback_for)Spring's
rollbackForis additive — it widens the set of exceptions that roll back (checked exceptions don't, by default). Rust has no checked/unchecked split: everyErralready rolls back, so an additive rule would be a no-op. The faithful rule here is therefore restrictive, and it is namedrollback_only_forso a Spring migrant can't writerollback_forand silently invert their rollback behavior. Writingrollback_foris a friendly compile error pointing at the two correct rules.Review
Adversarially reviewed before merge; the review caught and I fixed:
rollback_fornaming footgun (→rollback_only_for+ intercept-with-error),if-guard constraint (doc + parser),Tests & docs
TransactionManagerrecords the per-call commit-vs-rollback decision across all four cases (default /no_rollback_for/rollback_only_for/ both-with-overlap) on both the explicit-manager and process-global paths — 5 tests, plus the existing trybuild UI tests.07-persistence.md(with a "Notrollback_for" callout) + CHANGELOG.make cigreen: 316 suites, 4489 tests, 0 failed.First parity increment after the docs-correctness arc (#26–28). The roadmap's next-highest value lever is declarative
@HttpExchangeHTTP-interface clients.