Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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