Speeding up Azure development by not using Terraform
nitric.io
nitric.io
You fundamentally cannot build abstractions that are both easier *and* as flexible as the thing you are abstracting away, and so tools like this, as other commenters have noted, are great for a simple happy path, but that's almost never what you need once you're outside the tutorial and building something "real", which requires the very flexibility that has been abstracted away.
This is very similar to the argument against (most) ORMs and similar tools. For simple, happy path cases, you can avoid knowing SQL (or in Nitric's case, infrastructure management, platform-specific resources) and use whatever DSL/framework you have, but if you want to do something more complicated, you either have to hack and kludge your way through with the system, or drop down to the lower level and forego the abstraction entirely.
As an aside: Azure is a pain to use with Terraform as its API endpoints seem to be an order of magnitude slower than the equivalent GCP or AWS APIs, so things like `terraform plan` can throw you out of flow even on modestly sized projects. Going back and forth between Azure and other clouds is night and day.
I don't disagree with you, ORMs can be a great time-saver for your average CRUD app, where the SQL queries tend to be straightforward SELECT/UPDATE/DELETEs on primary keys, assuming you don't mind the overhead of learning the DSL. But as your querying requirements get more complicated, you either have to:
- Keep the DB queries simple and do the complicated stuff (joining, aggregating, etc) in the business logic, or
- Learn how to express your complicated SQL in the ORM's DSL, or
- Drop into the ORM's "raw" mode and write plain SQL
The first option might be fine for non-performance-sensitive stuff, but otherwise isn't making good use of the database, and the latter two options reveal the SQL behind the curtains, at which point the abstraction of the ORM is more or less totally broken.
[1] Raw SQL: https://github.com/bcspragu/stronk/blob/main/db/sqldb/sqldb....
[2] sqlc: https://github.com/bcspragu/logseq-sync/blob/main/db/sqlite/...
I mean there are 2k+ issues open https://github.com/typeorm/typeorm/issues/ - yes, not all of them are bugs and limitations, but if you filter the bugs, that's still more than 1000 ...
SQL is a high level, declarative language. You need to use good libraries that ensure queries can be safely composed and arguments passed, but you are not automatically "making a mess" if you're writing SQL unless you or your tooling is incompetent.
Personally, I really like Dapper (C#) as it solves the "object mapping" and "query composition" cases in a performant and ergonomic way while letting me write the (exact) query I want.
Easier doesn’t have to mean less configuration. It could be more sane defaults, better organised abstractions, clearer documentation, and/or handier tools.
For the very small environment which I have indirect responsibility for, I’d love a simple read-only GUI which could present the environment, its sources, and the connections between them.
The point of Nitric isn't to avoid knowing how your infrastucture works, it's just to speed up development and improve cohesion.
You use ARM templates, that support comes with a feature GA. Terraform has to wait for the updated GO SDK, and then you have to wait for them to build it into Terraform.
Bicep in theory carries a similar risk, but I have not heard any complaints so far. Maybe product teams release ARM and Bicep functionality at tbe same time?
If it reassures you, their own tooling uses the same APIs so everything is slow by definition. Also, their security posture is a joke with multiple (around 10?) cross-tenant vulneabilities in the past 2 years, many of which were trivial to exploit.
> If you wanted to be able to completely customise your infrastructure, using IaC can be practical. However, it is rare that an application requires any level of customisation beyond the default.
I'm assuming this project is targeting rapid prototype/"entrepreneur" market because I can assure you that basically every project I've ever worked on at BigTechCorp needed some kind of customization beyond the defaults.
It's not a tool I have ever thought was great!
As you pointed out, though, it's not an issue with Terraform, and the above case can be solved by manually managing the dependency with lifecycle management. So, I agree that most cases are often inflicted by things outside of Terraform.
As a database analogy, most of the problems I’ve seen are similar where people chose not to learn how their tools worked and then said it was the database’s fault that they were making billion-row joins or O(n) queries because they knew what they wanted and the database should have had Do What I Meant mode.
It's wrapping Pulumi and managing the state file for that. Yet another abstraction layer on-top of it all.
I'd love to be proven wrong if they can output Pulumi code from their application code?
Even then, I'm not sure what the monetization play here is? You're standing on the shoulders of Pulumi and you're open-source. If you go down the managed route and host the state files you'll be in direct competition with Pulumi's cloud offering...
1) Rampant copy-and-pasting instead of attempting any sort of code reuse
2) Reinventing the wheel poorly: instead of bringing in an existing module, they'll attempt to build their own half-baked one.
3) Poor ops practices like manually editing configuration through the console, then complaining about drift when TF overwrites their changes.
4) Lack of systems and networking fundamentals, like DNS or load balancing. If you don't understand it, how can you hope to configure something properly?Note this only applies to our OOTB providers which use Pulumi as our deployment engine of choice.
When you take away our providers Nitric becomes a communication layer between your application and any deployment engine that implements the Nitric deployment API: https://nitric.io/docs/reference/providers/custom/building-c...
This allows collecting a Bill of Materials of cloud concepts (e.g. APIs, topics, buckets, queues, policies etc.) and providing them however you like with code. This could be templating IaC, directly executing IaC, rolling your own solution or anything in between.
As for how we make money, currently we're doing consulting and dog-fooding our own tooling while working with existing teams struggling with more traditional deployment setups to make them more productive, their feedback is used to make own product even better :).
The Terraform you've written out looks like a lot, but the Nitric code is doing a huge amount of lifting in this example.
Not to mention that Nitric is wrapping a Pulumi provider (granted they are using `pulumi-azure-native` unlike the `pulumi-azure` provider that's just a Terraform wrapper).
You can do this exact kind of Function Serialization in Pulumi anyway: https://www.pulumi.com/docs/concepts/function-serialization/....
At the end of the day this article is abstracting a huge amount and whilst for startups it might be fine? When something goes wrong you'll want to be able to debug the random queue backlog in your stack.
For an example of this done beautifully in one language where they're managed close the loop with monitoring, I would checkout Encore: https://encore.dev/.
So, it's fast because it uses Pulumi... Does Pulumi no longer build on Terraform providers? (EDIT: looking, it seems like many Pulumi providers are still terraform wrappers...)
Either way, it feels like this is just a Pulumi feature.
Nitric infers the necessary cloud runtime by building a spec based on the resources you've imported into your application code.
At deployment time Pulumi is used to provision the resources (because we think it is an awesome tool).
Note that we could have also opted for any other IaC like CDKTF, and that you could either modify our providers or implement your own providers using your IaC of choice.
The exception are native providers which use new stuff like AWS Cloud Control API.
You might want to look at the repo I linked, check out the source, or hear it directly from the folks at Pulumi if you're interested.
Not just a feature of Pulumi, its just used as the IaC of choice for the out of the box deployment providers. This can be replaced with anything.
At its core Nitric is really a tool to collect infrastructure requirements from an application and those requirements are delivered to a server that is built using the Nitric deployment APIs.
Mixing the infra and application logic is the obvious path forward, though. Just as most applications don't need more than Rails and a single Postgres, most apps don't need customized infra. Simplifying the 80% unlocks cycles for more creative work. Suppose you are successful enough to have to define custom infra at some point. That's good for you. That's a great problem to have.
AI is going to standardize architectures going forward. Providing a simple set of tools in the same code as the logic makes the planner easier for Copilots and reduces the context windows. Terraform and application code have implicit dependencies. Colocating them lets you define explicit dependencies that are more understandable.
However, there are many use-cases without bespoke infra needs where it does suit.
Our hope is that people who genuinely are looking for assistance with their cloud deployment needs evaluate Nitric against their own projects requirements.
I can see a path forward for Nitric as platform engineering solution where standardization of architecture is achieved (with the aid of AI as well), through building Nitric custom providers: https://nitric.io/docs/reference/providers/custom/building-c...
Apart from the fact that C# examples seem to be lacking from the documentation at the moment, I’d also be interested to know if there is, or will be support for managed identities?
Edit. Just found the C# examples. Reading through now.
We are predominantly switching over to managed identities only to avoid key rotation hassles, and Entra workload federated identities in pipelines to remove those hardcoded identities in our pipelines.
I also wonder how you deal with combined function deployments. For example we deploy function apps or message consumers that process multiple message types as a bounded context, in one single app. I assume that Nitric would deploy each function endpoint as a separate app container?
Regardless of those niggles, I love the concept of combining the code and the infrastructure definitions together.
Maybe if you are small enough to just build a single application with some resources in a single vendor's playground all of the other IaC variations make sense, but that's hardly the reality. Either you're going to be part of something bigger, you're big enough to need multiple services, or you're so small that you can go straight back to the 90's and clickops+screenshot your way through the day.
While there might not be a one-size-fits-all solution, there is definitely a cutoff where CDK-style or single-vendor tools just can't do what is required.
Rather than poking around bespoke IaC on a per-project basis, you have the opportunity to inspect modularized and tested IaC which in this case is written in Pulumi and maintained by either the Nitric team or your team if you need to customize and move away from "out-of-box".
I work with the maintaining team, we're happy to discuss more. If you're curious then hop into our discord.
However, when it comes to solutions to problems "Separation of Concerns" is the best solution. I don't want my application code telling azure what resources to make. I want to tell azure what resources to make, and I want to be 100% sure that what is created is what I want because we're talking about something that costs my company money to run it.
While Terraform is a lot of code to get something defined and started (and I do think there's room for improvement), the degree of what you can define with it and how much you control are the key selling points.
My stack looks like this:
- framework magic (e.g., spring boot, or ror, or whatever)
- library magic
- terraform magic
- aws magic
- docker magic
- VM magic
- standard magic (OS, networking, decent stuff, etc.)
I suddenly had a mental image of something like a Discworld novel poking fun of corporate America by having a company called Standard Magic Inc that commercialises wizardry to the chagrin of the University wizards.
What I'm thinking is abstract cloud resource APIs I need into unified CRUD interface + has_diff.
Then essentially exposing idempotent functions (ensure_exists, ensure_deleted, ensure_updated) based on the unified interface.
No saved state state means slower. But it could be added fairly easily.
Something like this for personal use like scripts instead of terraform etc..
a) guys, look at this! It’s called Terraform and you can define your infra with HCL and keep it in git. Cool, right? Let’s get rid of these old Ansible files!
Five years later
b) Guys, we are deprecating Terraform. Please follow this guide on how to use CDK/Nitric/etc.
Wow. From hero to zero in such a short time span.
I wish I had CDK/Cloudformation skills, and not Terraform skills.
I hear the separation of concerns between IaC and application code argument quite a bit, but always see an inherent coupling between the two. If changes to your IaC require changes to your application code for your application to continue working then where is the separation of concerns? If its just deployment and runtime, this separation still exists within Nitric: https://nitric.io/docs/reference/providers/custom/building-c... (this is the same way we build our own deployment providers).
With Nitric the primary goal is to increase cohesion between an application and its running infrastructure without increasing coupling to that infrastructure. With current IaC application and infrastructure cohesion is inherently very low, while giving the appearance of low coupling (despite it being quite high).
And I'm sorry, but without some concrete drawings you can't just claim current IAC creates low cohesion and strong coupling without making people suspect you don't know what those terms mean.
What would be very useful is an article covering the points of how testing works with Nitric, exactly how it saves you time and why the cost/benefit analysis of adopting Nitric makes sense.
Fair point on the second, can I ask what you specifically mean by concrete drawings? I'd really like to understand your perspective on separation of concerns between IaC and application code better.
Thanks for content takeaway as well I really appreciate the feedback. The closest thing we have right now is a published customer use-case: https://nitric.io/case-studies/dropbio but I don't think this hits the mark for what you're after.
> without increasing coupling to that infrastructure
These are contradictory. I don't know what people are doing to fuck up their TF so much, but I've never seen an issue where TF and application code were coupled in a way that wasn't trivially solvable.
Of course, that depends on people not being really dumb.
I don't know what your experience is but "never" having seen any issues could also be bad thing, seeing the "don't" is just as valuable as the "do" when it comes to building experience.
Some concrete examples to demonstrate where coupling is trivially solvable would be good if you have some that you can share, where it wouldn't be trivially solvable what are the common mistakes that would be made in those cases?
If you have time to share your experience I'd really appreciate it.
The only real alternative is to use PowerShell or AZ CLI instead.
[1]: https://learn.microsoft.com/en-us/azure/azure-resource-manag...
“Use our weird language, to interact with our awful, slow API’s, nobody else uses it, our support probably doesn’t know it exists, and it’ll be full of weird, MS culture specific idioms and eldritch code that compiles down into our previous failed experiment”
Like, no? No thank you, can’t you just be normal?
https://github.com/Azure/AzureVM/issues/26
This is not a serious platform: https://learn.microsoft.com/en-us/troubleshoot/azure/virtual...
Can you begin to imagine how fucked up Compute is internally that this shit remains true for 6+ years? VMSS was an entire nightmare that someone tried to point out was a mistake that AWS and GCP brilliantly avoided. Sure enough, now the product is confusing and even further internally fractured by supporting both deployment models.
Sorry, having been on the inside of a couple places and a user of all of them, Azure comes in last place every single time.
re: nix, it does infra?
Dapr has some love on the .NET community, and apparently is used by Aspire, however I have seen little adoption of it on polyglot projects.
When not using terraform, I find the scripting approaches easier to follow up.
Furthermore, most companies don't need K8S either if they are using a cloud provider. It only starts being cheaper if you forget what you pay the DevOps team (or senior devs who do DevOps on top of code) AND have a huge number of resources. Otherwise you literally have to be in maybe the top 30 companies for K8S to actually save you money.
Again, you don't need Docker. You aren't CoreWeave or a cloud provider. You DON'T need to virtualize your hardware and parse it out in pieces on a moment's notice. You don't need K8S to do a rolling update for you when on the cloud, you don't need Ingress to load balance, you don't need ECS/EKS to break every day.
I am persisting in hopes someone convinces me I am wrong by the way. This isn't a rant, I have a decade of experience of someone building a fancy container/orchestrator infrastructure only for all of it to fall apart the moment the DevOps team leaves. Perpetual cost center that adds nothing.
And yes, I know, I am not doing it right and we need Flux to fix all our problems now.