diff --git a/EventSourcing.Demo.csproj b/EventSourcing.Demo.csproj
new file mode 100644
index 0000000..8d29ea7
--- /dev/null
+++ b/EventSourcing.Demo.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
diff --git a/EventSourcing.Demo.csproj b/EventSourcing.Demo.csproj
new file mode 100644
index 0000000..8d29ea7
--- /dev/null
+++ b/EventSourcing.Demo.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
diff --git a/Events.cs b/Events.cs
new file mode 100644
index 0000000..6184ee1
--- /dev/null
+++ b/Events.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ public interface IEvent {}
+
+ public record ProductShipped(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record ProductReceived(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record InventoryAdjusted(string Sku, int Quantity, string Reason, DateTime DateTime) : IEvent;
+}
\ No newline at end of file
diff --git a/EventSourcing.Demo.csproj b/EventSourcing.Demo.csproj
new file mode 100644
index 0000000..8d29ea7
--- /dev/null
+++ b/EventSourcing.Demo.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
diff --git a/Events.cs b/Events.cs
new file mode 100644
index 0000000..6184ee1
--- /dev/null
+++ b/Events.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ public interface IEvent {}
+
+ public record ProductShipped(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record ProductReceived(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record InventoryAdjusted(string Sku, int Quantity, string Reason, DateTime DateTime) : IEvent;
+}
\ No newline at end of file
diff --git a/ProductDbContext.cs b/ProductDbContext.cs
new file mode 100644
index 0000000..2d4c360
--- /dev/null
+++ b/ProductDbContext.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace EventSourcing.Demo
+{
+ public class Product
+ {
+ public string Sku { get; set; }
+ public int Received { get; set; }
+ public int Shipped { get; set; }
+ }
+
+ public class ProductDbContext : DbContext
+ {
+ public DbSet Products { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Entity().HasKey(x => x.Sku);
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ base.OnConfiguring(optionsBuilder);
+ optionsBuilder.UseInMemoryDatabase("Demo");
+ }
+ }
+}
\ No newline at end of file
diff --git a/EventSourcing.Demo.csproj b/EventSourcing.Demo.csproj
new file mode 100644
index 0000000..8d29ea7
--- /dev/null
+++ b/EventSourcing.Demo.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
diff --git a/Events.cs b/Events.cs
new file mode 100644
index 0000000..6184ee1
--- /dev/null
+++ b/Events.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ public interface IEvent {}
+
+ public record ProductShipped(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record ProductReceived(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record InventoryAdjusted(string Sku, int Quantity, string Reason, DateTime DateTime) : IEvent;
+}
\ No newline at end of file
diff --git a/ProductDbContext.cs b/ProductDbContext.cs
new file mode 100644
index 0000000..2d4c360
--- /dev/null
+++ b/ProductDbContext.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace EventSourcing.Demo
+{
+ public class Product
+ {
+ public string Sku { get; set; }
+ public int Received { get; set; }
+ public int Shipped { get; set; }
+ }
+
+ public class ProductDbContext : DbContext
+ {
+ public DbSet Products { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Entity().HasKey(x => x.Sku);
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ base.OnConfiguring(optionsBuilder);
+ optionsBuilder.UseInMemoryDatabase("Demo");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..a356ae7
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,122 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ class Program
+ {
+ static void Main()
+ {
+ var warehouseProductRepository = new WarehouseProductRepository();
+
+ var projection = new Projection(new ProductDbContext());
+ warehouseProductRepository.Subscribe(projection.ReceiveEvent);
+
+ var key = string.Empty;
+ while (key != "X")
+ {
+ Console.WriteLine("R: Receive Inventory");
+ Console.WriteLine("S: Ship Inventory");
+ Console.WriteLine("A: Inventory Adjustment");
+ Console.WriteLine("Q: Quantity On Hand");
+ Console.WriteLine("E: Events");
+ Console.WriteLine("P: Projection");
+ Console.Write("> ");
+ key = Console.ReadLine()?.ToUpperInvariant();
+ Console.WriteLine();
+
+ var sku = GetSkuFromConsole();
+ var warehouseProduct = warehouseProductRepository.Get(sku);
+
+ switch (key)
+ {
+ case "R":
+ var receiveInput = GetQuantity();
+ if (receiveInput.IsValid)
+ {
+ warehouseProduct.ReceiveProduct(receiveInput.Quantity);
+ Console.WriteLine($"{sku} Received: {receiveInput.Quantity}");
+ }
+ break;
+ case "S":
+ var shipInput = GetQuantity();
+ if (shipInput.IsValid)
+ {
+ warehouseProduct.ShipProduct(shipInput.Quantity);
+ Console.WriteLine($"{sku} Shipped: {shipInput.Quantity}");
+ }
+ break;
+ case "A":
+ var adjustmentInput = GetQuantity();
+ if (adjustmentInput.IsValid)
+ {
+ var reason = GetAdjustmentReason();
+ warehouseProduct.AdjustInventory(adjustmentInput.Quantity, reason);
+ Console.WriteLine($"{sku} Adjusted: {adjustmentInput.Quantity} {reason}");
+ }
+ break;
+ case "Q":
+ var currentQuantityOnHand = warehouseProduct.GetQuantityOnHand();
+ Console.WriteLine($"{sku} Quantity On Hand: {currentQuantityOnHand}");
+ break;
+ case "E":
+ Console.WriteLine($"Events: {sku}");
+ foreach (var evnt in warehouseProduct.GetAllEvents())
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Console.WriteLine($"{shipProduct.DateTime:u} {sku} Shipped: {shipProduct.Quantity}");
+ break;
+ case ProductReceived receiveProduct:
+ Console.WriteLine($"{receiveProduct.DateTime:u} {sku} Received: {receiveProduct.Quantity}");
+ break;
+ case InventoryAdjusted inventoryAdjusted:
+ Console.WriteLine($"{inventoryAdjusted.DateTime:u} {sku} Adjusted: {inventoryAdjusted.Quantity} {inventoryAdjusted.Reason}");
+ break;
+ }
+
+ }
+ break;
+ case "P":
+ Console.WriteLine($"Projection: {sku}");
+ var productProjection = projection.GetProduct(sku);
+ Console.WriteLine($"{sku} Received: {productProjection.Received}");
+ Console.WriteLine($"{sku} Shipped: {productProjection.Shipped}");
+ break;
+ }
+
+ warehouseProductRepository.Save(warehouseProduct);
+
+ Console.ReadLine();
+ Console.WriteLine();
+ }
+ }
+
+
+ private static string GetSkuFromConsole()
+ {
+ Console.Write("SKU: ");
+ return Console.ReadLine();
+ }
+
+ private static string GetAdjustmentReason()
+ {
+ Console.Write("Reason: ");
+ return Console.ReadLine();
+ }
+
+ private static (int Quantity, bool IsValid) GetQuantity()
+ {
+ Console.Write("Quantity: ");
+ if (int.TryParse(Console.ReadLine(), out var quantity))
+ {
+ return (quantity, true);
+ }
+ else
+ {
+ Console.WriteLine("Invalid Quantity.");
+ return (0, false);
+ }
+ }
+ }
+}
diff --git a/EventSourcing.Demo.csproj b/EventSourcing.Demo.csproj
new file mode 100644
index 0000000..8d29ea7
--- /dev/null
+++ b/EventSourcing.Demo.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
diff --git a/Events.cs b/Events.cs
new file mode 100644
index 0000000..6184ee1
--- /dev/null
+++ b/Events.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ public interface IEvent {}
+
+ public record ProductShipped(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record ProductReceived(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record InventoryAdjusted(string Sku, int Quantity, string Reason, DateTime DateTime) : IEvent;
+}
\ No newline at end of file
diff --git a/ProductDbContext.cs b/ProductDbContext.cs
new file mode 100644
index 0000000..2d4c360
--- /dev/null
+++ b/ProductDbContext.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace EventSourcing.Demo
+{
+ public class Product
+ {
+ public string Sku { get; set; }
+ public int Received { get; set; }
+ public int Shipped { get; set; }
+ }
+
+ public class ProductDbContext : DbContext
+ {
+ public DbSet Products { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Entity().HasKey(x => x.Sku);
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ base.OnConfiguring(optionsBuilder);
+ optionsBuilder.UseInMemoryDatabase("Demo");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..a356ae7
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,122 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ class Program
+ {
+ static void Main()
+ {
+ var warehouseProductRepository = new WarehouseProductRepository();
+
+ var projection = new Projection(new ProductDbContext());
+ warehouseProductRepository.Subscribe(projection.ReceiveEvent);
+
+ var key = string.Empty;
+ while (key != "X")
+ {
+ Console.WriteLine("R: Receive Inventory");
+ Console.WriteLine("S: Ship Inventory");
+ Console.WriteLine("A: Inventory Adjustment");
+ Console.WriteLine("Q: Quantity On Hand");
+ Console.WriteLine("E: Events");
+ Console.WriteLine("P: Projection");
+ Console.Write("> ");
+ key = Console.ReadLine()?.ToUpperInvariant();
+ Console.WriteLine();
+
+ var sku = GetSkuFromConsole();
+ var warehouseProduct = warehouseProductRepository.Get(sku);
+
+ switch (key)
+ {
+ case "R":
+ var receiveInput = GetQuantity();
+ if (receiveInput.IsValid)
+ {
+ warehouseProduct.ReceiveProduct(receiveInput.Quantity);
+ Console.WriteLine($"{sku} Received: {receiveInput.Quantity}");
+ }
+ break;
+ case "S":
+ var shipInput = GetQuantity();
+ if (shipInput.IsValid)
+ {
+ warehouseProduct.ShipProduct(shipInput.Quantity);
+ Console.WriteLine($"{sku} Shipped: {shipInput.Quantity}");
+ }
+ break;
+ case "A":
+ var adjustmentInput = GetQuantity();
+ if (adjustmentInput.IsValid)
+ {
+ var reason = GetAdjustmentReason();
+ warehouseProduct.AdjustInventory(adjustmentInput.Quantity, reason);
+ Console.WriteLine($"{sku} Adjusted: {adjustmentInput.Quantity} {reason}");
+ }
+ break;
+ case "Q":
+ var currentQuantityOnHand = warehouseProduct.GetQuantityOnHand();
+ Console.WriteLine($"{sku} Quantity On Hand: {currentQuantityOnHand}");
+ break;
+ case "E":
+ Console.WriteLine($"Events: {sku}");
+ foreach (var evnt in warehouseProduct.GetAllEvents())
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Console.WriteLine($"{shipProduct.DateTime:u} {sku} Shipped: {shipProduct.Quantity}");
+ break;
+ case ProductReceived receiveProduct:
+ Console.WriteLine($"{receiveProduct.DateTime:u} {sku} Received: {receiveProduct.Quantity}");
+ break;
+ case InventoryAdjusted inventoryAdjusted:
+ Console.WriteLine($"{inventoryAdjusted.DateTime:u} {sku} Adjusted: {inventoryAdjusted.Quantity} {inventoryAdjusted.Reason}");
+ break;
+ }
+
+ }
+ break;
+ case "P":
+ Console.WriteLine($"Projection: {sku}");
+ var productProjection = projection.GetProduct(sku);
+ Console.WriteLine($"{sku} Received: {productProjection.Received}");
+ Console.WriteLine($"{sku} Shipped: {productProjection.Shipped}");
+ break;
+ }
+
+ warehouseProductRepository.Save(warehouseProduct);
+
+ Console.ReadLine();
+ Console.WriteLine();
+ }
+ }
+
+
+ private static string GetSkuFromConsole()
+ {
+ Console.Write("SKU: ");
+ return Console.ReadLine();
+ }
+
+ private static string GetAdjustmentReason()
+ {
+ Console.Write("Reason: ");
+ return Console.ReadLine();
+ }
+
+ private static (int Quantity, bool IsValid) GetQuantity()
+ {
+ Console.Write("Quantity: ");
+ if (int.TryParse(Console.ReadLine(), out var quantity))
+ {
+ return (quantity, true);
+ }
+ else
+ {
+ Console.WriteLine("Invalid Quantity.");
+ return (0, false);
+ }
+ }
+ }
+}
diff --git a/Projection.cs b/Projection.cs
new file mode 100644
index 0000000..77213e8
--- /dev/null
+++ b/Projection.cs
@@ -0,0 +1,56 @@
+using System.Linq;
+
+namespace EventSourcing.Demo
+{
+ public class Projection
+ {
+ private readonly ProductDbContext _dbContext;
+
+ public Projection(ProductDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public void ReceiveEvent(IEvent evnt)
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Apply(shipProduct);
+ break;
+ case ProductReceived receiveProduct:
+ Apply(receiveProduct);
+ break;
+ }
+ }
+
+ public Product GetProduct(string sku)
+ {
+ var product = _dbContext.Products.SingleOrDefault(x => x.Sku == sku);
+ if (product == null)
+ {
+ product = new Product
+ {
+ Sku = sku
+ };
+ _dbContext.Products.Add(product);
+ }
+
+ return product;
+ }
+
+ private void Apply(ProductShipped shipProduct)
+ {
+ var product = GetProduct(shipProduct.Sku);
+ product.Shipped += shipProduct.Quantity;
+ _dbContext.SaveChanges();
+ }
+
+ private void Apply(ProductReceived productReceived)
+ {
+ var state = GetProduct(productReceived.Sku);
+ state.Received += productReceived.Quantity;
+ _dbContext.SaveChanges();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EventSourcing.Demo.csproj b/EventSourcing.Demo.csproj
new file mode 100644
index 0000000..8d29ea7
--- /dev/null
+++ b/EventSourcing.Demo.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
diff --git a/Events.cs b/Events.cs
new file mode 100644
index 0000000..6184ee1
--- /dev/null
+++ b/Events.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ public interface IEvent {}
+
+ public record ProductShipped(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record ProductReceived(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record InventoryAdjusted(string Sku, int Quantity, string Reason, DateTime DateTime) : IEvent;
+}
\ No newline at end of file
diff --git a/ProductDbContext.cs b/ProductDbContext.cs
new file mode 100644
index 0000000..2d4c360
--- /dev/null
+++ b/ProductDbContext.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace EventSourcing.Demo
+{
+ public class Product
+ {
+ public string Sku { get; set; }
+ public int Received { get; set; }
+ public int Shipped { get; set; }
+ }
+
+ public class ProductDbContext : DbContext
+ {
+ public DbSet Products { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Entity().HasKey(x => x.Sku);
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ base.OnConfiguring(optionsBuilder);
+ optionsBuilder.UseInMemoryDatabase("Demo");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..a356ae7
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,122 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ class Program
+ {
+ static void Main()
+ {
+ var warehouseProductRepository = new WarehouseProductRepository();
+
+ var projection = new Projection(new ProductDbContext());
+ warehouseProductRepository.Subscribe(projection.ReceiveEvent);
+
+ var key = string.Empty;
+ while (key != "X")
+ {
+ Console.WriteLine("R: Receive Inventory");
+ Console.WriteLine("S: Ship Inventory");
+ Console.WriteLine("A: Inventory Adjustment");
+ Console.WriteLine("Q: Quantity On Hand");
+ Console.WriteLine("E: Events");
+ Console.WriteLine("P: Projection");
+ Console.Write("> ");
+ key = Console.ReadLine()?.ToUpperInvariant();
+ Console.WriteLine();
+
+ var sku = GetSkuFromConsole();
+ var warehouseProduct = warehouseProductRepository.Get(sku);
+
+ switch (key)
+ {
+ case "R":
+ var receiveInput = GetQuantity();
+ if (receiveInput.IsValid)
+ {
+ warehouseProduct.ReceiveProduct(receiveInput.Quantity);
+ Console.WriteLine($"{sku} Received: {receiveInput.Quantity}");
+ }
+ break;
+ case "S":
+ var shipInput = GetQuantity();
+ if (shipInput.IsValid)
+ {
+ warehouseProduct.ShipProduct(shipInput.Quantity);
+ Console.WriteLine($"{sku} Shipped: {shipInput.Quantity}");
+ }
+ break;
+ case "A":
+ var adjustmentInput = GetQuantity();
+ if (adjustmentInput.IsValid)
+ {
+ var reason = GetAdjustmentReason();
+ warehouseProduct.AdjustInventory(adjustmentInput.Quantity, reason);
+ Console.WriteLine($"{sku} Adjusted: {adjustmentInput.Quantity} {reason}");
+ }
+ break;
+ case "Q":
+ var currentQuantityOnHand = warehouseProduct.GetQuantityOnHand();
+ Console.WriteLine($"{sku} Quantity On Hand: {currentQuantityOnHand}");
+ break;
+ case "E":
+ Console.WriteLine($"Events: {sku}");
+ foreach (var evnt in warehouseProduct.GetAllEvents())
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Console.WriteLine($"{shipProduct.DateTime:u} {sku} Shipped: {shipProduct.Quantity}");
+ break;
+ case ProductReceived receiveProduct:
+ Console.WriteLine($"{receiveProduct.DateTime:u} {sku} Received: {receiveProduct.Quantity}");
+ break;
+ case InventoryAdjusted inventoryAdjusted:
+ Console.WriteLine($"{inventoryAdjusted.DateTime:u} {sku} Adjusted: {inventoryAdjusted.Quantity} {inventoryAdjusted.Reason}");
+ break;
+ }
+
+ }
+ break;
+ case "P":
+ Console.WriteLine($"Projection: {sku}");
+ var productProjection = projection.GetProduct(sku);
+ Console.WriteLine($"{sku} Received: {productProjection.Received}");
+ Console.WriteLine($"{sku} Shipped: {productProjection.Shipped}");
+ break;
+ }
+
+ warehouseProductRepository.Save(warehouseProduct);
+
+ Console.ReadLine();
+ Console.WriteLine();
+ }
+ }
+
+
+ private static string GetSkuFromConsole()
+ {
+ Console.Write("SKU: ");
+ return Console.ReadLine();
+ }
+
+ private static string GetAdjustmentReason()
+ {
+ Console.Write("Reason: ");
+ return Console.ReadLine();
+ }
+
+ private static (int Quantity, bool IsValid) GetQuantity()
+ {
+ Console.Write("Quantity: ");
+ if (int.TryParse(Console.ReadLine(), out var quantity))
+ {
+ return (quantity, true);
+ }
+ else
+ {
+ Console.WriteLine("Invalid Quantity.");
+ return (0, false);
+ }
+ }
+ }
+}
diff --git a/Projection.cs b/Projection.cs
new file mode 100644
index 0000000..77213e8
--- /dev/null
+++ b/Projection.cs
@@ -0,0 +1,56 @@
+using System.Linq;
+
+namespace EventSourcing.Demo
+{
+ public class Projection
+ {
+ private readonly ProductDbContext _dbContext;
+
+ public Projection(ProductDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public void ReceiveEvent(IEvent evnt)
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Apply(shipProduct);
+ break;
+ case ProductReceived receiveProduct:
+ Apply(receiveProduct);
+ break;
+ }
+ }
+
+ public Product GetProduct(string sku)
+ {
+ var product = _dbContext.Products.SingleOrDefault(x => x.Sku == sku);
+ if (product == null)
+ {
+ product = new Product
+ {
+ Sku = sku
+ };
+ _dbContext.Products.Add(product);
+ }
+
+ return product;
+ }
+
+ private void Apply(ProductShipped shipProduct)
+ {
+ var product = GetProduct(shipProduct.Sku);
+ product.Shipped += shipProduct.Quantity;
+ _dbContext.SaveChanges();
+ }
+
+ private void Apply(ProductReceived productReceived)
+ {
+ var state = GetProduct(productReceived.Sku);
+ state.Received += productReceived.Quantity;
+ _dbContext.SaveChanges();
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 86387d9..ec2136a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,7 @@
-EventSourcingProjection
-===============
+# Event Sourcing: Projection
+
+## YouTube
+https://www.youtube.com/channel/UC3RKA4vunFAfrfxiJhPEplw
+
+## Prerequisite
+None
\ No newline at end of file
diff --git a/EventSourcing.Demo.csproj b/EventSourcing.Demo.csproj
new file mode 100644
index 0000000..8d29ea7
--- /dev/null
+++ b/EventSourcing.Demo.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
diff --git a/Events.cs b/Events.cs
new file mode 100644
index 0000000..6184ee1
--- /dev/null
+++ b/Events.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ public interface IEvent {}
+
+ public record ProductShipped(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record ProductReceived(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record InventoryAdjusted(string Sku, int Quantity, string Reason, DateTime DateTime) : IEvent;
+}
\ No newline at end of file
diff --git a/ProductDbContext.cs b/ProductDbContext.cs
new file mode 100644
index 0000000..2d4c360
--- /dev/null
+++ b/ProductDbContext.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace EventSourcing.Demo
+{
+ public class Product
+ {
+ public string Sku { get; set; }
+ public int Received { get; set; }
+ public int Shipped { get; set; }
+ }
+
+ public class ProductDbContext : DbContext
+ {
+ public DbSet Products { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Entity().HasKey(x => x.Sku);
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ base.OnConfiguring(optionsBuilder);
+ optionsBuilder.UseInMemoryDatabase("Demo");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..a356ae7
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,122 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ class Program
+ {
+ static void Main()
+ {
+ var warehouseProductRepository = new WarehouseProductRepository();
+
+ var projection = new Projection(new ProductDbContext());
+ warehouseProductRepository.Subscribe(projection.ReceiveEvent);
+
+ var key = string.Empty;
+ while (key != "X")
+ {
+ Console.WriteLine("R: Receive Inventory");
+ Console.WriteLine("S: Ship Inventory");
+ Console.WriteLine("A: Inventory Adjustment");
+ Console.WriteLine("Q: Quantity On Hand");
+ Console.WriteLine("E: Events");
+ Console.WriteLine("P: Projection");
+ Console.Write("> ");
+ key = Console.ReadLine()?.ToUpperInvariant();
+ Console.WriteLine();
+
+ var sku = GetSkuFromConsole();
+ var warehouseProduct = warehouseProductRepository.Get(sku);
+
+ switch (key)
+ {
+ case "R":
+ var receiveInput = GetQuantity();
+ if (receiveInput.IsValid)
+ {
+ warehouseProduct.ReceiveProduct(receiveInput.Quantity);
+ Console.WriteLine($"{sku} Received: {receiveInput.Quantity}");
+ }
+ break;
+ case "S":
+ var shipInput = GetQuantity();
+ if (shipInput.IsValid)
+ {
+ warehouseProduct.ShipProduct(shipInput.Quantity);
+ Console.WriteLine($"{sku} Shipped: {shipInput.Quantity}");
+ }
+ break;
+ case "A":
+ var adjustmentInput = GetQuantity();
+ if (adjustmentInput.IsValid)
+ {
+ var reason = GetAdjustmentReason();
+ warehouseProduct.AdjustInventory(adjustmentInput.Quantity, reason);
+ Console.WriteLine($"{sku} Adjusted: {adjustmentInput.Quantity} {reason}");
+ }
+ break;
+ case "Q":
+ var currentQuantityOnHand = warehouseProduct.GetQuantityOnHand();
+ Console.WriteLine($"{sku} Quantity On Hand: {currentQuantityOnHand}");
+ break;
+ case "E":
+ Console.WriteLine($"Events: {sku}");
+ foreach (var evnt in warehouseProduct.GetAllEvents())
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Console.WriteLine($"{shipProduct.DateTime:u} {sku} Shipped: {shipProduct.Quantity}");
+ break;
+ case ProductReceived receiveProduct:
+ Console.WriteLine($"{receiveProduct.DateTime:u} {sku} Received: {receiveProduct.Quantity}");
+ break;
+ case InventoryAdjusted inventoryAdjusted:
+ Console.WriteLine($"{inventoryAdjusted.DateTime:u} {sku} Adjusted: {inventoryAdjusted.Quantity} {inventoryAdjusted.Reason}");
+ break;
+ }
+
+ }
+ break;
+ case "P":
+ Console.WriteLine($"Projection: {sku}");
+ var productProjection = projection.GetProduct(sku);
+ Console.WriteLine($"{sku} Received: {productProjection.Received}");
+ Console.WriteLine($"{sku} Shipped: {productProjection.Shipped}");
+ break;
+ }
+
+ warehouseProductRepository.Save(warehouseProduct);
+
+ Console.ReadLine();
+ Console.WriteLine();
+ }
+ }
+
+
+ private static string GetSkuFromConsole()
+ {
+ Console.Write("SKU: ");
+ return Console.ReadLine();
+ }
+
+ private static string GetAdjustmentReason()
+ {
+ Console.Write("Reason: ");
+ return Console.ReadLine();
+ }
+
+ private static (int Quantity, bool IsValid) GetQuantity()
+ {
+ Console.Write("Quantity: ");
+ if (int.TryParse(Console.ReadLine(), out var quantity))
+ {
+ return (quantity, true);
+ }
+ else
+ {
+ Console.WriteLine("Invalid Quantity.");
+ return (0, false);
+ }
+ }
+ }
+}
diff --git a/Projection.cs b/Projection.cs
new file mode 100644
index 0000000..77213e8
--- /dev/null
+++ b/Projection.cs
@@ -0,0 +1,56 @@
+using System.Linq;
+
+namespace EventSourcing.Demo
+{
+ public class Projection
+ {
+ private readonly ProductDbContext _dbContext;
+
+ public Projection(ProductDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public void ReceiveEvent(IEvent evnt)
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Apply(shipProduct);
+ break;
+ case ProductReceived receiveProduct:
+ Apply(receiveProduct);
+ break;
+ }
+ }
+
+ public Product GetProduct(string sku)
+ {
+ var product = _dbContext.Products.SingleOrDefault(x => x.Sku == sku);
+ if (product == null)
+ {
+ product = new Product
+ {
+ Sku = sku
+ };
+ _dbContext.Products.Add(product);
+ }
+
+ return product;
+ }
+
+ private void Apply(ProductShipped shipProduct)
+ {
+ var product = GetProduct(shipProduct.Sku);
+ product.Shipped += shipProduct.Quantity;
+ _dbContext.SaveChanges();
+ }
+
+ private void Apply(ProductReceived productReceived)
+ {
+ var state = GetProduct(productReceived.Sku);
+ state.Received += productReceived.Quantity;
+ _dbContext.SaveChanges();
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 86387d9..ec2136a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,7 @@
-EventSourcingProjection
-===============
+# Event Sourcing: Projection
+
+## YouTube
+https://www.youtube.com/channel/UC3RKA4vunFAfrfxiJhPEplw
+
+## Prerequisite
+None
\ No newline at end of file
diff --git a/WarehouseProduct.cs b/WarehouseProduct.cs
new file mode 100644
index 0000000..fbd50be
--- /dev/null
+++ b/WarehouseProduct.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+
+namespace EventSourcing.Demo
+{
+ public class CurrentState
+ {
+ public int QuantityOnHand { get; set; }
+ }
+
+ public class WarehouseProduct
+ {
+ public string Sku { get; }
+ private readonly IList _allEvents = new List();
+ private readonly IList _uncommittedEvents = new List();
+
+ // Projection (Current State)
+ private readonly CurrentState _currentState = new();
+
+ public WarehouseProduct(string sku)
+ {
+ Sku = sku;
+ }
+
+ public void ShipProduct(int quantity)
+ {
+ if (quantity > _currentState.QuantityOnHand)
+ {
+ throw new InvalidDomainException("Ah... we don't have enough product to ship?");
+ }
+
+ AddEvent(new ProductShipped(Sku, quantity, DateTime.UtcNow));
+ }
+
+ public void ReceiveProduct(int quantity)
+ {
+ AddEvent(new ProductReceived(Sku, quantity, DateTime.UtcNow));
+ }
+
+ public void AdjustInventory(int quantity, string reason)
+ {
+ if (_currentState.QuantityOnHand + quantity < 0)
+ {
+ throw new InvalidDomainException("Cannot adjust to a negative quantity on hand.");
+ }
+
+ AddEvent(new InventoryAdjusted(Sku, quantity, reason, DateTime.UtcNow));
+ }
+
+ private void Apply(ProductShipped evnt)
+ {
+ _currentState.QuantityOnHand -= evnt.Quantity;
+ }
+
+ private void Apply(ProductReceived evnt)
+ {
+ _currentState.QuantityOnHand += evnt.Quantity;
+ }
+
+ private void Apply(InventoryAdjusted evnt)
+ {
+ _currentState.QuantityOnHand += evnt.Quantity;
+ }
+
+ public void ApplyEvent(IEvent evnt)
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Apply(shipProduct);
+ break;
+ case ProductReceived receiveProduct:
+ Apply(receiveProduct);
+ break;
+ case InventoryAdjusted inventoryAdjusted:
+ Apply(inventoryAdjusted);
+ break;
+ default:
+ throw new InvalidOperationException("Unsupported Event.");
+ }
+
+ _allEvents.Add(evnt);
+ }
+
+ public void AddEvent(IEvent evnt)
+ {
+ ApplyEvent(evnt);
+ _uncommittedEvents.Add(evnt);
+ }
+
+ public IList GetUncommittedEvents()
+ {
+ return new List(_uncommittedEvents);
+ }
+
+ public IList GetAllEvents()
+ {
+ return new List(_allEvents);
+ }
+
+ public void EventsCommitted()
+ {
+ _uncommittedEvents.Clear();
+ }
+
+ public int GetQuantityOnHand()
+ {
+ return _currentState.QuantityOnHand;
+ }
+ }
+
+ public class InvalidDomainException : Exception
+ {
+ public InvalidDomainException(string message) : base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/EventSourcing.Demo.csproj b/EventSourcing.Demo.csproj
new file mode 100644
index 0000000..8d29ea7
--- /dev/null
+++ b/EventSourcing.Demo.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
diff --git a/Events.cs b/Events.cs
new file mode 100644
index 0000000..6184ee1
--- /dev/null
+++ b/Events.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ public interface IEvent {}
+
+ public record ProductShipped(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record ProductReceived(string Sku, int Quantity, DateTime DateTime) : IEvent;
+
+ public record InventoryAdjusted(string Sku, int Quantity, string Reason, DateTime DateTime) : IEvent;
+}
\ No newline at end of file
diff --git a/ProductDbContext.cs b/ProductDbContext.cs
new file mode 100644
index 0000000..2d4c360
--- /dev/null
+++ b/ProductDbContext.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace EventSourcing.Demo
+{
+ public class Product
+ {
+ public string Sku { get; set; }
+ public int Received { get; set; }
+ public int Shipped { get; set; }
+ }
+
+ public class ProductDbContext : DbContext
+ {
+ public DbSet Products { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Entity().HasKey(x => x.Sku);
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ base.OnConfiguring(optionsBuilder);
+ optionsBuilder.UseInMemoryDatabase("Demo");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..a356ae7
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,122 @@
+using System;
+
+namespace EventSourcing.Demo
+{
+ class Program
+ {
+ static void Main()
+ {
+ var warehouseProductRepository = new WarehouseProductRepository();
+
+ var projection = new Projection(new ProductDbContext());
+ warehouseProductRepository.Subscribe(projection.ReceiveEvent);
+
+ var key = string.Empty;
+ while (key != "X")
+ {
+ Console.WriteLine("R: Receive Inventory");
+ Console.WriteLine("S: Ship Inventory");
+ Console.WriteLine("A: Inventory Adjustment");
+ Console.WriteLine("Q: Quantity On Hand");
+ Console.WriteLine("E: Events");
+ Console.WriteLine("P: Projection");
+ Console.Write("> ");
+ key = Console.ReadLine()?.ToUpperInvariant();
+ Console.WriteLine();
+
+ var sku = GetSkuFromConsole();
+ var warehouseProduct = warehouseProductRepository.Get(sku);
+
+ switch (key)
+ {
+ case "R":
+ var receiveInput = GetQuantity();
+ if (receiveInput.IsValid)
+ {
+ warehouseProduct.ReceiveProduct(receiveInput.Quantity);
+ Console.WriteLine($"{sku} Received: {receiveInput.Quantity}");
+ }
+ break;
+ case "S":
+ var shipInput = GetQuantity();
+ if (shipInput.IsValid)
+ {
+ warehouseProduct.ShipProduct(shipInput.Quantity);
+ Console.WriteLine($"{sku} Shipped: {shipInput.Quantity}");
+ }
+ break;
+ case "A":
+ var adjustmentInput = GetQuantity();
+ if (adjustmentInput.IsValid)
+ {
+ var reason = GetAdjustmentReason();
+ warehouseProduct.AdjustInventory(adjustmentInput.Quantity, reason);
+ Console.WriteLine($"{sku} Adjusted: {adjustmentInput.Quantity} {reason}");
+ }
+ break;
+ case "Q":
+ var currentQuantityOnHand = warehouseProduct.GetQuantityOnHand();
+ Console.WriteLine($"{sku} Quantity On Hand: {currentQuantityOnHand}");
+ break;
+ case "E":
+ Console.WriteLine($"Events: {sku}");
+ foreach (var evnt in warehouseProduct.GetAllEvents())
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Console.WriteLine($"{shipProduct.DateTime:u} {sku} Shipped: {shipProduct.Quantity}");
+ break;
+ case ProductReceived receiveProduct:
+ Console.WriteLine($"{receiveProduct.DateTime:u} {sku} Received: {receiveProduct.Quantity}");
+ break;
+ case InventoryAdjusted inventoryAdjusted:
+ Console.WriteLine($"{inventoryAdjusted.DateTime:u} {sku} Adjusted: {inventoryAdjusted.Quantity} {inventoryAdjusted.Reason}");
+ break;
+ }
+
+ }
+ break;
+ case "P":
+ Console.WriteLine($"Projection: {sku}");
+ var productProjection = projection.GetProduct(sku);
+ Console.WriteLine($"{sku} Received: {productProjection.Received}");
+ Console.WriteLine($"{sku} Shipped: {productProjection.Shipped}");
+ break;
+ }
+
+ warehouseProductRepository.Save(warehouseProduct);
+
+ Console.ReadLine();
+ Console.WriteLine();
+ }
+ }
+
+
+ private static string GetSkuFromConsole()
+ {
+ Console.Write("SKU: ");
+ return Console.ReadLine();
+ }
+
+ private static string GetAdjustmentReason()
+ {
+ Console.Write("Reason: ");
+ return Console.ReadLine();
+ }
+
+ private static (int Quantity, bool IsValid) GetQuantity()
+ {
+ Console.Write("Quantity: ");
+ if (int.TryParse(Console.ReadLine(), out var quantity))
+ {
+ return (quantity, true);
+ }
+ else
+ {
+ Console.WriteLine("Invalid Quantity.");
+ return (0, false);
+ }
+ }
+ }
+}
diff --git a/Projection.cs b/Projection.cs
new file mode 100644
index 0000000..77213e8
--- /dev/null
+++ b/Projection.cs
@@ -0,0 +1,56 @@
+using System.Linq;
+
+namespace EventSourcing.Demo
+{
+ public class Projection
+ {
+ private readonly ProductDbContext _dbContext;
+
+ public Projection(ProductDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public void ReceiveEvent(IEvent evnt)
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Apply(shipProduct);
+ break;
+ case ProductReceived receiveProduct:
+ Apply(receiveProduct);
+ break;
+ }
+ }
+
+ public Product GetProduct(string sku)
+ {
+ var product = _dbContext.Products.SingleOrDefault(x => x.Sku == sku);
+ if (product == null)
+ {
+ product = new Product
+ {
+ Sku = sku
+ };
+ _dbContext.Products.Add(product);
+ }
+
+ return product;
+ }
+
+ private void Apply(ProductShipped shipProduct)
+ {
+ var product = GetProduct(shipProduct.Sku);
+ product.Shipped += shipProduct.Quantity;
+ _dbContext.SaveChanges();
+ }
+
+ private void Apply(ProductReceived productReceived)
+ {
+ var state = GetProduct(productReceived.Sku);
+ state.Received += productReceived.Quantity;
+ _dbContext.SaveChanges();
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 86387d9..ec2136a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,7 @@
-EventSourcingProjection
-===============
+# Event Sourcing: Projection
+
+## YouTube
+https://www.youtube.com/channel/UC3RKA4vunFAfrfxiJhPEplw
+
+## Prerequisite
+None
\ No newline at end of file
diff --git a/WarehouseProduct.cs b/WarehouseProduct.cs
new file mode 100644
index 0000000..fbd50be
--- /dev/null
+++ b/WarehouseProduct.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+
+namespace EventSourcing.Demo
+{
+ public class CurrentState
+ {
+ public int QuantityOnHand { get; set; }
+ }
+
+ public class WarehouseProduct
+ {
+ public string Sku { get; }
+ private readonly IList _allEvents = new List();
+ private readonly IList _uncommittedEvents = new List();
+
+ // Projection (Current State)
+ private readonly CurrentState _currentState = new();
+
+ public WarehouseProduct(string sku)
+ {
+ Sku = sku;
+ }
+
+ public void ShipProduct(int quantity)
+ {
+ if (quantity > _currentState.QuantityOnHand)
+ {
+ throw new InvalidDomainException("Ah... we don't have enough product to ship?");
+ }
+
+ AddEvent(new ProductShipped(Sku, quantity, DateTime.UtcNow));
+ }
+
+ public void ReceiveProduct(int quantity)
+ {
+ AddEvent(new ProductReceived(Sku, quantity, DateTime.UtcNow));
+ }
+
+ public void AdjustInventory(int quantity, string reason)
+ {
+ if (_currentState.QuantityOnHand + quantity < 0)
+ {
+ throw new InvalidDomainException("Cannot adjust to a negative quantity on hand.");
+ }
+
+ AddEvent(new InventoryAdjusted(Sku, quantity, reason, DateTime.UtcNow));
+ }
+
+ private void Apply(ProductShipped evnt)
+ {
+ _currentState.QuantityOnHand -= evnt.Quantity;
+ }
+
+ private void Apply(ProductReceived evnt)
+ {
+ _currentState.QuantityOnHand += evnt.Quantity;
+ }
+
+ private void Apply(InventoryAdjusted evnt)
+ {
+ _currentState.QuantityOnHand += evnt.Quantity;
+ }
+
+ public void ApplyEvent(IEvent evnt)
+ {
+ switch (evnt)
+ {
+ case ProductShipped shipProduct:
+ Apply(shipProduct);
+ break;
+ case ProductReceived receiveProduct:
+ Apply(receiveProduct);
+ break;
+ case InventoryAdjusted inventoryAdjusted:
+ Apply(inventoryAdjusted);
+ break;
+ default:
+ throw new InvalidOperationException("Unsupported Event.");
+ }
+
+ _allEvents.Add(evnt);
+ }
+
+ public void AddEvent(IEvent evnt)
+ {
+ ApplyEvent(evnt);
+ _uncommittedEvents.Add(evnt);
+ }
+
+ public IList GetUncommittedEvents()
+ {
+ return new List(_uncommittedEvents);
+ }
+
+ public IList GetAllEvents()
+ {
+ return new List(_allEvents);
+ }
+
+ public void EventsCommitted()
+ {
+ _uncommittedEvents.Clear();
+ }
+
+ public int GetQuantityOnHand()
+ {
+ return _currentState.QuantityOnHand;
+ }
+ }
+
+ public class InvalidDomainException : Exception
+ {
+ public InvalidDomainException(string message) : base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/WarehouseProductRepository.cs b/WarehouseProductRepository.cs
new file mode 100644
index 0000000..672afcb
--- /dev/null
+++ b/WarehouseProductRepository.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+
+namespace EventSourcing.Demo
+{
+ public class WarehouseProductRepository
+ {
+ private readonly List> _projectionCallbacks = new();
+ private readonly Dictionary> _inMemoryStreams = new();
+
+ public WarehouseProduct Get(string sku)
+ {
+ var warehouseProduct = new WarehouseProduct(sku);
+
+ if (_inMemoryStreams.ContainsKey(sku))
+ {
+ foreach (var evnt in _inMemoryStreams[sku])
+ {
+ warehouseProduct.ApplyEvent(evnt);
+ }
+ }
+
+ return warehouseProduct;
+ }
+
+ public void Save(WarehouseProduct warehouseProduct)
+ {
+ if (_inMemoryStreams.ContainsKey(warehouseProduct.Sku) == false)
+ {
+ _inMemoryStreams.Add(warehouseProduct.Sku, new List());
+ }
+
+ var newEvents = warehouseProduct.GetUncommittedEvents();
+ _inMemoryStreams[warehouseProduct.Sku].AddRange(newEvents);
+ warehouseProduct.EventsCommitted();
+
+ foreach (var newEvent in newEvents)
+ {
+ foreach (var callback in _projectionCallbacks)
+ {
+ callback(newEvent);
+ }
+ }
+ }
+
+ public void Subscribe(Action callback)
+ {
+ _projectionCallbacks.Add(callback);
+ }
+ }
+}
\ No newline at end of file