
Iteration 2: Evolving to .NET Aspire
This is part of the First Challenge: Building a WebAPI with .NET Aspire Stack series.
The Evolution Begins
After days of wrestling with our ambitious architecture, I discovered .NET Aspire - Microsoft’s opinionated framework for building distributed applications with cloud-native patterns built right in. It promises simplified orchestration, service discovery, and observability out of the box. Think of it as the framework that takes all those microservice best practices and bakes them into a cohesive developer experience.
And of course, with Claude’s help, discarding our “previously beautiful architecture” to go with Aspire wasn’t a big deal at all. Not long after, I found myself trapped in an endless design, build, destroy, rinse and repeat cycle. Part of the issue was trying to squeeze too many “big ideas” into such a small timeframe, attempting to fit them all into Claude’s limited context window (aka “short-term” memory).
Claude’s smartness, quick-thinking, strong confidence, and vast knowledge about almost every aspect of system design created an impression that I was invincible in creating a backend system with all the state-of-the-art features I could think of. Reality kicked in hard though. It’s not by chance that the current backend systems of giants like Google, Microsoft, or Facebook can serve millions of online users with excellent performance. I remember reading that any web response over 200ms should be considered “slow” (from Google’s Web Fundamentals - they recommend aiming for sub-100ms server response times).
Even at Anthropic, making Claude really useful requires a seriously scalable backend system - a different story altogether. I asked Claude to do some research on its parent Anthropic’s infrastructure architecture to run Claude Sonnet 4. The results were fascinating:
Anthropic Infrastructure Report
Why Aspire?
I’ve been working with .NET for the last seven years, so naturally I picked something in the .NET ecosystem. .NET, especially with .NET Core, has proved to be incredibly popular and a favourite language/ecosystem for developers around the world. It has a great core team who always listen and debate how to make things better and faster - inside out, I mean in the philosophy of the language itself and in the supporting ecosystem around it.
Besides, Aspire gave me a great promise. Of course, in this day and age, I would not, and should not, need to worry about setting up server by server manually and running them on-premise (well, in fact running them on VPS hosting somewhere - even the term VPS is probably outdated by now).
Plot twist: I’m hosting this blog on my VPS which I keep just to play with some ideas I have. Another plot twist: this whole website was built by Claude with my prompts, and the contents were edited by Claude with my thoughts. I’ll definitely write another post about that. Remind me, Claude! (if you ever have that capacity!)
It Evolves Again Already!
Well, I need to warn any readers - my imagination went wild again by the time we’d got a “kind-of” working backend with Aspire and basic CRUD APIs. I wanted Event Sourcing and CQRS! If you haven’t heard about these patterns, I highly recommend reading Martin Fowler’s discussions on Event Sourcing and CQRS.
And naturally, I came to MartenDB and Wolverine. To provide some background, I have extensive experience working with a beautifully designed production system using Event Sourcing, Domain-Driven Design, and CQRS. Of course, I discussed extensively with Claude what I would love to have in an Event Sourcing CQRS system:
- Strongly typed IDs - Well, it’s arguably whether they’re required, but since we’re working with GUIDs most of the time, strongly typed IDs are very helpful
- A Query system with explicitly defined indexes - With the help of source generators, user-defined indexes and query-defined indexes can beautifully be merged and deduplicated before being registered into Marten
- Security down to the role level - Control which roles can execute which commands
Too many good ideas for such a limited context session, Anthropic!
And here is our “final” design document: DESIGN.md. WARNING! It’s just imagination running wild!
Will I Ever Stop?
One thing I’ve learnt during my years working across different industries of different sizes: software systems are repetitive patterns, but at the same time, there’s no one-size-fits-all solution for any problem.
Think about building houses - humanity has been building “something” over their heads from the beginning of time (I was about to use the word “roof” but really, there was no roof at the beginning - probably just some big leaves!). What are the main materials of a house? From leaves and clay, to wood, to bricks and mortar and steel… And yet no houses are exactly the same, even when built by the same developers at scale following identical plans. There will always be differences here and there.
Why is that? I believe it’s because we’re simply human, and we’re building for humans! We copy each other, but we put in something uniquely our own. We create similar homes, but at the same time different, based on what we anticipate our lives would be in those homes. Everyone has different ideas and different tastes.
At the end of the day, software is only as useful as it helps businesses’ day-to-day work become more efficient. Businesses have existed and done well for centuries without computers and software. Now, as AI agents come into the picture and transform software to 3.0 (as Andrej Karpathy discusses in “Software is Changing”) - yeah, it’s already 3.0 during half a lifetime of a typical human! - approaches to software will be much different.
I don’t know what the future holds, but one thing I’m sure of: we will build much more interesting software and systems, faster and better over time. But since they need to serve us humans, and humans will always be the main “constraints” of those software systems, I’m really excited to see what comes next!
Series Navigation: