Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b7f78b1
Adds AOT support to mocha
PascalSenn Apr 6, 2026
54dd88b
cleanup
PascalSenn Apr 6, 2026
95292ee
cleanup
PascalSenn Apr 6, 2026
bac5554
cleanup
PascalSenn Apr 7, 2026
2317754
Refactor logging methods to use ILogger extension methods and improve…
PascalSenn Apr 7, 2026
0f84eb3
Refactor messaging options to support AOT compatibility
PascalSenn Apr 7, 2026
aceaeac
Refactor saga state serializer handling for improved clarity and AOT …
PascalSenn Apr 7, 2026
4e59596
Refactor MessageBusBuilder and MessageTypeDescriptor for improved AOT…
PascalSenn Apr 7, 2026
19fd07e
Enhance AOT compatibility by refining generic type constraints and im…
PascalSenn Apr 7, 2026
64730df
Add diagnostic for open generic handlers and enhance AOT compatibility
PascalSenn Apr 7, 2026
c18f2ab
cleanup
PascalSenn Apr 9, 2026
c0ea569
Enhance documentation for CallSiteMessageTypeInspector and ImportedMo…
PascalSenn Apr 9, 2026
3e2cc97
makes mocha aot compatbile
PascalSenn Apr 9, 2026
6ffa939
cleanup
PascalSenn Apr 18, 2026
a811533
cleanup
PascalSenn Apr 18, 2026
f470080
cleanup
PascalSenn Apr 18, 2026
565ace4
more
PascalSenn May 6, 2026
52f00ef
Merge origin/main into pse/make-mocha-aot-compatible
PascalSenn Jun 28, 2026
0fd9adc
Refactor analyzers and generators to improve type handling and remove…
PascalSenn Jun 28, 2026
9d6940d
Enhance AOT compatibility and improve diagnostics in messaging framework
PascalSenn Jun 28, 2026
0636be8
Fix typo in diagnostics documentation for open generic handler regist…
PascalSenn Jun 29, 2026
4bdb91b
Add AOT compatibility properties to Directory.Build.props
PascalSenn Jun 29, 2026
d5a2dae
Merge branch 'main' into pse/make-mocha-aot-compatible
PascalSenn Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .codex
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>AotExample.Contracts</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Mocha\Mocha.csproj" />
<ProjectReference
Include="..\..\..\src\Mocha.Analyzers\Mocha.Analyzers.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using AotExample.Contracts.Events;
using AotExample.Contracts.Requests;

namespace AotExample.Contracts;

[JsonSerializable(typeof(OrderPlacedEvent))]
[JsonSerializable(typeof(OrderShippedEvent))]
[JsonSerializable(typeof(CheckInventoryRequest))]
[JsonSerializable(typeof(CheckInventoryResponse))]
public partial class AotExampleJsonContext : JsonSerializerContext;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using AotExample.Contracts;
using Mocha;

[assembly: MessagingModule(
"AotExampleContracts",
JsonContext = typeof(AotExampleJsonContext))]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Mocha.Sagas;

namespace AotExample.Contracts.Events;

public sealed class OrderPlacedEvent : ICorrelatable
{
public required string OrderId { get; init; }

public required string ProductName { get; init; }

public required int Quantity { get; init; }

public Guid? CorrelationId { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Mocha.Sagas;

namespace AotExample.Contracts.Events;

public sealed class OrderShippedEvent : ICorrelatable
{
public required string OrderId { get; init; }

public required string TrackingNumber { get; init; }

public Guid? CorrelationId { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Mocha;

namespace AotExample.Contracts.Requests;

public sealed class CheckInventoryRequest : IEventRequest<CheckInventoryResponse>
{
public required string ProductName { get; init; }
public required int Quantity { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace AotExample.Contracts.Requests;

public sealed class CheckInventoryResponse
{
public required bool IsAvailable { get; init; }
public required int QuantityOnHand { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RootNamespace>AotExample.FulfillmentService</RootNamespace>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AotExample.Contracts\AotExample.Contracts.csproj" />
<ProjectReference Include="..\..\..\src\Mocha\Mocha.csproj" />
<ProjectReference Include="..\..\..\src\Mocha.Transport.RabbitMQ\Mocha.Transport.RabbitMQ.csproj" />
<ProjectReference
Include="..\..\..\src\Mocha.Analyzers\Mocha.Analyzers.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using AotExample.Contracts.Events;
using AotExample.Contracts.Requests;
using Mocha;

namespace AotExample.FulfillmentService.Handlers;

public sealed class OrderPlacedHandler(IMessageBus messageBus, ILogger<OrderPlacedHandler> logger)
: IEventHandler<OrderPlacedEvent>
{
public async ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken)
{
logger.LogOrderReceived(message.OrderId, message.Quantity, message.ProductName);

// Check inventory with OrderService via bus request/response
var inventory = await messageBus.RequestAsync(
new CheckInventoryRequest { ProductName = message.ProductName, Quantity = message.Quantity },
cancellationToken);

if (!inventory.IsAvailable)
{
logger.LogCannotFulfillOrder(message.OrderId, inventory.QuantityOnHand, message.ProductName);
return;
}

var trackingNumber = $"TRK-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";

await messageBus.PublishAsync(
new OrderShippedEvent
{
OrderId = message.OrderId,
TrackingNumber = trackingNumber,
CorrelationId = message.CorrelationId
},
cancellationToken);

logger.LogOrderFulfilled(message.OrderId, trackingNumber);
}
}

internal static partial class Logs
{
[LoggerMessage(Level = LogLevel.Information, Message = "Received order {OrderId}: {Quantity}x {ProductName}")]
public static partial void LogOrderReceived(this ILogger logger, string orderId, int quantity, string productName);

[LoggerMessage(Level = LogLevel.Warning, Message = "Cannot fulfill order {OrderId}: only {QuantityOnHand} of {ProductName} on hand")]
public static partial void LogCannotFulfillOrder(this ILogger logger, string orderId, int quantityOnHand, string productName);

[LoggerMessage(Level = LogLevel.Information, Message = "Order {OrderId} fulfilled - tracking {TrackingNumber}")]
public static partial void LogOrderFulfilled(this ILogger logger, string orderId, string trackingNumber);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Mocha;
using Mocha.Transport.RabbitMQ;
using RabbitMQ.Client;

[assembly: MessagingModule("FulfillmentService")]

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseUrls("http://localhost:52310");

builder.Services.AddSingleton<IConnectionFactory>(
new ConnectionFactory { HostName = "localhost", Port = 5673 });

builder.Services.AddMessageBus().AddAotExampleContracts().AddFulfillmentService().AddRabbitMQ();

var app = builder.Build();

app.MapGet("/", () => "Fulfillment Service (AOT Example)");

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"profiles": {
"AotExample.FulfillmentService": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5247",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RootNamespace>AotExample.OrderService</RootNamespace>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AotExample.Contracts\AotExample.Contracts.csproj" />
<ProjectReference Include="..\..\..\src\Mocha\Mocha.csproj" />
<ProjectReference Include="..\..\..\src\Mocha.Abstractions\Mocha.Abstractions.csproj" />
<ProjectReference Include="..\..\..\src\Mocha.Mediator\Mocha.Mediator.csproj" />
<ProjectReference Include="..\..\..\src\Mocha.Transport.RabbitMQ\Mocha.Transport.RabbitMQ.csproj" />
<ProjectReference
Include="..\..\..\src\Mocha.Analyzers\Mocha.Analyzers.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Mocha.Mediator;

namespace AotExample.OrderService.Commands;

public sealed class PlaceOrderCommand : ICommand<PlaceOrderResult>
{
public required string ProductName { get; init; }
public required int Quantity { get; init; }
}

public sealed class PlaceOrderResult
{
public required string OrderId { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using AotExample.Contracts.Requests;
using Mocha;

namespace AotExample.OrderService.Handlers;

public sealed class CheckInventoryRequestHandler(ILogger<CheckInventoryRequestHandler> logger)
: IEventRequestHandler<CheckInventoryRequest, CheckInventoryResponse>
{
public ValueTask<CheckInventoryResponse> HandleAsync(
CheckInventoryRequest request,
CancellationToken cancellationToken)
{
var quantityOnHand = Random.Shared.Next(0, 20);
var isAvailable = quantityOnHand >= request.Quantity;

logger.LogInventoryCheck(
request.ProductName,
quantityOnHand,
request.Quantity,
isAvailable ? "available" : "insufficient");

return new ValueTask<CheckInventoryResponse>(
new CheckInventoryResponse { IsAvailable = isAvailable, QuantityOnHand = quantityOnHand });
}
}

internal static partial class Logs
{
[LoggerMessage(
Level = LogLevel.Information,
Message = "Inventory check for {ProductName}: {QuantityOnHand} on hand, requested {Quantity} - {Result}")]
public static partial void LogInventoryCheck(
this ILogger logger,
string productName,
int quantityOnHand,
int quantity,
string result);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using AotExample.OrderService.Queries;
using Mocha.Mediator;

namespace AotExample.OrderService.Handlers;

public sealed class GetOrderStatusQueryHandler
: IQueryHandler<GetOrderStatusQuery, GetOrderStatusResponse>
{
public ValueTask<GetOrderStatusResponse> HandleAsync(
GetOrderStatusQuery query,
CancellationToken cancellationToken)
{
return new ValueTask<GetOrderStatusResponse>(
new GetOrderStatusResponse
{
OrderId = query.OrderId,
Status = "Processing"
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using AotExample.Contracts.Events;
using AotExample.OrderService.Notifications;
using Mocha;
using Mocha.Mediator;

namespace AotExample.OrderService.Handlers;

public sealed class OrderShippedHandler(IPublisher publisher, ILogger<OrderShippedHandler> logger)
: IEventHandler<OrderShippedEvent>
{
public async ValueTask HandleAsync(OrderShippedEvent message, CancellationToken cancellationToken)
{
logger.LogOrderShipped(message.OrderId, message.TrackingNumber);

await publisher.PublishAsync(
new OrderStatusChangedNotification { OrderId = message.OrderId, Status = "Shipped" },
cancellationToken);
}
}

internal static partial class Logs
{
[LoggerMessage(Level = LogLevel.Information, Message = "Order {OrderId} shipped with tracking {TrackingNumber}")]
public static partial void LogOrderShipped(this ILogger logger, string orderId, string trackingNumber);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using AotExample.OrderService.Notifications;
using Mocha.Mediator;

namespace AotExample.OrderService.Handlers;

public sealed class OrderStatusChangedHandler(ILogger<OrderStatusChangedHandler> logger)
: INotificationHandler<OrderStatusChangedNotification>
{
public ValueTask HandleAsync(OrderStatusChangedNotification notification, CancellationToken cancellationToken)
{
logger.LogOrderStatusChanged(notification.OrderId, notification.Status);

return ValueTask.CompletedTask;
}
}

internal static partial class Logs
{
[LoggerMessage(Level = LogLevel.Information, Message = "Order {OrderId} status changed to {Status}")]
public static partial void LogOrderStatusChanged(this ILogger logger, string orderId, string status);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using AotExample.OrderService.Commands;
using Mocha.Mediator;

namespace AotExample.OrderService.Handlers;

public sealed class PlaceOrderCommandHandler(ILogger<PlaceOrderCommandHandler> logger)
: ICommandHandler<PlaceOrderCommand, PlaceOrderResult>
{
public ValueTask<PlaceOrderResult> HandleAsync(PlaceOrderCommand command, CancellationToken cancellationToken)
{
var orderId = Guid.NewGuid().ToString("N")[..8];

logger.LogOrderPlaced(orderId, command.Quantity, command.ProductName);

return new ValueTask<PlaceOrderResult>(new PlaceOrderResult { OrderId = orderId });
}
}

internal static partial class Logs
{
[LoggerMessage(Level = LogLevel.Information, Message = "Order {OrderId} placed: {Quantity}x {ProductName}")]
public static partial void LogOrderPlaced(this ILogger logger, string orderId, int quantity, string productName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Mocha.Mediator;

namespace AotExample.OrderService.Notifications;

public sealed class OrderStatusChangedNotification : INotification
{
public required string OrderId { get; init; }

public required string Status { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.Text.Json.Serialization;
using AotExample.OrderService.Sagas;

namespace AotExample.OrderService;

[JsonSerializable(typeof(OrderSagaState))]
public partial class OrderServiceJsonContext : JsonSerializerContext;
Loading
Loading