In mid-sized engineering organizations, infrastructural decoupling is often mistaken for architectural maturity. As application scope expands, the need to support customer-centric customization forces teams to create external boundaries for problems that should be addressed solely within the application context. In a recent engineering project, I evaluated an architecture in which customer-centric beta features resulted in the creation of an autonomous routing proxy microservice. The primary goal was to isolate clients from standard release cycles, but the result was increased operational complexity.
Anti-pattern: Network Routing for Business Logic
The core system consisted of a central monolithic service built on Java and Spring Boot. When strategic enterprise clients required early access to isolated API endpoints, the development team decided to intercept incoming traffic before it reached the main application. They created and deployed a custom proxy microservice, whose sole purpose was to inspect incoming client identification headers and route traffic to individual API deployment definitions.
Operating tax on duplication of infrastructure
This approach, while seemingly functional at first glance, created significant operational challenges. Because traffic was distributed between isolated upstream targets, the team had to maintain two parallel sets of API controllers and schemas. Documentation discrepancies quickly emerged, as endpoints in the core monolith evolved independently of custom client definitions. Furthermore, each incoming request incurred latency due to an additional network hop through the proxy server. Engineers were forced to update routing tables, inbound rules, and proxy code for standard feature deployments, turning routine deployments into multi-service releases.
Root Cause Analysis: Access Rights vs. Network Interactions.
The architectural error arose from a fundamental misclassification: the problem of granting access rights to an application was treated as a network routing problem.
Identify network and application related issues.
The network infrastructure exists to manage data transmission reliability, traffic distribution, and protocol translation. This layer addresses tasks such as packet inspection, state monitoring, global mutual TLS (mTLS) termination, and load balancing. These mechanisms operate independently of the business domain context.
In contrast, application-specific issues are entirely dependent on the domain context. Tenant identification, feature flags, role-based access control, and dynamic endpoint visibility require consideration of user sessions, subscription levels, and business permissions. Routing traffic through external proxy nodes to ensure tenant privileges forces the network layer to understand the domain logic, breaking the separation of concerns and creating tight coupling between the network topology and application code.
The network layer should remain completely independent of business domain rules. Separating routing and domain access rights prevents infrastructure complexity from scaling as functionality increases.
Framework-native governance: blurring service boundaries
To address the operational load of the proxy microservice, I integrated the routing layer back into the main Spring Boot monolith. Using the framework’s native extension points, I achieved complete client isolation without external network components.
Enforcing Access Boundaries with Spring Security
I replaced the header validation logic in the proxy service with attribute-based access control (ABAC) in Spring Security. By configuring custom security filters and an authorization manager in the SecurityFilterChain, the application evaluates tenant claims throughout the request lifecycle. Incoming requests are intercepted at runtime, where authorization rules evaluate the client identifier embedded in the security context.
Unreleased or beta endpoints are protected using custom security expressions. Clients without the required permissions receive standard HTTP 403 Forbidden or 404 Not Found responses directly from the underlying service, eliminating the need for external traffic separators.
Dynamic Filtering of OpenAPI Contracts
Handling client access within the application solved the endpoint authorization problem, but created a distinct documentation issue: enterprise clients required API documentation that only reflected the available schema, without access to unreleased endpoints or internal routes.
I solved this problem by implementing runtime OpenAPI configuration using springdoc-openapi. Instead of maintaining separate static OpenAPI definitions, I implemented dynamic OpenApiCustomizer components. These components validate the active security context of the authenticated user during documentation generation, removing unauthorized endpoints and schema definitions from the generated OpenAPI specification in JSON format. Clients receive a single-user view of the API of a multi-tenant application, hiding unreleased features and maintaining a single source of truth across the codebase.
The use of internal authorization and contract enforcement mechanisms eliminates the need for peripheral infrastructure while maintaining complete isolation between multiple users.
Framework for Strategic Decision Making on Infrastructure Boundaries
Implementing new microservices or proxy layers often creates the illusion of progress in the system, but any infrastructure constraint entails ongoing maintenance costs. Engineering managers must evaluate whether the proposed constraint addresses the problem domain or simply avoids the use of existing application functionality.
The rule of exhaustion of frameworks
Before implementing external network constraints, engineering teams should exhaust all capabilities of the application’s native platform. Modern frameworks like Spring Boot offer comprehensive tools for security, tenant routing, middleware interception, and dynamic documentation. If a technical requirement can be reliably met using framework constructs, it should remain within the service.
Maintenance factor
Architectural boundaries need to be reconsidered when the time spent maintaining infrastructure exceeds the time spent writing the underlying logic. If an external routing service requires more build pipelines, deployment configurations, and debugging time than the underlying functionality it protects, the abstraction is invalid. Returning misplaced infrastructure back to the application layer restores development velocity and stabilizes operational boundaries.
