Procore · 2019 – 2020
Payments that don't care who processes them
Designed a processor-agnostic payments architecture inside a large Rails monolith, and the service-module patterns that let teams work in it independently.
SanitizedThe situation
Payments needed to work across more than one processor, and the existing code assumed a single one throughout — not in a single integration layer, but distributed through the domain as assumptions about how payments behave.
It all lived inside a large monolith that many teams worked in simultaneously.
The constraints
- A monolith with many contributors is a social constraint as much as a technical one. An abstraction nobody wants to use is not an abstraction.
- Payments could not be taken offline for restructuring.
The call
Define the payment domain in terms of what the business does — authorize, capture, refund, reconcile — and treat each processor as an adapter, rather than abstracting over the union of two processors' APIs. Abstracting over vendor surface area produces a lowest-common-denominator interface that fits no one.
Introduce service-module patterns inside the monolith rather than extracting a service. The problem was ownership boundaries, not deployment boundaries, and extraction would have paid a large distributed-systems cost to solve an organisational one.
What happened
Payments became processor-agnostic at the domain level, with processor specifics isolated in adapters.
The service-module patterns outlived the payments work and gave teams a way to own areas of the monolith with real boundaries — improving autonomy and maintainability without a migration.
What I'd do differently
I under-invested in documenting why the module boundaries fell where they did. The patterns spread, but the reasoning did not travel with them, and later boundaries were sometimes drawn by imitation rather than by the same logic.