Event hierarchy pattern for event-sourced microservices. Covers abstract Event base class, generic Event<TData>, IEventData interface, EventType enum, concrete event classes with primary constructors, and event data as records. Trigger: event sourcing, domain events, event data, event types.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Implements Clean Architecture in Android and Kotlin Multiplatform projects: module layouts, dependency rules, UseCases, Repositories, domain models, and data layers with Room, SQLDelight, Ktor.
Event base class carries all metadata (Id, AggregateId, Sequence, UserId, Type, DateTime, Version)Event<TData> : Event adds a typed Data property constrained to IEventDataIEventData interface has a single [JsonIgnore] EventType Type { get; } propertyEvent<TData>IEventDataEventType enum (not string constants) identifies event types and drives the EF Core discriminatornamespace {Company}.{Domain}.Commands.Domain.Events;
public abstract class Event
{
public long Id { get; protected set; }
public Guid AggregateId { get; protected set; }
public int Sequence { get; protected set; }
public Guid? UserId { get; protected set; }
public EventType Type { get; protected set; }
public DateTime DateTime { get; protected set; }
public int Version { get; protected set; }
}
Key details:
Id is long (auto-generated by the database), NOT Guidprotected set -- only derived classes can set themType is an EventType enum value, NOT a stringUserId is nullable (Guid?) for system-generated eventsnamespace {Company}.{Domain}.Commands.Domain.Events;
public abstract class Event<TData> : Event
where TData : IEventData
{
protected Event(
Guid aggregateId,
int sequence,
Guid? userId,
TData data,
int version = 1
)
{
AggregateId = aggregateId;
Sequence = sequence;
UserId = userId;
Type = data.Type;
Data = data;
DateTime = DateTime.UtcNow;
Version = version;
}
public TData Data { get; protected set; }
}
Key details:
aggregateId, sequence, userId, data, and optional versionType is set from data.Type -- the event data knows its own typeDateTime is set to DateTime.UtcNow in the constructorVersion defaults to 1using System.Text.Json.Serialization;
namespace {Company}.{Domain}.Commands.Domain.Events.DataTypes;
public interface IEventData
{
[JsonIgnore]
EventType Type { get; }
}
Key details:
Type property is [JsonIgnore] so it is NOT serialized into the Data JSON columnEventType enum valuenamespace {Company}.{Domain}.Commands.Domain.Enums;
public enum EventType
{
OrderCreated,
OrderUpdated,
OrderItemsAdded,
OrderItemsRemoved,
OrderCompleted,
OrderCancelled,
InvoiceGenerated,
InvoiceUpdated
}
using {Company}.{Domain}.Commands.Domain.Events.DataTypes;
namespace {Company}.{Domain}.Commands.Domain.Events.Orders;
public class OrderCreated(
Guid aggregateId,
Guid? userId,
OrderCreatedData data,
int sequence = 1,
int version = 1) : Event<OrderCreatedData>(aggregateId, sequence, userId, data, version)
{
}
Key details:
aggregateId, userId, data, then optional sequence and versionsequence defaults to 1 (creation events are always sequence 1)namespace {Company}.{Domain}.Commands.Domain.Events.Orders;
public class OrderUpdated(
Guid aggregateId,
Guid? userId,
OrderUpdatedData data,
int sequence,
int version = 1) : Event<OrderUpdatedData>(aggregateId, sequence, userId, data, version)
{
}
Note: Non-creation events do NOT default sequence -- it must be passed explicitly.
using {Company}.{Domain}.Commands.Domain.Enums;
namespace {Company}.{Domain}.Commands.Domain.Events.DataTypes;
// Creation event data
public record OrderCreatedData(
string CustomerName,
decimal Total,
OrderStatus Status,
List<Guid> Items
) : IEventData
{
public EventType Type => EventType.OrderCreated;
}
// Update event data
public record OrderUpdatedData(
string CustomerName,
decimal Total,
OrderStatus Status
) : IEventData
{
public EventType Type => EventType.OrderUpdated;
}
// Collection modification event data
public record OrderItemsAddedData(
List<Guid> Items
) : IEventData
{
public EventType Type => EventType.OrderItemsAdded;
}
Key details:
IEventData with a body-expression Type propertyList<T>, and nested recordsEvents are typically created through extension methods on command interfaces:
namespace {Company}.{Domain}.Commands.Domain.Extensions;
public static class EventsExtensions
{
public static OrderCreated ToEvent(this ICreateOrderCommand command)
{
return new OrderCreated(
aggregateId: command.Id,
userId: command.UserId,
data: new OrderCreatedData(
command.CustomerName,
command.Total,
OrderStatus.Pending,
command.Items
)
);
}
public static OrderUpdated ToEvent(this IUpdateOrderCommand command, int sequence)
{
return new OrderUpdated(
aggregateId: command.OrderId,
userId: command.UserId,
data: new OrderUpdatedData(
command.CustomerName,
command.Total,
command.Status
),
sequence: sequence
);
}
}
| Anti-Pattern | Correct Approach |
|---|---|
| String constants for event types | Use EventType enum |
| Mutable event data classes | Use immutable records |
Event<TData> with init properties | Use protected constructor with parameters |
| Setting Type manually on events | Let data.Type set it via the constructor |
Guid for Event.Id | Use long Id (database auto-increment) |
| Public setters on Event properties | All setters must be protected set |
| System.Text.Json for IEventData.Type | Use [JsonIgnore] (System.Text.Json attribute) |
# Find Event base class
grep -r "abstract class Event$" --include="*.cs" src/Domain/
# Find Event<TData> generic class
grep -r "class Event<TData>" --include="*.cs" src/Domain/
# Find IEventData implementations
grep -r ": IEventData" --include="*.cs" src/Domain/
# Find EventType enum
grep -r "enum EventType" --include="*.cs" src/Domain/
# Find concrete event classes
grep -r "Event<.*Data>" --include="*.cs" src/Domain/Events/
Domain/Events/Event.csEventType enumIEventData with the new EventTypeEvent<TData>ToEvent) on the command interfaceGenericEventConfiguration and discriminator mapping