I am working on a CQRS
pattern. I have created one project related to this approach in which I can insert and retrieve data. I came to know that there are two different models Write Model(Commands) and Read Model(Query). I just want to know that my approach for write model is right or not. And how to use temporarily database for event sourcing when multiple users doing same operations.
Command.cs
public class Command : Message
{
}
public class Insert : Command
{
public readonly Guid Id;
public readonly string Name;
public Insert(Guid id, string name)
{
Id = id;
Name = name;
}
}
public class Update : Command
{
public readonly Guid Id;
public readonly string NewName;
public readonly int OriginalVersion;
public Update(Guid id, string newName)
{
Id = id;
NewName = newName;
}
}
public class Delete : Command
{
public Guid Id;
public readonly int OriginalVersion;
public Delete(Guid id)
{
Id = id;
}
}
Event.cs
public class Event:Message
{
public int Version;
}
public class Inserted : Event
{
public readonly Guid Id;
public readonly string Name;
public Inserted(Guid id, string name)
{
Id = id;
Name = name;
}
}
public class Updated : Event
{
public readonly Guid Id;
public readonly string NewName;
public readonly int OriginalVersion;
public Updated(Guid id, string newName)
{
Id = id;
NewName = newName;
}
}
public class Deleted : Event
{
public Guid Id;
public Deleted(Guid id)
{
Id = id;
}
}
EventStore.cs
public interface IEventStore
{
void SaveEvents(Guid aggregateId, IEnumerable<Event> events, int expectedVersion);
List<Event> GetEventsForAggregate(Guid aggregateId);
}
public class EventStore : IEventStore
{
private readonly IEventPublisher _publisher;
private struct EventDescriptor
{
public readonly Event EventData;
public readonly Guid Id;
public readonly int Version;
public EventDescriptor(Guid id, Event eventData, int version)
{
EventData = eventData;
Version = version;
Id = id;
}
}
public EventStore(IEventPublisher publisher)
{
_publisher = publisher;
}
private readonly Dictionary<Guid, List<EventDescriptor>> _current = new Dictionary<Guid, List<EventDescriptor>>();
public void SaveEvents(Guid aggregateId, IEnumerable<Event> events, int expectedVersion)
{
List<EventDescriptor> eventDescriptors;
if (!_current.TryGetValue(aggregateId, out eventDescriptors))
{
eventDescriptors = new List<EventDescriptor>();
_current.Add(aggregateId, eventDescriptors);
}
else if (eventDescriptors[eventDescriptors.Count - 1].Version != expectedVersion && expectedVersion != -1)
{
throw new ConcurrencyException();
}
var i = expectedVersion;
foreach (var @event in events)
{
i++;
@event.Version = i;
eventDescriptors.Add(new EventDescriptor(aggregateId, @event, i));
_publisher.Publish(@event);
}
}
public List<Event> GetEventsForAggregate(Guid aggregateId)
{
List<EventDescriptor> eventDescriptors;
if (!_current.TryGetValue(aggregateId, out eventDescriptors))
{
throw new AggregateNotFoundException();
}
return eventDescriptors.Select(desc => desc.EventData).ToList();
}
}
public class AggregateNotFoundException : Exception
{
}
public class ConcurrencyException : Exception
{
}
ReadModel.cs
public interface IReadModelFacade
{
IEnumerable<InventoryItemListDto> GetInventoryItems();
InventoryItemDetailsDto GetInventoryItemDetails(Guid id);
}
public class InventoryItemDetailsDto
{
public Guid Id;
public string Name;
public int CurrentCount;
public int Version;
public InventoryItemDetailsDto(Guid id, string name, int currentCount, int version)
{
Id = id;
Name = name;
CurrentCount = currentCount;
Version = version;
}
}
public class InventoryItemListDto
{
public Guid Id;
public string Name;
public InventoryItemListDto(Guid id, string name)
{
Id = id;
Name = name;
}
}
public class InventoryListView : Handles<Inserted>, Handles<Updated>
{
public void Handle(Inserted message)
{
BullShitDatabase.list.Add(new InventoryItemListDto(message.Id, message.Name));
}
public void Handle(Updated message)
{
var item = BullShitDatabase.list.Find(x => x.Id == message.Id);
item.Name = message.NewName;
}
}
public class InvenotryItemDetailView : Handles<Inserted>, Handles<Updated>
{
public void Handle(Inserted message)
{
BullShitDatabase.details.Add(message.Id, new InventoryItemDetailsDto(message.Id, message.Name, 0, 0));
}
public void Handle(Updated message)
{
InventoryItemDetailsDto d = GetDetailsItem(message.Id);
d.Name = message.NewName;
d.Version = message.Version;
}
private InventoryItemDetailsDto GetDetailsItem(Guid id)
{
InventoryItemDetailsDto d;
if (!BullShitDatabase.details.TryGetValue(id, out d))
{
throw new InvalidOperationException("did not find the original inventory this shouldnt happen");
}
return d;
}
}
public class ReadModelFacade : IReadModelFacade
{
public IEnumerable<InventoryItemListDto> GetInventoryItems()
{
return BullShitDatabase.list;
}
public InventoryItemDetailsDto GetInventoryItemDetails(Guid id)
{
return BullShitDatabase.details[id];
}
}
public static class BullShitDatabase
{
public static Dictionary<Guid, InventoryItemDetailsDto> details = new Dictionary<Guid, InventoryItemDetailsDto>();
public static List<InventoryItemListDto> list = new List<InventoryItemListDto>();
}
It should't matter whether you're using EventStore or any other storing mechanism, you should be coding against interfaces (contracts) anyway.
But first things first, you commands IMO are not properly defined, they should be immutable objects which carry data and represent a domain operation (CRUD or not), so why do you have methods defined in the commands?
It is not a problem defining a command as a class, you'll need one in the end, but why don't you have an interface as the base type for all the commands? (SOLID principles)
All the class names (commands/events) have to be meaningful, that said, Update, Delete... don't say much really.
Also I don't see where your service layer is. The service layer should be responsible for handling the commands, so how are you planning to do this?
Bellow you have an example of how I would do it (a tad abstract but it gives you an idea):
// Message definitions
public interface IMessage
{
Guid ID {get; set;}
}
public interface IEvent : IMessage
{ }
public interface ICommand : IMessage
{ }
public class DeleteUserCommand : ICommand
{
public Guid ID {get; set;}
public Guid UserId {get; set;}
}
public class UserDeletedEvent : IEvent
{
public Guid ID {get; set;}
public Guid UserId {get; set;}
}
// Repository definitions
public interface IRepository
{ }
public interface IUserRepository : IRepository
{
void DeleteUser(Guid userId);
}
public UserRepository : IUserRepository
{
public void DeleteUser(Guid userId)
{}
}
// Service definitions
public interface IService
{ }
public class UserService : IService, IHandles<DeleteUserCommand>
{
public IUserRepository UserRepository {get; set;}
public void Handle(DeleteUserCommand deleteUserCommand)
{
UserRepository.DeleteUser(deleteUserCommand.Id)
//raise event
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With