Skip to content

Commit fb8b0ce

Browse files
committed
evolution
1 parent 61d22a3 commit fb8b0ce

File tree

11 files changed

+234
-34
lines changed

11 files changed

+234
-34
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace RoyalCode.Events.Outbox.Abstractions;
2+
3+
/// <summary>
4+
/// Exception for when an event is not configured to be integrated into the Outbox.
5+
/// </summary>
6+
public sealed class MessateTypeNotConfiguredException(string messageType)
7+
: InvalidOperationException($"The Type {messageType} has not been configured, you need to configure the type before writing or reading a message.")
8+
{
9+
/// <summary>
10+
/// Creates a new exception.
11+
/// </summary>
12+
/// <param name="messageType">The message type.</param>
13+
public MessateTypeNotConfiguredException(Type messageType)
14+
: this(messageType.Name)
15+
{ }
16+
}

RoyalCode.EnterprisePatterns/RoyalCode.Events.Outbox.Abstractions/Options/OutboxOptions.cs

+46-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Text.Json;
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization.Metadata;
24

35
namespace RoyalCode.Events.Outbox.Abstractions.Options;
46

@@ -10,7 +12,7 @@ public class OutboxOptions
1012
/// <summary>
1113
/// The message types configured, with the serialisation metadata.
1214
/// </summary>
13-
public Dictionary<Type, TypeMetadata> Types { get; } = new();
15+
public Dictionary<Type, TypeMetadata> Types { get; } = [];
1416

1517
/// <summary>
1618
/// The standard serialisation options.
@@ -40,4 +42,46 @@ public OutboxOptions AddMessageType<T>(string typeName, JsonSerializerOptions? o
4042

4143
return this;
4244
}
45+
46+
/// <summary>
47+
/// Set up a new message type.
48+
/// </summary>
49+
/// <typeparam name="T">The type of message.</typeparam>
50+
/// <param name="typeName">Name of the message type.</param>
51+
/// <param name="typeInfo">Json type info for serialization.</param>
52+
/// <returns>The same instance of <see cref="OutboxOptions"/>.</returns>
53+
public OutboxOptions AddMessageType<T>(string typeName, JsonTypeInfo? typeInfo)
54+
{
55+
Types.Add(typeof(T), new TypeMetadata
56+
{
57+
PayloadType = typeof(T),
58+
TypeName = typeName,
59+
Version = 1,
60+
JsonTypeInfo = typeInfo,
61+
});
62+
63+
return this;
64+
}
65+
66+
/// <summary>
67+
/// Try to get the metadata of a type.
68+
/// </summary>
69+
/// <typeparam name="T">The type of event.</typeparam>
70+
/// <param name="metadata">Output, metadata.</param>
71+
/// <returns>True if it exists, false if it is not configured.</returns>
72+
public bool TryGetMetadata<T>([NotNullWhen(true)] out TypeMetadata? metadata)
73+
{
74+
return Types.TryGetValue(typeof(T), out metadata);
75+
}
76+
77+
/// <summary>
78+
/// Try to get the metadata of a type.
79+
/// </summary>
80+
/// <param name="type">The type of event.</param>
81+
/// <param name="metadata">Output, metadata.</param>
82+
/// <returns>True if it exists, false if it is not configured.</returns>
83+
public bool TryGetMetadata(Type type, [NotNullWhen(true)] out TypeMetadata? metadata)
84+
{
85+
return Types.TryGetValue(type, out metadata);
86+
}
4387
}

RoyalCode.EnterprisePatterns/RoyalCode.Events.Outbox.Abstractions/Options/TypeMetadata.cs

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.Json;
2+
using System.Text.Json.Serialization.Metadata;
23

34
namespace RoyalCode.Events.Outbox.Abstractions.Options;
45

@@ -26,4 +27,9 @@ public class TypeMetadata
2627
/// Optional serialisation options.
2728
/// </summary>
2829
public JsonSerializerOptions? SerializerOptions { get; set; }
30+
31+
/// <summary>
32+
/// Optional, serialization json type.
33+
/// </summary>
34+
public JsonTypeInfo? JsonTypeInfo { get; set; }
2935
}

RoyalCode.EnterprisePatterns/RoyalCode.Events.Outbox.Abstractions/Services/Defaults/Extensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public static void AddOutboxDefaultCoreServices(this IServiceCollection services
1919
if (services.Any(d => d.ServiceType == typeof(IOutboxService)))
2020
return;
2121

22-
services.AddTransient<IOutboxService, OutboxService>();
22+
services.AddTransient<IOutboxService, OutboxServiceBase>();
2323
services.AddTransient<IMessageDispatcher, MessageDispatcher>();
2424
services.AddTransient(typeof(MessageDispatcher<>));
2525
}

RoyalCode.EnterprisePatterns/RoyalCode.Events.Outbox.Abstractions/Services/Defaults/OutboxService.cs renamed to RoyalCode.EnterprisePatterns/RoyalCode.Events.Outbox.Abstractions/Services/Defaults/OutboxServiceBase.cs

+18-18
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,31 @@
88
namespace RoyalCode.Events.Outbox.Abstractions.Services.Defaults;
99

1010
/// <summary>
11-
/// Default implementation for <see cref="IOutboxService"/>.
11+
/// Abstract implementation for <see cref="IOutboxService"/>.
1212
/// </summary>
13-
public sealed class OutboxService : IOutboxService
13+
public abstract class OutboxServiceBase : IOutboxService
1414
{
1515
private readonly OutboxOptions options;
16-
private readonly ICreateMessageHandler createMessageHandler;
1716
private readonly IMessageDispatcher dispatcher;
1817

1918
/// <summary>
20-
/// Creates a new instance of <see cref="OutboxService"/>.
19+
/// Creates a new instance of <see cref="OutboxServiceBase"/>.
2120
/// </summary>
2221
/// <param name="options">The outbox options.</param>
23-
/// <param name="createMessageHandler">The handler to create new messages.</param>
2422
/// <param name="dispatcher">The dispatcher of messagens.</param>
25-
public OutboxService(
23+
protected OutboxServiceBase(
2624
IOptions<OutboxOptions> options,
27-
ICreateMessageHandler createMessageHandler,
2825
IMessageDispatcher dispatcher)
2926
{
3027
this.options = options.Value;
31-
this.createMessageHandler = createMessageHandler;
3228
this.dispatcher = dispatcher;
3329
}
3430

31+
/// <summary>
32+
/// The handler to create new messages.
33+
/// </summary>
34+
protected abstract ICreateMessageHandler CreateMessageHandler { get; }
35+
3536
/// <inheritdoc />
3637
public void Write(object message)
3738
{
@@ -40,12 +41,11 @@ public void Write(object message)
4041
var messageType = message.GetType();
4142

4243
if (!options.Types.TryGetValue(messageType, out var metadata))
43-
throw new InvalidOperationException(
44-
$"O Tipo {messageType.Name} não foi configurado, é necessário configurar o tipo antes de escrever uma mensagem.");
44+
throw new MessateTypeNotConfiguredException(messageType);
4545

46-
var settings = metadata.SerializerOptions ?? options.SerializerOptions;
47-
48-
var json = JsonSerializer.Serialize(message, settings);
46+
var json = metadata.JsonTypeInfo is not null
47+
? JsonSerializer.Serialize(metadata, metadata.JsonTypeInfo)
48+
: JsonSerializer.Serialize(message, metadata.SerializerOptions ?? options.SerializerOptions);
4949

5050
var request = new CreateMessage()
5151
{
@@ -54,7 +54,7 @@ public void Write(object message)
5454
Payload = json,
5555
};
5656

57-
createMessageHandler.Handle(request).EnsureSuccess();
57+
CreateMessageHandler.Handle(request).EnsureSuccess();
5858
}
5959

6060
/// <inheritdoc />
@@ -66,11 +66,11 @@ public async Task DispatchAsync(IEnumerable<OutboxMessage> messages, Cancellatio
6666
.FirstOrDefault(
6767
x => x.TypeName == message.MessageType
6868
&& x.Version == message.VersionType)
69-
?? throw new InvalidOperationException($"O tipo {message.MessageType} não foi encontrado.");
70-
71-
var settings = metadata.SerializerOptions ?? options.SerializerOptions;
69+
?? throw new MessateTypeNotConfiguredException(message.MessageType);
7270

73-
var payload = JsonSerializer.Deserialize(message.Payload, metadata.PayloadType, settings)!;
71+
var payload = metadata.JsonTypeInfo is not null
72+
? JsonSerializer.Deserialize(message.Payload, metadata.JsonTypeInfo)!
73+
: JsonSerializer.Deserialize(message.Payload, metadata.PayloadType, metadata.SerializerOptions ?? options.SerializerOptions)!;
7474

7575
await dispatcher.DispatchAsync(payload, ct);
7676
}

RoyalCode.EnterprisePatterns/RoyalCode.Events.Outbox.EntityFramework/Extensions/OutboxServiceCollectionExtensions.cs

+17-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using RoyalCode.Events.Outbox.Abstractions.Services.Defaults;
66
using RoyalCode.Events.Outbox.Abstractions.Services;
77
using RoyalCode.Events.Outbox.EntityFramework.Services.Handlers;
8+
using RoyalCode.Events.Outbox.EntityFramework.Services;
89

910
namespace Microsoft.Extensions.DependencyInjection;
1011

@@ -24,17 +25,27 @@ public static IServiceCollection AddOutboxServices<TDbContext>(this IServiceColl
2425
{
2526
ArgumentNullException.ThrowIfNull(services);
2627

27-
if (services.Any(d => d.ServiceType == typeof(OutboxService)))
28-
return services;
28+
services.AddOutboxServicesCore();
2929

30-
services.AddTransient<IMessageDispatcher, MessageDispatcher>();
31-
services.AddTransient(typeof(MessageDispatcher<>));
32-
services.AddTransient<OutboxService>();
30+
services.AddTransient(typeof(EventsUtils<TDbContext>));
3331

3432
services.AddTransient<IRegisterConsumerHandler, RegisterConsumerHandler<TDbContext>>();
3533
services.AddTransient<IGetMessagesHandler, GetMessagesHandler<TDbContext>>();
3634
services.AddTransient<ICommitConsumedHandler, CommitConsumedHandler<TDbContext>>();
37-
services.AddTransient<ICreateMessageHandler, CreateMessageHandler<TDbContext>>();
35+
36+
return services;
37+
}
38+
39+
public static IServiceCollection AddOutboxServicesCore(this IServiceCollection services)
40+
{
41+
ArgumentNullException.ThrowIfNull(services);
42+
43+
if (services.Any(d => d.ServiceType == typeof(OutboxServiceFactory)))
44+
return services;
45+
46+
services.AddTransient<IMessageDispatcher, MessageDispatcher>();
47+
services.AddTransient(typeof(MessageDispatcher<>));
48+
services.AddTransient<OutboxServiceFactory>();
3849

3950
return services;
4051
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.Options;
3+
using RoyalCode.Events.Outbox.Abstractions;
4+
using RoyalCode.Events.Outbox.Abstractions.Models;
5+
using RoyalCode.Events.Outbox.Abstractions.Options;
6+
using System.Text.Json;
7+
8+
namespace RoyalCode.Events.Outbox.EntityFramework.Services;
9+
10+
/// <summary>
11+
/// Utility for working with Outbox events.
12+
/// </summary>
13+
/// <typeparam name="TDbContext"></typeparam>
14+
public sealed class EventsUtils<TDbContext>
15+
where TDbContext: DbContext
16+
{
17+
private readonly OutboxOptions options;
18+
private readonly TDbContext dbContext;
19+
20+
/// <summary>
21+
/// Creates a new instance of <see cref="EventsUtils{TDbContext}"/>.
22+
/// </summary>
23+
/// <param name="dbContext">The DbContext.</param>
24+
/// <param name="options">The outbox options.</param>
25+
public EventsUtils(
26+
TDbContext dbContext,
27+
IOptions<OutboxOptions> options)
28+
{
29+
this.options = options.Value;
30+
this.dbContext = dbContext;
31+
}
32+
33+
/// <summary>
34+
/// Gets all the outbox events of the type specified by <typeparamref name="TEvent"/>.
35+
/// </summary>
36+
/// <typeparam name="TEvent">The event type.</typeparam>
37+
/// <returns>All events from the outbox.</returns>
38+
/// <exception cref="InvalidOperationException">
39+
/// If <typeparamref name="TEvent"/> not configured.
40+
/// </exception>
41+
public IEnumerable<TEvent> GetAll<TEvent>()
42+
{
43+
if (!options.TryGetMetadata<TEvent>(out var metadata))
44+
throw new MessateTypeNotConfiguredException(typeof(TEvent));
45+
46+
return dbContext.Set<OutboxMessage>()
47+
.Where(m => m.MessageType == metadata.TypeName)
48+
.Select(m => m.Payload)
49+
.AsEnumerable()
50+
.Select(payload =>
51+
{
52+
return (TEvent) (metadata.JsonTypeInfo is not null
53+
? JsonSerializer.Deserialize(payload, metadata.JsonTypeInfo)!
54+
: JsonSerializer.Deserialize(payload, metadata.PayloadType, metadata.SerializerOptions ?? options.SerializerOptions)!);
55+
})
56+
.ToList();
57+
}
58+
59+
/// <summary>
60+
/// Gets the last outbox event of a specific type <typeparamref name="TEvent"/>.
61+
/// </summary>
62+
/// <typeparam name="TEvent">The event type.</typeparam>
63+
/// <returns>The last event of the type <typeparamref name="TEvent"/> from the outbox.</returns>
64+
/// <exception cref="InvalidOperationException">
65+
/// If <typeparamref name="TEvent"/> not configured.
66+
/// </exception>
67+
public TEvent? GetLast<TEvent>()
68+
{
69+
var all = GetAll<TEvent>();
70+
return all.LastOrDefault();
71+
}
72+
}

RoyalCode.EnterprisePatterns/RoyalCode.Events.Outbox.EntityFramework/Services/Handlers/CreateMessageHandler.cs

+4-6
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@
77
namespace RoyalCode.Events.Outbox.EntityFramework.Services.Handlers;
88

99
/// <inheritdoc />
10-
/// <typeparam name="TDbContext">The <see cref="DbContext"/> type.</typeparam>
11-
public sealed class CreateMessageHandler<TDbContext> : ICreateMessageHandler
12-
where TDbContext : DbContext
10+
public sealed class CreateMessageHandler : ICreateMessageHandler
1311
{
14-
private readonly TDbContext dbContext;
12+
private readonly DbContext dbContext;
1513

1614
/// <summary>
17-
/// Cria novo handler com o <see cref="DbContext"/> com a configuração da entidade <see cref="OutboxMessage"/>;
15+
/// Create a new handler with the <see cref="DbContext"/> with the configuration of the <see cref="OutboxMessage"/> entity;
1816
/// </summary>
1917
/// <param name="dbContext">The <see cref="DbContext"/>.</param>
20-
public CreateMessageHandler(TDbContext dbContext)
18+
public CreateMessageHandler(DbContext dbContext)
2119
{
2220
this.dbContext = dbContext;
2321
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.Options;
3+
using RoyalCode.Events.Outbox.Abstractions.Contracts.Handlers;
4+
using RoyalCode.Events.Outbox.Abstractions.Options;
5+
using RoyalCode.Events.Outbox.Abstractions.Services;
6+
using RoyalCode.Events.Outbox.Abstractions.Services.Defaults;
7+
using RoyalCode.Events.Outbox.EntityFramework.Services.Handlers;
8+
9+
namespace RoyalCode.Events.Outbox.EntityFramework.Services;
10+
11+
/// <summary>
12+
/// Default implementation for <see cref="IOutboxService"/>.
13+
/// </summary>
14+
public sealed class OutboxService : OutboxServiceBase
15+
{
16+
/// <summary>
17+
/// Creates a new instance of <see cref="OutboxService"/>.
18+
/// </summary>
19+
/// <param name="db">The DbContext used to write the messages to the outbox entity.</param>
20+
/// <param name="options">The outbox options.</param>
21+
/// <param name="dispatcher">The dispatcher of messagens.</param>
22+
public OutboxService(DbContext db, IOptions<OutboxOptions> options, IMessageDispatcher dispatcher)
23+
: base(options, dispatcher)
24+
{
25+
CreateMessageHandler = new CreateMessageHandler(db);
26+
}
27+
28+
/// <inheritdoc />
29+
protected override ICreateMessageHandler CreateMessageHandler { get; }
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.Options;
3+
using RoyalCode.Events.Outbox.Abstractions.Options;
4+
using RoyalCode.Events.Outbox.Abstractions.Services;
5+
6+
namespace RoyalCode.Events.Outbox.EntityFramework.Services;
7+
8+
/// <summary>
9+
/// A factory to create instances of <see cref="IOutboxService"/> with the <see cref="OutboxService"/> type
10+
/// and a <see cref="DbContext"/>.
11+
/// </summary>
12+
/// <param name="options">The outbox options.</param>
13+
/// <param name="dispatcher">The dispatcher of messagens.</param>
14+
public sealed class OutboxServiceFactory(IOptions<OutboxOptions> options, IMessageDispatcher dispatcher)
15+
{
16+
17+
/// <summary>
18+
/// Creates a new <see cref="OutboxService"/> using the <paramref name="db"/>.
19+
/// </summary>
20+
/// <param name="db">The DbContext used to write the messages to the outbox entity.</param>
21+
/// <returns>A new <see cref="OutboxService"/></returns>
22+
public OutboxService CreateOutboxService(DbContext db) => new OutboxService(db, options, dispatcher);
23+
}

RoyalCode.EnterprisePatterns/RoyalCode.Events.Outbox.EntityFramework/Services/OutboxTracker.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public sealed class OutboxTracker : IDisposable
2222
/// <returns>A new instance of <see cref="OutboxTracker"/>.</returns>
2323
public static OutboxTracker Initialize(DbContext dbContext)
2424
{
25-
return new(dbContext, dbContext.GetService<IOutboxService>);
25+
return new(dbContext, () => dbContext.GetService<OutboxServiceFactory>().CreateOutboxService(dbContext));
2626
}
2727

2828
/// <summary>

0 commit comments

Comments
 (0)