Skip to main content

Achieving lean controllers: Incremental refactoring with Transactional Session

It doesn’t matter if you’re developing using MVC, WebAPI, or Razor pages—you want your controller code to be nice and lean. The more bloated that code is, the more coupling you have, and the closer you are to an unmanageable big ball of mud.

You probably already know that, but I’d bet not all of your controller code is as lean as you’d like it to be. Is it?

So that leaves the question… How do we get there?

🔗Getting lean

What if I told you that you could make your controllers “lean and mean” and modernize in incremental steps?
Decoupling the web tier from processing logic is achievable through messaging. Sending messages from the web tier to dedicated backend message handlers allows for a gradual migration of complexity away from the controllers, preventing the need for a risky, all-at-once approach.
And as a bonus, your system gets reliability, resilience, and better scalability.

As you begin to move the complex logic out of the controller and replace it with sending messages and publishing events, your controller gets leaner, more manageable, and really quite boring.1

However, there’s a catch. We want to maintain atomicity and consistency between our data and message operations. We don’t want data committed to the database unless the related messages/events get dispatched successfully. The same is true in the other direction. We don’t want any messages/events emitted unless the database transaction also succeeds.

Systems tend to use the transactional outbox pattern to ensure that data and messaging operations are kept consistent, and NServiceBus implements this pattern in its outbox feature, but that only works inside a message handler.

So, what do we do if we can’t avoid data operations in our controller actions and also need to send messages? Sometimes, this is unavoidable due to the design of a system. Some data has to be added to the database immediately, or the table view 2 won’t be updated when the page refreshes. How do we maintain consistency between data and message operations then?

🔗Web tier consistency

The NServiceBus TransactionalSession feature allows the Outbox feature to work in the web tier. Using the TransactionalSession feature, you can store data and send messages in the web tier without having to refactor your controller or redesign your UI.

Combining the outbox with the transactional session will solve the problem of messages sent or published outside the context of a message handler while maintaining consistency between your message and data operations.

Let’s see how easy it is to set up this feature using NServiceBus.

The first step is to add a reference to the NuGet package related to your chosen persistence and register it. 3 Next, the NServiceBus configuration code will need to enable the Outbox and TransactionalSession features:

endpointConfiguration.EnableOutbox();

//Each persistence has a specific Configure method
var persistence = config.UsePersistence<YourPersistenceOfChoice>();
persistence.EnableTransactionalSession();

Once you’ve configured the endpoint, you will have access to a session that can be obtained inside your controllers, and you can use them inside your controller actions.

public async Post(MyModel model, [FromServices]ITransactionalSession session)
{
   await session.Open(new YourPersistenceOpenSessionOptions(),
   cancellationToken: cancellationToken);
   await session.Send(new YourMessage(), cancellationToken);
   await session.Commit(cancellationToken);
}

Now that everything is configured, you can easily benefit from using TransactionalSession in your controllers and leave the big refactoring for later.

🔗The big win

Previously, trying to introduce messaging to our controllers involved moving all of the logic to the back end to avoid consistency problems like zombie records and ghost messages. This wasn’t always possible without significant code refactoring or UI redesigns.

By using the TransactionalSession feature, you can still keep some of those mixed concerns in your controllers, while staying safe from any data inconsistencies in your system.
It is still ideal to keep the Single Responsibility principle in mind and, as much as possible, try to extract any data operations from the controllers to be handled asynchronously as part of a message handler. But that work can come later, you don’t have to do it right now.

In short, TransactionalSession mitigates risky and rushed code changes while maintaining consistency. You can defer refactoring to a later point when you’re not facing endless requests from the business stakeholders while racing towards hard deadlines and make these changes at a time when you have a bit more breathing room to refactor without burning the house down.

You can focus on your business priorities without investing time in a big refactor or overcomplicated configuration. The clean code aspect can be handled later.

Check out the transactional session documentation or download one of our transactional session samples to get started.

Using the transactional session keeps things simple and reliable; it guarantees atomicity with the infrastructure and technology already at your disposal. You get that rock-solid atomicity without the hassle of a massive overhaul or confusing setup. Your existing tools are all you need!

Share on Twitter

About the authors

Irina Dominte

Irina Dominte is a solution architect at Particular who doesn't like to be forced into doing massive refactorings just to introduce a new feature.

Laila Bougria

Laila is a software engineer at Particular who thinks data and messages should play nicely together and convey the same story—all the time.


  1. In a good way. Boring code is reliable code!

  2. It's almost always the tables!

  3. Implementations exist for SQL databases, Cosmos DB, Azure Tables, DynamoDB, MongoDB, and RavenDB.

Don't miss a thing. Sign up today and we'll send you an email when new posts come out.
Thank you for subscribing. We'll be in touch soon.
 
We collect and use this information in accordance with our privacy policy.