Skip to content

Add API for changes to dispatch sub changes #48

@hahn-kev

Description

@hahn-kev

What

We want changes to be able to dispatch other changes. For example deletes:

public class DeleteEntryChange(Guid entityId) : EditChange<Entry>(entityId), IPolyType
{
    public static string TypeName => "delete:Entry";

    public override async ValueTask ApplyChange(Entry entry, IChangeContext context)
    {
        entry.DeletedAt = context.Commit.DateTime;
        await foreach (var obj in context.GetObjectsReferencing(EntityId, includeDeleted: false))
        {
            context.DispatchChange(new RemoveReferenceChange(obj));
        }
    }
}

or when we want to update objects which contain a copy of data

public class UpdateHeadwordChange(Guid entityId, string headword) : EditChange<Entry>(entityId), IPolyType
{
    public static string TypeName => "update:Entry:Headword";

    public override async ValueTask ApplyChange(Entry entry, IChangeContext context)
    {
        entry.Headword = headword;
        await foreach(var complexForm in context.GetObjectsReferencing(entry.Id).OfType<ComplexForm>())
        {
            context.DispatchChange(new UpdateComplexFormChange(complexForm.Id, headword));
        }
    }
}

this would also be used for data migrations, maybe previously a field was owned by an object, but you want to move that field to a different object, however you have old changes which reference the old object, you can now dispatch a change to the correct object.

Why

  • simplify delete handling code. Much of our current delete code could be moved into 2 dedicated types, one for a delete and one for removing a reference.
  • allow updating derived data, eg writing system exemplars, or complex forms.
  • data migrations, right now if you want to move a field from one object to another there's no way to write a change which allows that because a change can only modify the object it references in it's EntityId.

How

Add a new API to IChangeContext:

public interface IChangeContext
{
public CommitBase Commit { get; }
ValueTask<IObjectSnapshot?> GetSnapshot(Guid entityId);
public async ValueTask<T?> GetCurrent<T>(Guid entityId) where T : class
{
var snapshot = await GetSnapshot(entityId);
if (snapshot is null) return null;
return (T) snapshot.Entity.DbObject;
}
public async ValueTask<bool> IsObjectDeleted(Guid entityId) => (await GetSnapshot(entityId))?.EntityIsDeleted ?? true;
internal IObjectBase Adapt(object obj);
IAsyncEnumerable<object> GetObjectsReferencing(Guid entityId, bool includeDeleted = false);
IAsyncEnumerable<T> GetObjectsOfType<T>(string jsonTypeName, bool includeDeleted = false) where T : class;
}

void DispatchChanges(params Span<IChange> changes);

this would require a lot of changes to SnapshotWorker, after a change was applied we need to gather the changes it dispatched and apply them. Much of the code in MarkDeleted will prove a useful model I suspect, however MarkDeleted itself should go away entirely as it will be handled via changes now instead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions