# Supercharged Sagas - Creating Your First NServiceBus Saga

> This article continues the discussion on NServiceBus sagas and demonstrates how to implement your first saga.

- URL: https://blog.nimblepros.com/blogs/supercharged-sagas-creating-your-first-nservicebus-saga/
- Author: [Kyle McMaster](https://blog.nimblepros.com/authors/kyle/)
- Published: 2024-08-30
- Series: [NServiceBus](https://blog.nimblepros.com/series/nservicebus/)
- Categories: [Software Development](https://blog.nimblepros.com/categories/software-development/)
- Publisher: [NimblePros](https://blog.nimblepros.com/)

---

This post is part of a series on [NServiceBus](https://go.particular.net/using-nservicebus-2024). In this article, we will implement the happy path of our [example saga introduced in the previous article](https://blog.nimblepros.com/blogs/supercharged-sagas-introduction/). In this path, a contributor will go through a workflow to become verified in our system. Be sure to check out the sequence diagrams in the previous article if you'd like a refresher on where we are going with this process. Along the way we'll look at how NServiceBus enables us to define and orchestrate our saga efficiently. All the code for this series can be found in the [NServiceBusTutorial repository on GitHub](https://github.com/KyleMcMaster/NServiceBusTutorial).

## Starting the Saga In An Event Handler 🟢

The first step to kicking off our process is to start the saga when a `ContributorCreatedEvent` is published. The `StartContributorVerificationCommand` will contain the `ContributorId` so the saga can track the contributor. This is a constraint of our system that no two `ContributorVerificationSaga`s should be running per contributor at any given moment. The following is the updated `ContributorCreatedEventHandler` that sends the `StartContributorVerificationCommand` to start the saga.

```csharp
public class StartContributorVerificationCommand : ICommand
{
  public int ContributorId { get; init; }
}

public class ContributorCreatedEventHandler(ILogger<ContributorCreatedEventHandler> logger) 
  : IHandleMessages<ContributorCreatedEvent>
{
  public async Task Handle(ContributorCreatedEvent message, IMessageHandlerContext context)
  {
    // Other concerns abbreviated for simplicity
    await context.Send(new StartContributorVerificationCommand
    {
      ContributorId = message.ContributorId
    });
  }
}
```

But wait?!? 🚩 We haven't even defined our saga yet! Let's do that now.

## Defining Our Saga 🔨

In the previous article, we introduced the concept of a saga and how it can be used to manage the state of a long-running process. We also introduced the example saga we will be implementing in this article. The example saga is a simple process that orchestrates sending a message to a new contributor and waiting for a response for a certain amount of time. If the contributor responds within the time limit, the saga will complete successfully. This is the happy path of our saga and what we will focus on implementing in this article. 

To define our saga, we need to create a class `ContributorVerificationSaga` that inherits from `Saga<T>` where `T` is the type of the saga data. The saga data is the state of the saga and is used to store information about the saga. In our example, we will create a class called `ContributorVerificationSagaData` that inherits from `ContainSagaData`. The saga class will also implement a few other interfaces like `IAmStartedByMessages<T>`, `IHandleMessages<T>`, and `IHandleTimeouts<T>` which we will discuss as we define those messages and their handlers later.

```csharp
public class ContributorVerificationSaga : Saga<ContributorVerificationSagaData>,
  IAmStartedByMessages<StartContributorVerificationCommand>,
  IHandleMessages<ContributorVerifiedEvent>,
  IHandleTimeouts<ContributorVerificationSagaTimeout>
{
    // Handlers, mappings, and other methods will be defined here
}

public class ContributorVerificationSagaData : ContainSagaData
{
  public int ContributorId { get; set; }
}
```

Since we've already demonstrated how we will send the start message for the saga with the `StartContributorVerificationCommand`, let's move on to configuring our saga to process this message and define our handler. First we need to configure the `StartContributorVerificationCommand` message to map to an instance of our saga. We do this by overriding the `ConfigureHowToFindSaga` method in our saga class. This method is used to map incoming messages to an existing saga instance or to create a new saga instance if one does not exist. In our case, we will map the `ContributorId` in the `StartContributorVerificationCommand` to the `ContributorId` in the saga data. This will allow us to use our `ContributorId` as our unique identifier for the saga across our system. The following is the implementation of the `ConfigureHowToFindSaga` method in our `ContributorVerificationSaga` class.

```csharp
protected override void ConfigureHowToFindSaga(SagaPropertyMapper<ContributorVerificationSagaData> mapper)
{
  mapper.MapSaga(data => data.ContributorId)
    .ToMessage<StartContributorVerificationCommand>(message => message.ContributorId)
    // other mappings here
}
```

We can see the `ContributorId` in the `StartContributorVerificationCommand` is mapped to the `ContributorId` in the saga data. It's important to note that sagas do have their own Id but it is often hard to pass around that Id from various applications. It's easier to use a domain entity Id as the mapping for a saga since in most systems that Id could be the same across service boundaries (or able to be retrieved if needed). Now let's take a look at the handler for the `StartContributorVerificationCommand`.

```csharp
public async Task Handle(StartContributorVerificationCommand message, IMessageHandlerContext context)
{
  var verifyContributorCommand = new VerifyContributorCommand { ContributorId = message.ContributorId };
  await context.Send(verifyContributorCommand);

  // Saga timeouts covered in next article
  var timeout = new ContributorVerificationSagaTimeout { ContributorId = message.ContributorId };
  await RequestTimeout(context, DateTime.UtcNow.AddHours(24), timeout);
}
```

The `StartContributorVerificationCommand` handler does a lot of the workload for the saga by kicking off our two paths in our process. First, we send the `VerifyContributorCommand` to verify the contributor. This command will be handled by the Worker application. We also request that a [Timeout](https://docs.particular.net/nservicebus/sagas/timeouts) be registered for the saga. Since our use case is relatively simple, this timeout helps track whether the contributor does not respond within the 24 hour time limit and can be registered as soon as the saga begins. In the next article in the series, we will define the `ContributorVerificationSagaTimeout` class, cover how saga timeouts work, and some of the different strategies for managing them. For now, you can simply think of this as kicking off a timer that will go off if the saga does not complete before time runs out.

As we continue along our happy path, let's take a look at the `VerifyContributorCommand` and its handler.

## Contributor Verification ✔️

The `VerifyContributorCommandHandler` is responsible for sending a simple greeting to the new contributor and supplying them with a link to verify their account. This handler calls a `INotificationService` which simulates sending a text message to the contributor. The following is the implementation of the `VerifyContributorCommand` handler.

```csharp
public class VerifyContributorCommand : ICommand
{
  public int ContributorId { get; set; }
}

public class VerifyContributorCommandHandler(INotificationService _notificationService, IRepository<Contributor> _repository)
    : IHandleMessages<VerifyContributorCommand>
{
  public async Task Handle(VerifyContributorCommand message, IMessageHandlerContext context)
  {
    var contributor = await _repository.SingleOrDefaultAsync(new ContributorByIdSpec(message.ContributorId), context.CancellationToken);
    if (contributor is null)
    {
      throw new InvalidOperationException($"Contributor with Id {message.ContributorId} not found.");
    }
    string textMessage = "Welcome contributor! Please verify your phone number here: https://NimblePros.com/verify/123";
    await _notificationService.SendSmsAsync(contributor.PhoneNumber.Number, textMessage);
  }
}
```

Now we wait for the contributor to verify their account. ⏳

## Contributor Interaction 📱

Assuming the contributor has received the text message and clicked the link to verify their account, an HTTP request will be made to our Web application. This request will be handled by the `Verify` endpoint and mark the contributor as verified in our system.

```csharp
public class Verify(IMessageSession _messageSession, IRepository<Contributor> _repository)
  : Endpoint<UpdateContributorRequest, UpdateContributorResponse>
{
  // FastEndpoint configuration abbreviated for simplicity 
  
  public override async Task HandleAsync(
    UpdateContributorRequest request,
    CancellationToken cancellationToken)
  {
    var contributor = await _repository.GetByIdAsync(request.ContributorId, cancellationToken);
    if (contributor is null)
    {
      await SendNotFoundAsync(cancellationToken);
      return;
    }

    contributor.Verify();
    await _repository.SaveChangesAsync(cancellationToken);
    var verifiedEvent = new ContributorVerifiedEvent { ContributorId = contributor.Id };
    await _messageSession.Publish(verifiedEvent, cancellationToken);
  }
}
```

## Completing the Saga 🏁

The final step in our workflow is to handle the `ContributorVerifiedEvent` that is published by the `Verify` endpoint. This event will be handled by the `ContributorVerificationSaga`. Since our verification happy path has been executed successfully, we can call the aptly named method, `MarkAsComplete()`, to mark the saga as completed. The following is the implementation of the `ContributorVerifiedEvent` handler.

```csharp
public class ContributorVerifiedEvent : IEvent
{
  public int ContributorId { get; set; }
}

public Task Handle(ContributorVerifiedEvent message, IMessageHandlerContext context)
{
  MarkAsComplete();
  return Task.CompletedTask;
}
```

Since the `ContributorVerificationSaga` is now handling an additional message, we'll also need to update the `ConfigureHowToFindSaga` method to map the `ContributorId` in the `ContributorVerifiedEvent` to the `ContributorId` in the saga data. The following is the updated saga class definition and `ConfigureHowToFindSaga` method for all the messages we've defined so far.

```csharp
public class ContributorVerificationSaga : Saga<ContributorVerificationSagaData>,
  IAmStartedByMessages<StartContributorVerificationCommand>,
  IHandleMessages<ContributorVerifiedEvent>
{
  protected override void ConfigureHowToFindSaga(SagaPropertyMapper<ContributorVerificationSagaData> mapper)
  {
    mapper.MapSaga(data => data.ContributorId)
      .ToMessage<StartContributorVerificationCommand>(message => message.ContributorId)
      .ToMessage<ContributorVerifiedEvent>(message => message.ContributorId);
  }
  // ...
}
```

And that's it! We've successfully implemented the happy path of our `ContributorVerificationSaga`.

## Conclusion 🎉

 As usual, all the code necessary to run this saga is available in the [NServiceBusTutorial repository on GitHub](https://github.com/KyleMcMaster/NServiceBusTutorial). In [the next article in the series](https://blog.nimblepros.com/blogs/supercharged-sagas-timeout-tips/), we will cover how to handle timeouts in sagas when the contributor decides not to answer the text message. Stay tuned!

## Additional Resources 📚

- [Supercharged Sagas Introduction](https://blog.nimblepros.com/blogs/supercharged-sagas-introduction/)
- [NServiceBus Saga Documentation](https://docs.particular.net/tutorials/nservicebus-sagas/)
- [NServiceBusTutorial repository on GitHub](https://github.com/KyleMcMaster/NServiceBusTutorial)
