mojtaba/amini
Home/Writing/.NET · Angular
May 2025·7 min read·.NET · Angular

How I expose Python models behind a .NET API consumed by Angular

The Dekali stack is .NET + Angular with Python models behind the curtain. Here is the wiring that keeps latency under 200 ms on the order list.

A common anti-pattern in ML-heavy products is to expose the Python model directly to the frontend. The frontend ends up coupled to a language and a runtime that the backend team does not own. Every model retrain becomes a frontend deployment risk. The team running the .NET ERP at Dekali could not afford that coupling.

The pattern we settled on at Dekali looks like this. Python models live inside a sidecar FastAPI service, deployed alongside the .NET monolith on the same Azure resource group. The .NET API exposes the public, typed contract that the Angular frontend talks to. Inside the .NET API, a thin layer calls FastAPI over the cluster's internal network and adapts the response to the .NET domain model. From the frontend's point of view, there is no Python in the building.

Three details made this fast enough for the order list, where users expect sub-200 ms.

First, prediction caching. The vast majority of "what is the predicted delivery date for order X" lookups are repeats. We cache the prediction next to the order row, in the same SQL Server the order lives in. The cache key is hash(order_id, last_modified_at). The .NET layer reads the cache first and only calls FastAPI on miss or on stale-by-modification. Average response time on the order list dropped from 480 ms to 90 ms the day this shipped.

Second, contract testing. The .NET ↔ Python boundary is the highest-risk seam in the system: a silent breaking change in the model output destroys the .NET deserialiser, which destroys the Angular dashboard, all without a compile error. We run a small contract-test suite on every Python deploy that asserts the schema of every model output matches the .NET DTO it is bound to. The tests live in the FastAPI repo and import a copy of the DTO interfaces from a small shared "contracts" package. Schema drift becomes a CI failure, not a 3 AM page.

Third, a single .NET facade per business object. There is exactly one Controller in the .NET API that exposes order-related AI predictions — material forecast, delivery date, suggested supplier. Everything funnels through it. This keeps the surface area small. New AI features either fit into an existing endpoint or get a new endpoint behind the same facade. The Angular team gets a typed client, generated from the .NET OpenAPI spec, that they call without ever needing to know which features are model-backed and which are pure SQL.

The payoff is conventional .NET ergonomics on the frontend, full Python flexibility on the model side, and a deployable boundary in between that the team can test and version independently. Two years of running this pattern: zero model deploys broke the frontend, zero frontend changes required a model retrain. That is, in production AI, the actual success metric.