DEV Community

Cover image for The Two Levels of Software Development — And Why Most Enterprise Applications Fail Over Time
Leon Pennings
Leon Pennings

Posted on • Originally published at blog.leonpennings.com

The Two Levels of Software Development — And Why Most Enterprise Applications Fail Over Time

There are two fundamentally different levels in software development.

Level 1 — Getting the system to run

At this level the goal is straightforward:

  • the application compiles

  • the system deploys

  • features behave as expected

  • users can operate the software

If those conditions are met, the project is considered successful.

Most modern frameworks are extremely good at helping teams achieve this level. A stack such as Spring Framework provides a ready-made structure for building applications quickly: web infrastructure, dependency injection, persistence integration, configuration management, and more. With the right templates and tooling, teams can produce a working system in relatively little time.

Level 2 — Keeping the system economically evolvable

The second level is far harder.

Once the system has been running for several years, the real question becomes:

  • Can developers still reason about the business rules?

  • Can features be added without breaking existing behavior?

  • Does the cost of change remain predictable?

This is the level where software must remain economically viable. The system must evolve along with the business without collapsing under its own complexity.

Most of the industry focuses almost entirely on Level 1, because Level 2 is much harder to see.


The Observability Problem

In many engineering disciplines, different designs can be compared directly.

Two airplane designs can be tested against each other. Two bridge designs can be analyzed under the same load conditions. Engineers can evaluate alternatives objectively.

Enterprise software is different.

Most systems are unique implementations of a specific business domain. A logistics system for one company will not be rebuilt several times with different architectures just to compare which approach works best.

Because of that, organizations rarely observe multiple competing implementations of the same domain.

There is no side-by-side comparison like:

System A — rich domain model
System B — procedural service architecture
Enter fullscreen mode Exit fullscreen mode

running the same business processes for several years.

Instead there is only one system: the one that was built.

As a result, success is evaluated using the only clearly visible metric:

Does the application run?
Enter fullscreen mode Exit fullscreen mode

If the answer is yes, the architecture is usually considered successful.


The Rise of the Software Factory

Over time, the industry optimized for what was easiest to measure: producing working applications quickly.

This led to the emergence of standardized software production lines.

Typical enterprise stacks often follow a very familiar structure:

controller
service
repository
database
Enter fullscreen mode Exit fullscreen mode

Add REST APIs, containerization, messaging infrastructure, and often microservices, and a predictable pattern emerges.

Framework ecosystems reinforce this pattern. They provide conventions, templates, and project generators that make it easy to spin up new services quickly.

From an organizational perspective, this approach promises several advantages:

  • developers can move between projects easily

  • teams can scale rapidly

  • systems follow familiar patterns

  • onboarding new engineers becomes simpler

These promises are appealing, especially to organizations managing large engineering teams.

However, these advantages are rarely tested against alternative architectural approaches. Because most enterprise systems are built only once, there is no direct comparison showing whether a different design would actually have been more efficient or easier to evolve.

As a result, the perceived success of factory-style development often rests on a simple observation:

  • The application runs

  • Features are delivered

Without a comparable implementation built around a different architectural philosophy, it is difficult to see whether the chosen approach truly delivered its promised benefits.

The result is a development process that resembles a software factory: a standardized production line designed to produce working applications quickly.

Whether it produces the right kind of system for the domain is a different question entirely.


The Model T Problem

The logic behind this standardization is similar to the philosophy associated with Henry Ford and the Ford Model T.

The Model T revolutionized manufacturing through standardization. One of the famous ideas attributed to Ford was that customers could choose any color, as long as it was black.

This approach worked because the product itself was standardized.

Cars were produced for a broad market with relatively similar requirements.

Enterprise software is fundamentally different.

Each system represents a specific business domain:

  • logistics operations

  • insurance policies

  • trading platforms

  • healthcare workflows

These domains have very different requirements and behaviors.

In effect, each enterprise application needs a different type of vehicle.

Some domains resemble heavy trucks carrying complex transactional logic. Others behave more like high-performance machines, where performance and precision matter enormously. Some systems are small and lightweight.

Yet many development ecosystems attempt to solve all of them with the same architectural pattern — the equivalent of producing a Model T for every possible use case.


Why This Appears to Work

Despite the mismatch, the Model T architecture still appears successful.

After all, a Model T can still move forward. It can transport people and even carry small loads.

Similarly, standardized enterprise architectures can deliver features:

  • endpoints respond to requests

  • data is stored and retrieved

  • workflows execute

From the outside, the application works.

Because organizations rarely build the same system twice with different architectures, they never see a direct comparison. There is no competing design demonstrating that the system could have been far simpler or easier to evolve.

As long as the application runs and delivers features, the architecture appears to perform as expected.


The Hidden Cost of Factory Style Development

The real cost of factory-style architectures emerges gradually and usually in two dimensions: effort and functional quality.

Effort: Why development gets slower over time

In factory-style systems, implementing new functionality tends to require roughly the same effort every time.

Every feature follows the same pattern:

controller
service
repository
integration logic
Enter fullscreen mode Exit fullscreen mode

Because the architecture is primarily procedural, the system rarely accumulates reusable domain behavior. Each feature often introduces new service logic rather than building upon existing concepts.

As the system grows, the situation frequently worsens:

  • similar logic appears in multiple services

  • developers must read many parts of the system to understand behavior

  • debugging requires tracing through multiple layers and integrations

  • knowledge transfer becomes difficult

The effort required to implement new functionality often remains constant or even increases over time.

Function-driven systems behave very differently.

When a system evolves around a coherent domain model, the model itself becomes a growing knowledge base of the business. Domain objects accumulate responsibilities and reusable behavior.

As the model matures:

  • new features often extend existing objects

  • behavior is reused rather than reimplemented

  • developers can understand the system by understanding the model

Knowledge transfer becomes easier because the model tells the story of the application.

Debugging is also simpler. When rules live in their responsible objects, it becomes immediately clear where behavior originates. Developers do not need to search across multiple services implementing slightly different versions of the same logic.

Over time, the effort required to add functionality tends to decrease, because the model provides increasing leverage.

Quality: One version of the truth

Factory-style architectures often distribute business rules across multiple services.

It is common to find logic that is similar but not identical in different places:

  • slightly different validation rules

  • small variations in calculations

  • edge cases handled in one service but not another

These inconsistencies are rarely intentional. They appear gradually as new features are implemented independently.

The result is a system with multiple interpretations of the same business rule.

Function-driven systems address this differently.

Each business rule belongs to the object responsible for that concept. The rule has one canonical implementation.

If a system contains an Order concept, the logic related to orders lives with the Order object. If there is pricing logic, it belongs to the pricing model.

This creates a single version of the truth.

Rules are not scattered across services or hidden inside orchestration layers. They are located where the business concept itself lives.

This greatly reduces contradictions and makes the system far easier to reason about.


The Engineering Principle

Enterprise systems should be function-driven rather than tool-driven.

Architecture should begin with understanding the domain:

  • the concepts involved

  • the relationships between them

  • the rules that must remain consistent

Only after that understanding emerges should tools and frameworks be introduced to support the system.

Tools are valuable when they solve real problems in the running application:

  • persistence

  • messaging

  • scaling

  • reliability

But they should not dictate the structure of the domain model.


Why Rich Domain Models Help

A function-driven system begins with the domain model, not with architectural patterns or infrastructure.

Instead of starting with decisions like:

  • microservices

  • event-driven architecture

  • CQRS

  • messaging platforms

development begins with understanding the domain itself.

The first goal is to model the core concepts and their responsibilities.

Typical objects might represent concepts such as:

  • Order

  • Customer

  • Shipment

  • Invoice

These objects contain the behavior that defines the business logic.

At this stage, the focus is entirely on implementing the core functionality of the application.

Infrastructure concerns are introduced only when they become necessary.

For example:

  • persistence is added when data must be stored

  • messaging appears when asynchronous coordination is required

  • scaling mechanisms appear when load actually demands them

In practice, many enterprise systems never reach the scale that requires complex distributed architectures.

For the majority of applications, a well-designed domain model within a cohesive system is entirely sufficient.

Only when real operational constraints appear should the architecture evolve technically.

This approach keeps the system aligned with the domain while avoiding premature technical complexity.

The result is software that grows organically around the business model, rather than being constrained by predefined architectural templates.


Closing Thought

TThe software industry has become extremely good at producing applications that run.

Frameworks, templates, and standardized stacks make it possible to build complex systems faster than ever before.

But enterprise software is not a short-term product. It is a long-lived system that must evolve together with the business.

Designing such systems requires something different from a software factory. It requires starting with the domain, building a coherent model, and letting the architecture grow from the problem instead of from the tools.

Otherwise we keep producing the same solution for every problem:

another black Model T.

The problem with that approach is not aesthetic. It is economic.

When the architecture does not match the domain, the mismatch shows up in three places:

  • more engineering effort to implement and understand functionality

  • higher long-term costs as development slows and operational complexity increases

  • more fragile systems where business rules are scattered and difficult to reason about

In other words, the wrong architectural vehicle does not merely look inelegant — it makes the system harder and more expensive to operate for the rest of its lifetime.

Long-lived enterprise software requires something better than a one-size-fits-all production line.

It requires architectures that are designed around the domain they serve.

Top comments (0)