Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>AotExample.Contracts</RootNamespace>
<!-- Message types live here without handlers; handlers are in service projects -->
<NoWarn>$(NoWarn);MO0001</NoWarn>
</PropertyGroup>
<Import Project="..\..\..\src\Mocha.Analyzers\buildTransitive\Mocha.Analyzers.props" />
<ItemGroup>
<ProjectReference Include="..\..\..\src\Mocha\Mocha.csproj" />
<ProjectReference Include="..\..\..\src\Mocha.Mediator.Abstractions\Mocha.Mediator.Abstractions.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,4 @@
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.Mediator;

namespace AotExample.Contracts.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,8 @@
namespace AotExample.Contracts.Events;

public sealed class OrderPlacedEvent
{
public required string OrderId { get; init; }
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,8 @@
namespace AotExample.Contracts.Events;

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

public required string TrackingNumber { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Mocha.Mediator;

namespace AotExample.Contracts.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,8 @@
using Mocha.Mediator;

namespace AotExample.Contracts.Queries;

public sealed class GetOrderStatusQuery : IQuery<GetOrderStatusResponse>
{
public required string OrderId { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace AotExample.Contracts.Queries;

public sealed class GetOrderStatusResponse
{
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,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,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RootNamespace>AotExample.FulfillmentService</RootNamespace>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<Import Project="..\..\..\src\Mocha.Analyzers\buildTransitive\Mocha.Analyzers.props" />
<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,56 @@
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.LogInformation(
"Received order {OrderId}: {Quantity}x {ProductName}",
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.LogWarning(
"Cannot fulfill order {OrderId}: only {QuantityOnHand} of {ProductName} on hand",
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
},
cancellationToken);

logger.LogInformation(
"Order {OrderId} fulfilled — tracking {TrackingNumber}",
message.OrderId,
trackingNumber);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Mocha;
using Mocha.Transport.RabbitMQ;
using RabbitMQ.Client;

[assembly: MessagingModule("FulfillmentService")]

var builder = WebApplication.CreateBuilder(args);

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,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RootNamespace>AotExample.OrderService</RootNamespace>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<Import Project="..\..\..\src\Mocha.Analyzers\buildTransitive\Mocha.Analyzers.props" />
<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,31 @@
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.LogInformation(
"Inventory check for {ProductName}: {QuantityOnHand} on hand, requested {Quantity} — {Result}",
request.ProductName,
quantityOnHand,
request.Quantity,
isAvailable ? "available" : "insufficient");

return new ValueTask<CheckInventoryResponse>(
new CheckInventoryResponse
{
IsAvailable = isAvailable,
QuantityOnHand = quantityOnHand
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using AotExample.Contracts.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,30 @@
using AotExample.Contracts.Events;
using AotExample.Contracts.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.LogInformation(
"Order {OrderId} shipped with tracking {TrackingNumber}",
message.OrderId,
message.TrackingNumber);

await publisher.PublishAsync(
new OrderStatusChangedNotification
{
OrderId = message.OrderId,
Status = "Shipped"
},
cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using AotExample.Contracts.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.LogInformation(
"Order {OrderId} status changed to {Status}",
notification.OrderId,
notification.Status);

return ValueTask.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using AotExample.Contracts.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.LogInformation(
"Order {OrderId} placed: {Quantity}x {ProductName}",
orderId,
command.Quantity,
command.ProductName);

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