Case Study

Restoring F5 Development to a Multi-Service Learning Platform with .NET Aspire

How .NET Aspire turned a fragmented microservice topology into a single keypress for the development team, and held up as the team's first production-grade Aspire deployment

RD

The challenge

The platform was a small but genuine microservice topology: two Azure Functions, a backend-for-frontend API, a progress engine, a SQL database, Blob storage, and Redis. None of that is exotic on its own. The problem is that the moment you have more than three or four moving parts, the developer experience collapses into something that no longer fits on a single keypress.

The team had been heading in the direction of a docker-compose file plus a stack of "run this script before that script" runbooks. That works on paper. In practice, with around nine developers across three workstreams and roughly two-thirds of them junior, it would have meant a daily tax of "is my environment in a sensible state" before any actual work could start. It also meant the front-end developers in particular would end up running large parts of the stack against mocked dependencies, which is exactly how integration bugs land in production rather than on someone's laptop.

The other complication was that the codebase was deliberately split across three repositories: the learner dashboard, the progress engine, and the OneFile integration. The progress engine and OneFile integration repos were pulled into the dashboard repo as sub-repositories. Whatever the local development experience looked like, it had to come up as one coherent system across all three.

The results

Key results

  • F5 development experience restored across services, SQL, Blob storage and Redis
  • Single keypress brought up the entire ecosystem locally, with no manual orchestration
  • First production-grade Aspire deployment for both myself and the wider team
  • Compile-time, type-safe alternative to docker-compose adopted as the standard local dev path
  • Sub-repositories pulled into the host repo so the whole multi-repo system came up locally as one piece
  • Multi-repo Aspire workflow documented, including a working pattern for sub-repository commit pointer drift

From the first F5 in front of the team, the platform stood up locally as one system. Developers stopped having to orchestrate their own environment and started having to think about the work in front of them. New joiners (including the junior contingent) were running the platform locally on their first day rather than in their first week.

I have since taken Aspire into every subsequent project I own. The BPP engagement was the moment it stopped being "interesting tool I am playing with" and became "default local development orchestrator". I now describe it to other clients in the same way I described it to this team: a compile-time, type-safe alternative to docker-compose. That comparison has held up.

The pattern was not without its rough edges, and being honest about those is part of why I am willing to recommend Aspire to other teams. The most uncomfortable issue, covered in the technical deep-dive below, was sub-repository commit pointer drift in a multi-repo Aspire setup. It is solvable, but it is real. Anyone adopting Aspire across more than one repository should plan for it explicitly rather than discover it under deadline pressure.

The solution

I recommended .NET Aspire as the local development orchestrator from the first architecture session. I had been using Aspire on my own infrastructure for SoftWeb and was comfortable that it could carry production-team weight rather than just demo weight. This was its first commercial outing for me as a team workflow rather than a personal tool, and the first Aspire deployment for the wider team.

The Aspire app host owned the whole local topology. It started SQL, Blob storage and Redis as containerised dependencies, brought up the backend-for-frontend API and the progress engine as referenced .NET projects, and stood up the two Azure Functions alongside them. Service discovery, connection strings and ports were handled by Aspire rather than copy-pasted into appsettings files. The result, the first time we hit F5 in front of the team, was the entire ecosystem (services, dependencies, databases, storage, cache) coming up as one piece.

Because the system was split across three repositories, I set up two app hosts. The primary one pulled the progress engine and OneFile integration into the dashboard repository as sub-repositories and stood the whole platform up locally. A second, dashboard-only app host let people working purely on the dashboard run a tighter loop without paying the cost of starting the progress engine or OneFile flows.

I describe Aspire to clients as a compile-time, type-safe alternative to docker-compose. That description was load-bearing here. Junior developers who would have struggled to debug a misconfigured docker-compose file could navigate Aspire's configuration in their IDE, with type checking and refactoring tools doing the work that ad-hoc YAML never gives you.

Technical deep dive

App host shape

The primary app host wired up:

  • The Vue.js front end (run via the front-end toolchain, hosted in Aspire as an external resource for orchestration purposes)
  • The C# backend-for-frontend API (referenced .NET project)
  • The progress engine API (referenced .NET project, pulled in via the sub-repository)
  • Two Azure Functions for the OneFile ingestion and adapter layer (also via sub-repository)
  • SQL Server (containerised)
  • Azure Blob Storage emulator (containerised)
  • Redis (containerised)

A second, slimmer app host stood up only the dashboard, the BFF API, and SQL, for developers working purely on dashboard concerns who did not need the progress engine or OneFile flows running locally.

Sub-repository commit pointer drift

The single sharpest edge in a multi-repo Aspire setup is what happens when developers commit code that spans more than one repository. If a feature branch in the dashboard repo references a feature branch in the progress engine repo (because the dashboard work depends on the progress engine work), the parent repo records the sub-repository pointer at that feature branch commit. When the progress engine feature branch is merged into main and deleted, the next developer who pulls latest in the dashboard repo finds the sub-repo pointing at a commit that no longer exists on a known branch.

The mitigation we settled on was a discipline rule rather than a tooling fix: sub-repositories should always point at long-lived branches in the parent repo's commits, not at short-lived feature branches. Cross-repo work needed a deliberate handshake: get the progress engine change into a long-lived branch first, then advance the dashboard repo's pointer to match. There is more tooling to be built here. I did not solve the problem on this engagement, only contained it.

Why not docker-compose

A reasonable question is why not just use docker-compose, which is the muscle-memory answer for this shape of system. The honest answer is that docker-compose is fine when the team is comfortable with it. With a predominantly junior team, including bootcamp graduates and developers new to the languages they were working in, the cost of debugging environment issues in YAML is much higher than the cost of debugging them in a typed C# app host where the IDE can help. Aspire also let me model service-to-service dependencies and connection strings as first-class, refactor-aware references rather than as strings duplicated across files. For this team, that mattered more than it would for a more senior group.

What I would do differently

If I were starting again I would invest earlier in a documented sub-repository workflow rather than letting the team discover the pointer drift problem in flight. I would also push harder, earlier, for the dashboard-only app host as the default for front-end work, rather than letting it become a thing people opted into when the full ecosystem felt heavy. Both are workflow questions rather than Aspire questions, but they are the two things I would tighten up next time.

Ready to achieve similar results?

Let's discuss how we can help your organisation achieve these results.

Book a strategy call

Architecture Advisory

De-risk critical architecture decisions with on-demand senior advice. Get peer-level technical depth for complex systems, AI adoption strategies, and architectural reviews, without hiring a full-time architect.

Learn more →

AI-Augmented Development

Learn how to leverage AI effectively in your development process. Get proven AI-augmented practices for LLM integration and developer tooling, realistic guidance on when AI helps vs. hinders, and hands-on implementation support that fits your team's capability.

Learn more →