skills/microservice/command/event-design/SKILL.md
Use when designing domain events with Event<TData> hierarchy for event sourcing.
npx skillsauth add faysilalshareef/dotnet-ai-kit event-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 mappingdata-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.