DEV Community

Cover image for Building Portfolio Insights: Lessons from an Event‑Driven .NET Microservices Dashboard
Jose Maria Iriarte
Jose Maria Iriarte

Posted on

Building Portfolio Insights: Lessons from an Event‑Driven .NET Microservices Dashboard

Over the past weeks, I’ve been working on Portfolio Insights, a personal finance dashboard built on a microservices architecture using .NET 8, RabbitMQ, gRPC, and Docker. The project is a continuation of my exploration into distributed systems, following an earlier e‑commerce microservices application, but applied to a more analytics‑driven, number‑centric domain.

The goal was not to build a production‑ready finance app, but to design and implement a cohesive, end‑to‑end system that demonstrates how independent services can collaborate through synchronous and asynchronous communication, while still supporting a clean and understandable client experience.

📊 Portfolio Insights – Personal Finance Dashboard

This project is a demonstration of a .NET 8 microservices-based personal finance and analytics platform. It builds on the architectural patterns and infrastructure explored in my earlier E-Commerce Microservices project, applying them to a number‑driven, analytics‑focused domain.

The application allows users to manage investment portfolios, retrieve simulated market prices, compute analytics asynchronously, and receive notifications based on computed results. All services are containerized and orchestrated via Docker Compose, showcasing a realistic, end‑to‑end distributed system.

Building this application helped solidify my understanding of:

  • Event‑driven microservices
  • gRPC and HTTP communication
  • CQRS and layered architectures
  • Message‑based analytics pipelines
  • Vertical Slice and Clean Architectures
  • Containerized orchestration with Docker
  • Implementation of Cross-cutting concerns such as validations, logging, health checks and custom exception handling

🧩 Solution Overview

The solution consists of eight projects, all located at the same directory level:

  1. Market Data Service – Simulated market prices and price…

The Problem Space

At a high level, Portfolio Insights allows a user to:

  • Manage an investment portfolio (assets and quantities)
  • Retrieve simulated market prices
  • Compute portfolio analytics asynchronously
  • Receive notifications when analytics are refreshed

Under the hood, each of these responsibilities is handled by a separate microservice, each with its own datastore and lifecycle:

  • Market Data Service – simulates and publishes market prices
  • Portfolio Service – owns user portfolios and assets
  • Analytics Service – computes metrics from portfolio and market events
  • Notification Service – reacts to analytics results

A YARP API Gateway acts as a single entry point, and a Razor Pages web client consumes the system through HTTP calls routed via the gateway.

Portfolio Insights Dashboard

Architecture First, Code Second

One of the most important lessons from this project was the value of diagramming the system in advance.

With event‑driven architectures, complexity doesn’t come from individual services—it comes from how they interact. Before writing most of the code, I spent time mapping out:

  • Which service owns which data
  • Which interactions are synchronous (gRPC)
  • Which workflows are asynchronous (RabbitMQ events)
  • How state changes propagate through the system

This upfront design work paid off, especially once I started building the client application. In an event‑driven system, not every user action results in immediate, synchronous data. The client often triggers a command, waits for asynchronous processing, and then queries a different service for the result.

Understanding those flows early made it much easier to design consistent endpoints and avoid tight coupling between the UI and internal service logic.

Portfolio Insights - Portfolio

Communication Patterns in Practice

Portfolio Insights deliberately mixes communication styles:

gRPC is used where low‑latency, request/response interactions make sense—such as the Portfolio Service requesting current prices from the Market Data Service.

RabbitMQ with MassTransit handles event‑driven workflows—portfolio updates, market price changes, and analytics computations.

For example:

A user adds or updates assets in their portfolio.

  • The Portfolio Service publishes a PortfolioUpdatedEvent.
  • The Analytics Service consumes this event, joins it with the latest market prices, and computes metrics.
  • The Analytics Service publishes an AnalyticsComputedEvent.
  • The Notification Service reacts and stores user notifications.

The client doesn’t talk to all these services directly. Instead, it triggers the initial command and later queries the Analytics or Notification endpoints when data is ready.

Portfolio Insights - Analytics

Data Ownership and Technology Choices

Each service owns its own data and uses storage appropriate to its needs:

  • Portfolio Service uses PostgreSQL with Marten, treating the database as a document store.
  • Analytics Service uses SQL Server with a layered architecture (API, Application, Domain, Infrastructure).
  • Market Data and Notification Services use SQLite, Redis, and in‑memory stores where persistence requirements are lighter.

This approach reinforced an important principle: consistency across services matters less than clarity of ownership and intent.

The Client Perspective

From the UI side, this project highlighted how deeply backend architecture influences frontend design.

Because analytics are computed asynchronously, the client must:

  • Trigger actions that start background workflows
  • Display previously computed data
  • Refresh views once downstream services have processed events

Designing the Razor Pages client forced me to think carefully about when data should be requested and which service is responsible for it. This is another area where early architectural diagrams were invaluable.

Portfolio Insights - Notifications

What I Took Away

Beyond the specific technologies, this project reinforced a few broader lessons:

  • Event‑driven systems reward clear boundaries and explicit contracts
  • Diagramming workflows early prevents costly redesigns later
  • Client applications should reflect the realities of asynchronous systems
  • Docker Compose is an excellent way to reason about distributed systems as a whole

Portfolio Insights is ultimately a learning and portfolio project, but it represents a meaningful step forward in my understanding of modern .NET microservices, asynchronous communication, and system‑level design.

If you’re exploring similar architectures, my biggest recommendation is simple: design the flows before the code. In event‑driven systems, that clarity makes all the difference.

Take the Next Step

If this article gave you a clearer picture of how an event-driven microservices architecture comes together in a real-world .NET application, here are a few ways to continue exploring:

📁 Explore the GitHub Repository – Review the full source code to see how the services, message flows, API Gateway, and Razor Pages client fit together. Following the code alongside the architecture diagrams is the best way to reinforce how synchronous and asynchronous communication work in practice.

💡 Share the Architecture – If you know someone learning microservices, messaging, or distributed systems in .NET, share this project with them. It’s often easier to understand these concepts through a concrete, end-to-end example.

🖥 Experiment and Extend – Clone the project and try evolving it. Add new events, introduce additional analytics, or adjust how the client reacts to asynchronous workflows. Small changes are a great way to deepen your understanding of event-driven design.

👍 Star the Repository – If you find the project useful, consider starring the repository. It helps others discover the project and supports continued sharing of practical architecture examples.

Working through projects like this builds intuition that goes beyond individual frameworks—helping you design systems that scale in complexity while remaining understandable.

Top comments (0)