Solution Architecture in Practice: From Business Requirements to Structural Decisions
Thesis: every architectural decision is a promise made to the business. The work of a solutions architect is not to promise a lot. It is to promise only what can be explained, costed, and verified.

- The craft, without romance
After more than fifteen years designing and maintaining systems, I reduced the work to a translation with three questions I ask of every structural decision, mine or someone else’s:
Who pays for this? What business requirement justifies this decision?
What does it cost me? What complexity, risk, or rigidity am I buying?
What would make me undo it? What evidence would prove I was wrong?
The third is what separates maturity from conviction. A decision without a reversal trigger is not a decision: it’s a dogma with a diagram. And a decision without an explicit reason behind it—whether a business requirement, legal constraint, or risk mitigation—is just technical preference with a budget. The trigger does not always mean undoing: it can indicate review, containment, or progressive replacement, because not every decision can be reversed at an acceptable cost.
In this text I apply these questions to a real product I architected, which uses AI, is in production, and continues evolving. The method works for any business model. The decisions do not. That is the difference between a method and a recipe.
Regarding the case state: I describe recorded decisions and the target architecture. The production product coexists with legacy during the transition. I do not publish measured results I don’t have, because inventing a metric would be the opposite of this article’s thesis.
- The case and its premises
A software‑as‑a‑service platform that organizes the operation of small service providers and uses AI as a differentiator. It started as a proof of concept, became an MVP, and today serves real customers, with subscriptions in three plans, a free trial on the entry plan, and usage‑based pricing.
The premises that shape everything:
- small team, no slack to operate complexity;
- product in production: mistakes cost and there are customers depending on it;
- third‑party personal data under LGPD;
- many low‑ticket customers, making the cost to serve each a matter of survival;
- AI as a differentiator, not as an end.
- From business requirements to drivers
| Business fact | Driver | What it demands from the design |
|---|---|---|
| Personal data and commercial information of customers | Trust and isolation | Isolation is a core requirement, not a detail |
| Many low‑value customers | Low marginal cost | Avoid operating a new unit per customer |
| Free trial and usage‑based pricing | Immediate onboarding and consistent measurement | Automated provisioning and consumption metering |
| Small team | Evolvability | Operational simplicity over sophistication |
| AI as a differentiator | Substitutability and control | AI does not dictate the core shape |
| Product in production | Continuity | Controlled evolution, with possible rollback |
Continuity, here, has an operational definition: preserve customers’ critical operations and prevent data loss or inconsistency in any change. A brief outage outside critical hours is tolerable. Corrupting data is not.
- The five decisions: promise, cost, and reversal trigger
Each decision below follows the same format, on purpose. It forces you to state what is normally implicit.
Decision 1: isolation between customers in two layers
The promise. A customer’s data never appears to another. Multi‑tenancy is not a single choice; it is dimensions of sharing (application, database, schema, resources) that combine and condition each other. The decision was to share application, database, and schema, with logical isolation imposed in two layers: the application and the database itself.
| Model | Advantage | Trade‑off |
|---|---|---|
| Shared database and schema | Lower cost, simple provisioning | Relies more on logical isolation |
| Separate schemas in the same database | More explicit separation | Multiplying migrations |
| Database per customer | Strong operational isolation | Higher management and cost |
| Dedicated instance | Maximum autonomy | Higher cost per customer |
Why this one. Isolated cost alone does not decide: a database per customer, with good automation, could also be acceptable. The combination of marginal cost, immediate provisioning for the free trial, and a small team—where each extra unit is another item to migrate, monitor, and restore—tips the balance.
The cost. In a shared model, isolation ceases to be a by‑product of design and becomes a property that must be proven continuously. The second layer exists because the first will fail someday. The goal is that an authorization failure in the application does not become a data‑exposure incident, while the database enforces its policies with trusted credentials and contexts. It is a goal, not a guarantee: common‑cause failures, such as over‑privileged credentials, bring down both layers together.
The mechanism. The second layer relies on row‑level security policies in the relational database, on connection roles without privileges to bypass them, on reliable propagation of the client context to the session, and on negative access tests. Today, production isolation is enforced at the application layer. Enforcement in the database belongs to the target architecture and becomes a fulfilled promise only when negative tests demonstrate it.
What would make me undo it. A client whose contract requires physical separation; isolation tests that cannot be maintained at reasonable cost; or the cost of proving isolation surpassing the cost of actually separating.
Decision 2: a modular monolith, not microservices
The promise. Evolve quickly with few people, without paying the distribution tax.
Why this one. Microservices can offer deployment autonomy, independent scaling, and failure isolation, at the cost of distributed complexity. Many of those benefits are also attainable in a modular monolith, with separate work processes, resource isolation, and component‑level scaling, although the degree of independence is not equivalent. At this stage, the additional operational, observability, and coordination costs did not justify the expected benefits.
The defined target architecture concentrates the backend in a single modular codebase, with explicit boundaries between business capabilities and the ability to run operational components in separate processes when needed, such as asynchronous AI tasks. Within each module, the style is hexagonal. The two ideas solve different problems: the modular monolith splits the business; the hexagonal style orders the dependencies.
The cost. Boundaries that are not enforced do not exist. They require automatic verification of dependencies and public interfaces between modules, so that no one can reach another’s internals. Without that, you have a monolith with pretty folders.
What would make me undo it. Measured asymmetric scaling, not assumed; independent teams blocked by joint deployments; or boundaries that degrade despite verification.
Components and responsibilities, not rigid layers:
| Component | Responsibility |
|---|---|
| User interfaces | Three web front‑ends over the same API contract |
| API contract | Formal integration boundary |
| Business modules | Product capabilities, each with a public interface |
| Domain and application | Rules, invariants, authorization, and orchestration within each module |
| Ports and adapters | Persistence, AI, and integrations, replaceable |
| Data and platform | Database with per‑customer isolation, work processes, observability |
Decision 3: AI as replaceable infrastructure
The promise. AI delivers value without dictating the rest of the system.
Why this one. I separate the deterministic from the probabilistic. The business core—rules, billing, permissions—must be predictable. AI analyzes, recommends, and assists. With contracts and a deterministic simulator by default in development, I can swap providers without rewriting the domain, test at no cost, and prevent agent technology from contaminating the rules. None of this requires AI to run in the same process as the application. But technically replacing a provider is not the same as porting behavior: different models vary in quality, latency, cost, response format, and tooling, and the swap is only complete after a quality and security assessment.
The cost. Two cautions. Not everything involving AI is infrastructure: deciding when to request an evaluation, validating the result, and requiring human review are application policies. And when an output can affect people, the domain preserves the rules and invariants, AI provides assistive assessments, and the final decision remains under human authorization, validation, and supervision.
There is also a direct link to Decision 1. Every AI execution must respect its authorization context, whether user, service, or automated process. Administrative tasks may have a different scope from a user’s, provided it is explicit, minimal, and auditable. An execution that exceeds its scope could compromise the isolation guarantees established by the rest of the system.
What would make me undo it. A provider capability that the contract cannot express without distorting the domain; or the cost of maintaining the replacement layer exceeding the risk it mitigates.
Decision 4: the API contract as an artifact
The promise. Backend and interfaces evolve without silently breaking each other.
Why this one. The contract is formal and the client consumed by the front‑ends is generated from it. This reduces divergences and catches part of the incompatibilities already during development.
The cost. It does not catch everything. Behavioural changes, semantic differences, and drift between contract and actual implementation still reach production. Therefore the generated client needs contract and compatibility tests, and the versioning policy must consider that independent interfaces do not evolve together.
What would make me undo it. Recurring incompatibilities that the process does not capture, a sign that the contract describes intent rather than behaviour.
Decision 5: evolve the system without stopping the product
It is the most expensive decision and the easiest to take for the wrong reasons. There are four paths, often mixed:
| Path | Means | Benefit | Risk |
|---|---|---|---|
| Fix within the current architecture | Adjust what exists | Smallest change | Preserve structural limitations |
| Refactor internally | Reorganize current code | Lower migration risk | Residual debt |
| Replace piece‑by‑piece | New components take over capabilities, one by one | Controlled modernization | Integration during transition |
| Rewrite | New system replaces the old one | Redesigned boundaries | Regression, cost, and schedule |
The recorded architectural proposal was to rewrite the backend as a hexagonal modular monolith in Python, keeping TypeScript for the interfaces. Its execution remains conditioned on validating structural limitations, functional equivalence, and migration feasibility. Previously, the proposal was to keep the core in TypeScript and create AI as a separate service. That was revised. I treat the revision as part of the method: a decision is a hypothesis with a review trigger, and changing it based on evidence is maturity.
This decision is intentionally the one that demands the most scepticism, including my own. Hence I use a criterion before rewriting anything:
The rewrite test. I rewrite only if all four answers are “yes”:
- The limitation is structural, i.e., it does not

