-
-
Notifications
You must be signed in to change notification settings - Fork 521
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added sample business logic and conventional type name mapper
- Loading branch information
1 parent
4612f03
commit 2293cc7
Showing
7 changed files
with
255 additions
and
28 deletions.
There are no files selected for viewing
24 changes: 24 additions & 0 deletions
24
Sample/EventsVersioning/Talk/HotelManagement/EventStore/CommandHandler.cs
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 |
---|---|---|
@@ -0,0 +1,24 @@ | ||
namespace HotelManagement.EventStore; | ||
|
||
public class CommandHandler<T, TEvent>( | ||
IEventStore eventStore, | ||
Func<T, TEvent, T> evolve, | ||
Func<T> getInitial | ||
) where TEvent : notnull | ||
{ | ||
public async Task GetAndUpdate( | ||
Guid id, | ||
Func<T, TEvent[]> handle, | ||
CancellationToken ct | ||
) | ||
{ | ||
var events = await eventStore.ReadStream<TEvent>(id, ct); | ||
|
||
var state = events.Aggregate(getInitial(), evolve); | ||
|
||
var result = handle(state); | ||
|
||
if(result.Length > 0) | ||
await eventStore.AppendToStream(id, result.Cast<object>(), ct); | ||
} | ||
} |
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
47 changes: 37 additions & 10 deletions
47
Sample/EventsVersioning/Talk/HotelManagement/EventStore/EventTypeMapping.cs
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,20 +1,47 @@ | ||
namespace HotelManagement.EventStore; | ||
using System.Collections.Concurrent; | ||
|
||
namespace HotelManagement.EventStore; | ||
|
||
public class EventTypeMapping | ||
{ | ||
private readonly Dictionary<string, Type> mappings = new(); | ||
private readonly ConcurrentDictionary<string, Type?> typeMap = new(); | ||
private readonly ConcurrentDictionary<Type, string> typeNameMap = new(); | ||
|
||
public void AddCustomMap<T>(string eventTypeName) => AddCustomMap(typeof(T), eventTypeName); | ||
|
||
public EventTypeMapping Register<TEvent>(params string[] typeNames) | ||
public void AddCustomMap(Type eventType, string eventTypeName) | ||
{ | ||
var eventType = typeof(TEvent); | ||
typeNameMap.AddOrUpdate(eventType, eventTypeName, (_, typeName) => typeName); | ||
typeMap.AddOrUpdate(eventTypeName, eventType, (_, type) => type); | ||
} | ||
|
||
foreach (var typeName in typeNames) | ||
public string ToName<TEventType>() => ToName(typeof(TEventType)); | ||
|
||
public string ToName(Type eventType) => | ||
typeNameMap.GetOrAdd(eventType, _ => | ||
{ | ||
mappings.Add(typeName, eventType); | ||
} | ||
var eventTypeName = eventType.FullName!; | ||
|
||
return this; | ||
} | ||
typeMap.TryAdd(eventTypeName, eventType); | ||
|
||
return eventTypeName; | ||
}); | ||
|
||
public Type? ToType(string eventTypeName) => | ||
typeMap.GetOrAdd(eventTypeName, _ => | ||
{ | ||
var type = GetFirstMatchingTypeFromCurrentDomainAssembly(eventTypeName); | ||
|
||
if (type == null) | ||
return null; | ||
|
||
typeNameMap.TryAdd(type, eventTypeName); | ||
|
||
return type; | ||
}); | ||
|
||
public Type Map(string eventType) => mappings[eventType]; | ||
private static Type? GetFirstMatchingTypeFromCurrentDomainAssembly(string typeName) => | ||
AppDomain.CurrentDomain.GetAssemblies() | ||
.SelectMany(a => a.GetTypes().Where(x => x.FullName == typeName || x.Name == typeName)) | ||
.FirstOrDefault(); | ||
} |
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
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
86 changes: 86 additions & 0 deletions
86
Sample/EventsVersioning/Talk/HotelManagement/GuestStayAccounts/BusinessLogic.cs
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 |
---|---|---|
@@ -0,0 +1,86 @@ | ||
namespace HotelManagement.GuestStayAccounts; | ||
|
||
using static GuestStayAccountEvent; | ||
using static GuestStayAccountCommand; | ||
|
||
public abstract record GuestStayAccountCommand | ||
{ | ||
public record CheckIn( | ||
string ClerkId, | ||
string GuestStayId, | ||
string RoomId, | ||
DateTimeOffset Now | ||
): GuestStayAccountCommand; | ||
|
||
public record RecordCharge( | ||
string GuestStayAccountId, | ||
decimal Amount, | ||
DateTimeOffset Now | ||
): GuestStayAccountCommand; | ||
|
||
public record RecordPayment( | ||
string GuestStayAccountId, | ||
decimal Amount, | ||
DateTimeOffset Now | ||
): GuestStayAccountCommand; | ||
|
||
public record CheckOut( | ||
string ClerkId, | ||
string GuestStayAccountId, | ||
DateTimeOffset Now | ||
): GuestStayAccountCommand; | ||
|
||
private GuestStayAccountCommand() { } | ||
} | ||
|
||
public static class GuestStayAccountDecider | ||
{ | ||
public static GuestCheckedIn CheckIn(CheckIn command, GuestStayAccount state) => | ||
new GuestCheckedIn( | ||
$"{command.GuestStayId}:{command.RoomId}:{command.Now.Date:yyyy-MM-dd}", | ||
command.GuestStayId, | ||
command.RoomId, | ||
command.ClerkId, | ||
command.Now | ||
); | ||
|
||
public static ChargeRecorded RecordCharge(RecordCharge command, GuestStayAccount state) | ||
{ | ||
if (state.Status != GuestStayAccountStatus.Opened) | ||
throw new InvalidOperationException("Cannot record charge for not opened account"); | ||
|
||
return new ChargeRecorded(state.Id, command.Amount, command.Now); | ||
} | ||
|
||
public static PaymentRecorded RecordPayment(RecordPayment command, GuestStayAccount state) | ||
{ | ||
if (state.Status != GuestStayAccountStatus.Opened) | ||
throw new InvalidOperationException("Cannot record charge for not opened account"); | ||
|
||
return new PaymentRecorded(state.Id, command.Amount, command.Now); | ||
} | ||
|
||
public static GuestStayAccountEvent CheckOut(CheckOut command, GuestStayAccount state) | ||
{ | ||
if (state.Status != GuestStayAccountStatus.Opened) | ||
return new GuestCheckoutFailed( | ||
state.Id, | ||
command.ClerkId, | ||
GuestCheckoutFailed.FailureReason.NotOpened, | ||
command.Now | ||
); | ||
|
||
return state.IsSettled | ||
? new GuestCheckedOut( | ||
state.Id, | ||
command.ClerkId, | ||
command.Now | ||
) | ||
: new GuestCheckoutFailed( | ||
state.Id, | ||
command.ClerkId, | ||
GuestCheckoutFailed.FailureReason.BalanceNotSettled, | ||
command.Now | ||
); | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
Sample/EventsVersioning/Talk/HotelManagement/GuestStayAccounts/GuestStayAccount.cs
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 |
---|---|---|
@@ -0,0 +1,79 @@ | ||
namespace HotelManagement.GuestStayAccounts; | ||
|
||
using static GuestStayAccountEvent; | ||
|
||
public record GuestStayAccount( | ||
string Id, | ||
decimal Balance = 0, | ||
GuestStayAccountStatus Status = GuestStayAccountStatus.Opened | ||
) | ||
{ | ||
public bool IsSettled => Balance == 0; | ||
|
||
public GuestStayAccount Evolve(GuestStayAccountEvent @event) => | ||
@event switch | ||
{ | ||
GuestCheckedIn checkedIn => this with | ||
{ | ||
Id = checkedIn.GuestStayAccountId, Status = GuestStayAccountStatus.Opened | ||
}, | ||
ChargeRecorded charge => this with { Balance = Balance - charge.Amount }, | ||
PaymentRecorded payment => this with { Balance = Balance + payment.Amount }, | ||
GuestCheckedOut => this with { Status = GuestStayAccountStatus.CheckedOut }, | ||
GuestCheckoutFailed => this, | ||
_ => this | ||
}; | ||
|
||
public static readonly GuestStayAccount Initial = new("", default, default); | ||
} | ||
|
||
public enum GuestStayAccountStatus | ||
{ | ||
Opened = 1, | ||
CheckedOut = 2 | ||
} | ||
|
||
public abstract record GuestStayAccountEvent | ||
{ | ||
public record GuestCheckedIn( | ||
string GuestStayAccountId, | ||
string GuestStayId, | ||
string RoomId, | ||
string ClerkId, | ||
DateTimeOffset CheckedInAt | ||
): GuestStayAccountEvent; | ||
|
||
public record ChargeRecorded( | ||
string GuestStayAccountId, | ||
decimal Amount, | ||
DateTimeOffset RecordedAt | ||
): GuestStayAccountEvent; | ||
|
||
public record PaymentRecorded( | ||
string GuestStayAccountId, | ||
decimal Amount, | ||
DateTimeOffset RecordedAt | ||
): GuestStayAccountEvent; | ||
|
||
public record GuestCheckedOut( | ||
string GuestStayAccountId, | ||
string ClerkId, | ||
DateTimeOffset CheckedOutAt | ||
): GuestStayAccountEvent; | ||
|
||
public record GuestCheckoutFailed( | ||
string GuestStayAccountId, | ||
string ClerkId, | ||
GuestCheckoutFailed.FailureReason Reason, | ||
DateTimeOffset FailedAt | ||
): GuestStayAccountEvent | ||
{ | ||
public enum FailureReason | ||
{ | ||
NotOpened, | ||
BalanceNotSettled | ||
} | ||
} | ||
|
||
private GuestStayAccountEvent(){} | ||
} |