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
- 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.
- 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.
- 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/