GitHub: https://github.com/DCodeWorks/FinWiseNest
In our last post, we detailed the creation of our application’s core structure. We built a system where the frontend could read data from the backend and create new data. However, the user experience was still based on a simple request-and-response model.
Today, I want to talk about the next evolution: making our application feel alive and responsive. We’ll explore how we implemented a real-time feature that automatically updates a user’s portfolio moments after they add a new transaction, without requiring a manual page refresh.
In this article we will focus on below components in GREEN
Adding Message Bus with The Strategy Pattern
A key decision was choosing our message bus technology. Our production architecture calls for Azure Service Bus , but using it during development can be slow and add unnecessary costs. For local development, RabbitMQ running in a Docker container is a perfect free, fast, and powerful alternative.
To support both, we implemented the Strategy Pattern.
Instead of writing code that directly depended on RabbitMQ or Azure, we defined a simple IMessagingService
interface. This interface has one job: publish a message. Then, we created two separate classes that implement this interface:
-
RabbitMQMessagingService
: Contains the logic to publish messages to our local RabbitMQ instance. -
AzureServiceBusMessagingService
: Contains the logic to publish messages to Azure Service Bus.
In our application’s startup code, we use Dependency Injection to decide which strategy to use based on the environment:
if (builder.Environment.IsDevelopment())
{
// For local development, use the RabbitMQ implementation
builder.Services.AddSingleton<IMessagingService, RabbitMQMessagingService>();
}
else
{
// For production, use the Azure Service Bus version
builder.Services.AddSingleton<IMessagingService, AzureServiceBusMessagingService>();
}
This powerful pattern means we can switch between our local message broker and a cloud-native one by changing a single line of code, without altering any of our core business logic.
Before A Message Bus:
and after
The Problem: A Disconnected Experience
Our initial setup worked, but it had a limitation. A user would add a new transaction through a form, the data would be sent to our backend TransactionService
, and saved to the database. But the portfolio view on the screen would not change. The user had to manually refresh the page to see their updated holdings.
This delay creates a disconnected experience. In a modern financial application, users expect to see the results of their actions immediately. Our goal was to bridge this gap and make the UI react instantly to changes happening on the backend.
The Architecture of a Real-Time Flow
Here is the high-level plan, which you can visualize in the accompanying diagram:
To solve this, we needed a way for our server to send a notification directly to the user’s browser. A standard HTTP connection doesn’t allow this. The solution was to create a full, end-to-end asynchronous data flow using two key technologies: RabbitMQ and SignalR.
- User Action: A user submits a new transaction from the UI.
-
Event Publishing: The
TransactionService
saves the transaction and publishes aTransactionCreated
event to a RabbitMQ message queue. This service’s job is now done. -
Backend Processing: The
PortfolioService
, which is constantly listening to the queue, receives the event. It processes the information and updates theHoldings
table in the database. -
Push Notification: This is the new, crucial part. After the database is updated, the
PortfolioService
uses a SignalR Hub to broadcast a simple notification, like “PortfolioUpdated,” to all connected web clients. - UI Refresh: The React frontend, which has an open connection to the SignalR Hub, receives this notification. This triggers a function that tells Next.js to refresh its data, updating the portfolio display automatically.
Why This Architecture is Powerful
This design provides several key advantages that are important for a professional application:
-
Decoupling and Scalability: The
TransactionService
has no knowledge of the user interface or who needs to be updated. It simply announces that an event happened. This allows us to add more services in the future that can react to the same event without changing the original service. - Responsiveness: The user’s initial action of submitting the form is very fast because the backend simply accepts the request and publishes a message. The heavy work of updating the portfolio happens in the background without making the user wait.
- Excellent User Experience: The final result is a modern, dynamic interface. The application feels interactive and “live,” which builds user trust and satisfaction.
Conclusion
By combining our event-driven backend with SignalR for real-time communication, we have moved beyond a simple CRUD (Create, Read, Update, Delete) application. We now have a sophisticated system that provides immediate feedback to the user, reflecting the power and flexibility of our initial architectural decisions.
With this real-time foundation in place, our next focus will be on securing this entire flow by implementing user authentication and authorization, ensuring that each user’s data and notifications are kept private and secure.