Structure First: How I Design Business Software

Contents
  1. Abstract
  2. 1 Why structure comes first
  3. 2 Five principles
  4. 2.1 Separation of concerns
  5. 2.2 One dependency direction
  6. 2.3 Modules by business capability
  7. 2.4 The frontend mirrors the backend
  8. 2.5 AI where it’s needed
  9. 3 Reference architecture
  10. 4 What this structure costs
  11. 5 In practice
  12. Cite this article
  13. More writing

Abstract. Business software rarely gets hard because of one piece of code. It gets hard because of its size: many connected rules, relational data and requirements that keep changing. This article sets out the five principles I design by (separation of concerns, one dependency direction, modules by business capability, a frontend that mirrors the backend, and AI only where it does real work), the architecture they produce, and what that structure costs.

routes HTTP services use cases domain rules data access persistence dependencies point toward the domain
A drawing of the example this article works through.

Most of what I build is business software: the systems an organization runs its operations on, such as accounting, HR, inventory, hospital management and campaign management. This article is the approach behind all of it. The FastAPI article shows how I implement it, with the exact backend and frontend layouts I use. This one explains the reasons and the trade-offs.

Why structure comes first

The code in these systems is rarely hard on its own. They get difficult because of their size. There are many interconnected business rules, the data is relational, and requirements keep changing after launch. Whether a system like that stays easy to change depends mostly on its structure: where the module boundaries are, which way dependencies point, and whether the data model fits the domain.

I’ve seen what happens otherwise. My team rebuilt an internal enterprise tool whose modules had coupled and whose data integrity had broken down. After six to seven months of work, the client and the agency had concluded it wasn’t fixable. Neither the tools nor AI assistance caused the failure. The causes were a document store chosen for relational data, and nobody deciding early where the module boundaries should be. The full story is in the FastAPI article.

Five principles

These five principles shape how I structure a system. Each one exists for a reason, and the reasons matter more than the rules.

Separation of concerns

Each layer has one job. Routes handle HTTP, services orchestrate use cases, schemas and models hold domain rules, and data-access code owns persistence. A change to storage then doesn’t ripple into the API, and business rules can be tested without a web server.

One dependency direction

Dependencies point toward the domain, which stays independent of frameworks and infrastructure. Sessions, the current user and configuration are injected (Depends() in FastAPI) rather than imported, so they’re easy to replace in tests.

Modules by business capability

Code is grouped by what the business does, such as orders/ or inventory/, not by technical layer. Each module owns its models, schemas, services and routes, and other modules go through its services, not its tables. The modules deploy together, which keeps the system simple to run and debug. This layout is usually called a modular monolith. Because the boundaries are already clean, a module can become its own service later if there’s a concrete reason, such as independent scaling or a separate team.

The frontend mirrors the backend

Frontend features/ folders follow the same module boundaries, so a feature’s code lives in the same place on both sides of the API.

AI where it’s needed

AI goes where it does real work, such as automating a manual step or answering questions over the business’s own data. It isn’t added as a feature for its own sake. Agents act through the same services and permission checks as any other client, so they can’t bypass business rules.

Reference architecture

Figure 1 shows the architecture these principles produce. Frontends talk to one API layer. Behind it, the backend is a single deployable divided into business modules (§2.3), each with its own routes, services, schemas and models (§2.1). Modules share only core infrastructure: the database session, authentication, background jobs and caching. AI agents are called from the API and work through the same modules (§2.5).

Frontends Next.js, React API layer FastAPI, Django REST AI agents Pydantic AI, LangGraph Modular monolith Accounting HR Inventory Hospital Campaigns Auth, RBAC PostgreSQL SQLAlchemy 2.0, Alembic Redis, Celery Cache, background jobs Deployment Docker over SSH, deployctl
Figure 1. The reference architecture. Select a box to read what it is; the one marked with an arrow opens its code.

Table 1. The stack I use at each layer of Figure 1.

LayerTechnologies
FrontendsNext.js, React, Astro, Tailwind CSS
API layerFastAPI, Django, Django REST Framework
Business modulesRoutes, services, Pydantic schemas, async SQLAlchemy 2.0 models
DataPostgreSQL with Alembic migrations, Redis for caching
Background jobsCelery on Redis, monitored with Flower
AI agentsPydantic AI, LangGraph, RAG pipelines
DeliveryDocker on AWS, Microsoft Azure and DigitalOcean

What this structure costs

No structure is free. These are the costs I accept, and when I’d choose differently.

  • More decisions up front. Module boundaries have to be drawn before the first feature ships, and a boundary drawn in the wrong place is work to move later. I accept that cost because the alternative, no boundaries at all, is what made the rebuild in §1 necessary.
  • More files per feature. Each module has its own routes, services, schemas and models. For a small CRUD app or a prototype that’s ceremony, and a flat structure is fine until the business rules start to connect.
  • One deployable scales as one unit. If one module needs far more capacity than the rest, the whole application scales with it. That’s the concrete reason to extract that module into its own service, and clean boundaries (§2.3) make the extraction straightforward.
  • Boundaries hold by discipline, not by the network. In one codebase, nothing physically stops a module from querying another module’s tables. Keeping modules behind their services takes code review, and ideally an import rule checked in CI.
  • A schema up front. PostgreSQL asks for a schema and migrations before data goes in. For data that really is document-shaped, a document store can be the better fit. The mistake in §1 was using one for data that wasn’t.

In practice

The FastAPI article gives the exact backend and frontend layouts I use, and the open-source FastAPI backend template is the starting point many of our backends begin from. The client systems I’ve led with this approach are on the Client projects page.

Cite this article

@misc{babu2026structure,
  author       = {Babu, Amal},
  title        = {Structure First: How I Design Business Software},
  year         = {2026},
  howpublished = {\url{https://amal-babu-git.github.io/blog/coding-and-development/structure-first-business-software/}}
}

More writing

All writing