| name | state-view-slice |
| description | Generate Marten read models, projections, and Wolverine HTTP GET endpoints for STATE_VIEW slices |
STATE_VIEW Slice Skill
Overview
A STATE_VIEW slice projects event data into Marten read model documents. It generates:
- Read Model document (Marten
IDocument) in <Context>/Slices/<SliceName>
- Marten Projection (
SingleStreamProjection or MultiStreamProjection) in Internal/
- Wolverine HTTP GET endpoint in
<Context>/Slices/<SliceName>
- Tests (from specifications)
Note: STATE_VIEW slices do NOT contain commands or events. Events come from STATE_CHANGE slices.
Flow Pattern
Events (from STATE_CHANGE) → Marten Projection → ReadModel document → Wolverine GET endpoint
Java → C# / Critter Stack Mapping
| Java / Axon | C# / Critter Stack |
|---|
@Entity JPA class | Marten document class |
JpaRepository | IQuerySession.LoadAsync<T>() |
@EventHandler Projector | Marten SingleStreamProjection<T> |
@QueryHandler | Direct query in GET endpoint |
QueryGateway.query() | IQuerySession.LoadAsync<T>() |
Flyway migration | Marten auto-creates schema |
ProjectionLifecycle.Async | ProjectionLifecycle.Async |
ProjectionLifecycle.Inline | ProjectionLifecycle.Inline |
Code Generation Steps
Step 1: Create Read Model Document
Location: <Context>/Slices/<SliceName>/<SliceName>ReadModel.cs
using SlicingWorkshop.Common;
namespace SlicingWorkshop.<Context>.Slices.<SliceName>;
public class <SliceName>ReadModel : IReadModel
{
public Guid Id { get; set; }
public <Type> <FieldName> { get; set; }
}
Step 2: Create Marten Projection
Location: <Context>/Slices/<SliceName>/Internal/<SliceName>Projection.cs
Single stream (one aggregate):
using Marten.Events.Aggregation;
using SlicingWorkshop.Events;
namespace SlicingWorkshop.<Context>.Slices.<SliceName>.Internal;
public class <SliceName>Projection : SingleStreamProjection<<SliceName>ReadModel>
{
public void Apply(<EventName> @event, <SliceName>ReadModel readModel)
{
readModel.Id = @event.AggregateId;
}
}
Multi-stream:
using Marten.Events.Aggregation;
using SlicingWorkshop.Events;
namespace SlicingWorkshop.<Context>.Slices.<SliceName>.Internal;
public class <SliceName>Projection : MultiStreamProjection<<SliceName>ReadModel, Guid>
{
public <SliceName>Projection()
{
Identity<<EventName>>(e => e.AggregateId);
}
public void Apply(<EventName> @event, <SliceName>ReadModel readModel)
{
readModel.Id = @event.AggregateId;
}
}
Step 3: Register Projection in Program.cs
Add to the AddMarten options in Program.cs:
opts.Projections.Add<<SliceName>Projection>(ProjectionLifecycle.Async);
Step 4: Create Wolverine HTTP GET Endpoint
Location: <Context>/Slices/<SliceName>/<SliceName>Endpoint.cs
using Marten;
using Wolverine.Http;
namespace SlicingWorkshop.<Context>.Slices.<SliceName>;
public static class <SliceName>Endpoint
{
[WolverineGet("/api/<context>/<resource>/{id}")]
public static async Task<<SliceName>ReadModel?> Get(
Guid id,
IQuerySession session)
{
return await session.LoadAsync<<SliceName>ReadModel>(id);
}
}
Step 5: Create Tests
Location: <Context>/Slices/<SliceName>/Tests/<SliceName>ProjectionTests.cs
using Marten;
using SlicingWorkshop.Events;
using Xunit;
namespace SlicingWorkshop.<Context>.Slices.<SliceName>.Tests;
public class <SliceName>ProjectionTests : IClassFixture<AppFixture>
{
private readonly AppFixture _fixture;
public <SliceName>ProjectionTests(AppFixture fixture) => _fixture = fixture;
[Fact]
public async Task <SpecTitleAsPascalCase>()
{
var aggregateId = Guid.NewGuid();
using var session = _fixture.Store.LightweightSession();
session.Events.Append(aggregateId, new <EventName>(aggregateId ));
await session.SaveChangesAsync();
await _fixture.Store.WaitForNonStaleProjectionDataAsync(TimeSpan.FromSeconds(5));
using var querySession = _fixture.Store.QuerySession();
var result = await querySession.LoadAsync<<SliceName>ReadModel>(aggregateId);
Assert.NotNull(result);
}
}
Field Type Mapping
| config.json Type | C# Type | Marten notes |
|---|
| UUID | Guid | Default Id type |
| String | string | |
| Date | DateOnly | |
| Integer | int | |
| Boolean | bool | |
| Decimal | decimal | |
| Multiple cardinality | List | Initialize in constructor |
| optional: true | T? | |
Conventions
- Marten manages schema automatically — no migration files needed
- Use
ProjectionLifecycle.Inline for strongly consistent reads (same transaction)
- Use
ProjectionLifecycle.Async for eventually consistent, high-throughput projections
- Read models are plain C# classes — no attributes required for Marten document storage
- GET endpoints return
null → Wolverine HTTP maps to 404 automatically