// Case study · Platform / architecture
Platform component framework
The shared libraries and contracts a payment platform's dozen services are built on — a typed object-mapper, domain primitives, query/criteria and versioned service contracts — so everything speaks the same language.
The problem
A payment platform isn't one service — it's many (gateway, processing, directory, facade, webhooks…). Without shared foundations each one reinvents DTOs, mapping, money handling and provider contracts, and they drift apart — every integration point becomes a translation bug waiting, with real money behind it.
The approach
I built and owned a set of shared components: a typed AutoMapper for DTO ↔ domain mapping, domain primitives (money, ids, value objects), a criteria / query abstraction, and versioned contracts between services (gate, processing, connector, directory). Services depend on the contracts, not on each other's internals.
The AutoMapper
The mapper is the interesting one: it handles nested DTO collections and maps, lazy copying, and custom operations (map from a source property, from a container / collection), with bounded caches and cycle detection so mapping deep, self-referential payment objects stays fast and safe.
The decision & trade-off
Investing in shared libraries and hard contracts slows the very first feature — you build the foundation before the house. But it made every later service cheaper, consistent and safe to change: a contract bump is a visible, versioned event, not a silent break in production.
Architecture
A layered set of packages — primitives → mapping / criteria → service contracts (gate / processing / connector / directory) → SDK bundles — that every service composes. Change flows through versioned contracts and generated mappings, not copy-paste.
Outcome
A dozen services on one shared vocabulary — DTOs, money, mapping and contracts that behave the same everywhere, so cross-service changes are boring instead of dangerous.
What I'd do differently
Codegen more of the contract and mapping boilerplate from the start — the AutoMapper's generated-mapping caching arrived later than it should have.
Architecture diagram
Stack
- PHP
- Symfony
- DDD
- CQRS
Have a similar problem?
Tell me about it — I reply within a day.