Project · Architecture

Order acceptance and read-view lab

Check immutable domain input, local identity rejection, event intent, and read snapshots in six console scenarios. The memory adapter is not durable transactional storage.

All C# source files below are loaded together in the playground. Files written by a run live in its virtual directory.

Program.cs

var checks = OrderChecks.Run();
foreach (var check in checks) Console.WriteLine($"{check.Name}:{(check.Passed ? "PASS" : "FAIL")}");
Console.WriteLine($"passed:{checks.Count(check => check.Passed)}/{checks.Count}");
return checks.All(check => check.Passed) ? 0 : 1;

Domain/Order.cs

public sealed record Order
{
    public string Id { get; }
    public int Units { get; }
    public Order(string id, int units)
    {
        if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Id required", nameof(id));
        if (units < 1) throw new ArgumentOutOfRangeException(nameof(units));
        Id = id; Units = units;
    }
}
public record Accepted(string Id, int Units);

Application/Orders.cs

public interface IOrderWrite { bool TrySave(Order order, Accepted message); }
public interface IOrderRead { IReadOnlyList<Order> Read(); }
public sealed class AcceptOrder(IOrderWrite store) { public bool Execute(Order order) => store.TrySave(order, new Accepted(order.Id, order.Units)); }
public record OrderView(int Count, int Units);
public sealed class ReadOrders(IOrderRead store)
{
    public OrderView Execute() { var snapshot = store.Read(); return new(snapshot.Count, snapshot.Sum(order => order.Units)); }
}

Adapters/MemoryOrders.cs

public sealed class MemoryOrders : IOrderRead, IOrderWrite
{
    private readonly Dictionary<string, Order> orders = new(StringComparer.Ordinal);
    private readonly List<Accepted> outbox = new();
    public IReadOnlyList<Accepted> Outbox => outbox.ToArray();
    public IReadOnlyList<Order> Read() => orders.Values.ToArray();
    public bool TrySave(Order order, Accepted message)
    {
        if (order.Id != message.Id || order.Units != message.Units) throw new ArgumentException("Mismatched event", nameof(message));
        if (orders.ContainsKey(order.Id)) return false;
        orders.Add(order.Id, order); outbox.Add(message); return true;
    }
}

Checks/OrderChecks.cs

public static class OrderChecks
{
    public static List<(string Name, bool Passed)> Run()
    {
        List<(string Name, bool Passed)> checks = [];
        var first = new MemoryOrders();
        bool accepted = new AcceptOrder(first).Execute(new Order("A1", 2));
        checks.Add(("valid", accepted && new ReadOrders(first).Execute() == new OrderView(1, 2) && first.Outbox.Count == 1));
        var duplicate = new MemoryOrders();
        var writeDuplicate = new AcceptOrder(duplicate);
        bool initial = writeDuplicate.Execute(new Order("A1", 2));
        bool repeated = writeDuplicate.Execute(new Order("A1", 2));
        checks.Add(("duplicate", initial && !repeated && duplicate.Read().Count == 1 && duplicate.Outbox.Count == 1));
        var separate = new MemoryOrders();
        var writeSeparate = new AcceptOrder(separate);
        writeSeparate.Execute(new Order("A1", 2)); writeSeparate.Execute(new Order("B1", 3));
        checks.Add(("separate-ids", new ReadOrders(separate).Execute() == new OrderView(2, 5) && separate.Outbox.Count == 2));
        var snapshots = new MemoryOrders();
        var writer = new AcceptOrder(snapshots);
        writer.Execute(new Order("A1", 2));
        var captured = snapshots.Read();
        writer.Execute(new Order("B1", 1));
        checks.Add(("snapshot", captured.Count == 1 && snapshots.Read().Count == 2));
        var query = new ReadOrders(separate);
        checks.Add(("query-no-effects", query.Execute() == new OrderView(2, 5) && query.Execute() == new OrderView(2, 5) && separate.Outbox.Count == 2));
        bool rejected = false;
        try { _ = new Order("bad", 0); } catch (ArgumentOutOfRangeException) { rejected = true; }
        checks.Add(("invalid-domain", rejected));
        return checks;
    }
}

ConsoleLab.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <LangVersion>13.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>