After dealing with that problem and enduring the pain of it for years, I finally switched to C#/.NET. It has the necessary tooling to achieve this and more.
Rewriting a lot of things was time well spent rather than trying to tame the dynamic nature of Python and my tendency to overuse it.
And I can't believe I'm writing this after all these years evangelizing Python and dynamically typed languages.
Pleasantly surprised to see .NET shared on HN! I've had a lot of success with it in my career building several SaaS platforms from the ground-up. The tooling is great. It's wild how productive a small startup team can be on the .NET stack using a clean architecture.
Can you please give a short summary of why I should give C#/.NET a go for my backend services?
I've been fighting battles in Python backend services to get a nicely decoupled API, logic and DB layers for a while...but sqlalchelmy, alembic and flask/django/fastAPI are my safety blankets
Other commenters have good specific points but I'll add one overarching theme: .NET is developed by a well-funded corporation that is incentivized to bring all the popular innovations from other ecosystems back to the .NET world in a cohesive form. If something becomes popular in another programming ecosystem and people want it, we'll get it in .NET and it'll be done in the same style as everything else we have. It's pretty refreshing working with a system that was designed to work together rather than cobbling bits together.
My journey went through mypy, then typescript for frontend, the typescript on node. The story here was the type system is so much better that it allowed better prototyping, larger codebases and confidence.
I’ve done a lot of C# and Java now over the last few years, and I don’t love their type system, esp compared to typescript, but they scale much better against large codebases - especially with tooling like bazel.
I’ve been looking at Haskell and Rust a lot to fill this intermediary: code that’s performant, with a very expressive type system.
I maintain(ed) a number of popular python packages, and that journey lasted for nearly a decade.
This is my experience, having gone the route you're looking. Haskell (~6 month trial) was unproductive for me. Primarily the ecosystem is full of abandonware. Secondarily it lures you into spending way too much time refactoring stuff into the most concise possible form, which you can no longer understand (and frequently needs rewritten completely because the tiniest change to the most concise possible form invariably explodes through several layers when you have to make changes later). Rust (~3 month trial) may be great for codebases where you'd legitimately consider C / C++, but too much work otherwise; I personally wasn't doing anything that I'd use C for, so it was not worth it.
I ended up being very happy with F# as a middle ground for several years, but eventually migrated back to C# as they started adding more and more F# features. The primary challenge with F# was the parity mismatch with the underlying runtime, so you end up having to write a fair amount of non-idiomatic F# to interop with common libraries. But otherwise it's great. (I also tried Scala for a year and hated it: too many ways to do any one thing).
> - Being able to use an IDE with it’s full power is nice.
In my experience, Visual Studio is much slower when developing. My Python workflow affords a 200ms red-green-refactor loop, while VS is on the order of several seconds.
This might not seem like much, but it has a great impact on my engagement, flow, and satisfaction.
> Rebuilding the project to run the tests adds a bit of time, yes.
It costs money, but I've paid for NCrunch for a fair few years and find it invaluable for this reason. It doesn't even need you to save changes before it spots them and runs affected tests in the background.
If cost is an issue you can also start `dotnet watch test` going in a terminal/command prompt for non-interactive live-reload testing.
I am not so sure it's worth it. In general, developer time is much more valuable than machine time.
Maybe if you're building a large high-performance server, you should invest in performance. But otherwise, if you only look at at computational complexity, and batch up/avoid I/O when possible, you're fine.
I grew up with BASIC and made it to Java by way of assembly, C, C++ and C#. This year I put some Rust into production tooling. I've used python along the way, but usually as a scripting tool. I've never worked at a company whose codebase involves a lot of python. So beware of confirmation bias in my thinking.
What follows is my opinion, I am aware it is my opinion, but in my sphere of influence, it is not up for debate when it comes to writing code. It might occasionally be a conversation over lunch.
I put python in the same bucket as BASIC. It's not a production language. "developer time is much more valuable than machine time." Yes. Absolutely. Iteration speed is vital. But it is vital in more than just the test loop. It is important in the "minor refactoring of various classes" up to the "major refactoring of entire systems" loop too. And python just doesn't make that easy. It actively makes it difficult. It makes comprehension difficult. It's difficult to look at python code in a code-review and have a good idea of what the classes involved are. I don't even write scripts in it any more. I've found that any script that is worth writing is likely to grow and evolve over time, and if it is not written in something like C# or Java, then it will become an intolerable mess. I've seen entire organizations that are basically cargo culting.
I encourage you to learn a statically typed language and its tooling.
All of the benefits of static typing save a ton of developer time in other places. No one is talking about saving machine time as the primary benefit of static typing.
Static typing in .NET saves developer time on a massive scale. Sure, the compile times and startup times may be longer (and tests may take longer to run for that reason), but the languages also allow for editor/IDE tooling that boosts developer productivity massively. Visual Studio with Resharper or Rider may seem expensive, but if you work with these tools full time, they pay for their cost multiple times over in almost no time.
There is a delay, true. Inevitable with the compilation phase, and I do find it irritating that my Go stuff builds so much faster. That said, there's reasonable (not perfect) live reloading happening these days which helps somewhat.
One reason is that entity framework is the best ORM out there. It blows sqlalchemy and alembic out of the water imo (I’ve used both a bunch).
Another reason is that decoupling and adding layers to your code is more part of the culture. Look up “domain driven design C#” or “onion architecture C#” and there will be a lot of resources on how to achieve it. There is stuff out there for Python as well (and the concepts translate between languages), but not nearly as much.
It feels a lot more professional than other ecosystems. For example, they actually talk about layering/coupling as professionals should! People actually seem to talk about architecture as well rather than blindly believing that the conventions forced on them by a framework are sufficient for all use cases.
I especially like the gradient in the .NET world from micro ORMs to full-fledged ORMS. Most ecosystems seem to develop a big ORM that constantly accrues features (and bugs) and eventually becomes enshrined as a "best practice" because it acts as a kitchen sink.
+1 I was much happier using Dapper compared to EF. I figure if it's good enough to run stackoverflow, it's probably good enough for whatever I happen to be doing.
The amount of open source in dotnet is great. (I think more than Java? My impression of that is dominated by Apache etc., though my experience in the Java ecosystem is limited. Presumably people in Java land would expect the same of dotnet being dominated by Microsoft, but that's really not the case).
I haven't played with SQLAlchemy in a while, but I was comparing EF core to the Django ORM, and EF core seemed to be lacking in features. There were a few things missing but the two that pop to mind are Window function and Case statements.
Rewriting a lot of things was time well spent rather than trying to tame the dynamic nature of Python and my tendency to overuse it.
And I can't believe I'm writing this after all these years evangelizing Python and dynamically typed languages.