Once a legacy model is importable from Python, exposing it over HTTP looks like a small step — and it can be, if you take it with the right respect for what the model actually is. Engineering models are not CRUD applications. This post covers the decisions that matter when you put one behind an API.
First: is your model stateful?
Most legacy Fortran and some C++ is stateful — COMMON blocks, static file-scope variables, or an internal configure-then-read session shape. This determines your concurrency story, and it is not optional to answer.
- Fortran with COMMON blocks: the state is process-global. Interleaved calls silently corrupt each other's answers. The generated service holds a process-wide lock across each native call — sequential inside a process, scale by running more processes.
- C++ with file-scope statics: a fresh library instance per request protects request-independent calls, but static state behaves like COMMON: either verify the model is stateless or serialize.
- Session-shaped models: calls like
configure(p, t)followed bycalculate()are not safely shared between tenants; one tenant's configuration is another's surprise.
nativegate encodes this in what it generates, rather than asking you to remember it: Fortran services get the lock; the service refuses more than one native call at a time per process. If your capacity math requires removing that lock, the correct answer is more replicas, not less correctness.
What the generated service gives you
The generated service is an ordinary FastAPI app, and the
operations-grade parts are generated with it rather than promised
for later: API-key authentication, rate limiting, a request-size
cap, request IDs and access logging, an exception handler,
/healthz and /readyz with SIGTERM
draining, a multi-stage non-root container, and Kubernetes
manifests that match the container's user. Deploying one looks
like:
What it deliberately does not give you
- Per-call isolation. A segfault in native code takes the worker down. There is no sandbox, no per-call timeout. The mitigations are process-level: restarts, replica counts, and not exposing routines to callers who can trigger the crash.
- Multi-tenancy. These services are for internal, single-tenant deployment — behind your existing boundary, not internet-facing shared infrastructure.
- Unbounded payloads. Array size caps are a memory guard at the HTTP layer, not a physics-level bounds check.
These are stated plainly because someone will otherwise discover each of them during an incident rather than a design review.
The precondition: verify before you serve
An API multiplies the number of people standing behind the
numerics. Which is exactly why the baseline check comes first:
record known configurations with ngate golden record,
wire ngate golden verify into CI, and only then start
serving. The API's job is to widen access to correct answers —
the golden record is what makes "correct" a maintained property
instead of a one-time audit.