Modern engineering disciplines do not begin with tools. They begin with first principles.
When engineers design a better refrigerator, they do not start by selecting a compressor brand or a control board. They start by reasoning about thermodynamics, heat transfer, energy efficiency, material constraints, and usage patterns. Only once the problem is understood at a fundamental level do components and implementations enter the discussion.
Software engineering, however, routinely reverses this process.
Frameworks, architectural templates, and established layering conventions are often selected before the problem is clearly understood. The result is not engineering, but assembly — and the cost of that inversion accumulates silently over time.
What First-Principles Engineering Actually Means
First-principles engineering is the discipline of reasoning from what must be true, rather than from precedent or convention.
It asks:
What problem exists independently of our tools?
What constraints are inherent?
What behavior must be preserved regardless of implementation?
What cannot be simplified away?
Everything else — tooling, frameworks, architectural patterns — is secondary.
This approach is well understood in physical engineering. It is far less consistently applied in software.
The Singleton Problem in Software Systems
One reason this inversion persists is that every software system is a singleton.
A software system:
is built once,
under unique historical conditions,
with no parallel reference implementation.
There is no second version of the same system, built differently, operating under identical constraints, against which outcomes can be compared. There is no control group.
As long as the system functions, produces output, and does not catastrophically fail, its architectural quality is effectively unobservable from within.
This creates a dangerous illusion:
inefficiency looks like necessity,
complexity looks inherent,
suboptimal structure looks inevitable.
“Do what we did before” becomes a self-reinforcing rule — not because it is optimal, but because deviation offers no immediately measurable benefit.
In such an environment, first-principles reasoning is not optional. It is the only way to evaluate quality.
Domain Models as First-Principles Applied to Software
In software, domain modeling is the practical expression of first-principles engineering.
A domain model answers foundational questions:
What concepts exist in this business?
What do they mean?
What rules govern their behavior?
What responsibilities belong together?
What must remain true over time?
This is not a technical exercise. It is a semantic one.
The domain model is where essential complexity is made explicit. It is where the system acknowledges reality instead of abstracting it away.
Without this step, architecture has nothing solid to stand on.
Essential vs. Accidental Complexity (Brooks Revisited)
In No Silver Bullet, Fred Brooks distinguished between:
essential complexity — inherent to the problem, and
accidental complexity — introduced by solutions.
His core insight was that only accidental complexity can be reduced. Essential complexity must be understood and managed.
What follows directly from this — though Brooks did not state it explicitly — is that essential complexity must be represented somewhere.
If it is not represented deliberately in a domain model, it will surface implicitly:
in coordination logic,
in framework configuration,
in runtime behavior,
in operational fragility.
A domain model is not an optional design choice. It is the location where essential complexity is allowed to exist openly.
Why Technical Layering Is Accidental by Default
Standard architectural layering — controller, service, repository, entity — is rarely derived from business reality.
These layers describe:
technology concerns,
data access strategies,
request handling mechanics.
They do not describe:
business responsibilities,
domain invariants,
conceptual ownership.
Unless a layer corresponds to a real business boundary, it introduces indirection without reducing essential complexity. By definition, it adds accidental complexity.
Layering should be a consequence of domain understanding, not a substitute for it.
When layers are imposed without business cause, they fragment responsibility and force behavior into procedural orchestration. Complexity is not reduced; it is merely relocated.
The Boundary Problem: External Systems Are Domain Concepts
A common failure mode in software architecture is the segregation of external systems into a "technical ghetto" of adapters, clients, and integration layers. Engineers often say, "This is the Paypal Client," or "This is the SAP Integration." From a first-principles perspective, this is a mistake. To the business, Paypal is not a "client." It is a Payment Processor. To the business, SAP is not an "integration." It is an Inventory Authority.
When we model these as technical adapters, we leak accidental complexity into the domain. The domain model begins to worry about "calling an API" or "handling a timeout." In a proper first-principles architecture, an external system is simply another Domain Object.
Encapsulation at the Boundary If a business process requires checking a customer's credit, the domain model should contain an object—let’s call it CreditAuthority.
What it represents: The concept of trust and risk verification.
What it hides: The fact that the answer comes from a REST call to Experian, a SOAP request to a legacy mainframe, or a hardcoded rule.
To the code using it, CreditAuthority is indistinguishable from any other object in memory.
It has methods defined by business language (
assessRisk(customer)).It respects domain invariants.
It creates a hard boundary.
The Failure of "Service Layers" Standard framework architectures (like Spring) encourage pulling this logic out of the object and into a "Service." The Service acts as a procedural script: "Get data from object A, call Client B, save to Repository C." This is where the model becomes anemic. The CreditAuthority ceases to exist as a concept, replaced by a procedural CreditService that knows too much about HTTP, JSON, and wiring.
The Responsibility of the Object If a domain object represents an external reality, the mechanism of communication is that object's private responsibility.
The caller does not want to know about retries.
The caller does not want to know about authentication tokens.
The caller does not want to know about JSON serialization.
The caller only wants the defined functionality.
By pushing the "how" (HTTP/SQL/SOAP) deep inside the domain object, we achieve true encapsulation. The external system becomes a collaborator, not a piece of infrastructure. Lifecycle mismatches (like keeping a connection open) are solved by facades or adapters internal to that object boundary. The domain remains pure not because it avoids reality, but because it forces reality to conform to its contract.
Why the Domain Must Dominate the Architecture
In a first-principles architecture:
the domain defines structure,
technology adapts to it,
and infrastructure remains replaceable.
Layering, adapters, facades, persistence strategies — all of these are valid only insofar as they serve the domain model.
When this relationship is inverted, the domain becomes a passenger in its own system.
The Emperor’s New Clothes: Why Frameworks Are Not Architecture
Standard industry practice often confuses the wrapper with the contents. Frameworks like Spring are frequently cited as the "architecture" of a system, but this is a category error. If you need a database, you use PostgreSQL. If you need an ORM, you use Hibernate. If you need a web server, you use Jetty. Spring is none of these. It is a wrapper. It is a complex mechanism for gluing these actual infrastructure components together.
When we view this through First Principles, we must ask: What essential problem does the framework solve?
Does it solve a business rule? No.
Does it store data? No.
Does it process network requests? No (the embedded server does).
It primarily solves "Dependency Injection" and "Lifecycle Management"—problems that largely cease to exist in a properly designed Object-Oriented system.
Dependency Wiring: Is solved natively by the constructor.
new Person(name)is explicit, type-safe, and compilation-verified.@Autowiredis implicit, magical, and runtime-fragile.Lifecycle Management: Is solved by explicit interaction boundaries.
Configuration: Is solved by externalizing database credentials and storing behavioral toggles in the database itself.
The Cost of "Default" Engineering
Because the framework provides templates (Controller, Service, Repository), engineers fill them in without reasoning about the domain.
Logic lives in Services (Procedural programming).
Data lives in Entities (Anemic structures).
Context is lost in wiring.
The result is that 99% of "modern" enterprise applications are actually procedural transaction scripts wrapped in heavy object-oriented syntax. The domain model—the only place where essential complexity can be managed—is hollowed out. The framework adds layers (Service, Repo, Entity) without Business Cause. By definition, layering without business cause is accidental complexity.
We are fitting the problem to the tool, accepting a massive footprint of accidental complexity (the framework) to avoid the "difficulty" of writing standard object constructors.
Why This Matters More in Software Than Anywhere Else
Because software systems are singletons, architectural mistakes do not announce themselves clearly. They manifest as:
slower change,
higher coordination cost,
fragile deployments,
growing cognitive load.
These symptoms are normalized over time.
Only a first-principles approach — grounded in a rich domain model — provides an internal reference point against which architectural decisions can be evaluated.
Conclusion
Engineering does not start with tools. It starts with understanding.
In software, that understanding is captured in the domain model. It is where essential complexity lives, where responsibility is clarified, and where architecture gains meaning.
Fred Brooks taught us that accidental complexity must be minimized. First-principles engineering tells us how.
Everything else — frameworks, layers, conventions — is optional.
And anything optional should never be allowed to define the system.
Top comments (0)