-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
217 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,254 @@ | ||
using System; | ||
#region (c) 2010-2012 Lokad All rights reserved | ||
|
||
// You must not remove this notice, or any other, from this software. | ||
// This document is the property of LOKAD SAS and must not be disclosed. | ||
|
||
#endregion | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
|
||
namespace E003_event_sourcing_basics | ||
{ | ||
class Program | ||
{ | ||
static void Main(string[] args) | ||
{ | ||
// let's talk about entities. | ||
// entity is something that has a name that is unique within a certain zone of interest | ||
// or this area has only one instance of this entity. | ||
|
||
// given name, we can always know how to find that entity (or a postal service will know that) | ||
// We say that entity is identified by name. in software that name is called identifier or identity | ||
|
||
// entity can change looks, behaviors, but it will still keep identifier. That's how we can find it. | ||
// or send it a message. | ||
|
||
// Now, let's talk about entities. | ||
|
||
// in previous lessons we've learned that message is a remote and decoupled equivalent | ||
// of a message call. | ||
|
||
// So, now we will model entity called factory. We will write code that captures | ||
// actions it can be commanded to carry out. However, we will express these as method | ||
// calls instead of the commands. Names of method calls will be same as names of commands | ||
// parameters will match to the members. | ||
|
||
// it will be shorter right now (besides, you'll learn than message == method call), | ||
// besides, this approach will have additional meaning in real development down the road. | ||
|
||
|
||
public sealed class FactoryImplementation1 | ||
{ | ||
// this is linguistically equivalent to command that is sent to this factory | ||
// public class AssignEmployeeToFactory | ||
// { | ||
// public string EmployeeName { get; set; } | ||
// } | ||
public void AssignEmployeeToFactory(string employeeName) {} | ||
public void TransferShipmentToCargoBay(string shipmentName, CarPart[] parts) {} | ||
public void UnloadShipmentFromCargoBay(string employeeName) {} | ||
public void ProduceCar(string employeeName, string carModel) {} | ||
} | ||
} | ||
|
||
|
||
public sealed class ProductBasketState | ||
{ | ||
public ProductBasketState(IEnumerable<object> events) | ||
// these methods will contain following elements (which can be really complex | ||
// or can be optional) | ||
// Checks (check if operation is allowed) | ||
// some work that might involve calculations, thinking, access to some tooling | ||
// Event that we write to journal to mark the work as being done. | ||
|
||
|
||
public sealed class FactoryImplementation2 | ||
{ | ||
Products = new Dictionary<string, double>(); | ||
foreach (var e in events) | ||
// this is linguistically equi | ||
public void AssignEmployeeToFactory(string employeeName) | ||
{ | ||
// CheckIfEmployeeCanBeAssignedToFactory(employeeName); | ||
// DoPaperWork(); | ||
// RecordThatEmployeeAssignedToFactory(employeeName); | ||
} | ||
|
||
public void TransferShipmentToCargoBay(string shipmentName, CarPart[] parts) | ||
{ | ||
// CheckIfCargoBayHasFreeSpace(parts); | ||
// DoRealWork("unloading supplies..."); | ||
// DoPaperWork("Signing that shipment acceptance form"); | ||
// RecordThatSuppliesAreAvailableInCargoBay() | ||
} | ||
|
||
public void UnloadShipmentFromCargoBay(string employeeName) | ||
{ | ||
Mutate(e); | ||
// DoRealWork("passing supplies"); | ||
// RecordThatSuppliesWereUnloadedFromCargoBay() | ||
} | ||
|
||
public void ProduceCar(string employeeName, string carModel) | ||
{ | ||
// CheckIfWeHaveEnoughSpareParts | ||
// CheckIfEmployeeIsAvailable | ||
// DoRealWork | ||
// RecordThatCarWasProduced | ||
} | ||
} | ||
|
||
public void Mutate(object e) | ||
|
||
// Now let's "unwrap" AssignEmployeeToFactory | ||
// we'll start by adding a list of employees | ||
|
||
public class FactoryImplementation3 | ||
{ | ||
((dynamic) this).When((dynamic) e); | ||
// THE Factory Journal! | ||
public List<IEvent> JournalOfFactoryEvents = new List<IEvent>(); | ||
|
||
|
||
List<string> _ourListOfEmployeeNames = new List<string>(); | ||
|
||
|
||
public void AssignEmployeeToFactory(string employeeName) | ||
{ | ||
Print("?> Command: Assign employee {0} to factory", employeeName); | ||
// CheckIfEmployeeCanBeAssignedToFactory(employeeName); | ||
if (_ourListOfEmployeeNames.Contains(employeeName)) | ||
{ | ||
// yes, this is really weird check, but this factory has really strict rules. | ||
// manager should've remembered that | ||
RecordThat(new EmployeeKickedOutOfFactory() | ||
{ | ||
EmployeeName = employeeName, | ||
Reason = string.Format("the name of '{0}' only one employee can have", employeeName) | ||
}); | ||
return; | ||
} | ||
|
||
if (employeeName == "bender") | ||
{ | ||
RecordThat(new EmployeeKickedOutOfFactory() | ||
{ | ||
EmployeeName = employeeName, | ||
Reason = "Guys with name 'bender' are trouble." | ||
}); | ||
return; | ||
} | ||
|
||
DoPaperWork("Assign employee to the factory"); | ||
|
||
RecordThat(new EmployeeAssignedToFactory() | ||
{ | ||
EmployeeName = employeeName | ||
}); | ||
// DoPaperWork(); | ||
// RecordThatEmployeeAssignedToFactory(employeeName); | ||
} | ||
|
||
void DoPaperWork(string workName) | ||
{ | ||
Print(" > Work: papers... {0}... ", workName); | ||
Thread.Sleep(2000); | ||
|
||
} | ||
void RecordThat(IEvent e) | ||
{ | ||
// we record by jotting down notes in our journal | ||
JournalOfFactoryEvents.Add(e); | ||
// we also announce this event inside factory. | ||
// so that all workers will immediately know | ||
// what is going inside | ||
((dynamic) this).AnnounceInsideFactory((dynamic) e); | ||
|
||
// ok, we also print to console, just because we want to know | ||
Print("!> Event: {0}", e); | ||
} | ||
|
||
|
||
|
||
|
||
|
||
void AnnounceInsideFactory(EmployeeAssignedToFactory e) | ||
{ | ||
_ourListOfEmployeeNames.Add(e.EmployeeName); | ||
} | ||
void AnnounceInsideFactory(EmployeeKickedOutOfFactory e) | ||
{ | ||
// this is a trick. Don't care about it for now | ||
} | ||
} | ||
|
||
public IDictionary<string, double> Products { get; private set; } | ||
public class EmployeeAssignedToFactory : IEvent | ||
{ | ||
public string EmployeeName; | ||
|
||
public void When(ProductAddedToShoppingBasket e) | ||
public override string ToString() | ||
{ | ||
return string.Format("new worker joins our forces: '{0}'", EmployeeName); | ||
} | ||
} | ||
public class EmployeeKickedOutOfFactory : IEvent | ||
{ | ||
if (!Products.ContainsKey(e.Name)) | ||
public string EmployeeName; | ||
public string Reason; | ||
|
||
public override string ToString() | ||
{ | ||
Products[e.Name] = 0; | ||
return string.Format("'{0}' is not allowed, because '{1}'", EmployeeName, Reason); | ||
} | ||
Products[e.Name] += e.Quantity; | ||
} | ||
} | ||
|
||
public interface ICommand {} | ||
// let's run this implementation | ||
public static void RunFactoryImplementation3() | ||
{ | ||
var factory = new FactoryImplementation3(); | ||
factory.AssignEmployeeToFactory("yoda"); | ||
factory.AssignEmployeeToFactory("luke"); | ||
factory.AssignEmployeeToFactory("yoda"); | ||
factory.AssignEmployeeToFactory("bender"); | ||
} | ||
|
||
public interface IEvent {} | ||
|
||
[Serializable] | ||
public class AddProductToShoppingBasket : ICommand | ||
{ | ||
public string Name; | ||
public double Quantity; | ||
|
||
|
||
|
||
|
||
|
||
static void Main(string[] args) | ||
{ | ||
// let's try running this | ||
RunFactoryImplementation3(); | ||
} | ||
|
||
static void Print(string format, params object[] args) | ||
{ | ||
if (format.StartsWith("!")) | ||
Console.ForegroundColor = ConsoleColor.DarkGreen; | ||
else if (format.StartsWith("?")) | ||
{ | ||
Console.ForegroundColor = ConsoleColor.DarkBlue; | ||
Console.WriteLine(); | ||
} | ||
else if (format.StartsWith(" >")) | ||
Console.ForegroundColor = ConsoleColor.DarkYellow; | ||
else Console.ForegroundColor = ConsoleColor.Gray; | ||
Console.WriteLine(format, args); | ||
} | ||
} | ||
[Serializable] | ||
public class ProductAddedToShoppingBasket : IEvent | ||
|
||
public interface IEvent | ||
{ | ||
public string Name; | ||
public double Quantity; | ||
|
||
} | ||
|
||
[Serializable] | ||
public class RemoveProductFromShoppingBasket : ICommand | ||
public sealed class Factory | ||
{ | ||
public string Name; | ||
public double Quantity; | ||
} | ||
public void AssignEmployeeToFactory(string name) {} | ||
|
||
public void ShipSuppliesToCargoBay(string shipment, CarPart[] parts) {} | ||
|
||
[Serializable] | ||
public class ApplyDiscountCardToBasket | ||
{ | ||
public DiscountType Discount; | ||
public void UnloadSuppliesFromCargoBay(string employee) {} | ||
|
||
public void ProduceCar(string employee, string carModel) {} | ||
} | ||
public enum DiscountType | ||
|
||
public sealed class CarPart | ||
{ | ||
Standard, Premium, LoyalShopper | ||
public string Name; | ||
public int Quantity; | ||
} | ||
|
||
//public class DiscountAddedToProduct | ||
//{ | ||
// public string Name; | ||
// public double | ||
//} | ||
} | ||
} |