Collections, indexes, and documents can usually remain unchanged while Ambiten is introduced around the existing persistence layer.
Migration
Migration to Ambiten is not necessarily a rewrite.
It is a transition toward clearer execution boundaries around MongoDB access, context propagation, model operations, tenant infrastructure, transactions, middleware, and instrumentation.
The goal is to preserve existing MongoDB data and application behavior where possible while progressively moving execution concerns into a more explicit runtime structure.
Ambiten is designed for incremental adoption.
Adopt Ambiten one execution boundary at a time.
Preserve existing MongoDB data while progressively introducing AmbitenClient, models, context binding, tenant infrastructure, transactions, middleware, and instrumentation.
Start with direct AmbitenClient usage or model-based access, then add context, transactions, tenant infrastructure, and adapters where they are useful.
Execution state, model operation state, tenant infrastructure, transaction ownership, and persistence behavior become distinct responsibilities.
What changes during migration
Most MongoDB applications already have:
collections
indexes
document structures
query flows
service boundaries
transaction patternsMigration changes how execution is coordinated around those systems.
Instead of allowing tenant identity, database selection, transaction sessions, query policy, and instrumentation metadata to move through application code in unrelated ways, Ambiten provides clearer runtime boundaries for carrying and resolving that state.
A model-driven execution path can become:
Execution Boundary
↓
AmbitenContext
↓
Application Logic
↓
AmbitenModel
↓
Effective ModelContext
↓
Schema / Middleware
↓
Infrastructure Resolution
↓
AmbitenClient
↓
MongoDBNot every application needs every layer immediately.
Migration does not require one starting point
Ambiten supports progressive adoption.
A low-level MongoDB application can begin with:
Application
↓
AmbitenClient
↓
MongoDBA model-oriented application may instead begin with:
Application
↓
AmbitenModel
↓
AmbitenClient
↓
MongoDBRuntime context can then be introduced where execution state needs to flow across operations:
Execution Boundary
↓
AmbitenContext
↓
Application
↓
AmbitenModelThis allows migration to follow the architecture that already exists rather than forcing every project through the same sequence.
A common migration progression
One common path is:
Existing MongoDB Access
↓
AmbitenClient
↓
AmbitenSchema + AmbitenModel
↓
AmbitenContext
↓
Tenant Infrastructure
↓
Transaction Boundaries
↓
Adapters / InstrumentationAnother application may introduce only part of that stack.
The important goal is to make each adopted boundary explicit and testable.
Introducing AmbitenClient
Existing direct MongoDB code can migrate incrementally through AmbitenClient.
import {
AmbitenClient
} from "@ambiten/core";
const client =
new AmbitenClient({
uri:
process.env.MONGODB_URI,
options: {
dbName: "my-app"
}
});
await client.connect();Direct client usage remains a supported Ambiten execution model.
Applications do not need to introduce schemas, models, adapters, or multi-tenancy before Ambiten becomes useful.
Introducing models
When reusable model behavior is useful, wrap existing collections with AmbitenSchema and AmbitenModel.
const UserModel =
new AmbitenModel({
collectionName: "users",
schema: userSchema,
provider: client
});An existing MongoDB collection usually does not need to be recreated simply because it is now accessed through Ambiten.
However, review schema assumptions, lifecycle behavior, middleware, and document conventions before treating an existing collection as fully equivalent to a new Ambiten model.
Direct driver calls can then be replaced progressively where model behavior is beneficial.
Introducing context
Once operations need execution-scoped state, introduce AmbitenContext.
await AmbitenContext.run(
{
tenantId: "tenant-a"
},
async () => {
await UserModel.find({});
}
);AmbitenContext carries the broader execution state.
During model execution, AmbitenModel derives the persistence-facing state used by the operation:
explicit operation ModelContext
↓
active AmbitenContext
↓
model defaults
↓
Effective ModelContextThis distinction matters.
AmbitenContext
= execution-scoped runtime state
ModelContext
= persistence-facing operation stateContext therefore does not simply replace every parameter previously passed through application code.
It carries state that genuinely belongs to the execution.
Moving tenant handling
Older systems may pass tenant identity through multiple service methods:
await UserService.create(
data,
tenantId
);A runtime-bound execution can instead establish tenant identity once:
await AmbitenContext.run(
{
tenantId: "tenant-a"
},
async () => {
await UserModel.create(
data
);
}
);The model can then inherit the tenant identity through its Effective ModelContext.
Tenant identity and tenant infrastructure should remain separate:
Who is this execution for?
→ TenantResolver
Carry tenant identity
→ AmbitenContext
Where does the tenant live?
→ TenantConfigResolver /
MultiTenantManager
Give me the MongoClient
→ TenantClientResolver /
MultiTenantManagerTenant-aware execution does not replace authentication or authorization.
Moving transaction handling
Legacy applications often pass MongoDB sessions manually.
Before:
await UserModel.create(
data,
{
session
}
);
await AuditModel.create(
log,
{
session
}
);An explicit Ambiten transaction boundary can carry the session through the execution:
await AmbitenContext.withTransaction(
async () => {
await UserModel.create(
data
);
await AuditModel.create(
log
);
}
);The session flow becomes:
Transaction Boundary
↓
AmbitenContext.session
↓
AmbitenModel.mergeCtx()
↓
ModelContext.session
↓
Participating OperationsThe enclosing transaction boundary owns:
start
commit
rollback
completionParticipating Ambiten operations can reuse that session without every application layer manually forwarding it.
This applies to participating MongoDB work.
External APIs, queues, filesystems, object storage, and other side effects do not automatically become part of the MongoDB transaction.
Migrating from Mongoose
A Mongoose migration usually involves both data-access APIs and execution behavior.
For example, an existing application may combine tenant filtering and explicit session propagation:
await User.findOneAndUpdate(
{
_id: id,
tenantId
},
data,
{
session
}
);An Ambiten model operation may instead execute inside an already established tenant and transaction context:
await UserModel.findOneAndUpdate(
{
_id: id
},
data
);Whether the tenant identifier should remain in the query depends on the application's tenant topology.
For example:
Database-per-tenant
→ tenant separation may occur through infrastructure resolutionwhile:
Shared collection
→ tenant discrimination may still need to appear in the filterMigration should therefore preserve the application's actual isolation model rather than mechanically removing tenant fields from queries.
Migrating from Prisma
Prisma and Ambiten use different abstractions.
Prisma structures access around a generated client and schema model.
Ambiten structures MongoDB execution around clients, models, runtime context, and infrastructure resolution.
A simple operation may look similar at the surface.
Before:
await prisma.user.create({
data
});After:
await UserModel.create(
data
);But this should not be treated as a one-to-one API migration.
If Prisma is being used with MongoDB, review:
schema definitions
generated types
relation assumptions
transactions
indexes
query semantics
middleware
application servicesbefore replacing Prisma access.
Ambiten is specifically oriented around MongoDB runtime execution rather than acting as a general replacement for every Prisma architecture.
Preserving existing data
Ambiten operates against MongoDB infrastructure.
Existing:
collections
indexes
documents
document identifierscan usually remain intact.
Migration happens primarily around how those resources are accessed and how execution state reaches persistence operations.
That does not mean every existing application convention should remain unchanged.
Review:
schema expectations
soft-delete fields
tenant topology
index strategy
transaction assumptions
connection lifecycle
middleware behavioras each model is migrated.
Migrating middleware
Existing persistence hooks can often move toward Ambiten schema middleware where the behavior belongs close to the data boundary.
For example:
userSchema.pre(
"updateOne",
async (ctx) => {
ctx.update.$set = {
...(ctx.update.$set || {}),
updatedAt:
new Date()
};
}
);Good middleware candidates include:
normalization
timestamps
persistence metadata
soft-delete behavior
query shaping
persistence-level policyApplication authentication, authorization, payment policy, approval workflows, and unrelated business rules should not be moved into middleware merely because middleware exists.
Migrating instrumentation
Legacy applications may scatter logging around data access:
console.log(
"Creating user"
);
await UserModel.create(
data
);Instrumentation can instead use structured execution information where appropriate.
await measureQuery(
{
operation:
"create",
collectionName:
"users",
extra: {
feature:
"user.create"
}
},
async () => {
return UserModel.create(
data
);
}
);AmbitenContextState can carry runtime metadata such as:
tenantId
requestId
dbName
loggerMeta
debug
meta
observer
budgetAmbiten makes execution metadata available to instrumentation.
The logging, tracing, metrics, or telemetry backend remains responsible for transporting and storing those signals.
Gradual replacement strategy
Migration does not need to happen all at once.
Existing MongoDB access and Ambiten-based access can coexist while individual execution paths are migrated.
For example:
Legacy Path A
→ MongoDB Driver
Migrated Path B
→ AmbitenModel
→ AmbitenClient
→ MongoDBThis can reduce migration risk.
However, care is required when old and new access patterns participate in the same transaction or tenant-sensitive workflow.
A legacy driver operation will not automatically inherit Ambiten context merely because nearby Ambiten model operations do.
If the two paths share a transaction, session participation must remain explicit and compatible.
Recommended migration order
A practical migration sequence is:
1. Inventory current MongoDB access
2. Introduce AmbitenClient where useful
3. Introduce AmbitenSchema and AmbitenModel
4. Validate existing collection behavior
5. Establish AmbitenContext around execution boundaries
6. Move tenant identity into execution context
7. Configure tenant infrastructure where required
8. Replace manual session propagation with transaction boundaries
9. Migrate persistence-oriented middleware
10. Introduce instrumentation
11. Replace remaining legacy access progressively
12. Validate runtime behavior before removing the old pathNot every application needs every step.
Validate each migration stage
A successful build is not enough to prove a migration is correct.
Verify behavior such as:
database resolution
collection resolution
context propagation
tenant resolution
transaction participation
middleware execution
soft-delete behavior
connection reuse
shutdown behaviorFor multi-tenant systems, verify the actual isolation model as well.
Examples include:
tenant A resolves only intended infrastructure
tenant B resolves its own infrastructure
shared-collection filters include required tenant constraints
dynamic tenant discovery resolves expected configurationCommon migration mistakes
One common mistake is trying to migrate the entire persistence layer at once.
Incremental migration makes runtime differences easier to isolate and test.
Another mistake is assuming:
using AmbitenContext
=
all infrastructure is now automaticContext carries execution state.
The model, provider, tenant infrastructure, and transaction boundary still have distinct responsibilities.
Another common issue is mixing manual session handling and runtime-managed transaction participation without a clear ownership model.
For a transaction, decide which boundary owns:
session creation
transaction start
commit
rollback
completionTenant-aware systems should also avoid treating tenant identity as equivalent to authorization.
Finally, do not recreate process-level infrastructure for every request.
Prefer reuse of:
AmbitenClient
MongoClient
models
schemas
providers
MultiTenantManagerwhile keeping execution-specific state inside:
AmbitenContextMental model
Before migration, execution state may travel through application layers:
Controller
↓ tenantId / session / db
Service
↓ tenantId / session / db
Repository
↓ tenantId / session / db
MongoDBAfter introducing Ambiten runtime boundaries:
Execution Boundary
↓
AmbitenContext
↓
Application Logic
↓
AmbitenModel
↓
Effective ModelContext
↓
Infrastructure Resolution
↓
MongoDBThe shift is not:
manual everything
→ automatic everythingIt is:
scattered responsibility
→ explicit runtime responsibilitySummary
Migration to Ambiten is a transition toward a more explicit MongoDB execution model.
Existing data can usually remain in place while applications progressively introduce:
AmbitenClient
AmbitenSchema
AmbitenModel
AmbitenContext
tenant infrastructure
transaction boundaries
middleware
instrumentation
adaptersThe central migration model is:
Preserve MongoDB Data
↓
Adopt Ambiten Access
↓
Establish Execution Context
↓
Bind Model Operations
↓
Resolve Infrastructure
↓
MongoDBAmbiten does not require every application to adopt the entire runtime at once.
The goal is to introduce the amount of structure required by the system while keeping execution responsibilities clear as the application grows.
