scaffold-aggregate
Create a new event-sourced aggregate with proper Apply methods, event handling, and Marten configuration. Use this when adding a new domain entity that needs event sourcing.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Follow this guide to create a new event-sourced aggregate in the ApiService following strict Event Sourcing patterns.
-
Define the Initial Event
- Create a
recordinsrc/BookStore.ApiService/Events/ -
Naming: Past tense (e.g.,
AuthorCreated, notCreateAuthor) - Properties: Include all initial state properties
-
IDs: Use
Guidfor aggregate ID -
Timestamp: Use
DateTimeOffset -
Example:
namespace BookStore.ApiService.Events; public record AuthorCreated( Guid Id, string Name, string Biography, DateTimeOffset CreatedAt );
- Create a
-
Create the Aggregate
- Create a
recordinsrc/BookStore.ApiService/Aggregates/ -
Template:
namespace BookStore.ApiService.Aggregates; public record Author { public Guid Id { get; init; } public string Name { get; init; } = string.Empty; public string Biography { get; init; } = string.Empty; public bool Deleted { get; init; } public int Version { get; init; } // Factory method for new aggregates public static Author Create(AuthorCreated @event) { return new Author { Id = @event.Id, Name = @event.Name, Biography = @event.Biography }; } // Apply method for Marten (MUST be void, single parameter) public void Apply(AuthorCreated @event) { // Marten uses this for event replay - do not return anything } // Apply method for subsequent events public void Apply(AuthorUpdated @event) { // Handle state changes } }
- Create a
-
Add Behavior Methods
- Add methods to aggregate that return events
- Pattern: Validate → Return Event
-
Example:
public AuthorUpdated Update(string name, string biography) { if (Deleted) throw new InvalidOperationException("Cannot update deleted author"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Name is required", nameof(name)); return new AuthorUpdated(Id, name, biography, DateTimeOffset.UtcNow); }
-
Configure Marten
- Open
src/BookStore.ApiService/Program.cs - Add aggregate to Marten's event store:
builder.Services.AddMarten(options => { // Existing configuration... // Add your aggregate options.Events.StreamIdentity = StreamIdentity.AsGuid; });
- Open
-
Create Unit Tests
- Create test file in
tests/BookStore.ApiService.UnitTests/Aggregates/ -
Test Pattern:
using TUnit.Core; using TUnit.Assertions.Extensions; public class AuthorTests { [Test] public async Task Create_ReturnsValidAggregate() { // Arrange var created = new AuthorCreated( Guid.CreateVersion7(), "Martin Fowler", "Author and speaker", DateTimeOffset.UtcNow ); // Act var author = Author.Create(created); // Assert await Assert.That(author.Id).IsEqualTo(created.Id); await Assert.That(author.Name).IsEqualTo("Martin Fowler"); } [Test] public async Task Update_DeletedAuthor_ThrowsException() { // Arrange var author = new Author { Deleted = true }; // Act & Assert await Assert.That(() => author.Update("New Name", "Bio")) .Throws<InvalidOperationException>(); } }
- Create test file in
-
Verify Analyzer Compliance
- Run
dotnet buildto check for BS1xxx-BS4xxx warnings - Ensure:
- ✅ Events are
recordtypes (BS1001) - ✅ Apply methods are
voidwith single parameter (BS1002) - ✅ Aggregates use proper patterns (BS2xxx)
- ✅ Events are
- Run
-
Next Steps
- Use
/scaffold-projectionto create read models from your aggregate's events - Use
/scaffold-writeto create complete command/handler/endpoint flow - Use
/scaffold-testto create integration tests - Use
/verify-featureto ensure everything works
- Use
Related Skills
Prerequisites:
- None - this is a foundational skill for event sourcing
Next Steps:
-
/scaffold-projection- Create read models from aggregate events -
/scaffold-write- Add commands, handlers, and endpoints for this aggregate -
/scaffold-test- Create integration tests -
/verify-feature- Run all verification checks
See Also:
- scaffold-write - Complete write operation workflow
- event-sourcing-guide - Event Sourcing patterns
- marten-guide - Marten event store integration
- analyzer-rules - Code analyzer rules (BS1xxx-BS4xxx)
- ApiService AGENTS.md - Backend patterns and conventions
Key Rules to Remember
-
Apply Methods: MUST be
voidwith single event parameter (Marten convention) - Behavior Methods: Return events, don't mutate state directly
-
IDs: Use
Guid.CreateVersion7()for new aggregates -
Immutability: Use
recordtypes withinitproperties - Validation: Validate in behavior methods, throw exceptions for invalid operations
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-26
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!