Back to list
aalmada

scaffold-aggregate

by aalmada

Full-stack .NET online book store application with event-sourced backend API and Blazor frontend, orchestrated by Aspire.

13🍴 0📅 Jan 24, 2026

SKILL.md


name: scaffold-aggregate description: 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.

Follow this guide to create a new event-sourced aggregate in the ApiService following strict Event Sourcing patterns.

  1. Define the Initial Event

    • Create a record in src/BookStore.ApiService/Events/
    • Naming: Past tense (e.g., AuthorCreated, not CreateAuthor)
    • Properties: Include all initial state properties
    • IDs: Use Guid for aggregate ID
    • Timestamp: Use DateTimeOffset
    • Example:
      namespace BookStore.ApiService.Events;
      
      public record AuthorCreated(
          Guid Id,
          string Name,
          string Biography,
          DateTimeOffset CreatedAt
      );
      
  2. Create the Aggregate

    • Create a record in src/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
          }
      }
      
  3. 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);
      }
      
  4. 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;
      });
      
  5. 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>();
          }
      }
      
  6. Verify Analyzer Compliance

    • Run dotnet build to check for BS1xxx-BS4xxx warnings
    • Ensure:
      • ✅ Events are record types (BS1001)
      • ✅ Apply methods are void with single parameter (BS1002)
      • ✅ Aggregates use proper patterns (BS2xxx)
  7. Next Steps

    • Use /scaffold-projection to create read models from your aggregate's events
    • Use /scaffold-write to create complete command/handler/endpoint flow
    • Use /scaffold-test to create integration tests
    • Use /verify-feature to ensure everything works

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:

Key Rules to Remember

  • Apply Methods: MUST be void with single event parameter (Marten convention)
  • Behavior Methods: Return events, don't mutate state directly
  • IDs: Use Guid.CreateVersion7() for new aggregates
  • Immutability: Use record types with init properties
  • Validation: Validate in behavior methods, throw exceptions for invalid operations

Score

Total Score

75/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon