Breaking up a document-checking monolith
Splitting a long-lived Python and Java monolith into services along the seams that already existed, one extraction at a time, with the monolith still serving traffic throughout.
- Role
- Backend developer
- Client
- Credit-risk and fraud-prevention firm
- Year
- 2022
A document-checking and credit-risk system that had been added to steadily for several years. Nothing about it was badly written; it had simply accumulated enough responsibilities that a change to one part meant redeploying all of it, and a slow report could hold up an ingest.
Splitting along seams that already existed
The useful part of the work was not the extraction, it was deciding where to cut. The codebase already had informal boundaries — ingest, checking, scoring, reporting — visible mostly in which tables each area touched. We took those as the starting point and moved one at a time, beginning with the least entangled, keeping the monolith in front of traffic and having it call the new service rather than switching clients over on day one.
Each service got an HTTP interface agreed before implementation, versioned in a small shared spec repository so that the consuming side could be written against it in parallel. That was the piece I would keep in any future version of this: writing the contract down first made the disagreements happen in a review rather than in integration.
@RestController
@RequestMapping("/api/v1/documents")
class DocumentCheckController {
private final DocumentCheckService checks;
DocumentCheckController(DocumentCheckService checks) {
this.checks = checks;
}
@PostMapping("/{reference}/checks")
ResponseEntity<CheckResult> run(@PathVariable String reference,
@Valid @RequestBody CheckRequest request) {
return checks.find(reference)
.map(document -> ResponseEntity.ok(checks.run(document, request)))
.orElseGet(() -> ResponseEntity.notFound().build());
}
}
- Java and Spring Boot for the request-serving services, built with Maven
- Python for the scraping, parsing and data-manipulation work feeding business analysis
- Postgres throughout, with each service owning its own tables rather than sharing a schema
- Shared API definitions reviewed before either side was written
The scraping and parsing side stayed in Python, which suited it: the input formats changed often enough that the ability to fix a parser quickly mattered more than anything else. I also spent a fair amount of this period bringing new joiners up to speed on the codebase, which is a decent test of whether the boundaries you have drawn are real ones.