Latest / Tech Leadership with Fexingo: Engineering Managers, CTOs, and Technical Leadership Conversations / How One CTO Uses Architecture Fitness Functions to Prevent Drift
Transcript
- Lucas: You know that moment when you're reviewing a pull request and you see a service reaching directly into another service's database — and you think, 'we agreed never to do that' — but somewhere along the way, someone made a pragmatic shortcut and now it's in production? Luna: And then six months later you're untangling a data consistency bug that traces back to that one shortcut. Yeah, I've been there. Lucas: Right. So today I want to talk about architecture fitness functions — automated, testable guardrails that catch that kind of drift the moment it happens, not during a quarterly architecture review when it's already too late. Luna: This is something I've seen a few teams adopt, but it's still surprisingly rare. What's a concrete example? Lucas: Let's use a mid-stage fintech — about 40 engineers — that decided to adopt a hexagonal architecture. They defined clear boundaries: domain logic in the core, adapters for infrastructure, and strict dependency rules — core can't depend on adapters, adapters can depend on core but not on each other. The usual stuff. Luna: And the team agreed on it, but then a new feature came in, a deadline hit, and someone imported a database driver directly into the domain layer. Lucas: Exactly. And the code review missed it because the reviewer was focused on business logic. So their CTO — let's call him Raj — decided to codify those rules as automated tests. He used a library called ArchUnit for Java, which lets you write assertions like 'classes in the domain package should only depend on classes in the domain package'. Luna: So it's basically unit tests for your architecture. Lucas: Exactly. They wrote about 20 fitness functions in total. Things like: 'no class in the adapter layer should be called from the core layer', 'all public methods in the core must have an interface', 'no cyclic dependencies between modules'. Then they wired these into their CI pipeline — every pull request runs the full suite. If a change violates a fitness function, the build fails. Luna: And does it actually work? I mean, I can imagine teams just overriding or deleting the tests when they're in a rush. Lucas: That's the key question. Raj made a deliberate choice: the fitness function tests were owned by the architecture guild, not individual teams. To modify or skip one, you needed a pull request that went through the guild. And they also made the test suite visible on a dashboard — the team could see exactly which constraints were enforced. Luna: So it's not just a gate, it's also a communication tool. New hires can look at the fitness functions and understand the architecture rules without reading a document. Lucas: That's exactly how Raj described it. He said the fitness functions became the living architecture documentation. The rules were no longer in a wiki that rots — they were executable code. And because they were version-controlled, you could trace when a rule was added or modified, and why. Luna: I love that. But what about higher-level constraints? Like, 'we shouldn't have more than three services talking to the same database' — can fitness functions handle that? Lucas: They can, but you need tools that go beyond static analysis. Raj's team eventually added a fitness function that scanned their runtime dependency graph — using OpenTelemetry traces — and flagged any database that had more than three distinct service callers. That one actually caught a problem where a new team had inadvertently connected to an existing database because it was convenient. Luna: So the fitness function concept scales from compile-time checks to runtime enforcement. That's powerful. Lucas: Right. And the beauty is that you can start small. You don't need a grand architecture blueprint. Pick the one rule that your team violates most often — maybe it's 'no direct database access from the controller layer' — and write a fitness function for it this week. See if it changes behavior. Luna: I've actually tried something similar with a team I advised. We wrote a fitness function that enforced that every public API endpoint had a corresponding integration test. The number of untested endpoints dropped from about 20 percent to near zero in two sprints. Lucas: Nice. That's a perfect example of a non-architecture but still structural fitness function. The term originally comes from a 2017 book by Rebecca Parsons and her ThoughtWorks colleagues — 'Building Evolutionary Architectures'. They define fitness functions as any mechanism that provides an objective integrity assessment of some architectural characteristic. Luna: So it's a broad concept. Latency budgets, security scanning thresholds — those are fitness functions too. Lucas: Exactly. But I think the most impactful ones are the simple, binary rules. Because they're easy to write, easy to understand, and they create a shared language. When a developer sees 'BUILD FAILED: FitnessFunction_DomainLayerNoInfrastructureDependency', they know exactly what they did wrong and why it matters. Luna: And it removes the blame. It's not a person rejecting your code — it's the system reflecting the team's agreed-upon principles. Lucas: That's huge for team culture. Raj said after they introduced fitness functions, architecture discussions became less about policing and more about evolving the rules. People would propose new fitness functions in the same way they'd propose a new feature. Luna: But there's a risk, right? Too many fitness functions and you strangle developer velocity. Every change triggers five different guardrails. Lucas: Absolutely. Raj had a rule of thumb: no more than 30 fitness functions total, and each one must be justified by a historical incident. If you can't point to a time when violating this rule caused real pain, don't automate it. He also reviewed the fitness function suite every quarter and dropped rules that were no longer relevant. Luna: That keeps it lean. And it forces the team to be honest about what actually matters. Lucas: One more thing I want to highlight: fitness functions can also be aspirational. You can write a rule that says 'all new services must use OpenTelemetry for tracing' — even if existing services don't. Then over time, as teams refactor, they bring old services into compliance. The fitness function acts like a migration tracker. Luna: That's smart. It's like a to-do list that enforces itself. Lucas: And it surfaces progress. Raj's team had a dashboard showing which services were compliant with each fitness function. They celebrated when a legacy service finally cleared a rule. It gave the architecture work visibility and momentum. Luna: Okay, I'm sold. But if someone listening wants to start tomorrow, what's the single first step? Lucas: Write one fitness function. Identify the architectural rule your team breaks most often that has caused an incident in the past. Use a static analysis tool like ArchUnit for Java, or for other languages, check out tools like TsArch for TypeScript, or even simple regex checks in your CI script. Start with that one rule, and let the team see the feedback cycle in action. Luna: And when the build fails for the first time, resist the urge to disable the test. Instead, have a conversation about whether the rule still makes sense. That's where the real value comes from. Lucas: Exactly. The fitness function isn't the end goal — it's the catalyst for a better architectural conversation. And that conversation is what prevents drift. Luna: You know, this kind of episode is exactly why people tell us they support the show. A handful of listeners chip in monthly through buy me a coffee dot com slash fexingo, and that's what keeps us ad-free and focused on practical stuff like this. Lucas: Yeah, it's a small group, but it makes a real difference. It means we can spend time digging into concrete examples rather than chasing headlines. Luna: Anyway — back to fitness functions. One thing I want to circle back on: how do you handle false positives? A rule that's too broad could flag legitimate changes. Lucas: Great question. Raj handled that by having a 'suppression mechanism' — a file where you could annotate a specific exemption with a reason and an expiry date. That way, if a legitimate case came up, it was documented and temporary. The fitness function dashboard would show active suppressions, so the guild could review them periodically. Luna: So it's not a rigid gate — it's a flexible contract that bends intentionally. Lucas: Exactly. And that flexibility is what keeps the team from resenting the system. They know there's a process for exceptions, but it's visible and temporary. The default is compliance, and deviation requires justification. Luna: Alright, I'm going to write a fitness function this week. Maybe for our internal dashboard — 'no hardcoded API keys in code'. Lucas: Perfect. And let us know how it goes. That's the kind of small win that builds momentum.