The projection feature was originally not designed very well and has a number of issues:
Unsafe modifying methods issue - the delete and save methods can be called without read, so there will be no objects in the cache, making it impossible to read previous projections, which leads to memory leaks. Due to this behavior, many methods like deleteIfExist have appeared, and because of the use of regular delete, we have repeatedly encountered bugs in production.
Projection isn't found in tx cache after save in tx Issue #31
This is a separable functionality and should not be in the core part. Because of this feature, I personally had to create workarounds, bypass maneuvers, and think a lot about how not to break anything while making changes in the transaction and inMemory parts.
Where we want to go:
There is a ProjectionTable which acts as a proxy to Table, with overridden insert, save, and delete methods that implement the logic for working with projections.
createProjections becomes a field of ProjectionTable in the form of Supplier<List<Entity<?>>>.
ProjectionCache is removed.
Since ProjectionTable is no longer a general solution but works only with Entity with projections, we can fail in save and delete if the object is not in the cache. This ensures that working with projections will be safe, and we can forget about unsafe methods and methods like deleteIfExists.
How the migration process will look:
First, implement the new MigrationProjectionsCache, which immediately calls the methods table.{save,delete} without waiting for a commit.
Initially, enable it without a flag and run tests for all projects, ensuring they are green.
Then, leave the feature under a flag and release the service, ensuring that the service also works.
Add a method withoutProjections() to the table that sets a property indicating that projections should not be processed.
Remove ProjectionCache, leaving the logic from MigrationProjectionsCache directly in the Table methods under the withoutProjections flag.
Create a ProjectionsTable proxy that accepts Table<E> in the constructor and sets withoutProjections() on the proxied object.
In ProjectionsTable, override only the insert, save, and delete methods with the projection logic and new error handling.
In the Entity class with the base implementation of createProjections, throw a special CreateProjectionNotOverridedException.
In Table, catch the exception and if withoutProjections is set, fail; otherwise, log an error with a requirement to migrate to the new logic.
After all services migrated remove logic from table, withoutProjections flag and method and move all projection code to separate module
TODO: think how to protect services which can call tx.table(MyEntityWithProjections.class).{insert,save,delete} directly
The projection feature was originally not designed very well and has a number of issues:
Where we want to go:
How the migration process will look:
MigrationProjectionsCache
, which immediately calls the methodstable.{save,delete}
without waiting for a commit.withoutProjections()
to the table that sets a property indicating that projections should not be processed.ProjectionCache
, leaving the logic fromMigrationProjectionsCache
directly in theTable
methods under thewithoutProjections
flag.ProjectionsTable
proxy that acceptsTable<E>
in the constructor and setswithoutProjections()
on the proxied object.ProjectionsTable
, override only theinsert
,save
, anddelete
methods with the projection logic and new error handling.Entity
class with the base implementation ofcreateProjections
, throw a specialCreateProjectionNotOverridedException
.Table
, catch the exception and ifwithoutProjections
is set, fail; otherwise, log an error with a requirement to migrate to the new logic.TODO: think how to protect services which can call tx.table(MyEntityWithProjections.class).{insert,save,delete} directly