Skip to content

Commit

Permalink
Added idempotency exercises
Browse files Browse the repository at this point in the history
  • Loading branch information
oskardudycz committed Sep 4, 2024
1 parent 31cdcf2 commit 3496f86
Show file tree
Hide file tree
Showing 64 changed files with 4,671 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,25 @@

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>_04_Idempotency</RootNamespace>
<RootNamespace>Idempotency</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.1"/>
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0"/>
<PackageReference Include="Ogooreck" Version="0.8.2"/>
<PackageReference Include="GitHubActionsTestLogger" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.9.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.8"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Concurrent;

namespace Idempotency.Core;

public class CommandBus
{
public async ValueTask Send(object[] commands, CancellationToken ct)
{
foreach (var command in commands)
{
if (!commandHandlers.TryGetValue(command.GetType(), out var handler))
continue;

foreach (var middleware in middlewares)
middleware(command);

await handler(command, ct);
}
}

public CommandBus Handle<T>(Func<T, CancellationToken, ValueTask> eventHandler)
{
commandHandlers[typeof(T)] = (command, ct) => eventHandler((T)command, ct);

return this;
}

public void Use(Action<object> middleware) =>
middlewares.Add(middleware);

private readonly ConcurrentDictionary<Type, Func<object, CancellationToken, ValueTask>> commandHandlers = new();
private readonly List<Action<object>> middlewares = [];
}
32 changes: 32 additions & 0 deletions Workshops/EventDrivenArchitecture/04-Idempotency/Core/Database.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Text.Json;

namespace Idempotency.Core;

public class Database
{
private readonly Dictionary<string, object> storage = new();

public ValueTask Store<T>(Guid id, T obj, CancellationToken _) where T : class
{
storage[GetId<T>(id)] = obj;

return ValueTask.CompletedTask;
}

public ValueTask Delete<T>(Guid id, CancellationToken _)
{
storage.Remove(GetId<T>(id));
return ValueTask.CompletedTask;
}

public ValueTask<T?> Get<T>(Guid id, CancellationToken _) where T : class =>
ValueTask.FromResult(
storage.TryGetValue(GetId<T>(id), out var result)
?
// Clone to simulate getting new instance on loading
JsonSerializer.Deserialize<T>(JsonSerializer.Serialize((T)result))
: null
);

private static string GetId<T>(Guid id) => $"{typeof(T).Name}-{id}";
}
44 changes: 44 additions & 0 deletions Workshops/EventDrivenArchitecture/04-Idempotency/Core/EventBus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Collections.Concurrent;

namespace Idempotency.Core;

public class EventBus
{
public async ValueTask Publish(object[] events, CancellationToken ct)
{
foreach (var @event in events)
{
foreach (var middleware in middlewares)
middleware(@event);

if (!eventHandlers.TryGetValue(@event.GetType(), out var handlers))
continue;

foreach (var handler in handlers)
await handler(@event, ct);
}
}

public EventBus Subscribe<T>(Func<T, CancellationToken, ValueTask> eventHandler)
{
Func<object, CancellationToken, ValueTask> handler = (@event, ct) => eventHandler((T)@event, ct);

eventHandlers.AddOrUpdate(
typeof(T),
_ => [handler],
(_, handlers) =>
{
handlers.Add(handler);
return handlers;
}
);

return this;
}

public void Use(Action<object> middleware) =>
middlewares.Add(middleware);

private readonly ConcurrentDictionary<Type, List<Func<object, CancellationToken, ValueTask>>> eventHandlers = new();
private readonly List<Action<object>> middlewares = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using FluentAssertions;

namespace Idempotency.Core;

public class MessageCatcher
{
public List<object> Published { get; } = [];

public void Catch(object message) =>
Published.Add(message);

public void Reset() => Published.Clear();

public void ShouldNotReceiveAnyMessage() =>
Published.Should().BeEmpty();

public void ShouldReceiveSingleMessage<T>(T message)
{
Published.Should().HaveCount(1);
Published.OfType<T>().Should().HaveCount(1);
Published.Single().Should().Be(message);
}

public void ShouldReceiveMessages(object[] messages) =>
Published.Should().BeEquivalentTo(messages);
}
Loading

0 comments on commit 3496f86

Please sign in to comment.