Skip to content
This repository has been archived by the owner on Feb 20, 2021. It is now read-only.

Commit

Permalink
Merge pull request #346 from jdom/exception-thrown-when-out-of-sync-s…
Browse files Browse the repository at this point in the history
…tores

Exception thrown with out-of-sync stores
Reviewed
  • Loading branch information
fsimonazzi committed May 5, 2012
2 parents 9e3e393 + 895eeb7 commit aa89285
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 71 deletions.
16 changes: 14 additions & 2 deletions source/Conference/Registration.Tests/EventSourcingTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Registration.Tests
using System;
using System.Collections.Generic;
using System.Linq;
using Infrastructure;
using Infrastructure.EventSourcing;
using Infrastructure.Messaging;
using Infrastructure.Messaging.Handling;
Expand Down Expand Up @@ -76,7 +77,7 @@ public TEvent ThenHasOne<TEvent>() where TEvent : IVersionedEvent
return @event;
}

class RepositoryStub : IEventSourcedRepository<T>
private class RepositoryStub : IEventSourcedRepository<T>
{
public readonly List<IVersionedEvent> History = new List<IVersionedEvent>();
private readonly Action<T> onSave;
Expand All @@ -91,7 +92,7 @@ internal RepositoryStub(Action<T> onSave)
throw new InvalidCastException(
"Type T must have a constructor with the following signature: .ctor(Guid, IEnumerable<IVersionedEvent>)");
}
this.entityFactory = (id, events) => (T)constructor.Invoke(new object[] { id, events });
this.entityFactory = (id, events) => (T) constructor.Invoke(new object[] { id, events });
}

T IEventSourcedRepository<T>.Find(Guid id)
Expand All @@ -109,6 +110,17 @@ void IEventSourcedRepository<T>.Save(T eventSourced)
{
this.onSave(eventSourced);
}

T IEventSourcedRepository<T>.Get(Guid id)
{
var entity = ((IEventSourcedRepository<T>)this).Find(id);
if (Equals(entity, default(T)))
{
throw new EntityNotFoundException(id, "Test");
}

return entity;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Registration.Handlers
using Registration.ReadModel;
using Registration.ReadModel.Implementation;

// TODO: should work correctly with out of order messages instead of dropping events!
public class ConferenceViewModelGenerator :
IEventHandler<ConferenceCreated>,
IEventHandler<ConferenceUpdated>,
Expand Down
31 changes: 9 additions & 22 deletions source/Conference/Registration/Handlers/OrderCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,14 @@ public void Handle(RegisterToConference command)

public void Handle(MarkSeatsAsReserved command)
{
var order = repository.Find(command.OrderId);

if (order != null)
{
order.MarkAsReserved(this.pricingService, command.Expiration, command.Seats);
repository.Save(order);
}
var order = repository.Get(command.OrderId);
order.MarkAsReserved(this.pricingService, command.Expiration, command.Seats);
repository.Save(order);
}

public void Handle(RejectOrder command)
{
var order = repository.Find(command.OrderId);

if (order != null)
{
order.Expire();
Expand All @@ -74,24 +69,16 @@ public void Handle(RejectOrder command)

public void Handle(AssignRegistrantDetails command)
{
var order = repository.Find(command.OrderId);

if (order != null)
{
order.AssignRegistrant(command.FirstName, command.LastName, command.Email);
repository.Save(order);
}
var order = repository.Get(command.OrderId);
order.AssignRegistrant(command.FirstName, command.LastName, command.Email);
repository.Save(order);
}

public void Handle(ConfirmOrderPayment command)
{
var order = repository.Find(command.OrderId);

if (order != null)
{
order.ConfirmPayment();
repository.Save(order);
}
var order = repository.Get(command.OrderId);
order.ConfirmPayment();
repository.Save(order);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,8 @@ public void Handle(SeatAssignmentsCreated @event)
using (var context = this.contextFactory.Invoke())
{
var dto = context.Find<PricedOrder>(@event.OrderId);
if (dto != null)
{
dto.AssignmentsId = @event.SourceId;
context.SaveChanges();
}
dto.AssignmentsId = @event.SourceId;
context.SaveChanges();
}
}
}
Expand Down
28 changes: 9 additions & 19 deletions source/Conference/Registration/Handlers/SeatAssignmentsHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,23 @@ public SeatAssignmentsHandler(IEventSourcedRepository<Order> ordersRepo, IEventS

public void Handle(OrderPaymentConfirmed @event)
{
var order = this.ordersRepo.Find(@event.SourceId);
if (order != null)
{
var assignments = order.CreateSeatAssignments();

assignmentsRepo.Save(assignments);
}
var order = this.ordersRepo.Get(@event.SourceId);
var assignments = order.CreateSeatAssignments();
assignmentsRepo.Save(assignments);
}

public void Handle(AssignSeat command)
{
var assignments = this.assignmentsRepo.Find(command.SeatAssignmentsId);
if (assignments != null)
{
assignments.AssignSeat(command.Position, command.Attendee);
assignmentsRepo.Save(assignments);
}
var assignments = this.assignmentsRepo.Get(command.SeatAssignmentsId);
assignments.AssignSeat(command.Position, command.Attendee);
assignmentsRepo.Save(assignments);
}

public void Handle(UnassignSeat command)
{
var assignments = this.assignmentsRepo.Find(command.SeatAssignmentsId);
if (assignments != null)
{
assignments.Unassign(command.Position);
assignmentsRepo.Save(assignments);
}
var assignments = this.assignmentsRepo.Get(command.SeatAssignmentsId);
assignments.Unassign(command.Position);
assignmentsRepo.Save(assignments);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace Registration.Handlers
using Registration.Events;
using Registration.ReadModel;

// TODO: should work correctly with out of order messages instead of dropping events!
public class SeatAssignmentsViewModelGenerator :
IEventHandler<SeatAssignmentsCreated>,
IEventHandler<SeatAssigned>,
Expand Down
33 changes: 11 additions & 22 deletions source/Conference/Registration/Handlers/SeatsAvailabilityHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,38 +36,27 @@ public SeatsAvailabilityHandler(IEventSourcedRepository<SeatsAvailability> repos

public void Handle(MakeSeatReservation command)
{
var availability = this.repository.Find(command.ConferenceId);
if (availability != null)
{
availability.MakeReservation(command.ReservationId, command.Seats);
this.repository.Save(availability);
}
// TODO: what if there's no aggregate? how do we tell the process?
// TODO: what if there's no aggregate? how do we tell the registration process?
var availability = this.repository.Get(command.ConferenceId);
availability.MakeReservation(command.ReservationId, command.Seats);
this.repository.Save(availability);
}

public void Handle(CancelSeatReservation command)
{
var availability = this.repository.Find(command.ConferenceId);
if (availability != null)
{
availability.CancelReservation(command.ReservationId);
this.repository.Save(availability);
}
// TODO: what if there's no aggregate? how do we tell the process?
var availability = this.repository.Get(command.ConferenceId);
availability.CancelReservation(command.ReservationId);
this.repository.Save(availability);
}

public void Handle(CommitSeatReservation command)
{
var availability = this.repository.Find(command.ConferenceId);
if (availability != null)
{
availability.CommitReservation(command.ReservationId);
this.repository.Save(availability);
}
// TODO: what if there's no aggregate? how do we tell the process?
var availability = this.repository.Get(command.ConferenceId);
availability.CommitReservation(command.ReservationId);
this.repository.Save(availability);
}

// Events from the conference BC
// Commands created from events from the conference BC

public void Handle(AddSeats command)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,32 @@ private static string Serialize(object graph)
return writer.ToString();
}
}

public class when_reading_inexistant_entity
{
private Guid id;
private Mock<IEventStore> eventStore;
private AzureEventSourcedRepository<TestEntity> sut;

public when_reading_inexistant_entity()
{
this.eventStore = new Mock<IEventStore>();
this.sut = new AzureEventSourcedRepository<TestEntity>(eventStore.Object, Mock.Of<IEventStoreBusPublisher>(), new JsonTextSerializer());
this.id = Guid.NewGuid();
}

[Fact]
public void when_finding_then_returns_null()
{
Assert.Null(sut.Find(id));
}

[Fact]
public void when_getting_then_throws()
{
var actual = Assert.Throws<EntityNotFoundException>(() => sut.Get(id));
Assert.Equal(id, actual.EntityId);
Assert.Equal("TestEntity", actual.EntityType);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ public T Find(Guid id)
return null;
}


public T Get(Guid id)
{
var entity = this.Find(id);
if (entity == null)
{
throw new EntityNotFoundException(id, sourceType);
}

return entity;
}

public void Save(T eventSourced)
{
// TODO: guarantee that only incremental versions of the event are stored
Expand Down
64 changes: 64 additions & 0 deletions source/Infrastructure/Infrastructure/EntityNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// ==============================================================================================================
// Microsoft patterns & practices
// CQRS Journey project
// ==============================================================================================================
// ©2012 Microsoft. All rights reserved. Certain content used with permission from contributors
// http://cqrsjourney.github.com/contributors/members
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and limitations under the License.
// ==============================================================================================================

namespace Infrastructure
{
using System;
using System.Runtime.Serialization;

[Serializable]
public class EntityNotFoundException : Exception
{
private readonly Guid entityId;
private readonly string entityType;

public EntityNotFoundException()
{
}

public EntityNotFoundException(Guid entityId) : base(entityId.ToString())
{
this.entityId = entityId;
}

public EntityNotFoundException(Guid entityId, string entityType)
: base(entityType + ": " + entityId.ToString())
{
this.entityId = entityId;
this.entityType = entityType;
}

public EntityNotFoundException(Guid entityId, string entityType, string message, Exception inner)
: base(message, inner)
{
this.entityId = entityId;
this.entityType = entityType;
}

protected EntityNotFoundException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}

public Guid EntityId
{
get { return this.entityId; }
}

public string EntityType
{
get { return this.entityType; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,21 @@ namespace Infrastructure.EventSourcing

public interface IEventSourcedRepository<T> where T : IEventSourced
{
/// <summary>
/// Tries to retrieve the event sourced entity.
/// </summary>
/// <param name="id">The id of the entity</param>
/// <returns>The hydrated entity, or null if it does not exist.</returns>
T Find(Guid id);

/// <summary>
/// Retrieves the event sourced entity.
/// </summary>
/// <param name="id">The id of the entity</param>
/// <returns>The hydrated entity</returns>
/// <exception cref="EntityNotFoundException">If the entity is not found.</exception>
T Get(Guid id);

void Save(T eventSourced);
}
}
3 changes: 2 additions & 1 deletion source/Infrastructure/Infrastructure/Infrastructure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<Compile Include="Blob\IBlobStorage.cs" />
<Compile Include="Database\IAggregateRoot.cs" />
<Compile Include="Database\IDataContext.cs" />
<Compile Include="EntityNotFoundException.cs" />
<Compile Include="EventSourcing\EventSourced.cs" />
<Compile Include="EventSourcing\IEventSourced.cs" />
<Compile Include="EventSourcing\IEventSourcedRepository.cs" />
Expand Down Expand Up @@ -86,4 +87,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ public T Find(Guid id)
}
}

public T Get(Guid id)
{
var entity = this.Find(id);
if (entity == null)
{
throw new EntityNotFoundException(id, sourceType);
}

return entity;
}

public void Save(T eventSourced)
{
// TODO: guarantee that only incremental versions of the event are stored
Expand Down

0 comments on commit aa89285

Please sign in to comment.