🕐 Reading time: 6 minutes
Tomorrow I'll submit my last production release request on DetectION, the anti-money-laundering project I've worked on for almost three years. I laid the first bricks at the beginning of 2024, and the first release of the core module, the BFF, dates back to March of that year. Today that module is at version 3.15.1, with about 800 classes and 1,300 tests – roughly 1,100 unit and 200 integration tests. Widening the view to the other modules in production, we pass 1,000 classes and reach about 1,800 tests: in total, about 41,000 lines of application code and another 37,000 of tests, excluding comments and blank lines.
I'm biased, I admit it. But I'm genuinely happy with the code I'm leaving behind, and before turning the page I wanted to share a couple of choices that, looking back, have paid off. Without going too deep into the domain: we're talking about form, not content. Then things change: a new role in Data Architecture is waiting for me, a more horizontal world that cuts across products, with new tools, languages and technologies to explore.
📌 The core idea: a matrix
The BFF (Backend For Frontend) is fully reactive – Spring WebFlux and R2DBC, from the endpoint all the way down to the database without a single blocking call. To keep it tidy, I pictured it as a matrix:
- vertical axis = the domains, i.e. the product's business areas (anomaly, report, subject, user…)
- horizontal axis = the layers, i.e. the responsibilities: who authorises, who orchestrates the logic, who converts, who talks to the database or to external services
A class's name is its coordinate in the matrix: AnomalyController, AnomalyService, AnomalyServiceImpl, AnomalyRepository, AnomalyConverter. If you know the domain and the layer, you already know what the class is called and which package to find it in. It sounds trivial, but on a project this size it's the difference between searching for the code and knowing where it is.
📌 One layer, one responsibility
I had already touched on layers in pill #10, talking about the data-model migration. Here the rule is simple: each layer does one thing only and knows only the one below it.
request ⇄ controller ⇄ [converter] ⇄ service ⇄ [converter] ⇄ serviceImpl ⇄ repository / external clients
↓
security
The request goes down and the response comes back up, except for security: the controller queries it first, to authenticate and authorise the user, and from there the chain moves on to the service. Converters appear twice: between serviceImpl and service they translate the objects coming from external sources into the business model; between service and controller they translate the model into the DTOs exposed to the frontend (and the other way round, for inputs). Two rules must never be broken: the controller contains no logic, and the business service never talks directly to the database or to external clients – it always goes through the serviceImpl.
The latter is perhaps the most underrated layer: it decouples the business logic from external sources, decides which one to query, logs inputs and execution times, and handles exceptions when a system is unavailable. It's where a technical error becomes a readable application error: instead of an anonymous R2DBC stack trace, an error code that tells you which operation was running and with which input.
Everything is written in a functional style, adopted systematically across the whole project: converters are Functions and BiFunctions, and they slot straight into the .map() and the various .zip calls of the reactive pipelines, with no wrapping lambdas. The functional paradigm and WebFlux go very well together, and they're what keeps even the longest pipelines readable.
📌 A library born along the way
Working on DetectION, I realised that a lot of logic – structured logging, exception handling, conversions, a small engine to run SQL queries written in real .sql files and map them onto objects – had nothing product-specific about it. It was useful to anyone working with WebFlux, and there was no library on the market that brought it all together. So I gathered it into Reactive Toolkit, a library I wrote and grew alongside the project, shared across the modules. Being completely domain-agnostic, it could potentially even become open source. Fun fact: it's the same library that orchestrates part of this site's backend.
📌 Sonar: a tool, not a judge
In DetectION, Sonar works on two levels:
1. in the IDE, with SonarQube for IDE, as daily hygiene: the warning shows up while you type, when fixing it costs a few seconds. In the project's changelog, the line «fixed some Sonar issues» appears almost ritually, sprint after sprint – and in one sprint in particular we closed more than a hundred in one go.
2. in the pipeline, with SonarQube: every build also runs the analysis and updates the company's code-quality dashboard, the one in the screenshot below.
The result, as of today, on the core module: A on Security, Reliability, Maintainability and Security Hotspots Reviewed, zero vulnerabilities and zero reliability issues. The only nine maintainability issues are false positives related to the use of a builder – leave those out and the count is zero. Coverage at 91.2% and duplicated code at 0.5%. Reactive Toolkit, smaller (about 1,700 lines of code), is all A, with coverage at 94.8% and zero duplication.
An important choice behind that 91.2%: we excluded DTOs and models from the coverage calculation, unless they contain logic. A getter generated by Lombok has nothing to test, and counting it only inflates the percentage. Better a lower but real number, one that actually measures the logic – a topic I had already touched on in pill #23 about the illusion of coverage.
Then there are the security issues. It's a topic that has never gone out of fashion, but it's back in the spotlight today, now that there's talk of AI capable of finding and exploiting a system's vulnerabilities. The same AI, though, can also work for the right side: Sonar spots the problem and AI, if well guided, helps fix it. We recently closed two: a temporary file path built too trustingly from user input, and an encryption scheme that guaranteed confidentiality but not integrity – an encrypted message could be tampered with without anyone noticing. I'm no cybersecurity expert, but that's exactly the point: Sonar doesn't just tell you something is wrong, it helps you understand why.
Sonar isn't a judge to be pleased at the end of a sprint: it's a very good tool that keeps re-reading your code. Sometimes it's wrong, but we appreciate it all the same – a rule can be disabled at a specific spot, where it makes no sense, while staying active elsewhere, as long as it's a conscious decision and not a shortcut.
📌 One last thing: documentation
When you know you're leaving a project, you realise how much knowledge lives only in your head. Over the last few weeks I've written six guides: project bootstrap, the matrix architecture, repositories, the error catalogue, the XBRL validator and the integration-test setup – for those who can use Docker and those who can't. Code should be as self-explanatory as possible, but that's not enough: it tells you what it does, rarely why it was chosen to do it that way.
Thanks to everyone who shared these years with me. DetectION is in excellent hands! 🙏
See you at the next pill! ☕
