Skip to content

Commit

Permalink
"Quick" Append Events. Closes GH-3138
Browse files Browse the repository at this point in the history
Docs on the quick append option

Few more tests for metadata on the new quick append mode.

More tests on the quick append

First end to end runs of the quick append mechanism

Roughed in QuickEventAppender and configuration logic for switching append modes

First successful pass at code generation for the quick append function

Lot more preparatory work for "quick append". Put back the old AppendEventFunction

Refactored on the code generation for append event operations in preparation for the quick append

Roughed in IncrementStreamVersionBy*** for later
  • Loading branch information
jeremydmiller committed Jul 2, 2024
1 parent 4300ac2 commit ff5dc73
Show file tree
Hide file tree
Showing 44 changed files with 2,864 additions and 248 deletions.
20 changes: 20 additions & 0 deletions docs/events/appending.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ The event data is persisted to two tables:
Events can be captured by either starting a new stream or by appending events to an existing stream. In addition, Marten has some tricks up its sleeve for dealing
with concurrency issues that may result from multiple transactions trying to simultaneously append events to the same stream.

## "Rich" vs "Quick" Appends <Badge type="tip" text="7.21" />

Before diving into starting new event streams or appending events to existing streams, just know that there are two different
modes of event appending you can use with Marten:

snippet: sample_configuring_event_append_mode

The classic `Rich` mode will append events in a two step process where the local session will first determine all possible
metadata for the events about to be appended such that inline projections can use event versions and the global event sequence
numbers at the time that the inline projections are created.

The newer `Quick` mode eschews version and sequence metadata in favor of performing the event append and stream creation
operations with minimal overhead. The improved performance comes at the cost of not having the `IEvent.Version` and `IEvent.Sequence`
information available at the time that inline projections are executed.

If using inline projections for a single stream (`SingleStreamProjection` or _snapshots_) and the `Quick` mode, the Marten team
highly recommends using the `IRevisioned` interface on your projected aggregate documents so that Marten can "move" the version
set by the database operations to the version of the projected documents loaded from the database later. Mapping a custom member
to the `Revision` metadata will work as well.

## Starting a new Stream

You can **optionally** start a new event stream against some kind of .Net type that theoretically marks the type of stream you're capturing.
Expand Down
24 changes: 24 additions & 0 deletions src/EventSourcingTests/EventGraphTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,30 @@ public void has_any_is_true_with_any_events()
theGraph.IsActive(null).ShouldBeTrue();
}

[Fact]
public void default_append_mode_is_rich()
{
theGraph.AppendMode.ShouldBe(EventAppendMode.Rich);
theGraph.EventAppender.ShouldBeOfType<RichEventAppender>();
}

[Fact]
public void switch_to_quick()
{
theGraph.AppendMode = EventAppendMode.Quick;
theGraph.EventAppender.ShouldBeOfType<QuickEventAppender>();
theGraph.AppendMode.ShouldBe(EventAppendMode.Quick);
}

[Fact]
public void switch_to_quick_and_back_to_rich()
{
theGraph.AppendMode = EventAppendMode.Quick;
theGraph.AppendMode = EventAppendMode.Rich;
theGraph.AppendMode.ShouldBe(EventAppendMode.Rich);
theGraph.EventAppender.ShouldBeOfType<RichEventAppender>();
}

public class HouseRemodeling
{
public Guid Id { get; set; }
Expand Down
119 changes: 0 additions & 119 deletions src/EventSourcingTests/EventStoreSchemaV3ToV4Tests.cs

This file was deleted.

29 changes: 29 additions & 0 deletions src/EventSourcingTests/QuickAppend/Examples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Marten;
using Marten.Events;
using Microsoft.Extensions.Hosting;

namespace EventSourcingTests.QuickAppend;

public class Examples
{
public static async Task configure()
{
#region sample_configuring_event_append_mode

var builder = Host.CreateApplicationBuilder();
builder.Services.AddMarten(opts =>
{
// This is the default Marten behavior from 4.0 on
opts.Events.AppendMode = EventAppendMode.Rich;

// Lighter weight mode that should result in better
// performance, but with a loss of available metadata
// within inline projections
opts.Events.AppendMode = EventAppendMode.Quick;
})
.UseNpgsqlDataSource();

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;
using JasperFx.Core;

namespace EventSourcingTests.QuickAppend;

public class QuestPartyWithStringIdentifier
{
private readonly IList<string> _members = new List<string>();

public string[] Members
{
get
{
return _members.ToArray();
}
set
{
_members.Clear();
_members.AddRange(value);
}
}

public IList<string> Slayed { get; } = new List<string>();

public void Apply(MembersJoined joined)
{
if (joined.Members != null)
_members.Fill(joined.Members);
}

public void Apply(MembersDeparted departed)
{
_members.RemoveAll(x => departed.Members.Contains(x));
}

public void Apply(QuestStarted started)
{
Name = started.Name;
}

public string Key { get; set; }

public string Name { get; set; }

public string Id { get; set; }

public override string ToString()
{
return $"Quest party '{Name}' is {Members.Join(", ")}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Xunit;

namespace EventSourcingTests.QuickAppend;

[CollectionDefinition("quick_string_identified_streams")]
public class StringIdentifiedStreamsCollection: ICollectionFixture<StringIdentifiedStreamsFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Marten.Events;
using Marten.Events.Projections;
using Marten.Testing.Harness;

namespace EventSourcingTests.QuickAppend;

public class StringIdentifiedStreamsFixture: StoreFixture
{
public StringIdentifiedStreamsFixture(): base("quick_string_identified_streams")
{
Options.Events.AppendMode = EventAppendMode.Quick;
Options.Events.StreamIdentity = StreamIdentity.AsString;
Options.Projections.Snapshot<QuestPartyWithStringIdentifier>(SnapshotLifecycle.Inline);

Options.Events.AddEventType(typeof(MembersJoined));
Options.Events.AddEventType(typeof(MembersDeparted));
Options.Events.AddEventType(typeof(QuestStarted));
}
}
45 changes: 45 additions & 0 deletions src/EventSourcingTests/QuickAppend/quick_append_end_to_end.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Threading.Tasks;
using Marten.Events;
using Marten.Testing.Harness;
using Xunit;
using Shouldly;

namespace EventSourcingTests.QuickAppend;

public class quick_append_end_to_end : OneOffConfigurationsContext
{
public quick_append_end_to_end()
{
StoreOptions(opts =>
{
opts.Events.AppendMode = EventAppendMode.Quick;
opts.Events.MetadataConfig.CausationIdEnabled = true;
opts.Events.MetadataConfig.CorrelationIdEnabled = true;
opts.Events.MetadataConfig.HeadersEnabled = true;
});
}

[Fact]
public async Task append_with_metadata_using_function()
{
theSession.CorrelationId = "lotr";
theSession.CausationId = "fellowship";
theSession.SetHeader("color", "blue");

var streamId =
theSession.Events.StartStream<Quest>(new QuestStarted(), new MembersJoined(1, "Hobbiton", "Frodo", "Sam")).Id;
await theSession.SaveChangesAsync();

var events = await theSession.Events.FetchStreamAsync(streamId);

foreach (var e in events)
{
e.Sequence.ShouldBeGreaterThan(0);
e.Version.ShouldBeGreaterThan(0);

e.CorrelationId.ShouldBe(theSession.CorrelationId);
e.CausationId.ShouldBe(theSession.CausationId);
e.Headers["color"].ShouldBe("blue");
}
}
}
Loading

0 comments on commit ff5dc73

Please sign in to comment.