rebus-org / Rebus.SqlServer

:bus: Microsoft SQL Server transport and persistence for Rebus
https://mookid.dk/category/rebus
Other
43 stars 44 forks source link

Generic saga data name handling #52

Closed MrMDavidson closed 5 years ago

MrMDavidson commented 5 years ago

This supersedes #38 to provide improved saga name handling. It is by default backwards compatible with prior versions... otherwise everyone's sagas would break.

Introduces a new ISagaTypeNamingStrategy that can be registered in the injector. If one is not registered at the time the SqlServerSagaStorage is constructed then a default, LegacySagaTypeNamingStrategy will be used which generates the name as per the existing method (Type.Name). However some other versions are provided;

  1. Sha512SagaTypeNamingStrategy: Generates a SHA2-512 hash of the types simple assembly qualified name (This means that, for instance, GenericSagaContainer<SomeUnderlyingType> will be hashed based on something like GenericSagaContainer'1[[SomeUnderlyingType]]. As such, hashes will not clash. Additionally it will take a portion of the hash up to the maximum allowed bytes. According to https://crypto.stackexchange.com/questions/161/should-i-use-the-first-or-last-bits-from-a-sha-256-hash/163#163 this should be safe to do.
    1. CachedSagaTypeNamingStrategy: Intended to wrap around another instance of a ISagaTypeNamingStrategy so that costly saga type name calculations will only be performed once.
  2. HumanReadableHashedSagaTypeNamingStrategy: Extends Sha512SagaTypeNamingStrategy so that a portion of the class name is prefixed before the hash. For an input of SampleProject.DeleteCustomerSaga it'd generate an output something like DeleteCustHc2dpZJ/0b4cJEQB8rCqQXPYtheEx instead of Hc2dpZJ/0b4cJEQB8rCqQXPYtheEx78Tvgm7o7H which is a little harder to peek at the database and see what's going on.

Consumers can register their own implementation of ISagaTypeNamingStrategy with Injectionist prior to setting up SqlSagaStorage.

WARNING: Using anything other than the default LegacySagaTypeNamingStrategy will break any inflight sagas... migration of sagas to new naming schemes is left as an exercise for the reader 😜


Rebus is MIT-licensed. The code submitted in this pull request needs to carry the MIT license too. By leaving this text in, I hereby acknowledge that the code submitted in the pull request has the MIT license and can be merged with the Rebus codebase.

MrMDavidson commented 5 years ago

Just a note on this; I'm not sure how to manage this, it ties into #50, but I'd suggest going forward for "new" installs making either the SHA2-512, or human+SHA2-512, based naming scheme the default. They both have the advantage of not imposing a limit on the length of the saga data type name as well as allowing generic saga data types to not conflict on the same correlation id (Eg. BaseSagaData<RemoveCustomer> will no longer clash with BaseSagaData<AnnualBirthdayReminder>).

I wonder if it's worth introducing the concept of some kind of "Compatability" level and using a similar approach to ASP.Net Core where you might configure the saga something like this...

config.Sagas(o => o.StoreInSqlServer(SqlSagaCompatabilityVersion.Version_1_0, "connectionString", "Saga", "SagaIndex"));

As API changes are made you'd introduce SqlSagaCompatabilityVersion.Version_1_1 (or whatever). And as a result the saga storage would switch to using a different saga name strategy as a result. Docs would then be updated to say to use SqlSagaCompatabilityVersion.Version_1_1 and so new "users" would get the new naming. But users upgrading wouldn't have everything break on them. 🤔