A payments platform has one Payment entity used by checkout, settlement, refunds, reconciliation and reporting, and it has grown to eighty fields. What is the problem and what is the fix?
Show the full answer Hide the answer
The problem
One model is serving several contexts that mean different things by "payment".
To checkout it is an authorisation attempt with a customer, an amount and a method. To settlement it is an obligation to move money between parties with fees and a settlement date. To reconciliation it is a record to be matched against a provider statement. To reporting it is a row with dimensions.
A single class satisfying all of them has the union of every field, most of which are irrelevant or meaningless in any given context — and the fields that are meaningless are where the bugs live, because somebody eventually populates one.
The fix
A model per bounded context, with an explicit translation between them. Checkout's payment, settlement's payment and reconciliation's payment are different types sharing an identifier.
That sounds like duplication and it is not: they encode different invariants. Checkout enforces that an amount is positive and a method is valid; settlement enforces that debits equal credits; reconciliation enforces that a match is one-to-one. Merging them means the union of invariants applies everywhere, which is either wrong or unenforced.
What the translation looks like
Events, usually. Checkout publishes "payment authorised"; settlement consumes it and creates its own record with its own lifecycle. The consuming context owns its interpretation, which is what allows each to evolve independently.
The crucial property is that no context reaches into another's model. Sharing the class across contexts, even by import, recreates the coupling with extra ceremony.
Where the boundaries actually are
Found through change history and through language, not through modelling exercises. Where two teams use the same word differently, there is a context boundary. Where two parts of the system have never needed to change together, there is a candidate boundary.
The eighty-field entity is itself the evidence: each cluster of fields that only one part of the system reads is a context asking to be separated.