Skip to content

Commit

Permalink
Implemented EventStoreDB Application Logic tests
Browse files Browse the repository at this point in the history
  • Loading branch information
oskardudycz committed May 2, 2024
1 parent b1adfe9 commit 333defa
Show file tree
Hide file tree
Showing 36 changed files with 554 additions and 485 deletions.
2 changes: 1 addition & 1 deletion Core/Configuration/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static T GetRequiredConfig<T>(this IConfiguration configuration, string c
$"{typeof(T).Name} configuration wasn't found for '${configurationKey}' key");

public static string GetRequiredConnectionString(this IConfiguration configuration, string configurationKey) =>
configuration.GetConnectionString("Incidents")
configuration.GetConnectionString(configurationKey)
?? throw new InvalidOperationException(
$"Configuration string with name '${configurationKey}' was not found");
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
using Oakton;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;

[assembly: TestFramework("ApplicationLogic.EventStoreDB.Tests.AssemblyFixture", "ApplicationLogic.EventStoreDB.Tests")]
[assembly: CollectionBehavior(DisableTestParallelization = true)]

namespace ApplicationLogic.EventStoreDB.Tests;

public sealed class AssemblyFixture : XunitTestFramework
{
public AssemblyFixture(IMessageSink messageSink)
:base(messageSink)
{
OaktonEnvironment.AutoStartHost = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ public Task CantAddProductItemToNotExistingShoppingCart(string apiPrefix) =>
)
.Then(NOT_FOUND);


[Theory]
[Trait("Category", "SkipCI")]
[InlineData("immutable")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Marten.CommandLine" Version="7.8.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Marten.AspNetCore" Version="7.8.0" />
<PackageReference Include="Marten" Version="7.8.0" />
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.2.1"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Core\Core.csproj" />
<ProjectReference Include="..\..\..\Core\Core.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ namespace ApplicationLogic.EventStoreDB.Core.Entities;

public interface IAggregate
{
public void Apply(object @event);
public void Evolve(object @event);

object[] DequeueUncommittedEvents();
}

public abstract class Aggregate<TEvent>: IAggregate
{
public Guid Id { get; protected set; } = default!;
public Guid Id { get; protected set; }

private readonly Queue<object> uncommittedEvents = new();
private readonly Queue<TEvent> uncommittedEvents = new();

protected virtual void Apply(TEvent @event) { }
public abstract void Evolve(TEvent @event);

public object[] DequeueUncommittedEvents()
public TEvent[] DequeueUncommittedEvents()
{
var dequeuedEvents = uncommittedEvents.ToArray();

Expand All @@ -24,16 +24,19 @@ public object[] DequeueUncommittedEvents()
return dequeuedEvents;
}

protected void Enqueue(object @event)
protected void Enqueue(TEvent @event)
{
uncommittedEvents.Enqueue(@event);
}

public void Apply(object @event)
public void Evolve(object @event)
{
if(@event is not TEvent typed)
return;

Apply(typed);
Evolve(typed);
}

object[] IAggregate.DequeueUncommittedEvents() =>
DequeueUncommittedEvents().Cast<object>().ToArray();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Text.Json;
using ApplicationLogic.EventStoreDB.Core.Entities;
using ApplicationLogic.EventStoreDB.Core.Exceptions;
using EventStore.Client;

namespace ApplicationLogic.EventStoreDB.Core.EventStoreDB;

public static class EventStoreDBExtensions
{
public static Task<T?> AggregateStream<T, TEvent>(
this EventStoreClient eventStore,
Func<T> getInitial,
Guid id,
CancellationToken ct = default
) where T : Aggregate<TEvent> =>
throw new NotImplementedException("EventStoreDB Extensions not implemented!");

public static Task<T?> AggregateStream<T, TEvent>(
this EventStoreClient eventStore,
Func<T, TEvent, T> evolve,
Func<T> getInitial,
Guid id,
CancellationToken cancellationToken = default
) where T : class =>
throw new NotImplementedException("EventStoreDB Extensions not implemented!");

public static Task Add<T>(this EventStoreClient eventStore, Guid id, T aggregate, CancellationToken ct)
where T : class, IAggregate =>
throw new NotImplementedException("EventStoreDB Extensions not implemented!");

public static Task Add<T>(this EventStoreClient eventStore, Guid id, object[] events, CancellationToken ct)
where T : class =>
throw new NotImplementedException("EventStoreDB Extensions not implemented!");

public static Task GetAndUpdate<T, TEvent>(
this EventStoreClient eventStore,
Func<T> getInitial,
Guid id,
Action<T> handle,
CancellationToken ct
)
where T : Aggregate<TEvent>
where TEvent: notnull =>
throw new NotImplementedException("EventStoreDB Extensions not implemented!");

public static Task GetAndUpdate<T, TEvent>(
this EventStoreClient eventStore,
Func<T, TEvent, T> evolve,
Func<T> getInitial,
Guid id,
Func<T, TEvent[]> handle,
CancellationToken ct
)
where T : class
where TEvent: notnull =>
throw new NotImplementedException("EventStoreDB Extensions not implemented!");
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using ApplicationLogic.EventStoreDB.Core.Marten;
using ApplicationLogic.EventStoreDB.Core.EventStoreDB;
using ApplicationLogic.EventStoreDB.Immutable.Pricing;
using Core.Validation;
using Marten;
using Marten.Schema.Identity;
using EventStore.Client;
using Microsoft.AspNetCore.Mvc;
using static Microsoft.AspNetCore.Http.TypedResults;
using static System.DateTimeOffset;
Expand All @@ -22,13 +21,13 @@ public static WebApplication ConfigureImmutableShoppingCarts(this WebApplication
var productItems = shoppingCart.MapGroup("product-items");

shoppingCarts.MapPost("",
async (IDocumentSession session,
async (EventStoreClient eventStore,
Guid clientId,
CancellationToken ct) =>
{
var shoppingCartId = CombGuidIdGeneration.NewGuid();
var shoppingCartId = Uuid.NewUuid().ToGuid();

await session.Add<ShoppingCart>(shoppingCartId,
await eventStore.Add<ShoppingCart>(shoppingCartId,
[Handle(new OpenShoppingCart(shoppingCartId, clientId.NotEmpty(), Now))], ct);

return Created($"/api/immutable/clients/{clientId}/shopping-carts/{shoppingCartId}", shoppingCartId);
Expand All @@ -38,14 +37,14 @@ await session.Add<ShoppingCart>(shoppingCartId,
productItems.MapPost("",
async (
IProductPriceCalculator pricingCalculator,
IDocumentSession session,
EventStoreClient eventStore,
Guid shoppingCartId,
AddProductRequest body,
CancellationToken ct) =>
{
var productItem = body.ProductItem.NotNull().ToProductItem();

await session.GetAndUpdate<ShoppingCart>(shoppingCartId,
await eventStore.GetAndUpdate(shoppingCartId,
state =>
[
Handle(pricingCalculator,
Expand All @@ -59,7 +58,7 @@ await session.GetAndUpdate<ShoppingCart>(shoppingCartId,

productItems.MapDelete("{productId:guid}",
async (
IDocumentSession session,
EventStoreClient eventStore,
Guid shoppingCartId,
[FromRoute] Guid productId,
[FromQuery] int? quantity,
Expand All @@ -72,7 +71,7 @@ await session.GetAndUpdate<ShoppingCart>(shoppingCartId,
unitPrice.NotNull().Positive()
);

await session.GetAndUpdate<ShoppingCart>(shoppingCartId,
await eventStore.GetAndUpdate(shoppingCartId,
state => [Handle(new RemoveProductItemFromShoppingCart(shoppingCartId, productItem, Now), state)],
ct);

Expand All @@ -81,23 +80,23 @@ await session.GetAndUpdate<ShoppingCart>(shoppingCartId,
);

shoppingCart.MapPost("confirm",
async (IDocumentSession session,
async (EventStoreClient eventStore,
Guid shoppingCartId,
CancellationToken ct) =>
{
await session.GetAndUpdate<ShoppingCart>(shoppingCartId,
await eventStore.GetAndUpdate(shoppingCartId,
state => [Handle(new ConfirmShoppingCart(shoppingCartId, Now), state)], ct);

return NoContent();
}
);

shoppingCart.MapDelete("",
async (IDocumentSession session,
async (EventStoreClient eventStore,
Guid shoppingCartId,
CancellationToken ct) =>
{
await session.GetAndUpdate<ShoppingCart>(shoppingCartId,
await eventStore.GetAndUpdate(shoppingCartId,
state => [Handle(new CancelShoppingCart(shoppingCartId, Now), state)], ct);

return NoContent();
Expand All @@ -106,11 +105,11 @@ await session.GetAndUpdate<ShoppingCart>(shoppingCartId,

shoppingCart.MapGet("",
async Task<IResult> (
IQuerySession session,
EventStoreClient eventStore,
Guid shoppingCartId,
CancellationToken ct) =>
{
var result = await session.Events.AggregateStreamAsync<ShoppingCart>(shoppingCartId, token: ct);
var result = await eventStore.GetShoppingCart(shoppingCartId, ct);

return result is not null ? Ok(result) : NotFound();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
using ApplicationLogic.EventStoreDB.Core.Marten;
using ApplicationLogic.EventStoreDB.Immutable.Pricing;
using Marten;
using EventStore.Client;
using ApplicationLogic.EventStoreDB.Core.EventStoreDB;

namespace ApplicationLogic.EventStoreDB.Immutable.ShoppingCarts;
using static ShoppingCartEvent;

public static class Configure
{
private const string ModulePrefix = "immutable";
public static IServiceCollection AddImmutableShoppingCarts(this IServiceCollection services)
{
public static IServiceCollection AddImmutableShoppingCarts(this IServiceCollection services) =>
services.AddSingleton<IProductPriceCalculator>(FakeProductPriceCalculator.Returning(100));
public static Task GetAndUpdate(
this EventStoreClient eventStore,
Guid id,
Func<ShoppingCart, ShoppingCartEvent[]> handle,
CancellationToken ct
) =>
eventStore.GetAndUpdate(
ShoppingCart.Evolve,
ShoppingCart.Initial,
id,
handle,
ct
);

return services;
}
public static StoreOptions ConfigureImmutableShoppingCarts(this StoreOptions options)
{
options.Projections.LiveStreamAggregation<ShoppingCart>();

// this is needed as we're sharing document store and have event types with the same name
options.MapEventWithPrefix<ShoppingCartOpened>(ModulePrefix);
options.MapEventWithPrefix<ProductItemAddedToShoppingCart>(ModulePrefix);
options.MapEventWithPrefix<ProductItemRemovedFromShoppingCart>(ModulePrefix);
options.MapEventWithPrefix<ShoppingCartConfirmed>(ModulePrefix);
options.MapEventWithPrefix<ShoppingCartCanceled>(ModulePrefix);

return options;
}
public static Task<ShoppingCart?> GetShoppingCart(
this EventStoreClient eventStore,
Guid id,
CancellationToken ct
) =>
eventStore.AggregateStream<ShoppingCart, ShoppingCartEvent>(
ShoppingCart.Evolve,
ShoppingCart.Initial,
id,
ct
);
}
Loading

0 comments on commit 333defa

Please sign in to comment.