Skip to content

Architectural Change for Handling Dynamic Routes in Production under Docker #1

@Mo-Ko

Description

@Mo-Ko

Summary

The application currently fails to serve dynamically generated mock API endpoints when running in a containerized production environment. While the /generate endpoint successfully returns a 200 OK status and a URL for the new mock API, any attempt to call that URL results in a 404 Not Found error.

This issue does not occur in a simple local development environment (e.g., uvicorn main:app --reload), but is consistently reproducible in the Docker environment, even when configured to use a single worker.

Symptoms

  1. A POST request to /generate successfully creates a mock API configuration in memory.
  2. The application's log confirms that the route has been "registered."
  3. Previously, the Swagger/OpenAPI documentation at /docs would update to show the new mock endpoint, but requests to it would fail.
  4. All GET, POST, etc. requests to the newly generated mock URL (e.g., /mocks/my_api_123abc/users) return 404 Not Found.

Root Cause Analysis

The core of the problem is an architectural mismatch between the application's design and the process model of a production web server like Uvicorn.

The application was designed to modify its own global state at runtime by calling app.include_router() or app.mount() after the server has already started. This approach is fundamentally unreliable due to Process Memory Isolation.

  • The running Uvicorn server creates an optimized routing stack at startup.
  • Lightweight, in-memory modifications to the FastAPI app object (like adding a route to the app.router.routes list) are not automatically detected by the live server loop.
  • The state (the list of active routes) is stored in the memory of a disposable worker process. This state can be lost during server reloads or is not shared across different processes, leading to inconsistent behavior. The result is that the server "thinks" the route doesn't exist, even though our Python code has registered it.

Proposed Solution: The Proxy/Dispatch Pattern

To solve this, we must refactor the application to stop modifying its own routes at runtime and adopt a more stable "Proxy" or "Dispatch" pattern.

  1. Immutable Server: The FastAPI app object will be treated as immutable after startup. No new routes will be added dynamically.

  2. Stateful Manager: The MockManager service will be redesigned. Instead of trying to register live routers, it will act as a simple, in-memory stateful registry that stores the schemas (e.g., the OpenAPI JSON) of the active mock APIs, indexed by a unique api_id.

  3. Static Proxy Route: We will create a single, static "catch-all" route in router.py at startup:

    @router.api_route("/mocks/{api_id}/{sub_path:path}", methods=["GET", "POST", ...])
  4. Dynamic Dispatch: When a request hits this proxy route, the following will happen:

    • The route will capture the api_id and the sub_path.
    • It will call the MockManager's new dispatch_mock_request method.
    • The dispatch method will look up the schema for the given api_id in its registry.
    • It will then parse the schema, find the definition for the requested sub_path, generate a fake response on the fly, and return it.

Acceptance Criteria / Tasks

  • Refactor MockManager to store API schemas in a dictionary instead of APIRouter or FastAPI objects.
  • Implement the dispatch_mock_request method in MockManager to dynamically handle incoming requests based on a stored schema.
  • Remove all calls to app.include_router or app.mount from the /generate logic.
  • Implement the static, catch-all proxy route in api/router.py.
  • Update the /generate endpoint and GenerationService to use the new mock_manager.register_rest_api_schema method.
  • Acknowledge and document that as a trade-off of this stable architecture, the Swagger/OpenAPI docs will no longer show individual mock endpoints, but will instead show the single proxy route.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions