So, I wanted to share my decision making experience on how I chose carbonite vs papertrail – for my own specific needs, others may have different requirements.
And maybe get insight from others in the community about other possible metrics, alternatives, or things to think about-- or even their own use cases when having chosen one solution or another…
Caveat that what I did was an amount of “playing around” and superficial analysis… nothing so technical or deeply exhaustive-- i can’t really know if I decided correctly until i actually build out the thing.
Problem Statement
A new Ash project is core to a new subdomain of our business, and of course, EventDriven notifications are a go to choice for signaling between different bounded contexts (outside of this project). And also, because of the writings/speakings of Pat Helland, Joe Armstrong and Rich Hickey, I definitely wanted to embrace the benefits – though not without challenges – of the ideas of immutability (where we can with data and identity), messaging/coordinations, etc. especially across bounded contexts, autonomous computing fiefdoms (per Helland).
I chose Ash for its Resource oriented declarative philosophy, and I’m lazy and don’t want to write GraphQL or migrations.
So I was looking for:
- Compatibility with Ash
- Emit events to messaging fabric – Primary Goal
- Domain Events vs persistence CDC?
- Transactional Outbox Pattern, w/ understood sender ordering guarantees?
- Have audit trail of changes – Major Goal
- some resources we want stronger auditing on changes for debugging and customer support.
- Can we model immutability for general non-functional benefits?
- And where we need mutability of resources, can we preseve past knowledge/fact?
- Revert Versions – not a goal
- Access past Versions – A mixed bag where it “depends” based on the problem domain… to balance append-only data (e.g. just log events) vs tracking mutating versions (e.g. changing of a webhook connection target – active, inactive, url changes).
Options
So for auditing and versioning in Ash, I found two immediate options:
- Carbonite (github, hex)
- PaperTrail (github, hex) with Ash Extension
Carbonite
Pros
- transactional consistency – the change is persisted w/ resource all or none, unlike post-action notifications in elixir.
- multiple ash resources changed in same transaction are explicitly tracked
- “changes” table FK to “transactions” table, also includes postgres transaction ids.
- we can set custom metadata on the transaction itself
- specify actor of change
- we can write the initiating ash-resource action… translate this to a “domain event”?
- has transactional outbox processing
Cons, Gotchas, Coinflips
- persistence level CDC, not domain level events
- Implemented using DB Triggers – mixed bag here for me.
- Outbox processing and ordering has subtle guarantees in ordering and processing.
PaperTrail
Pros
- transactional consistency – the change is persisted w/ resource all or none, unlike post-action notifications in elixir.
- strict mode foreign keys constraints for relating a resource to its versions
- modeled as saved versions, and with the ability to look up old versions
- straight forward wrapper around ecto
- integration as Ash extension – still experimental though
- has specialized originator and (of change) metadata to signify the actor of the change and the source processor of the change
- open ended metadata for the change
Cons, Gotchas, Coinflips
- different ash resources changed in the same transaction each get their own event row, but there is no strong correlation between the two rows.
- I’ll have to write something completely custom to try for outbox processing.
Conclusion
I ended up using Carbonite because of my own use-case needs…
Related: I didn’t find anything in Ash to support event-sourcing itself … Though, maybe commanded has some sort of future integration with Ash… haven’t started any exploration in how commanded works-- and I don’t personally see the need for event-sourcing complexity in my own needs.
Mainly, the key deciding factor was the outbox processing and that different changing resources would all be strongly correlated by a transaction-- i.e. an action on an “Aggregate Root” would also manage other subresources such as changing “tags” (a different as resource, not actually embedded resource)… while for papertrail, I didn’t see the extra transactional outbox handling/sending, or the same correlation capability.
For “immutability” vs “versioning” … I think I can get away with, for my problem domain at least, modeling relevant resources as INSERT-only to provide immutability as needed… and that I only needed audit logging for the other mutatable entities/resource, and I would not need to revert or show the user past revisions.
As for compatibility concerns for Ash, while AshPaperTrail extension existed, I saw a past PR to get carbonite to work with ash… so just fine there.
Though, I’m still fleshing out how to best design “Domain-Level Events” out of this persistence level trigger-based CDC
- Maybe that each transaction is annotated (metadata) with the AshResource action used, and translate that to some single Domain Event.
- or maybe create additional domain events ash resources–persisted to also be captured by the carbonite CDC.
- outbox processor handles the transaction, sees a domain event row inserted… and sends that out instead of the raw resource data change.
- that are explicitly created as a result of a related ash resource action, which would give me possibility of many events to one action and better conformity to the domain.
And that is me mostly completely emotional-gut decision making, with a rationalization of arbitrary picked metrics.
I hope that eventually I’ll have enough experience working with it that I can have a more rational, experience/fact based, retrospective.
And I’d like to hear from others about if they had similar/different evaluations, share anything else in this field, or if there were other alternatives?
thanks!