Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Student Aggregate #1

Merged
merged 9 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions .dockerignore

This file was deleted.

1 change: 0 additions & 1 deletion StudentEnrollmentConsoleApp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
docker-compose.yml = docker-compose.yml
.gitignore = .gitignore
.dockerignore = .dockerignore
global.json = global.json
README.md = README.md
EndProjectSection
Expand Down
21 changes: 0 additions & 21 deletions StudentEnrollmentConsoleApp/Dockerfile

This file was deleted.

6 changes: 2 additions & 4 deletions StudentEnrollmentConsoleApp/Events/Event.cs
Original file line number Diff line number Diff line change
@@ -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!;
}
35 changes: 35 additions & 0 deletions StudentEnrollmentConsoleApp/Events/EventTypeMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Concurrent;

namespace StudentEnrollmentConsoleApp.Events;

public class EventTypeMapper
{
public static readonly EventTypeMapper Instance = new();

private readonly ConcurrentDictionary<string, Type?> _typeMap = new();
private readonly ConcurrentDictionary<Type, string> _typeNameMap = new();

public string ToName<TEventType>() => 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;
});
}
6 changes: 2 additions & 4 deletions StudentEnrollmentConsoleApp/Events/StudentCreated.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
7 changes: 7 additions & 0 deletions StudentEnrollmentConsoleApp/Events/StudentEmailChanged.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace StudentEnrollmentConsoleApp.Events;

public record StudentEmailChanged : Event
{
public required string Email { get; init; }
public DateTime ChangedAtUtc { get; init; }
}
6 changes: 2 additions & 4 deletions StudentEnrollmentConsoleApp/Events/StudentEnrolled.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
9 changes: 0 additions & 9 deletions StudentEnrollmentConsoleApp/Events/StudentUnEnrolled.cs

This file was deleted.

10 changes: 0 additions & 10 deletions StudentEnrollmentConsoleApp/Events/StudentUpdated.cs

This file was deleted.

7 changes: 7 additions & 0 deletions StudentEnrollmentConsoleApp/Events/StudentWithdrawn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace StudentEnrollmentConsoleApp.Events;

public record StudentWithdrawn : Event
{
public required string CourseName { get; init; }
public DateTime WithdrawnAtUtc { get; init; }
}
113 changes: 81 additions & 32 deletions StudentEnrollmentConsoleApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,58 +1,107 @@
using System.Text;
using System.Text.Json;
using EventStore.Client;
using StudentEnrollmentConsoleApp;
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 studentId = $"student-{id}";
var now = DateTime.Now;
var streamId = $"student-{id}";

var studentCreated = new StudentCreated
{
StudentId = studentId,
FullName = "Erik Shafer",
Email = "erik.shafer@eventstore.com",
DateOfBirth = new DateTime(1987, 1, 1)
};
const string eventType = nameof(studentCreated); //"StudentCreated";
const string streamName = "some-stream";
var created = new EventData(
Uuid.NewUuid(),
"StudentCreated",
JsonSerializer.SerializeToUtf8Bytes(new StudentCreated
{
Id = streamId,
FullName = "Erik Shafer",
Email = "erik.shafer@eventstore.com",
DateOfBirth = new DateTime(1987, 1, 1),
CreatedAtUtc = DateTime.UtcNow
})
);

var inMemoryDb = new StudentDatabase();
inMemoryDb.Append(studentCreated);
var enrolled = new EventData(
Uuid.NewUuid(),
"StudentEnrolled",
JsonSerializer.SerializeToUtf8Bytes(new StudentEnrolled
{
Id = streamId,
CourseName = "From Zero to Hero: REST APis in .NET",
EnrolledAtUtc = DateTime.UtcNow
})
);

var emailChanged = new EventData(
Uuid.NewUuid(),
"StudentEmailChanged",
JsonSerializer.SerializeToUtf8Bytes(new StudentEmailChanged
{
Id = streamId,
Email = "erik.shafer.changed.his.email@eventstore.com",
ChangedAtUtc = DateTime.UtcNow
})
);

// Our EventStoreDB (ESDB)
const string connectionString = "esdb://admin:changeit@localhost:2113?tls=false&tlsVerifyCert=false";
var settings = EventStoreClientSettings.Create(connectionString);
var client = new EventStoreClient(settings);

var eventData = new EventData(
Uuid.NewUuid(),
eventType,
JsonSerializer.SerializeToUtf8Bytes(studentCreated)
);

// Append to ESDB
await client.AppendToStreamAsync(
streamName,
streamId,
StreamState.Any,
new[] { eventData },
new[] { created, enrolled, emailChanged },
cancellationToken: default
);

var result = client.ReadStreamAsync(
// Read from ESDB
var readStreamResult = client.ReadStreamAsync(
Direction.Forwards,
streamName,
streamId,
StreamPosition.Start,
cancellationToken: default
);
var eventStream = await readStreamResult.ToListAsync();

var events = await result.ToListAsync();
// Write out the events from the stream
Console.WriteLine("Events from selected stream: ");
foreach (var resolved in eventStream)
{
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("");
}

foreach (var @event in events)
// Write out all the courses the student enrolled in
var enrolledCourses = eventStream
.Where(re => re.Event.EventType == "StudentEnrolled")
.Select(re => JsonSerializer.Deserialize<StudentEnrolled>(re.Event.Data.ToArray()))
.Select(se => se!.CourseName)
.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)
{
Console.WriteLine($"EventType: {@event.Event.EventId}");
Console.WriteLine($"EventStreamId: {@event.Event.EventStreamId}");
Console.WriteLine($"EventType: {@event.Event.EventType}");
var data = Encoding.UTF8.GetString(@event.Event.Data.ToArray());
Console.WriteLine($"Data: {data}");
Console.WriteLine("-----");
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("");
57 changes: 57 additions & 0 deletions StudentEnrollmentConsoleApp/Student.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using StudentEnrollmentConsoleApp.Events;

namespace StudentEnrollmentConsoleApp;

public class Student
{
public string Id { get; set; } = default!;
public string FullName { get; set; } = default!;
public string Email { get; set; } = default!;
public DateTime DateOfBirth { get; set; }
public DateTime CreatedAtUtc { get; set; }
public List<string> EnrolledCourses { get; set; } = [];

public void Apply(Event @event)
{
switch (@event)
{
case StudentCreated created:
Apply(created);
break;
case StudentEmailChanged emailChanged:
Apply(emailChanged);
break;
case StudentEnrolled enrolled:
Apply(enrolled);
break;
case StudentWithdrawn withdrawn:
Apply(withdrawn);
break;
}
}

private void Apply(StudentCreated @event)
{
Id = @event.Id;
FullName = @event.FullName;
Email = @event.Email;
CreatedAtUtc = @event.CreatedAtUtc;
}

private void Apply(StudentEmailChanged @event)
{
Email = @event.Email;
}

private void Apply(StudentEnrolled @event)
{
if (EnrolledCourses.Contains(@event.CourseName) is false)
EnrolledCourses.Add(@event.CourseName);
}

private void Apply(StudentWithdrawn @event)
{
if (EnrolledCourses.Contains(@event.CourseName))
EnrolledCourses.Add(@event.CourseName);
}
}
20 changes: 0 additions & 20 deletions StudentEnrollmentConsoleApp/StudentDatabase.cs

This file was deleted.

Loading