Skip to content

Commit

Permalink
Boom! Can use [Edit] and IStorageAction types with RavenDb
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremydmiller committed Jan 4, 2025
1 parent b7be46b commit 0bbf7e9
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected override void configureWolverine(WolverineOptions opts)
opts.Policies.AutoApplyTransactions();
}

public override async Task<Todo?> Load(Guid id)
public override async Task<Todo?> Load(string id)
{
var store = Host.DocumentStore();
await using var session = store.QuerySession();
Expand Down
53 changes: 53 additions & 0 deletions src/Persistence/RavenDbTests/Code.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// <auto-generated/>
#pragma warning disable
using Raven.Client.Documents;

namespace Internal.Generated.WolverineHandlers
{
// START: CreateTodoHandler1536167811
public class CreateTodoHandler1536167811 : Wolverine.Runtime.Handlers.MessageHandler
{
private readonly Raven.Client.Documents.IDocumentStore _documentStore;

public CreateTodoHandler1536167811(Raven.Client.Documents.IDocumentStore documentStore)
{
_documentStore = documentStore;
}



public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation)
{
// The actual message body
var createTodo = (Wolverine.ComplianceTests.CreateTodo)context.Envelope.Message;


// Open a new document session
// message context to support the outbox functionality
using var asyncDocumentSession = _documentStore.OpenAsyncSession();
context.EnlistInOutbox(new Wolverine.RavenDb.Internals.RavenDbEnvelopeTransaction(asyncDocumentSession, context));

// The actual message execution
var outgoing1 = Wolverine.ComplianceTests.TodoHandler.Handle(createTodo);

if (outgoing1 != null)
{
await asyncDocumentSession.StoreAsync(outgoing1.Entity, cancellation).ConfigureAwait(false);
}


// Commit any outstanding RavenDb changes
await asyncDocumentSession.SaveChangesAsync(cancellation).ConfigureAwait(false);


// Have to flush outgoing messages just in case Marten did nothing because of https://github.com/JasperFx/wolverine/issues/536
await context.FlushOutgoingMessagesAsync().ConfigureAwait(false);

}

}

// END: CreateTodoHandler1536167811


}
2 changes: 1 addition & 1 deletion src/Persistence/RavenDbTests/saga_storage_compliance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public IHost BuildHost<TSaga>()

opts.CodeGeneration.GeneratedCodeOutputPath = AppContext.BaseDirectory.ParentDirectory().ParentDirectory().ParentDirectory().AppendPath("Internal", "Generated");
opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto;

// Shouldn't be necessary, but apparently is. Type scanning is not working
// for some reason across the compliance tests
opts.Discovery.IncludeType<StringBasicWorkflow>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.Extensions.DependencyInjection;
using Raven.Client.Documents;
using Wolverine;
using Wolverine.ComplianceTests;
using Wolverine.RavenDb;

namespace RavenDbTests;

[Collection("raven")]
public class using_storage_return_types_and_entity_attributes : StorageActionCompliance
{
private readonly DatabaseFixture _fixture;

public using_storage_return_types_and_entity_attributes(DatabaseFixture fixture)
{
_fixture = fixture;
}

protected override void configureWolverine(WolverineOptions opts)
{
var store = _fixture.StartRavenStore();

// You *must* register the store after the RavenDb envelope storage
opts.UseRavenDbPersistence();
opts.Services.AddSingleton(store);
opts.Policies.AutoApplyTransactions();
opts.Durability.Mode = DurabilityMode.Solo;

opts.CodeGeneration.ReferenceAssembly(typeof(Wolverine.RavenDb.IRavenDbOp).Assembly);
}

public override async Task<Todo?> Load(string id)
{
var store = Host.Services.GetRequiredService<IDocumentStore>();
using var session = store.OpenAsyncSession();
return await session.LoadAsync<Todo>(id);
}

public override async Task Persist(Todo todo)
{
var store = Host.Services.GetRequiredService<IDocumentStore>();
using var session = store.OpenAsyncSession();
await session.StoreAsync(todo);
await session.SaveChangesAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,42 @@ public Frame DetermineStoreFrame(Variable variable, IServiceContainer container)

public Frame DetermineDeleteFrame(Variable variable, IServiceContainer container)
{
throw new NotImplementedException();
return new DeleteDocumentFrame(variable);
}

public Frame DetermineStorageActionFrame(Type entityType, Variable action)
{
throw new NotImplementedException();
var method = typeof(RavenDbStorageActionApplier).GetMethod("ApplyAction")
.MakeGenericMethod(entityType);

var call = new MethodCall(typeof(RavenDbStorageActionApplier), method);
call.Arguments[1] = action;

return call;
}
}

public static class RavenDbStorageActionApplier
{
public static async Task ApplyAction<T>(IAsyncDocumentSession session, IStorageAction<T> action)
{
if (action.Entity == null) return;

switch (action.Action)
{
case StorageAction.Delete:
session.Delete(action.Entity!);
break;
case StorageAction.Insert:
case StorageAction.Store:
case StorageAction.Update:
await session.StoreAsync(action.Entity);
break;
}
}
}


internal class DeleteDocumentFrame : SyncFrame
{
private readonly Variable _saga;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Raven.Client.Documents;
using Raven.Client.Documents.Operations;
using Raven.Client.Documents.Queries;
using Wolverine.Attributes;
using Wolverine.Persistence.Durability;
using Wolverine.Persistence.Sagas;
using Wolverine.RavenDb.Internals;
Expand All @@ -21,6 +22,7 @@ public static WolverineOptions UseRavenDbPersistence(this WolverineOptions optio
options.Services.AddSingleton<IMessageStore, RavenDbMessageStore>();
options.CodeGeneration.InsertFirstPersistenceStrategy<RavenDbPersistenceFrameProvider>();
options.Services.AddHostedService<DeadLetterQueueReplayer>();
options.CodeGeneration.ReferenceAssembly(typeof(WolverineRavenDbExtensions).Assembly);
return options;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ protected override void configureWolverine(WolverineOptions opts)
// Nothing, just use the in memory persistor
}

public override Task<Todo?> Load(Guid id)
public override Task<Todo?> Load(string id)
{
return Task.FromResult(Host.Services.GetRequiredService<InMemorySagaPersistor>().Load<Todo>(id));
}
Expand Down
51 changes: 29 additions & 22 deletions src/Testing/Wolverine.ComplianceTests/StorageActionCompliance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace Wolverine.ComplianceTests;

public abstract class StorageActionCompliance : IAsyncLifetime
{
public List<IDisposable> Disposables = new();

public async Task InitializeAsync()
{
Host = await Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
Expand All @@ -25,21 +27,26 @@ public async Task InitializeAsync()

public async Task DisposeAsync()
{
foreach (var disposable in Disposables)
{
disposable.Dispose();
}

await Host.StopAsync();
}


public IHost Host { get; set; }

// These two methods will be changed
public abstract Task<Todo?> Load(Guid id);
public abstract Task<Todo?> Load(string id);

public abstract Task Persist(Todo todo);

[Fact]
public async Task use_insert_as_return_value()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
var tracked = await Host.InvokeMessageAndWaitAsync(command);

// Should NOT be trying to send the entity as a cascading message
Expand All @@ -53,7 +60,7 @@ public async Task use_insert_as_return_value()
[Fact]
public async Task use_store_as_return_value()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
var tracked = await Host.InvokeMessageAndWaitAsync(command);

// Should NOT be trying to send the entity as a cascading message
Expand All @@ -67,7 +74,7 @@ public async Task use_store_as_return_value()
[Fact]
public async Task use_entity_attribute_with_id()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
await Host.InvokeMessageAndWaitAsync(command);

await Host.InvokeMessageAndWaitAsync(new RenameTodo(command.Id, "New name"));
Expand All @@ -79,7 +86,7 @@ public async Task use_entity_attribute_with_id()
[Fact]
public async Task use_entity_attribute_with_entity_id()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
await Host.InvokeMessageAndWaitAsync(command);

await Host.InvokeMessageAndWaitAsync(new RenameTodo2(command.Id, "New name2"));
Expand All @@ -91,7 +98,7 @@ public async Task use_entity_attribute_with_entity_id()
[Fact]
public async Task use_entity_attribute_with_explicit_id()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
await Host.InvokeMessageAndWaitAsync(command);

await Host.InvokeMessageAndWaitAsync(new RenameTodo3(command.Id, "New name3"));
Expand All @@ -104,7 +111,7 @@ public async Task use_entity_attribute_with_explicit_id()
[Fact]
public async Task use_delete_as_return_value()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
var tracked = await Host.InvokeMessageAndWaitAsync(command);

// Should NOT be trying to send the entity as a cascading message
Expand All @@ -121,8 +128,8 @@ public async Task use_delete_as_return_value()
[Fact]
public async Task use_generic_action_as_insert()
{
var shouldInsert = new MaybeInsertTodo(Guid.NewGuid(), "Pick up milk", true);
var shouldDoNothing = new MaybeInsertTodo(Guid.NewGuid(), "Start soup", false);
var shouldInsert = new MaybeInsertTodo(Guid.NewGuid().ToString(), "Pick up milk", true);
var shouldDoNothing = new MaybeInsertTodo(Guid.NewGuid().ToString(), "Start soup", false);

await Host.InvokeMessageAndWaitAsync(shouldInsert);
await Host.InvokeMessageAndWaitAsync(shouldDoNothing);
Expand All @@ -134,7 +141,7 @@ public async Task use_generic_action_as_insert()
[Fact]
public async Task use_generic_action_as_delete()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
await Host.InvokeMessageAndWaitAsync(command);

await Host.InvokeMessageAndWaitAsync(new AlterTodo(command.Id, "New text", StorageAction.Delete));
Expand All @@ -145,7 +152,7 @@ public async Task use_generic_action_as_delete()
[Fact]
public async Task use_generic_action_as_update()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
await Host.InvokeMessageAndWaitAsync(command);

await Host.InvokeMessageAndWaitAsync(new AlterTodo(command.Id, "New text", StorageAction.Update));
Expand All @@ -156,7 +163,7 @@ public async Task use_generic_action_as_update()
[Fact]
public async Task use_generic_action_as_store()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
await Host.InvokeMessageAndWaitAsync(command);

await Host.InvokeMessageAndWaitAsync(new AlterTodo(command.Id, "New text", StorageAction.Store));
Expand All @@ -167,7 +174,7 @@ public async Task use_generic_action_as_store()
[Fact]
public async Task do_nothing_as_generic_action()
{
var command = new CreateTodo(Guid.NewGuid(), "Write docs");
var command = new CreateTodo(Guid.NewGuid().ToString(), "Write docs");
await Host.InvokeMessageAndWaitAsync(command);

await Host.InvokeMessageAndWaitAsync(new AlterTodo(command.Id, "New text", StorageAction.Nothing));
Expand Down Expand Up @@ -195,23 +202,23 @@ public async Task do_nothing_if_generic_storage_action_is_null()

public class Todo
{
public Guid Id { get; set; }
public string Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

public record CreateTodo(Guid Id, string Name);
public record CreateTodo2(Guid Id, string Name);
public record CreateTodo(string Id, string Name);
public record CreateTodo2(string Id, string Name);

public record DeleteTodo(Guid Id);
public record DeleteTodo(string Id);

public record RenameTodo(Guid Id, string Name);
public record RenameTodo2(Guid TodoId, string Name);
public record RenameTodo3(Guid Identity, string Name);
public record RenameTodo(string Id, string Name);
public record RenameTodo2(string TodoId, string Name);
public record RenameTodo3(string Identity, string Name);

public record AlterTodo(Guid Id, string Name, StorageAction Action);
public record AlterTodo(string Id, string Name, StorageAction Action);

public record MaybeInsertTodo(Guid Id, string Name, bool ShouldInsert);
public record MaybeInsertTodo(string Id, string Name, bool ShouldInsert);

public record ReturnNullInsert;
public record ReturnNullStorageAction;
Expand Down

0 comments on commit 0bbf7e9

Please sign in to comment.