How to Build Long-Living Software Systems

Published: (December 14, 2025 at 12:56 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Most software systems do not fail because of bad technology choices.
They fail because they stop learning. Frameworks age, architectures fall in and out of fashion, and teams change. The fatal problem is when a system’s internal understanding of the business slowly diverges from reality—until even small changes become risky, expensive, and unpredictable.

Why Systems Fail

When the domain model no longer reflects the business, rewrites, new architectures, and fresh frameworks are proposed, but the underlying issue remains technical‑agnostic. A long‑living system is not one that stays unchanged; it is one that can evolve continuously and incrementally without destabilizing itself.

Characteristics of Long‑Living Systems

  • New functionality can usually be added locally.
  • Existing behavior rarely breaks when features are introduced.
  • Developers reason about the system in domain terms, not technical ones.
  • Large rewrites are unnecessary, even after years of evolution.
  • The codebase reflects how the business actually operates and “speaks” the business language.

These properties emerge only when the conceptual model evolves at the same pace as the business. When evolution stops, conceptual tension accumulates, responsibilities no longer align cleanly, and incremental change becomes impossible.

User Stories as Domain Validation

A user story is a flat depiction of a slice of functionality the system should support—it captures what is desired, not how responsibilities should be structured internally. Treating stories as mere work orders erodes the domain model.

Questions to Ask Before Implementation

  1. How does this story fit the current domain model?
  2. Which existing objects, by their original definition, should participate?
  3. Does this story reveal a missing concept or responsibility?
  4. Do existing responsibility boundaries still hold?
  • If the model absorbs the story naturally, implementation is straightforward.
  • If it does not, the friction is a modeling signal, not an implementation problem. Skipping this step shifts responsibility decisions into services, workflows, and orchestration code, where they silently undermine the model. Over time, the domain model ceases to govern behavior and degrades into a passive data schema.

“Model First” Principles

“Model first” does not mean designing everything up front or abstracting from reality. It means:

  • The domain model is the primary artifact.
  • Implementation exists to serve the model.
  • Responsibility boundaries drive code structure.
  • Technical convenience never overrides conceptual correctness.

Frameworks, libraries, and architectures should be interchangeable over time; the domain model that accurately reflects the business should not be.

The Danger of Anemic Domain Models

An anemic domain model is often described as “entities without logic,” but this definition is incomplete. A domain model is a structure of responsibilities; state is a consequence of responsibility, not its definition. An anemic model emerges when responsibility is displaced from the model into other parts of the code.

You can have logic in objects and still have an anemic model if:

  • Decisions are made outside objects that own the relevant responsibility, regardless of state.
  • Logic is implemented outside objects that define the conceptual boundary, even when those objects exist in the model.
  • Invariants are enforced procedurally (through workflows or services) rather than structurally (through the model).
  • Domain objects become mere coordination artifacts, while meaningful behavior is pushed into external orchestrators.

Example: Domain Interaction Object

A domain interaction object may define:

  • When and how the domain is entered.
  • Which consistency or transactional scope applies.
  • Which invariants must hold across the interaction.

It may have little or no persistent state, yet it owns critical responsibilities. If transaction management or consistency boundaries are introduced outside that interaction (e.g., at the service or infrastructure level), the object loses its meaning even though it still exists.

Framework Influence – The Spring Boot Example

Frameworks exert strong gravitational forces on system shape, especially in enterprise environments. Choosing Spring Boot, for instance, commits a team to:

  • A yearly framework update cycle.
  • Ongoing licensing or support costs.

This forced churn consumes engineering attention regardless of whether the business domain itself has changed. Even in stable domains, teams must continually:

  • Adapt to framework evolution.
  • Address deprecations.
  • Re‑validate infrastructure concerns.

Spring’s defaults and ecosystem strongly incentivize a procedural, service‑centric design:

  • Stateless services.
  • Dependency‑injected orchestration.
  • Transactional workflows.
  • Passive domain objects.

The result is fragmented responsibility and anemic models.

Transaction Management as a Domain Concern

Transactions are often treated as a purely technical concern—something to configure or annotate at the service level. In reality, a transaction defines a business‑level consistency boundary and answers questions such as:

  • What constitutes a meaningful unit of work?
  • Which changes must succeed or fail together?
  • When may intermediate state be observed?
  • What does “all‑or‑nothing” actually mean in this domain?

When transaction boundaries are introduced outside the objects that define the domain interaction, the model loses ownership of a core responsibility. The system may remain technically correct, but it becomes conceptually unstable.

Continuous Modeling Improves Delivery Speed

There is a persistent belief that continuous modeling slows delivery. In practice, the opposite is often true. Teams that deliberately practice evolutionary domain development—treating each story as a modeling opportunity—tend to deliver functionality significantly faster.

By keeping the domain model aligned with the business, teams reduce accidental complexity, avoid large rewrites, and maintain a system that can change safely and incrementally.

Back to Blog

Related posts

Read more »