Explore Projections

Projections are queryable models used for the reading part of our application. We can design projections in such a way that we can manage what data we want to store and by what will be searched. Events are the basis for projections data.

For using projections we should update the configuration file for both API and Service.

{% code title="appsettings.json" %}

  "Persistence": { /* ... */ },
  "Projections": {
      "Cassandra": {
        "ConnectionString": "Contact Points=127.0.0.1;Port=9042;Default Keyspace=taskmanager_projections"
      }
  }

{% endcode %}

And add some dependencies.

dotnet add package Cronus.Projection.Cassandra

Create a projection for querying tasks created by the user

{% tabs %} {% tab title="TaskProjection" %}

[DataContract(Name = "c94513d1-e5ee-4aae-8c0f-6e85b63a4e03")]
    public class TaskProjection : ProjectionDefinition<TaskProjectionData, UserId>,
        IEventHandler<TaskCreated>
    {
        public TaskProjection()
        {
            Subscribe<TaskCreated>(x => x.UserId);
        }

        public Task HandleAsync(TaskCreated @event)
        {
            TaskDto task = new TaskDto();
            State.Tasks.Add(task);

            return Task.CompletedTask;
        }
    }

{% endtab %}

{% tab title="TaskProjectionData" %}

 [DataContract(Name = "565e099a-5ca2-4258-87b1-4091a9d2c945")]
    public class TaskProjectionData
    {
        public TaskProjectionData()
        {
            Tasks = new List<TaskDto>();
        }

        [DataMember(Order = 1)]
        public List<TaskDto> Tasks { get; set; }
    }

{% endtab %} {% endtabs %}

Every time the event will occur it will be handled and persist in its state.

Read the state

Inject IProjectionReader that will be responsible for getting the projection state by Id on which projection was subscribed before: Subscribe<TaskCreated>(x => x.UserId).

[ApiController]
[Route("Tasks")]
public class TaskController : ControllerBase
{
private readonly IPublisher<CreateTask> _publisher;
private readonly IProjectionReader _projectionReader;

public TaskController(IPublisher<CreateTask> publisher, IProjectionReader reader)
{
    _publisher = publisher;
    _projectionReader = reader;
}

//.... create task code ..//

[HttpGet("user/{userId}/")]
public async Task<IActionResult> GetTasksByUserIdAsync(string userId)
{
    UserId UserId = new UserId(userId);

    ReadResult<TaskProjection> readResult = await _projectionReader.GetAsync<TaskProjection>(UserId);

    if (readResult.IsSuccess == false)
        return NotFound();

    return Ok(readResult.Data.State.Tasks);
}

Connect Dashboard

If we hit this controller immediately after the first start, it could lead to a probable read error. \ We need to give it some time to initialize our new projection store and build new versions of the projections. For an empty event store, it could take less than a few seconds but in order not to wait for this and verify that all working properly, we will check it manually.

Cronus Dashboard is a UI management tool for the Cronus framework.\ It hosts inside our Application so add this missing code to our background service.

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("Starting service...");
        cronusHost.Start();

        // Dashboard configuration
        cronusDashboard = CronusApi.GetHost();
        cronusApi.Provider = cronusDashboard.Services;
        await cronusDashboard.StartAsync().ConfigureAwait(false);

        logger.LogInformation("Service started!");
    }

Start our Cronus Service and API.

In the dashboard select the Connections tab and click New Connection.\ Set the predefined port for the Cronus endpoint: http://localhost:7477 and specify your connection name. Click Check and then Add Connection.\ After you add a connection select it from the drop-down menu and navigate to the Projections tab.\ You would be able to see all projections in the system.

A live green badge means that the projection is synchronized with ES and ready to use.

Now we would be able to request a controller with userId. GetAsync method of IProjectionReader will restore all events related to projection and apply them to the state.