Refactoring an approval system without publishing the approval hierarchy
A real approval-routing refactor taught me that the hard part is not adding another rule. It is making the decision path understandable when configuration changes, fallbacks fire, and the “obvious” fix still does not explain the result.
Where the project started
The original goal sounded straightforward: make an approval process reflect the organization more accurately and make the changing business values easier to maintain. The existing implementation had grown over time, so routing behavior was spread across custom logic, workflow behavior, and configuration that had become difficult to reason about as one system.
I wanted the redesign to reduce the amount of organizational knowledge embedded directly in deployed code. The business should be able to update routine mappings and thresholds without needing a developer to edit source every time someone changes responsibility.
Where it got messy
The first difficult lesson came during testing. A transaction still resolved to an employee nobody expected after the most obvious routing source had been changed. That is the kind of moment where it is tempting to add another condition that forces the “right” answer. I did not want to do that because it would have hidden the reason the original decision was happening.
A separate configuration cleanup also exposed an assumption in the code: one supporting record set was treated as if it would always exist. Removing that data did not just change routing; it caused the implementation to fail in a way that made the dependency obvious. Both issues pointed to the same weakness—too much behavior depended on implicit assumptions.
The part I struggled with
The design question was not whether I could write the logic. It was deciding where each kind of rule belonged. If everything goes into code, the system becomes rigid. If everything goes into configuration, the configuration itself becomes a programming language nobody can safely reason about.
I ended up separating stable decision behavior from business-owned values that were expected to change. I also paid much more attention to fallback behavior: what happens when a mapping is missing, inactive, duplicated, or incomplete should be a deliberate design decision rather than an accidental branch.
What I changed
The refactor moved changing routing data into administrator-maintained configuration, kept the actual decision behavior in a smaller amount of workflow/script logic, and treated exceptional paths as exceptions instead of allowing them to become the default model.
I also consolidated duplicate decisions where the same person could legitimately appear more than once in the chain. That sounds cosmetic, but it matters both to the user experience and to debugging because it reduces noise in the final route.
What I learned
The biggest improvement was not a new routing rule. It was making the system easier to explain. If I cannot answer “why did this transaction choose this person?” from the available configuration and logs, the implementation is not finished.
That lesson now shapes how I approach approval work: business values should be maintainable, fallbacks should be explicit, and the decision path should be observable enough that a future administrator does not need to reverse-engineer the script to understand a transaction.
Where it landed
The resulting design was more configurable and more resilient to ordinary organizational change. More importantly, the tests that had been confusing at the start became useful evidence: they exposed where the old implementation relied on hidden assumptions and helped define what the new design needed to make visible.
Approval systems & SuiteFlow
See the broader service scope, common problems, and other related work.
View service