Architectural choice 1: putting business logic in the database
It starts innocuously. A stored procedure here, a trigger there. The database is fast, the logic works, and the developer writes less code. But after two years the business logic is scattered across dozens of interdependent procedures. Adjusting a pricing rule means figuring out which triggers you are touching. Connecting a second application to the same database is impossible without duplicating or breaking that logic. Scalability in custom software begins with a clear separation: the database stores data, the application decides. Once that boundary blurs, you pay for it later in weeks of rework per change.
Architectural choice 2: coupling everything synchronously without event-driven patterns
In a small system with ten users, a direct synchronous coupling between modules is perfectly fine. System A calls system B, waits for a response, and moves on. That works until the volume grows. With a hundred concurrent users, or an integration with an external system that occasionally responds slowly, those wait times become visible. One slow link brings the entire chain down. Event-driven patterns, where a module places a message and the receiving module processes it at its own pace, are not always necessary. But if you know the system needs to grow in volume or in the number of integrations, the absence of that layer is an architectural choice you will pay back. Adding it after the fact typically costs more than building it in from the start.
Architectural choice 3: unclear domain boundaries in custom software architecture
The third choice is the hardest to spot, because it lives not in the code but in the structure of the system. When an order module also maintains inventory information, writes customer data, and creates invoice lines, there is no clear domain boundary. Everything is connected to everything. That feels efficient in a small team, but the moment you add a second team, want to scale a module independently, or need to build a new feature, every change requires an analysis of the entire system. Clear domain boundaries, where each module has a single responsibility and communicates through explicit interfaces, are what separate software that scales from software that has a ceiling.
When is a 'too simple' architecture acceptable?
Not every situation calls for a fully distributed, event-driven architecture from day one. If you are validating an internal process, testing a new market, or serving a small team that will remain stable, a simpler setup is often the better choice. Complexity has a price: more infrastructure, more knowledge required, more overhead in the build. The question is not whether your architecture is perfect, but whether you can see the debt you are taking on. Technical debt is acceptable when you know what you are trading off and when you plan to settle it. It becomes a problem when the debt stays hidden: when nobody on the team knows that business logic lives in the database, that there is no domain boundary, or that every integration is synchronous. Then you pay twice: once when building the new feature that does not fit, and again when cleaning up what was already there.
When do you end up paying architectural debt twice?
There are three moments when the debt spirals out of control. The first is an acquisition or merger, where two systems need to work together and each carries its own hidden logic. The second is a mandatory external integration, such as new regulation or a client that requires an API integration. The third is a scaling spike: a new contract, a seasonal peak, or sudden growth the system was not designed for. At each of those moments you are not only facing the question of how to build the new functionality, but also how to untangle what is already there. An architecture that scales from day one requires more time upfront. But that investment pays back at exactly the moments when you have the least time for remediation work.
