Gabriel Schenker

Gabriel Schenker

The fallacy of abstractions

The fallacy of abstractions

I was reading a recent post of Martin Dilger where he discusses the fallacy of abstractions and it made me think about our domain, Life & Health and the reality as I see it every day.

As developers, by habit or training, we tend to look for patterns and generalize — to avoid duplication and to "future-proof" the system. So when we encounter something like credit card payments, we think:

Hmm, maybe we'll have PayPal, direct debit, or crypto later. Let's design a generic Payment entity with a type field and polymorphic handling.

At first glance, that sounds architecturally elegant — but in business terms, it's often misguided.

The Problem

  1. We're abstracting before understanding the domain:

Business processes — especially in areas like L&H insurance — are not just variations of a type.

Each payment method, underwriting scenario, or policy type comes with:

  • Different regulations
  • Different data structures
  • Different error and reconciliation rules
  • Different operational flows

By introducing a generic abstraction, we flatten these domain differences, losing critical meaning.

  1. It breaks the alignment with business language

In Domain-Driven Design (DDD) terms: Instead of speaking in the business language ("Premium collection via direct debit," "One-time credit card charge for policy activation"), the codebase ends up speaking in technical generalizations ("Payment.Type = CREDIT_CARD").

This erodes the ubiquitous language and makes collaboration between business experts and engineers harder.

  1. It adds unnecessary complexity

That abstraction layer rarely pays off. When the next payment type arrives, the new logic doesn't quite fit the old abstraction — so we developers add exceptions, flags, or branching behavior. Over time, the abstraction collapses under its own weight.

A Better Approach

Instead of asking,

"How can we make this generic?" we should ask, "What's stable in the domain, and what actually varies?"

In the payments example:

  • The concept of collecting a premium is stable.
  • The means of collecting it (credit card, direct debit, SEPA, etc.) may vary in process, not in type.

So the model should reflect that distinction:

  • Define a PremiumCollection domain concept.
  • Then model specific processes as separate flows or bounded contexts: (CreditCardCollection, SepaDirectDebitCollection, BacsDirectDebitCollection, etc.)

Each can evolve independently, and abstraction can emerge later — organically — if it truly makes sense.

How This Shows Up in L&H Insurance

We can see the same pattern in:

  • Underwriting – we developers want a "GenericUnderwritingEngine" for all products, but each product (term life, whole of life, disability, etc.) has unique logic.
  • Policy Servicing – we developers want a "GenericChangeRequest" or "Mid Term Adjustment" (MTA) object, but name changes, beneficiary updates, and coverage increases have different rules and audit needs.
  • Claims – we developers create a "GenericClaim" instead of modelling the nuanced workflows per product line.

Each abstraction loses domain specificity — and eventually needs to be undone or worked around when business reality pushes back.

🎯 Key Takeaway

Don't abstract business processes — model them.

Good design emerges from deep domain understanding, not from anticipating future variability that might never happen.

The right level of abstraction becomes clear after you've seen a few concrete cases.

Or as Kent Beck said:

"Make it work, make it right, make it fast — and only then, make it abstract."

In the next post I will discuss how #EventModeling can help us to do a better job and avoid harmful abstractions but expose the uniqueness of each business process.

Originally published on LinkedIn on 13 Nov 2025: https://www.linkedin.com/pulse/fallacy-abstractions-gabriel-n-schenker-qtx0f/