From 13323ac5a7f04e5070f39a1e219bca30aa836a09 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Wed, 19 Jan 2022 20:45:21 +0100 Subject: [PATCH] Added example of using Composite Keys with Marten --- CQRS.Tests/CQRS.Tests.csproj | 2 +- Core.Marten/Core.Marten.csproj | 2 +- Core.Tests/Core.Tests.csproj | 2 +- .../EventSourcing.Integration.Tests.csproj | 2 +- .../CompositeIds/CompositeIdsTests.cs | 297 ++++++++++++++++++ .../Marten.Integration.Tests.csproj | 3 +- MediatR.Tests/MediatR.Tests.csproj | 2 +- .../MeetingsManagement.Api.csproj | 2 +- ...MeetingsManagement.IntegrationTests.csproj | 2 +- .../MeetingsManagement.csproj | 2 +- .../MeetingsSearch.IntegrationTests.csproj | 2 +- .../Tickets.Tests/Tickets.Tests.csproj | 2 +- .../01-CreateStreamsTable.csproj | 2 +- .../02-CreateEventsTable.csproj | 2 +- .../03-CreateAppendEventFunction.csproj | 2 +- .../03-OptimisticConcurrency.csproj | 2 +- .../04-EventStoreMethods.csproj | 2 +- .../05-StreamAggregation.csproj | 2 +- .../06-TimeTraveling/06-TimeTraveling.csproj | 2 +- .../07-AggregateAndRepository.csproj | 2 +- .../08-Snapshots/08-Snapshots.csproj | 2 +- .../09-Projections/09-Projections.csproj | 2 +- .../10-ProjectionsWithMarten.csproj | 2 +- .../EventStoreBasics.Tests.csproj | 2 +- .../EventStoreBasics/EventStoreBasics.csproj | 2 +- .../BuildYourOwnEventStore/Tools/Tools.csproj | 2 +- 26 files changed, 322 insertions(+), 26 deletions(-) create mode 100644 Marten.Integration.Tests/CompositeIds/CompositeIdsTests.cs diff --git a/CQRS.Tests/CQRS.Tests.csproj b/CQRS.Tests/CQRS.Tests.csproj index dce343f1f..43fd2678f 100644 --- a/CQRS.Tests/CQRS.Tests.csproj +++ b/CQRS.Tests/CQRS.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/Core.Marten/Core.Marten.csproj b/Core.Marten/Core.Marten.csproj index b6901bc88..9c5b88f71 100644 --- a/Core.Marten/Core.Marten.csproj +++ b/Core.Marten/Core.Marten.csproj @@ -7,7 +7,7 @@ - + diff --git a/Core.Tests/Core.Tests.csproj b/Core.Tests/Core.Tests.csproj index 1dc78660b..04250e69d 100644 --- a/Core.Tests/Core.Tests.csproj +++ b/Core.Tests/Core.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/EventSourcing.Integration.Tests/EventSourcing.Integration.Tests.csproj b/EventSourcing.Integration.Tests/EventSourcing.Integration.Tests.csproj index ce0d98c7a..3cb2950a9 100644 --- a/EventSourcing.Integration.Tests/EventSourcing.Integration.Tests.csproj +++ b/EventSourcing.Integration.Tests/EventSourcing.Integration.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/Marten.Integration.Tests/CompositeIds/CompositeIdsTests.cs b/Marten.Integration.Tests/CompositeIds/CompositeIdsTests.cs new file mode 100644 index 000000000..dc9e38cb0 --- /dev/null +++ b/Marten.Integration.Tests/CompositeIds/CompositeIdsTests.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Marten.Integration.Tests.TestsInfrastructure; +using Newtonsoft.Json; +using Weasel.Postgresql; +using Xunit; + +namespace Marten.Integration.Tests.CompositeIds; + +public class StronglyTypedValue: IEquatable> where T: IComparable +{ + public T Value { get; } + + public StronglyTypedValue(T value) + { + Value = value; + } + + public bool Equals(StronglyTypedValue? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return EqualityComparer.Default.Equals(Value, other.Value); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((StronglyTypedValue)obj); + } + + public override int GetHashCode() + { + return EqualityComparer.Default.GetHashCode(Value); + } + + public static bool operator ==(StronglyTypedValue? left, StronglyTypedValue? right) + { + return Equals(left, right); + } + + public static bool operator !=(StronglyTypedValue? left, StronglyTypedValue? right) + { + return !Equals(left, right); + } +} + +public class ReservationId: StronglyTypedValue +{ + public ReservationId(Guid value) : base(value) + { + } +}; + +public class CustomerId: StronglyTypedValue +{ + public CustomerId(Guid value) : base(value) + { + } +}; + +public class SeatId: StronglyTypedValue +{ + public SeatId(Guid value) : base(value) + { + } +}; + +public class ReservationNumber: StronglyTypedValue +{ + public ReservationNumber(string value) : base(value) + { + } +}; + +public record TentativeReservationCreated( + ReservationId ReservationId, + SeatId SeatId, + CustomerId CustomerId, + ReservationNumber Number +); + +public record ReservationSeatChanged( + ReservationId ReservationId, + SeatId SeatId +); + + +public record ReservationConfirmed( + ReservationId ReservationId +); + + +public record ReservationCancelled( + ReservationId ReservationId +); + +public abstract class Aggregate + where TKey: StronglyTypedValue + where T : IComparable +{ + + public TKey AggregateId { get; set; } = default!; + + public T Id + { + get => AggregateId.Value; + set {} + } + + public int Version { get; protected set; } + + [JsonIgnore] private readonly Queue uncommittedEvents = new(); + + public object[] DequeueUncommittedEvents() + { + var dequeuedEvents = uncommittedEvents.ToArray(); + + uncommittedEvents.Clear(); + + return dequeuedEvents; + } + + protected void Enqueue(object @event) + { + uncommittedEvents.Enqueue(@event); + } +} + +public enum ReservationStatus +{ + Tentative, + Confirmed, + Cancelled +} + +public class Reservation : Aggregate +{ + public CustomerId CustomerId { get; private set; } = default!; + + public SeatId SeatId { get; private set; } = default!; + + public ReservationNumber Number { get; private set; } = default!; + + public ReservationStatus Status { get; private set; } + + + public static Reservation CreateTentative( + SeatId seatId, + CustomerId customerId) + { + return new Reservation( + new ReservationId(Guid.NewGuid()), + seatId, + customerId, + new ReservationNumber(Guid.NewGuid().ToString()) + ); + } + + private Reservation(){} + + private Reservation( + ReservationId aggregateId, + SeatId seatId, + CustomerId customerId, + ReservationNumber reservationNumber + ) + { + var @event = new TentativeReservationCreated( + aggregateId, + seatId, + customerId, + reservationNumber + ); + + Enqueue(@event); + Apply(@event); + } + + + public void ChangeSeat(SeatId newSeatId) + { + if(Status != ReservationStatus.Tentative) + throw new InvalidOperationException($"Changing seat for the reservation in '{Status}' status is not allowed."); + + var @event = new ReservationSeatChanged(AggregateId, newSeatId); + + Enqueue(@event); + Apply(@event); + } + + public void Confirm() + { + if(Status != ReservationStatus.Tentative) + throw new InvalidOperationException($"Only tentative reservation can be confirmed (current status: {Status}."); + + var @event = new ReservationConfirmed(AggregateId); + + Enqueue(@event); + Apply(@event); + } + + public void Cancel() + { + if(Status != ReservationStatus.Tentative) + throw new InvalidOperationException($"Only tentative reservation can be cancelled (current status: {Status})."); + + var @event = new ReservationCancelled(AggregateId); + + Enqueue(@event); + Apply(@event); + } + + public void Apply(TentativeReservationCreated @event) + { + AggregateId = @event.ReservationId; + SeatId = @event.SeatId; + CustomerId = @event.CustomerId; + Number = @event.Number; + Status = ReservationStatus.Tentative; + Version++; + } + + public void Apply(ReservationSeatChanged @event) + { + SeatId = @event.SeatId; + Version++; + } + + public void Apply(ReservationConfirmed @event) + { + Status = ReservationStatus.Confirmed; + Version++; + } + + public void Apply(ReservationCancelled @event) + { + Status = ReservationStatus.Cancelled; + Version++; + } +} + + +public class CompositeIdsTests: MartenTest +{ + private const string FirstTenant = "Tenant1"; + private const string SecondTenant = "Tenant2"; + + protected override IDocumentSession CreateSession(Action? setStoreOptions = null) + { + var store = DocumentStore.For(options => + { + options.Connection(Settings.ConnectionString); + options.AutoCreateSchemaObjects = AutoCreate.All; + options.DatabaseSchemaName = SchemaName; + options.Events.DatabaseSchemaName = SchemaName; + options.UseDefaultSerialization(nonPublicMembersStorage: NonPublicMembersStorage.All); + + options.Projections.SelfAggregate(); + }); + + return store.OpenSession(); + } + + [Fact] + public void GivenAggregateWithCompositeId_WhenAppendedEvent_LiveAndInlineAggregationWorks() + { + var seatId = new SeatId(Guid.NewGuid()); + var customerId = new CustomerId(Guid.NewGuid()); + + var reservation = Reservation.CreateTentative(seatId, customerId); + var @event = reservation.DequeueUncommittedEvents().Single(); + + //1. Create events + EventStore.Append(reservation.Id, @event); + + Session.SaveChanges(); + + //2. Get live agregation + var issuesListFromLiveAggregation = EventStore.AggregateStream(reservation.Id); + + //3. Get inline aggregation + var issuesListFromInlineAggregation = Session.Load(reservation.Id); + + issuesListFromLiveAggregation.Should().NotBeNull(); + issuesListFromInlineAggregation.Should().NotBeNull(); + + issuesListFromLiveAggregation!.Id.Should().Be(reservation.Id); + issuesListFromInlineAggregation!.Id.Should().Be(reservation.Id); + } +} + + diff --git a/Marten.Integration.Tests/Marten.Integration.Tests.csproj b/Marten.Integration.Tests/Marten.Integration.Tests.csproj index eb4aead3b..7aef79137 100644 --- a/Marten.Integration.Tests/Marten.Integration.Tests.csproj +++ b/Marten.Integration.Tests/Marten.Integration.Tests.csproj @@ -20,10 +20,9 @@ - + - diff --git a/MediatR.Tests/MediatR.Tests.csproj b/MediatR.Tests/MediatR.Tests.csproj index 9e214f03f..a2057a0a1 100644 --- a/MediatR.Tests/MediatR.Tests.csproj +++ b/MediatR.Tests/MediatR.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/Sample/MeetingsManagement/MeetingsManagement.Api/MeetingsManagement.Api.csproj b/Sample/MeetingsManagement/MeetingsManagement.Api/MeetingsManagement.Api.csproj index cfc4fae84..78997caff 100644 --- a/Sample/MeetingsManagement/MeetingsManagement.Api/MeetingsManagement.Api.csproj +++ b/Sample/MeetingsManagement/MeetingsManagement.Api/MeetingsManagement.Api.csproj @@ -7,7 +7,7 @@ - + diff --git a/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/MeetingsManagement.IntegrationTests.csproj b/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/MeetingsManagement.IntegrationTests.csproj index 5fd9fc6f9..596ec83b6 100644 --- a/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/MeetingsManagement.IntegrationTests.csproj +++ b/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/MeetingsManagement.IntegrationTests.csproj @@ -17,7 +17,7 @@ - + diff --git a/Sample/MeetingsManagement/MeetingsManagement/MeetingsManagement.csproj b/Sample/MeetingsManagement/MeetingsManagement/MeetingsManagement.csproj index 566297ced..1cfc94792 100644 --- a/Sample/MeetingsManagement/MeetingsManagement/MeetingsManagement.csproj +++ b/Sample/MeetingsManagement/MeetingsManagement/MeetingsManagement.csproj @@ -8,7 +8,7 @@ - + diff --git a/Sample/MeetingsManagement/MeetingsSearch.IntegrationTests/MeetingsSearch.IntegrationTests.csproj b/Sample/MeetingsManagement/MeetingsSearch.IntegrationTests/MeetingsSearch.IntegrationTests.csproj index 7a9802abe..0e0456cdd 100644 --- a/Sample/MeetingsManagement/MeetingsSearch.IntegrationTests/MeetingsSearch.IntegrationTests.csproj +++ b/Sample/MeetingsManagement/MeetingsSearch.IntegrationTests/MeetingsSearch.IntegrationTests.csproj @@ -17,7 +17,7 @@ - + diff --git a/Sample/Tickets/Tickets.Tests/Tickets.Tests.csproj b/Sample/Tickets/Tickets.Tests/Tickets.Tests.csproj index ade5244a1..1447b4f39 100644 --- a/Sample/Tickets/Tickets.Tests/Tickets.Tests.csproj +++ b/Sample/Tickets/Tickets.Tests/Tickets.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/01-CreateStreamsTable/01-CreateStreamsTable.csproj b/Workshops/BuildYourOwnEventStore/01-CreateStreamsTable/01-CreateStreamsTable.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/01-CreateStreamsTable/01-CreateStreamsTable.csproj +++ b/Workshops/BuildYourOwnEventStore/01-CreateStreamsTable/01-CreateStreamsTable.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/02-CreateEventsTable/02-CreateEventsTable.csproj b/Workshops/BuildYourOwnEventStore/02-CreateEventsTable/02-CreateEventsTable.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/02-CreateEventsTable/02-CreateEventsTable.csproj +++ b/Workshops/BuildYourOwnEventStore/02-CreateEventsTable/02-CreateEventsTable.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/03-CreateAppendEventFunction/03-CreateAppendEventFunction.csproj b/Workshops/BuildYourOwnEventStore/03-CreateAppendEventFunction/03-CreateAppendEventFunction.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/03-CreateAppendEventFunction/03-CreateAppendEventFunction.csproj +++ b/Workshops/BuildYourOwnEventStore/03-CreateAppendEventFunction/03-CreateAppendEventFunction.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/03-OptimisticConcurrency/03-OptimisticConcurrency.csproj b/Workshops/BuildYourOwnEventStore/03-OptimisticConcurrency/03-OptimisticConcurrency.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/03-OptimisticConcurrency/03-OptimisticConcurrency.csproj +++ b/Workshops/BuildYourOwnEventStore/03-OptimisticConcurrency/03-OptimisticConcurrency.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/04-EventStoreMethods/04-EventStoreMethods.csproj b/Workshops/BuildYourOwnEventStore/04-EventStoreMethods/04-EventStoreMethods.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/04-EventStoreMethods/04-EventStoreMethods.csproj +++ b/Workshops/BuildYourOwnEventStore/04-EventStoreMethods/04-EventStoreMethods.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/05-StreamAggregation/05-StreamAggregation.csproj b/Workshops/BuildYourOwnEventStore/05-StreamAggregation/05-StreamAggregation.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/05-StreamAggregation/05-StreamAggregation.csproj +++ b/Workshops/BuildYourOwnEventStore/05-StreamAggregation/05-StreamAggregation.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/06-TimeTraveling/06-TimeTraveling.csproj b/Workshops/BuildYourOwnEventStore/06-TimeTraveling/06-TimeTraveling.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/06-TimeTraveling/06-TimeTraveling.csproj +++ b/Workshops/BuildYourOwnEventStore/06-TimeTraveling/06-TimeTraveling.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/07-AggregateAndRepository/07-AggregateAndRepository.csproj b/Workshops/BuildYourOwnEventStore/07-AggregateAndRepository/07-AggregateAndRepository.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/07-AggregateAndRepository/07-AggregateAndRepository.csproj +++ b/Workshops/BuildYourOwnEventStore/07-AggregateAndRepository/07-AggregateAndRepository.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/08-Snapshots/08-Snapshots.csproj b/Workshops/BuildYourOwnEventStore/08-Snapshots/08-Snapshots.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/08-Snapshots/08-Snapshots.csproj +++ b/Workshops/BuildYourOwnEventStore/08-Snapshots/08-Snapshots.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/09-Projections/09-Projections.csproj b/Workshops/BuildYourOwnEventStore/09-Projections/09-Projections.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/09-Projections/09-Projections.csproj +++ b/Workshops/BuildYourOwnEventStore/09-Projections/09-Projections.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/10-ProjectionsWithMarten/10-ProjectionsWithMarten.csproj b/Workshops/BuildYourOwnEventStore/10-ProjectionsWithMarten/10-ProjectionsWithMarten.csproj index 2e68e196c..73e015292 100644 --- a/Workshops/BuildYourOwnEventStore/10-ProjectionsWithMarten/10-ProjectionsWithMarten.csproj +++ b/Workshops/BuildYourOwnEventStore/10-ProjectionsWithMarten/10-ProjectionsWithMarten.csproj @@ -8,7 +8,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/EventStoreBasics.Tests/EventStoreBasics.Tests.csproj b/Workshops/BuildYourOwnEventStore/EventStoreBasics.Tests/EventStoreBasics.Tests.csproj index 18afe2888..07160a30e 100644 --- a/Workshops/BuildYourOwnEventStore/EventStoreBasics.Tests/EventStoreBasics.Tests.csproj +++ b/Workshops/BuildYourOwnEventStore/EventStoreBasics.Tests/EventStoreBasics.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/EventStoreBasics/EventStoreBasics.csproj b/Workshops/BuildYourOwnEventStore/EventStoreBasics/EventStoreBasics.csproj index 5425bf9ca..4b3d96e52 100644 --- a/Workshops/BuildYourOwnEventStore/EventStoreBasics/EventStoreBasics.csproj +++ b/Workshops/BuildYourOwnEventStore/EventStoreBasics/EventStoreBasics.csproj @@ -9,7 +9,7 @@ - + diff --git a/Workshops/BuildYourOwnEventStore/Tools/Tools.csproj b/Workshops/BuildYourOwnEventStore/Tools/Tools.csproj index e35b08bda..07ec5ac64 100644 --- a/Workshops/BuildYourOwnEventStore/Tools/Tools.csproj +++ b/Workshops/BuildYourOwnEventStore/Tools/Tools.csproj @@ -10,7 +10,7 @@ - +