Example #1
0
        public async Task <T> Load <T>(string id) where T : Aggregate, new()
        {
            Ensure.NotEmptyString(id, nameof(id));

            var stream    = StreamName.For <T>(id);
            var aggregate = new T();

            try {
                await _eventStore.ReadStream(stream, StreamReadPosition.Start, Fold);
            }
            catch (Exceptions.StreamNotFound e) {
                throw new Exceptions.AggregateNotFound <T>(id, e);
            }

            return(aggregate);

            void Fold(StreamEvent streamEvent)
            {
                var evt = Deserialize(streamEvent);

                if (evt == null)
                {
                    return;
                }

                aggregate !.Fold(evt);
            }

            object?Deserialize(StreamEvent streamEvent)
            => _serializer.Deserialize(streamEvent.Data.AsSpan(), streamEvent.EventType);
        }
Example #2
0
    public async Task ShouldProjectImported()
    {
        var evt    = DomainFixture.CreateImportBooking();
        var stream = StreamName.For <Booking>(evt.BookingId);

        var append = await Instance.EventStore.AppendEvents(
            stream,
            ExpectedStreamVersion.Any,
            new[] {
            new StreamEvent(Guid.NewGuid(), evt, new Metadata(), "application/json", 0)
        },
            CancellationToken.None
            );

        await Task.Delay(500);

        var expected = new BookingDocument(evt.BookingId)
        {
            RoomId       = evt.RoomId,
            CheckInDate  = evt.CheckIn,
            CheckOutDate = evt.CheckOut,
            Position     = append.GlobalPosition
        };

        var actual = await Instance.Mongo.LoadDocument <BookingDocument>(evt.BookingId);

        actual.Should().Be(expected);
    }
    public async Task read_snapshot_when_loading_aggregate()
    {
        var now              = DateTime.UtcNow;
        var esStore          = new EsEventStore(GetEventStoreClient(), "snapshot_test");
        var esAggregateStore = new EsAggregateStore(esStore, 5);

        var aggregate = new Day();

        var slots = new List <ScheduledSlot>
        {
            new ScheduledSlot(TimeSpan.FromMinutes(10), now),
            new ScheduledSlot(TimeSpan.FromMinutes(10), now.AddMinutes(10)),
            new ScheduledSlot(TimeSpan.FromMinutes(10), now.AddMinutes(20)),
            new ScheduledSlot(TimeSpan.FromMinutes(10), now.AddMinutes(30)),
            new ScheduledSlot(TimeSpan.FromMinutes(10), now.AddMinutes(40))
        };

        aggregate.Schedule(new DoctorId(Guid.NewGuid()), DateTime.UtcNow, slots, Guid.NewGuid);

        await esAggregateStore.Save(aggregate,
                                    new CommandMetadata(new CorrelationId(Guid.NewGuid()), new CausationId(Guid.NewGuid())));

        await esStore.TruncateStream(StreamName.For <Day>(aggregate.Id), Convert.ToUInt64(aggregate.GetChanges().Count()));

        var reloadedAggregate = await esAggregateStore.Load <Day>(aggregate.Id);

        Assert.Equal(5, reloadedAggregate.Version);
    }
    public async Task write_snapshot_if_threshold_reached()
    {
        var now              = DateTime.UtcNow;
        var esStore          = new EsEventStore(GetEventStoreClient(), "snapshot_test");
        var esAggregateStore = new EsAggregateStore(esStore, 5);

        var aggregate = new Day();

        var slots = new List <ScheduledSlot>
        {
            new ScheduledSlot(TimeSpan.FromMinutes(10), now),
            new ScheduledSlot(TimeSpan.FromMinutes(10), now.AddMinutes(10)),
            new ScheduledSlot(TimeSpan.FromMinutes(10), now.AddMinutes(20)),
            new ScheduledSlot(TimeSpan.FromMinutes(10), now.AddMinutes(30)),
            new ScheduledSlot(TimeSpan.FromMinutes(10), now.AddMinutes(40))
        };

        aggregate.Schedule(new DoctorId(Guid.NewGuid()), DateTime.UtcNow, slots, Guid.NewGuid);

        await esAggregateStore.Save(aggregate,
                                    new CommandMetadata(new CorrelationId(Guid.NewGuid()), new CausationId(Guid.NewGuid())));

        var snapshotEnvelope = await esStore.LoadSnapshot(StreamName.For <Day>(aggregate.Id));

        var snapshot = snapshotEnvelope?.Snapshot as DaySnapshot;

        Assert.NotNull(snapshot);
    }
Example #5
0
        public async Task <T> Load <T>(string id, CancellationToken cancellationToken) where T : Aggregate, new()
        {
            Ensure.NotEmptyString(id, nameof(id));

            var stream    = StreamName.For <T>(id);
            var aggregate = new T();

            try {
                await _eventStore.ReadStream(stream, StreamReadPosition.Start, Fold, cancellationToken).Ignore();
            }
            catch (Exceptions.StreamNotFound e) {
                throw new Exceptions.AggregateNotFound <T>(id, e);
            }

            return(aggregate);

            void Fold(StreamEvent streamEvent)
            {
                var evt = Deserialize(streamEvent);

                if (evt == null)
                {
                    return;
                }

                aggregate !.Fold(evt);
            }
        }
Example #6
0
    public async Task MapEnrichedCommand()
    {
        var store = new InMemoryEventStore();

        var app = new WebApplicationFactory <Program>()
                  .WithWebHostBuilder(
            builder => builder.ConfigureServices(services => services.AddAggregateStore(_ => store))
            );

        var httpClient = app.CreateClient();

        using var client = new RestClient(httpClient, disposeHttpClient: true).UseSerializer(
                  () => new SystemTextJsonSerializer(
                      new JsonSerializerOptions(JsonSerializerDefaults.Web).ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)
                      )
                  );

        var cmd = new BookRoom(
            "123",
            "123",
            LocalDate.FromDateTime(DateTime.Now),
            LocalDate.FromDateTime(DateTime.Now.AddDays(1)),
            100
            );

        var response = await client.PostJsonAsync("/book", cmd);

        response.Should().Be(HttpStatusCode.OK);

        var storedEvents = await store.ReadEvents(
            StreamName.For <Booking>(cmd.BookingId),
            StreamReadPosition.Start,
            100,
            default
            );

        var actual = storedEvents.FirstOrDefault();

        actual.Should().NotBeNull();

        actual !.Payload.Should()
        .BeEquivalentTo(
            new BookingEvents.RoomBooked(
                cmd.BookingId,
                cmd.RoomId,
                cmd.CheckIn,
                cmd.CheckOut,
                cmd.Price,
                TestData.GuestId
                )
            );
    }
Example #7
0
        public async Task Store <T>(T aggregate)
            where T : Aggregate
        {
            Ensure.NotNull(aggregate, nameof(aggregate));

            if (aggregate.Changes.Count == 0)
            {
                return;
            }

            var stream          = StreamName.For <T>(aggregate.GetId());
            var expectedVersion = new ExpectedStreamVersion(aggregate.OriginalVersion);

            await _eventStore.AppendEvents(stream, expectedVersion, aggregate.Changes.Select(ToStreamEvent).ToArray());

            StreamEvent ToStreamEvent(object evt)
            => new(TypeMap.GetTypeName(evt), _serializer.Serialize(evt), null, _serializer.ContentType);
        }
Example #8
0
    public DayArchiverProcessManager(
        IColdStorage coldStorage,
        IArchivableDaysRepository archivableDaysRepository,
        ICommandStore commandStore,
        TimeSpan threshold,
        IEventStore eventStore,
        Func <Guid> idGenerator
        )
    {
        _commandStore = commandStore;
        _idGenerator  = idGenerator;

        When <DayScheduled>(async(e, m) =>
        {
            await archivableDaysRepository.Add(new ArchivableDay(e.DayId, e.Date));
        });

        When <CalendarDayStarted>(async(e, m) =>
        {
            var archivableDays = await archivableDaysRepository.FindAll(e.Date.Add(threshold));
            foreach (var day in archivableDays)
            {
                await SendCommand(day.Id, m.CorrelationId, m.CausationId);
            }
        });

        When <DayScheduleArchived>(async(e, m) =>
        {
            var streamName = StreamName.For <Day>(e.DayId);

            var events = await eventStore.LoadEvents(streamName);
            coldStorage.SaveAll(events);

            var lastVersion = await eventStore.GetLastVersion(streamName);

            if (lastVersion.HasValue)
            {
                await eventStore.TruncateStream(streamName, lastVersion.Value);
            }
        });
    }
Example #9
0
        public async Task Store <T>(T aggregate)
            where T : Aggregate
        {
            if (aggregate == null)
            {
                throw new ArgumentNullException(nameof(aggregate));
            }

            if (aggregate.Changes.Count == 0)
            {
                return;
            }

            var stream          = StreamName.For <T>(aggregate.GetId());
            var expectedVersion = new ExpectedStreamVersion(aggregate.Version);

            await _eventStore.AppendEvents(stream, expectedVersion, aggregate.Changes.Select(ToStreamEvent).ToArray());

            JsonStreamEvent ToStreamEvent(object evt)
            => new(TypeMap.GetTypeName(evt), _serializer.Serialize(evt));
        }
Example #10
0
    public async Task StoreInitial()
    {
        var cmd = new Commands.BookRoom(
            Auto.Create <string>(),
            Auto.Create <string>(),
            LocalDate.FromDateTime(DateTime.Today),
            LocalDate.FromDateTime(DateTime.Today.AddDays(2)),
            Auto.Create <decimal>()
            );

        var expected = new Change[] {
            new(
                new BookingEvents.RoomBooked(
                    cmd.BookingId,
                    cmd.RoomId,
                    cmd.CheckIn,
                    cmd.CheckOut,
                    cmd.Price
                    ),
                "RoomBooked"
                )
        };

        var result = await Service.Handle(cmd, default);

        result.Success.Should().BeTrue();
        result.Changes.Should().BeEquivalentTo(expected);

        var evt = await EventStore.ReadEvents(
            StreamName.For <Booking>(cmd.BookingId),
            StreamReadPosition.Start,
            1,
            CancellationToken.None
            );

        evt[0].Payload.Should().BeEquivalentTo(result.Changes !.First().Event);
    }
    public async Task StreamSubscriptionGetsDeletedEvents()
    {
        var service = new BookingService(Instance.AggregateStore);

        var categoryStream = new StreamName("$ce-Booking");

        ulong?startPosition = null;

        try {
            var last = await Instance.Client.ReadStreamAsync(
                Direction.Backwards,
                categoryStream,
                StreamPosition.End,
                1
                ).ToArrayAsync();

            startPosition = last[0].OriginalEventNumber;
        }
        catch (StreamNotFound) { }

        const int produceCount = 20;
        const int deleteCount  = 5;

        var commands = Enumerable.Range(0, produceCount)
                       .Select(_ => DomainFixture.CreateImportBooking())
                       .ToArray();

        await Task.WhenAll(
            commands.Select(x => service.Handle(x, CancellationToken.None))
            );

        var delete = Enumerable.Range(5, deleteCount).Select(x => commands[x]).ToList();

        await Task.WhenAll(
            delete
            .Select(
                x => Instance.EventStore.DeleteStream(
                    StreamName.For <Booking>(x.BookingId),
                    ExpectedStreamVersion.Any,
                    CancellationToken.None
                    )
                )
            );

        var handler = new TestHandler();

        const string subscriptionId = "TestSub";

        var subscription = new StreamSubscription(
            Instance.Client,
            new StreamSubscriptionOptions {
            StreamName     = categoryStream,
            SubscriptionId = subscriptionId,
            ResolveLinkTos = true,
            ThrowOnError   = true
        },
            new NoOpCheckpointStore(startPosition),
            new ConsumePipe()
            .AddFilterLast(new MessageFilter(x => !x.MessageType.StartsWith("$")))
            .AddDefaultConsumer(handler)
            );

        var log = _loggerFactory.CreateLogger("Test");

        await subscription.SubscribeWithLog(log);

        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(200));

        while (handler.Count < produceCount - deleteCount && !cts.IsCancellationRequested)
        {
            await Task.Delay(100);
        }

        await subscription.UnsubscribeWithLog(log);

        log.LogInformation("Received {Count} events", handler.Count);

        var actual = handler.Processed
                     .Select(x => (x.Message as BookingEvents.BookingImported) !.BookingId)
                     .ToList();

        log.LogInformation("Actual contains {Count} events", actual.Count);

        actual
        .Should()
        .BeEquivalentTo(commands.Except(delete).Select(x => x.BookingId));
    }