diff --git a/StudentEnrollmentConsoleApp/Events/Event.cs b/StudentEnrollmentConsoleApp/Events/Event.cs index 93326e5..c46e770 100644 --- a/StudentEnrollmentConsoleApp/Events/Event.cs +++ b/StudentEnrollmentConsoleApp/Events/Event.cs @@ -1,8 +1,6 @@ namespace StudentEnrollmentConsoleApp.Events; -public abstract class Event +public abstract record Event { - public abstract string StreamId { get; } - - public DateTime CreatedAtUtc { get; set; } + public string Id { get; init; } = default!; } \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Events/EventTypeMapper.cs b/StudentEnrollmentConsoleApp/Events/EventTypeMapper.cs new file mode 100644 index 0000000..2eaecbd --- /dev/null +++ b/StudentEnrollmentConsoleApp/Events/EventTypeMapper.cs @@ -0,0 +1,35 @@ +using System.Collections.Concurrent; + +namespace StudentEnrollmentConsoleApp.Events; + +public class EventTypeMapper +{ + public static readonly EventTypeMapper Instance = new(); + + private readonly ConcurrentDictionary _typeMap = new(); + private readonly ConcurrentDictionary _typeNameMap = new(); + + public string ToName() => ToName(typeof(TEventType)); + + public string ToName(Type eventType) => _typeNameMap.GetOrAdd(eventType, _ => + { + var eventTypeName = eventType.FullName!; + _typeMap.TryAdd(eventTypeName, eventType); + return eventTypeName; + }); + + public Type? ToType(string eventTypeName) => _typeMap.GetOrAdd(eventTypeName, _ => + { + var type = AppDomain.CurrentDomain + .GetAssemblies() + .SelectMany(a => a.GetTypes().Where(x => x.FullName == eventTypeName || x.Name == eventTypeName)) + .FirstOrDefault(); + + if (type == null) + return null; + + _typeNameMap.TryAdd(type, eventTypeName); + + return type; + }); +} \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Events/StudentCreated.cs b/StudentEnrollmentConsoleApp/Events/StudentCreated.cs index cd410da..f196749 100644 --- a/StudentEnrollmentConsoleApp/Events/StudentCreated.cs +++ b/StudentEnrollmentConsoleApp/Events/StudentCreated.cs @@ -1,11 +1,9 @@ namespace StudentEnrollmentConsoleApp.Events; -public class StudentCreated : Event +public record StudentCreated : Event { - public required string StudentId { get; init; } public required string FullName { get; init; } public required string Email { get; init; } public required DateTime DateOfBirth { get; init; } - - public override string StreamId => StudentId; + public DateTime CreatedAtUtc { get; init; } } \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Events/StudentEmailChanged.cs b/StudentEnrollmentConsoleApp/Events/StudentEmailChanged.cs new file mode 100644 index 0000000..686878d --- /dev/null +++ b/StudentEnrollmentConsoleApp/Events/StudentEmailChanged.cs @@ -0,0 +1,7 @@ +namespace StudentEnrollmentConsoleApp.Events; + +public record StudentEmailChanged : Event +{ + public required string Email { get; init; } + public DateTime ChangedAtUtc { get; init; } +} \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Events/StudentEnrolled.cs b/StudentEnrollmentConsoleApp/Events/StudentEnrolled.cs index faee8d3..aecbf81 100644 --- a/StudentEnrollmentConsoleApp/Events/StudentEnrolled.cs +++ b/StudentEnrollmentConsoleApp/Events/StudentEnrolled.cs @@ -1,9 +1,7 @@ namespace StudentEnrollmentConsoleApp.Events; -public class StudentEnrolled : Event +public record StudentEnrolled : Event { - public required string StudentId { get; init; } public required string CourseName { get; init; } - - public override string StreamId => StudentId; + public DateTime EnrolledAtUtc { get; init; } } \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Events/StudentUnEnrolled.cs b/StudentEnrollmentConsoleApp/Events/StudentUnEnrolled.cs deleted file mode 100644 index 07bb6e8..0000000 --- a/StudentEnrollmentConsoleApp/Events/StudentUnEnrolled.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace StudentEnrollmentConsoleApp.Events; - -public class StudentUnEnrolled : Event -{ - public required string StudentId { get; init; } - public required string CourseName { get; init; } - - public override string StreamId => StudentId; -} \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Events/StudentUpdated.cs b/StudentEnrollmentConsoleApp/Events/StudentUpdated.cs deleted file mode 100644 index a57ea8c..0000000 --- a/StudentEnrollmentConsoleApp/Events/StudentUpdated.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace StudentEnrollmentConsoleApp.Events; - -public class StudentUpdated : Event -{ - public required string StudentId { get; init; } - public required string FullName { get; init; } - public required string Email { get; init; } - - public override string StreamId => StudentId; -} \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Events/StudentWithdrawn.cs b/StudentEnrollmentConsoleApp/Events/StudentWithdrawn.cs new file mode 100644 index 0000000..446b80c --- /dev/null +++ b/StudentEnrollmentConsoleApp/Events/StudentWithdrawn.cs @@ -0,0 +1,7 @@ +namespace StudentEnrollmentConsoleApp.Events; + +public record StudentWithdrawn : Event +{ + public required string CourseName { get; init; } + public DateTime WithdrawnAtUtc { get; init; } +} \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Program.cs b/StudentEnrollmentConsoleApp/Program.cs index 4cf91e3..0644d32 100644 --- a/StudentEnrollmentConsoleApp/Program.cs +++ b/StudentEnrollmentConsoleApp/Program.cs @@ -3,6 +3,12 @@ using EventStore.Client; using StudentEnrollmentConsoleApp.Events; +// Register events to a singleton for ease-of-reference +EventTypeMapper.Instance.ToName(typeof(StudentCreated)); +EventTypeMapper.Instance.ToName(typeof(StudentEnrolled)); +EventTypeMapper.Instance.ToName(typeof(StudentWithdrawn)); +EventTypeMapper.Instance.ToName(typeof(StudentEmailChanged)); + var id = Guid.Parse("a662d446-4920-415e-8c2a-0dd4a6c58908"); var streamId = $"student-{id}"; @@ -11,10 +17,11 @@ "StudentCreated", JsonSerializer.SerializeToUtf8Bytes(new StudentCreated { - StudentId = streamId, + Id = streamId, FullName = "Erik Shafer", Email = "erik.shafer@eventstore.com", - DateOfBirth = new DateTime(1987, 1, 1) + DateOfBirth = new DateTime(1987, 1, 1), + CreatedAtUtc = DateTime.UtcNow }) ); @@ -23,19 +30,20 @@ "StudentEnrolled", JsonSerializer.SerializeToUtf8Bytes(new StudentEnrolled { - StudentId = streamId, - CourseName = "From Zero to Hero: REST APis in .NET" + Id = streamId, + CourseName = "From Zero to Hero: REST APis in .NET", + EnrolledAtUtc = DateTime.UtcNow }) ); -var updated = new EventData( +var emailChanged = new EventData( Uuid.NewUuid(), - "StudentUpdated", - JsonSerializer.SerializeToUtf8Bytes(new StudentUpdated + "StudentEmailChanged", + JsonSerializer.SerializeToUtf8Bytes(new StudentEmailChanged { - StudentId = streamId, - FullName = "Erik Shafer", - Email = "erik.shafer.new.email@eventstore.com", + Id = streamId, + Email = "erik.shafer.changed.his.email@eventstore.com", + ChangedAtUtc = DateTime.UtcNow }) ); @@ -48,7 +56,7 @@ await client.AppendToStreamAsync( streamId, StreamState.Any, - new[] { created, enrolled, updated }, + new[] { created, enrolled, emailChanged }, cancellationToken: default ); @@ -62,13 +70,14 @@ await client.AppendToStreamAsync( var eventStream = await readStreamResult.ToListAsync(); // Write out the events from the stream -foreach (var @event in eventStream) +Console.WriteLine("Events from selected stream: "); +foreach (var resolved in eventStream) { - Console.WriteLine($"EventId: {@event.Event.EventId}"); - Console.WriteLine($"EventStreamId: {@event.Event.EventStreamId}"); - Console.WriteLine($"EventType: {@event.Event.EventType}"); - Console.WriteLine($"Data: {Encoding.UTF8.GetString(@event.Event.Data.ToArray())}"); - Console.WriteLine("---"); + Console.WriteLine($"\tEventId: {resolved.Event.EventId}"); + Console.WriteLine($"\tEventStreamId: {resolved.Event.EventStreamId}"); + Console.WriteLine($"\tEventType: {resolved.Event.EventType}"); + Console.WriteLine($"\tData: {Encoding.UTF8.GetString(resolved.Event.Data.ToArray())}"); + Console.WriteLine(""); } // Write out all the courses the student enrolled in @@ -76,7 +85,23 @@ await client.AppendToStreamAsync( .Where(re => re.Event.EventType == "StudentEnrolled") .Select(re => JsonSerializer.Deserialize(re.Event.Data.ToArray())) .Select(se => se!.CourseName) - .ToArray(); + .ToList(); +Console.WriteLine("Courses enrolled in: "); +enrolledCourses.ForEach(ec => Console.WriteLine($"\t- {ec}")); +Console.WriteLine(""); + +// Write out using the mapper +Console.WriteLine("Deserialized events:"); +foreach (var resolved in eventStream) +{ + var eventType = EventTypeMapper.Instance.ToType(resolved.Event.EventType); + + if (eventType == null) + break; + + var deserializedEvent = JsonSerializer.Deserialize(Encoding.UTF8.GetString(resolved.Event.Data.Span), eventType); + + Console.WriteLine($"\t{deserializedEvent}"); +} -Console.WriteLine($"Courses enrolled in: {enrolledCourses}"); Console.WriteLine(""); \ No newline at end of file diff --git a/StudentEnrollmentConsoleApp/Student.cs b/StudentEnrollmentConsoleApp/Student.cs index d55e12e..817cfb6 100644 --- a/StudentEnrollmentConsoleApp/Student.cs +++ b/StudentEnrollmentConsoleApp/Student.cs @@ -4,11 +4,12 @@ namespace StudentEnrollmentConsoleApp; public class Student { - public string Id { get; set; } - public string FullName { get; set; } - public string Email { get; set; } + public string Id { get; set; } = default!; + public string FullName { get; set; } = default!; + public string Email { get; set; } = default!; public DateTime DateOfBirth { get; set; } - public List EnrolledCourses { get; set; } = new(); + public DateTime CreatedAtUtc { get; set; } + public List EnrolledCourses { get; set; } = []; public void Apply(Event @event) { @@ -17,28 +18,28 @@ public void Apply(Event @event) case StudentCreated created: Apply(created); break; - case StudentUpdated updated: - Apply(updated); + case StudentEmailChanged emailChanged: + Apply(emailChanged); break; case StudentEnrolled enrolled: Apply(enrolled); break; - case StudentUnEnrolled unEnrolled: - Apply(unEnrolled); + case StudentWithdrawn withdrawn: + Apply(withdrawn); break; } } private void Apply(StudentCreated @event) { - Id = @event.StudentId; + Id = @event.Id; FullName = @event.FullName; Email = @event.Email; + CreatedAtUtc = @event.CreatedAtUtc; } - private void Apply(StudentUpdated @event) + private void Apply(StudentEmailChanged @event) { - FullName = @event.FullName; Email = @event.Email; } @@ -48,7 +49,7 @@ private void Apply(StudentEnrolled @event) EnrolledCourses.Add(@event.CourseName); } - private void Apply(StudentUnEnrolled @event) + private void Apply(StudentWithdrawn @event) { if (EnrolledCourses.Contains(@event.CourseName)) EnrolledCourses.Add(@event.CourseName);