diff --git a/OrderProcessing.Api.Tests/Infrastructure/CustomWebApplicationFactory.cs b/OrderProcessing.Api.Tests/Infrastructure/CustomWebApplicationFactory.cs index e31fcff..a2a8d4e 100644 --- a/OrderProcessing.Api.Tests/Infrastructure/CustomWebApplicationFactory.cs +++ b/OrderProcessing.Api.Tests/Infrastructure/CustomWebApplicationFactory.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestPlatform.TestHost; using OrderProcessing.Api.Data; +using OrderProcessing.Api.Features.Orders.Queries.ReadModel; using System.Data.Common; namespace OrderProcessing.Api.Tests.Infrastructure; @@ -22,8 +23,7 @@ public CustomWebApplicationFactory() _connection.Open(); } - protected override void ConfigureWebHost( - IWebHostBuilder builder) + protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Testing"); @@ -31,13 +31,11 @@ protected override void ConfigureWebHost( { // Remove the SQL Server DbContext registration // from the production application. - services.RemoveAll< - DbContextOptions>(); + services.RemoveAll>(); services.RemoveAll(); - services.RemoveAll< - IDbContextOptionsConfiguration>(); + services.RemoveAll>(); // All test DbContext instances use the same open // SQLite connection. @@ -51,6 +49,10 @@ protected override void ConfigureWebHost( options.UseSqlite(connection); }); + + services.RemoveAll(); + + services.AddSingleton(); }); } diff --git a/OrderProcessing.Api.Tests/Infrastructure/TestOrderReadModelReader.cs b/OrderProcessing.Api.Tests/Infrastructure/TestOrderReadModelReader.cs new file mode 100644 index 0000000..ed525a8 --- /dev/null +++ b/OrderProcessing.Api.Tests/Infrastructure/TestOrderReadModelReader.cs @@ -0,0 +1,61 @@ +using OrderProcessing.Api.Features.Orders.Queries.ReadModel; +using OrderProcessing.ReadModels.Orders; + +namespace OrderProcessing.Api.Tests.Infrastructure; + +public sealed class TestOrderReadModelReader : IOrderReadModelReader +{ + public const int ExistingOrderId = 88001; + + public Task GetByIdAsync(int orderId, CancellationToken cancellationToken) + { + if (orderId != ExistingOrderId) + { + return Task.FromResult(null); + } + + return Task.FromResult(new OrderReadModel + { + OrderId = ExistingOrderId, + CustomerId = + TestDataSeeder.CustomerId, + CustomerName = + "Integration Test Customer", + Status = "Pending", + TotalAmount = 24.99m, + CreatedAtUtc = + new DateTime( + 2026, + 8, + 1, + 10, + 0, + 0, + DateTimeKind.Utc), + + LastUpdatedAtUtc = + new DateTime( + 2026, + 8, + 1, + 10, + 0, + 0, + DateTimeKind.Utc), + + Items = + [ + new OrderItemReadModel + { + ProductId = + TestDataSeeder.ProductId, + ProductName = + "Integration Test Product", + Quantity = 1, + UnitPrice = 24.99m, + LineTotal = 24.99m + } + ] + }); + } +} \ No newline at end of file diff --git a/OrderProcessing.Api.Tests/Integration/ApiIntegrationTests.cs b/OrderProcessing.Api.Tests/Integration/ApiIntegrationTests.cs index d974a3c..26b284b 100644 --- a/OrderProcessing.Api.Tests/Integration/ApiIntegrationTests.cs +++ b/OrderProcessing.Api.Tests/Integration/ApiIntegrationTests.cs @@ -347,35 +347,6 @@ private async Task GetProductStockAsync( .SingleAsync(); } - //private async Task CreateTestOrderAsync( - //int quantity = 1, - //int customerId = TestDataSeeder.CustomerId) - //{ - // var request = new CreateOrderRequest - // { - // CustomerId = customerId, - // Items = - // [ - // new CreateOrderItemRequest - // { - // ProductId = TestDataSeeder.ProductId, - // Quantity = quantity - // } - // ] - // }; - - // var response = await Client.PostAsJsonAsync( - // "/api/orders", - // request); - - // response.EnsureSuccessStatusCode(); - - // return await response.Content - // .ReadFromJsonAsync() - // ?? throw new InvalidOperationException( - // "The create-order response was empty."); - //} - [Fact] public async Task CancelOrder_CreatesCancellationAuditLog() @@ -698,4 +669,28 @@ public async Task CancelOrder_PersistsOrderCancelledEventInOutbox() default, integrationEvent.CancelledAtUtc); } + + [Fact] + public async Task GetOrderById_WhenReadModelExists_ReturnsOrder() + { + var response = await Client.GetAsync( + $"/api/orders/" + + $"{TestOrderReadModelReader.ExistingOrderId}"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var order = await response.Content.ReadFromJsonAsync(); + + Assert.NotNull(order); + + Assert.Equal(TestOrderReadModelReader.ExistingOrderId, order.Id); + } + + [Fact] + public async Task GetOrderById_WhenReadModelDoesNotExist_ReturnsNotFound() + { + var response = await Client.GetAsync("/api/orders/999999"); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } } \ No newline at end of file diff --git a/OrderProcessing.Api.Tests/Unit/GetOrderByIdQueryHadlerTests.cs b/OrderProcessing.Api.Tests/Unit/GetOrderByIdQueryHadlerTests.cs new file mode 100644 index 0000000..85eaadb --- /dev/null +++ b/OrderProcessing.Api.Tests/Unit/GetOrderByIdQueryHadlerTests.cs @@ -0,0 +1,89 @@ +using OrderProcessing.Api.Entities; +using OrderProcessing.Api.Exceptions; +using OrderProcessing.Api.Features.Orders.Queries.GetOrderById; +using OrderProcessing.Api.Features.Orders.Queries.ReadModel; +using OrderProcessing.ReadModels.Orders; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderProcessing.Api.Tests.Unit +{ + public class GetOrderByIdQueryHadlerTests + { + + [Fact] + public async Task Handle_WhenOrderExists_ReturnsMappedResponse() + { + // Arrange + var readModel = new OrderReadModel + { + OrderId = 123, + CustomerId = 456, + CustomerName = "John Smith", + Status = "Completed", + TotalAmount = 99.99m, + CreatedAtUtc = DateTime.UtcNow.AddMinutes(-10), + CompletedAtUtc = DateTime.UtcNow, + LastUpdatedAtUtc = DateTime.UtcNow, + + Items = + [ + new OrderItemReadModel + { + ProductId = 10, + ProductName = "Keyboard", + Quantity = 1, + UnitPrice = 99.99m, + LineTotal = 99.99m + } + ] + }; + + var reader = new FakeOrderReadModelReader(readModel); + + var handler = new GetOrderByIdQueryHandler(reader); + + // Act + var result = await handler.Handle(new GetOrderByIdQuery(123), CancellationToken.None); + + // Assert + Assert.Equal(123, result.Id); + Assert.Equal(456, result.CustomerId); + Assert.Equal("John Smith", result.CustomerName); + Assert.Equal(OrderStatus.Completed, result.Status); + Assert.Equal(99.99m, result.TotalAmount); + Assert.Single(result.Items); + } + + [Fact] + public async Task Handle_WhenOrderDoesNotExist_ThrowsNotFoundException() + { + var handler = + new GetOrderByIdQueryHandler( + new FakeOrderReadModelReader(null)); + + var action = () => handler.Handle( + new GetOrderByIdQuery(999), + CancellationToken.None); + + await Assert.ThrowsAsync( + action); + } + + private sealed class FakeOrderReadModelReader : IOrderReadModelReader + { + private readonly OrderReadModel? _order; + + public FakeOrderReadModelReader(OrderReadModel? order) + { + _order = order; + } + + public Task GetByIdAsync(int orderId, CancellationToken cancellationToken) + { + return Task.FromResult(_order?.OrderId == orderId ? _order : null); + } + } + } +} diff --git a/OrderProcessing.Api/Configuration/MongoDbOptions.cs b/OrderProcessing.Api/Configuration/MongoDbOptions.cs new file mode 100644 index 0000000..1bc47c6 --- /dev/null +++ b/OrderProcessing.Api/Configuration/MongoDbOptions.cs @@ -0,0 +1,12 @@ +namespace OrderProcessing.Api.Configuration; + +public sealed class MongoDbOptions +{ + public const string SectionName = "MongoDb"; + + public string ConnectionString { get; set; } = "mongodb://localhost:27017"; + + public string DatabaseName { get; set; } = "OrderProcessingReadDb"; + + public string OrdersCollectionName { get; set; } = "orders"; +} \ No newline at end of file diff --git a/OrderProcessing.Api/Features/Orders/Queries/GetOrderById/GetOrderByIdQueryHandler.cs b/OrderProcessing.Api/Features/Orders/Queries/GetOrderById/GetOrderByIdQueryHandler.cs index 12b9d77..2d37506 100644 --- a/OrderProcessing.Api/Features/Orders/Queries/GetOrderById/GetOrderByIdQueryHandler.cs +++ b/OrderProcessing.Api/Features/Orders/Queries/GetOrderById/GetOrderByIdQueryHandler.cs @@ -2,39 +2,50 @@ using Microsoft.EntityFrameworkCore; using OrderProcessing.Api.Data; using OrderProcessing.Api.DTOs.Orders; +using OrderProcessing.Api.Entities; using OrderProcessing.Api.Exceptions; +using OrderProcessing.Api.Features.Orders.Queries.ReadModel; namespace OrderProcessing.Api.Features.Orders.Queries.GetOrderById; public sealed class GetOrderByIdQueryHandler : IRequestHandler { - private readonly OrderProcessingDbContext _dbContext; - - public GetOrderByIdQueryHandler(OrderProcessingDbContext dbContext) + private readonly IOrderReadModelReader _reader; + public GetOrderByIdQueryHandler(IOrderReadModelReader reader) { - _dbContext = dbContext; + _reader = reader; } public async Task Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) { - return await _dbContext.Orders - .AsNoTracking() - .Where(order => order.Id == request.OrderId) - .Select(order => new OrderResponse - { - Id = order.Id, - CustomerId = order.CustomerId, - CustomerName = - order.Customer.FirstName + " " + - order.Customer.LastName, - Status = order.Status, - TotalAmount = order.TotalAmount, - CreatedAtUtc = order.CreatedAtUtc, - CompletedAtUtc = order.CompletedAtUtc, - CancelledAtUtc = order.CancelledAtUtc, - Items = order.Items - .OrderBy(item => item.Id) - .Select(item => new OrderItemResponse + var readModel = await _reader.GetByIdAsync(request.OrderId, cancellationToken); + + if (readModel is null) + { + throw new NotFoundException($"Order with id {request.OrderId} was not found."); + } + + if (!Enum.TryParse(readModel.Status, ignoreCase: true, out var status)) + { + throw new InvalidOperationException( + $"Order read model {readModel.OrderId} " + + $"contains invalid status '{readModel.Status}'."); + } + + return new OrderResponse + { + Id = readModel.OrderId, + CustomerId = readModel.CustomerId, + CustomerName = readModel.CustomerName, + Status = status, + TotalAmount = readModel.TotalAmount, + CreatedAtUtc = readModel.CreatedAtUtc, + CompletedAtUtc = readModel.CompletedAtUtc, + CancelledAtUtc = readModel.CancelledAtUtc, + + Items = readModel.Items + .Select(item => + new OrderItemResponse { ProductId = item.ProductId, ProductName = item.ProductName, @@ -42,10 +53,7 @@ public async Task Handle(GetOrderByIdQuery request, CancellationT UnitPrice = item.UnitPrice, LineTotal = item.LineTotal }) - .ToList() - }) - .FirstOrDefaultAsync(cancellationToken) - ?? throw new NotFoundException( - $"Order with id {request.OrderId} was not found."); + .ToList() + }; } } \ No newline at end of file diff --git a/OrderProcessing.Api/Features/Orders/Queries/ReadModel/IOrderReadModelReader.cs b/OrderProcessing.Api/Features/Orders/Queries/ReadModel/IOrderReadModelReader.cs new file mode 100644 index 0000000..1647e9c --- /dev/null +++ b/OrderProcessing.Api/Features/Orders/Queries/ReadModel/IOrderReadModelReader.cs @@ -0,0 +1,8 @@ +using OrderProcessing.ReadModels.Orders; + +namespace OrderProcessing.Api.Features.Orders.Queries.ReadModel; + +public interface IOrderReadModelReader +{ + Task GetByIdAsync(int orderId, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/OrderProcessing.Api/Features/Orders/Queries/ReadModel/MongoOrderReadModelReader.cs b/OrderProcessing.Api/Features/Orders/Queries/ReadModel/MongoOrderReadModelReader.cs new file mode 100644 index 0000000..8f58504 --- /dev/null +++ b/OrderProcessing.Api/Features/Orders/Queries/ReadModel/MongoOrderReadModelReader.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Options; +using MongoDB.Driver; +using OrderProcessing.Api.Configuration; +using OrderProcessing.ReadModels.Orders; + +namespace OrderProcessing.Api.Features.Orders.Queries.ReadModel; + +public sealed class MongoOrderReadModelReader : IOrderReadModelReader +{ + private readonly IMongoCollection _orders; + + public MongoOrderReadModelReader(IMongoClient mongoClient, IOptions options) + { + var mongoOptions = options.Value; + + var database = mongoClient.GetDatabase(mongoOptions.DatabaseName); + + _orders = database.GetCollection(mongoOptions.OrdersCollectionName); + } + + public Task GetByIdAsync(int orderId, CancellationToken cancellationToken) + { + return _orders.Find(order => order.OrderId == orderId) + .FirstOrDefaultAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/OrderProcessing.Api/OrderProcessing.Api.csproj b/OrderProcessing.Api/OrderProcessing.Api.csproj index b6a871d..773b6c7 100644 --- a/OrderProcessing.Api/OrderProcessing.Api.csproj +++ b/OrderProcessing.Api/OrderProcessing.Api.csproj @@ -20,6 +20,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -30,6 +31,7 @@ + diff --git a/OrderProcessing.Api/Program.cs b/OrderProcessing.Api/Program.cs index bacc8ac..09c712e 100644 --- a/OrderProcessing.Api/Program.cs +++ b/OrderProcessing.Api/Program.cs @@ -1,8 +1,12 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using MongoDB.Driver; using OrderProcessing.Api.BackgroundJobs; +using OrderProcessing.Api.Configuration; using OrderProcessing.Api.Data.Seeding; using OrderProcessing.Api.Extensions; +using OrderProcessing.Api.Features.Orders.Queries.ReadModel; using OrderProcessing.Api.Services.Auditing; using OrderProcessing.Api.Services.Customers; using OrderProcessing.Api.Services.Messaging; @@ -133,6 +137,27 @@ public static async Task Main(string[] args) "RabbitMQ email queue name is required when RabbitMQ is enabled.") .ValidateOnStart(); + + builder.Services + .AddOptions() + .Bind(builder.Configuration.GetSection(MongoDbOptions.SectionName)) + .Validate(options => !string.IsNullOrWhiteSpace(options.ConnectionString), + "MongoDB connection string is required.") + .Validate(options => !string.IsNullOrWhiteSpace(options.DatabaseName), + "MongoDB database name is required.") + .Validate(options => !string.IsNullOrWhiteSpace(options.OrdersCollectionName), + "MongoDB orders collection name is required.") + .ValidateOnStart(); + + builder.Services.AddSingleton(serviceProvider => + { + var options = serviceProvider.GetRequiredService>().Value; + + return new MongoClient(options.ConnectionString); + }); + + builder.Services.AddSingleton(); + var app = builder.Build(); if (app.Environment.IsDevelopment()) diff --git a/OrderProcessing.Api/appsettings.json b/OrderProcessing.Api/appsettings.json index faa3dd8..3b8f58d 100644 --- a/OrderProcessing.Api/appsettings.json +++ b/OrderProcessing.Api/appsettings.json @@ -60,5 +60,10 @@ "RetryMaxDelayMilliseconds": 30000, "ClientProvidedName": "order-processing-api-publisher", "NetworkRecoveryIntervalSeconds": 5 + }, + "MongoDb": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "OrderProcessingReadDb", + "OrdersCollectionName": "orders" } } diff --git a/OrderProcessing.ReadModelWorker.Tests/ReadModelWorker.cs b/OrderProcessing.ReadModelWorker.Tests/ReadModelWorker.cs index acdcafd..0909a4c 100644 --- a/OrderProcessing.ReadModelWorker.Tests/ReadModelWorker.cs +++ b/OrderProcessing.ReadModelWorker.Tests/ReadModelWorker.cs @@ -1,9 +1,9 @@ using System.Text.Json; using Microsoft.Extensions.Logging.Abstractions; using OrderProcessing.Contracts.Orders; +using OrderProcessing.ReadModels.Orders; using OrderProcessing.ReadModelWorker.Messaging; using OrderProcessing.ReadModelWorker.Persistence; -using OrderProcessing.ReadModelWorker.ReadModels; namespace OrderProcessing.ReadModelWorker.Tests; diff --git a/OrderProcessing.ReadModelWorker/Messaging/OrderEventProjectionHandler.cs b/OrderProcessing.ReadModelWorker/Messaging/OrderEventProjectionHandler.cs index b45f6c3..8e56878 100644 --- a/OrderProcessing.ReadModelWorker/Messaging/OrderEventProjectionHandler.cs +++ b/OrderProcessing.ReadModelWorker/Messaging/OrderEventProjectionHandler.cs @@ -1,7 +1,7 @@ using System.Text.Json; using OrderProcessing.Contracts.Orders; +using OrderProcessing.ReadModels.Orders; using OrderProcessing.ReadModelWorker.Persistence; -using OrderProcessing.ReadModelWorker.ReadModels; namespace OrderProcessing.ReadModelWorker.Messaging; diff --git a/OrderProcessing.ReadModelWorker/OrderProcessing.ReadModelWorker.csproj b/OrderProcessing.ReadModelWorker/OrderProcessing.ReadModelWorker.csproj index 4f6f369..37185ba 100644 --- a/OrderProcessing.ReadModelWorker/OrderProcessing.ReadModelWorker.csproj +++ b/OrderProcessing.ReadModelWorker/OrderProcessing.ReadModelWorker.csproj @@ -15,5 +15,6 @@ + diff --git a/OrderProcessing.ReadModelWorker/Persistence/IOrderReadModelRepository.cs b/OrderProcessing.ReadModelWorker/Persistence/IOrderReadModelRepository.cs index 01336d9..9e7a35a 100644 --- a/OrderProcessing.ReadModelWorker/Persistence/IOrderReadModelRepository.cs +++ b/OrderProcessing.ReadModelWorker/Persistence/IOrderReadModelRepository.cs @@ -1,4 +1,4 @@ -using OrderProcessing.ReadModelWorker.ReadModels; +using OrderProcessing.ReadModels.Orders; namespace OrderProcessing.ReadModelWorker.Persistence; diff --git a/OrderProcessing.ReadModelWorker/Persistence/MongoDbInitializer.cs b/OrderProcessing.ReadModelWorker/Persistence/MongoDbInitializer.cs index e99b9c5..d389a2a 100644 --- a/OrderProcessing.ReadModelWorker/Persistence/MongoDbInitializer.cs +++ b/OrderProcessing.ReadModelWorker/Persistence/MongoDbInitializer.cs @@ -1,5 +1,5 @@ using MongoDB.Driver; -using OrderProcessing.ReadModelWorker.ReadModels; +using OrderProcessing.ReadModels.Orders; namespace OrderProcessing.ReadModelWorker.Persistence; diff --git a/OrderProcessing.ReadModelWorker/Persistence/MongoOrderReadModelRepository.cs b/OrderProcessing.ReadModelWorker/Persistence/MongoOrderReadModelRepository.cs index 25115cc..2a90163 100644 --- a/OrderProcessing.ReadModelWorker/Persistence/MongoOrderReadModelRepository.cs +++ b/OrderProcessing.ReadModelWorker/Persistence/MongoOrderReadModelRepository.cs @@ -1,5 +1,5 @@ using MongoDB.Driver; -using OrderProcessing.ReadModelWorker.ReadModels; +using OrderProcessing.ReadModels.Orders; namespace OrderProcessing.ReadModelWorker.Persistence; diff --git a/OrderProcessing.ReadModelWorker/Persistence/OrderReadModelStore.cs b/OrderProcessing.ReadModelWorker/Persistence/OrderReadModelStore.cs index d36ad79..7c2aac1 100644 --- a/OrderProcessing.ReadModelWorker/Persistence/OrderReadModelStore.cs +++ b/OrderProcessing.ReadModelWorker/Persistence/OrderReadModelStore.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Options; using MongoDB.Driver; +using OrderProcessing.ReadModels.Orders; using OrderProcessing.ReadModelWorker.Configuration; -using OrderProcessing.ReadModelWorker.ReadModels; namespace OrderProcessing.ReadModelWorker.Persistence; diff --git a/OrderProcessing.ReadModelWorker/ReadModels/OrderItemReadModel.cs b/OrderProcessing.ReadModelWorker/ReadModels/OrderItemReadModel.cs deleted file mode 100644 index 1a38816..0000000 --- a/OrderProcessing.ReadModelWorker/ReadModels/OrderItemReadModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace OrderProcessing.ReadModelWorker.ReadModels; - -public sealed class OrderItemReadModel -{ - public int ProductId { get; set; } - - public required string ProductName { get; set; } - - public int Quantity { get; set; } - - public decimal UnitPrice { get; set; } - - public decimal LineTotal { get; set; } -} \ No newline at end of file diff --git a/OrderProcessing.ReadModelWorker/ReadModels/OrderReadModel.cs b/OrderProcessing.ReadModelWorker/ReadModels/OrderReadModel.cs deleted file mode 100644 index 7db2ded..0000000 --- a/OrderProcessing.ReadModelWorker/ReadModels/OrderReadModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -using MongoDB.Bson.Serialization.Attributes; - -namespace OrderProcessing.ReadModelWorker.ReadModels; - -public sealed class OrderReadModel -{ - [BsonId] - public int OrderId { get; set; } - - public int CustomerId { get; set; } - - public required string CustomerName { get; set; } - - public required string Status { get; set; } - - public decimal TotalAmount { get; set; } - - public DateTime CreatedAtUtc { get; set; } - - public DateTime? CompletedAtUtc { get; set; } - - public DateTime? CancelledAtUtc { get; set; } - - public List Items { get; set; } = []; - - public DateTime LastUpdatedAtUtc { get; set; } -} \ No newline at end of file diff --git a/OrderProcessing.ReadModels/OrderProcessing.ReadModels.csproj b/OrderProcessing.ReadModels/OrderProcessing.ReadModels.csproj new file mode 100644 index 0000000..8b9f323 --- /dev/null +++ b/OrderProcessing.ReadModels/OrderProcessing.ReadModels.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/OrderProcessing.ReadModels/Orders/OrderItemReadModel.cs b/OrderProcessing.ReadModels/Orders/OrderItemReadModel.cs new file mode 100644 index 0000000..e57c80f --- /dev/null +++ b/OrderProcessing.ReadModels/Orders/OrderItemReadModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderProcessing.ReadModels.Orders +{ + public sealed class OrderItemReadModel + { + public int ProductId { get; set; } + + public required string ProductName { get; set; } + + public int Quantity { get; set; } + + public decimal UnitPrice { get; set; } + + public decimal LineTotal { get; set; } + } +} diff --git a/OrderProcessing.ReadModels/Orders/OrderReadModel.cs b/OrderProcessing.ReadModels/Orders/OrderReadModel.cs new file mode 100644 index 0000000..51e5a0f --- /dev/null +++ b/OrderProcessing.ReadModels/Orders/OrderReadModel.cs @@ -0,0 +1,31 @@ +using MongoDB.Bson.Serialization.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderProcessing.ReadModels.Orders +{ + public sealed class OrderReadModel + { + [BsonId] + public int OrderId { get; set; } + + public int CustomerId { get; set; } + + public required string CustomerName { get; set; } + + public required string Status { get; set; } + + public decimal TotalAmount { get; set; } + + public DateTime CreatedAtUtc { get; set; } + + public DateTime? CompletedAtUtc { get; set; } + + public DateTime? CancelledAtUtc { get; set; } + + public List Items { get; set; } = []; + + public DateTime LastUpdatedAtUtc { get; set; } + } +} diff --git a/OrderProcessingPlatform.slnx b/OrderProcessingPlatform.slnx index 6f9cc13..094ba02 100644 --- a/OrderProcessingPlatform.slnx +++ b/OrderProcessingPlatform.slnx @@ -7,5 +7,6 @@ +