Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #346 from jdom/exception-thrown-when-out-of-sync-s…

…tores

Exception thrown with out-of-sync stores
Reviewed
  • Loading branch information...
commit aa89285962fbccfef368116e461f36fc9943906d 2 parents 9e3e393 + 895eeb7
fsimonazzi fsimonazzi authored
16 source/Conference/Registration.Tests/EventSourcingTestHelper.cs
View
@@ -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;
@@ -76,7 +77,7 @@ public void When(IEvent @event)
return @event;
}
- class RepositoryStub : IEventSourcedRepository<T>
+ private class RepositoryStub : IEventSourcedRepository<T>
{
public readonly List<IVersionedEvent> History = new List<IVersionedEvent>();
private readonly Action<T> onSave;
@@ -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)
@@ -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;
+ }
}
}
}
1  source/Conference/Registration/Handlers/ConferenceViewModelGenerator.cs
View
@@ -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>,
31 source/Conference/Registration/Handlers/OrderCommandHandler.cs
View
@@ -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();
@@ -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);
}
}
}
7 source/Conference/Registration/Handlers/PricedOrderViewModelGenerator.cs
View
@@ -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();
}
}
}
28 source/Conference/Registration/Handlers/SeatAssignmentsHandler.cs
View
@@ -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);
}
}
}
1  source/Conference/Registration/Handlers/SeatAssignmentsViewModelGenerator.cs
View
@@ -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>,
33 source/Conference/Registration/Handlers/SeatsAvailabilityHandler.cs
View
@@ -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)
{
28 source/Infrastructure/Azure/Infrastructure.Azure.Tests/EventSourcing/AzureEventSourcedRepositoryFixture.cs
View
@@ -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);
+ }
+ }
}
12 source/Infrastructure/Azure/Infrastructure.Azure/EventSourcing/AzureEventSourcedRepository.cs
View
@@ -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
64 source/Infrastructure/Infrastructure/EntityNotFoundException.cs
View
@@ -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; }
+ }
+ }
+}
13 source/Infrastructure/Infrastructure/EventSourcing/IEventSourcedRepository.cs
View
@@ -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  source/Infrastructure/Infrastructure/Infrastructure.csproj
View
@@ -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" />
@@ -86,4 +87,4 @@
<Target Name="AfterBuild">
</Target>
-->
-</Project>
+</Project>
11 source/Infrastructure/Sql/Infrastructure.Sql/EventSourcing/SqlEventSourcedRepository.cs
View
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.