How can I safely rollback a Fastify plugin after a schema change without breaking encapsulation?
0 reputation · 16 Sept 2021, 14:59 UTC
0 reputation · 16 Sept 2021, 14:59 UTC
I am developing a Fastify plugin that adds a decorator based on the current database schema. When I need to alter the schema, I want to be able to revert the plugin’s changes if the migration fails, while keeping the plugin encapsulated and its dependencies validated through fastify-plugin.
The plugin is registered with a name, dependencies, and encapsulate:true to avoid leaking decorators. I am unsure how to safely deregister the plugin or remove its decorators without affecting other plugins, and whether Fastify provides a built‑in mechanism for versioned plugin loading based on migration status.
What is the recommended way to deregister a plugin registered with fastify-plugin? How can I safely remove decorators added by the plugin? Is there a pattern to version the plugin and conditionally load based on migration status?
Fastify does not provide a built-in mechanism to "deregister" a plugin or remove decorators once they have been attached to the server instance. Because the avvio boot sequence is designed to be additive and immutable during the server's runtime, attempting to manually delete decorators from the Fastify instance is unsafe and can lead to unstable application states.
To achieve a rollback without breaking encapsulation or risking runtime errors, you must shift from a destructive rollback (removing a plugin) to a versioned transition (switching logic). The recommended pattern is to implement a versioned schema strategy within the plugin itself.
Instead of relying on the plugin to detect the schema, pass the required schema version as an option during registration. This allows you to control the plugin's behavior from your main entry point based on the result of your migration status.
// plugin.js
const fp = require('fastify-plugin')
async function schemaPlugin(fastify, opts) {
const version = opts.schemaVersion || 'v1';
if (version === 'v2') {
fastify.decorate('dbSchema', newSchemaV2());
} else {
fastify.decorate('dbSchema', newSchemaV1());
}
}
module.exports = fp(schemaPlugin, { name: 'schema-manager', fastify: '4.x' });
To handle migration failures, wrap your registration in a logic block that checks the migration state before the server fully boots. If a migration fails, the application should either fail to start or fall back to the previous stable version configuration.
fastify-plugin to ensure dependencies are met before the versioned logic is applied.encapsulate: true setting on the registration call. This ensures that if you register multiple versions of a plugin in different scopes, they do not leak into the global namespace.This approach assumes you are using Fastify v4.x or newer and that your schema changes are managed via an external migration tool. To verify this implementation:
schemaVersion: 'v2'.'v1'.fastify.dbSchema reflects the v1 structure without any residual v2 properties.Diagnostic Detail Needed: Are you attempting to perform this rollback while the server is actively handling traffic (Hot Reload), or is this part of a deployment pipeline restart?
Use comments to ask for clarification. Post a solution as an answer.
26,525 reputation · 16 Sept 2021, 21:48 UTC
To build on the versioned transition strategy, it is important to consider the synchronization between the database state and the Fastify instance. If a migration fails halfway through, the application may enter a "split-brain" state where the database is at version 1 but the plugin has already decorated the server with version 2 logic.
To prevent this, avoid performing the migration inside the plugin's registration function. Instead, execute migrations in a separate pre-boot phase. You can then pass the verified schema version as an option to the plugin:
// Recommended Boot Sequence
async function start() {
try {
const version = await migrationTool.migrate(); // Verifies DB state
await fastify.register(schemaPlugin, { schemaVersion: version });
} catch (err) {
// Handle migration failure before Fastify finishes booting
process.exit(1);
}
}
This ensures that the plugin only decorates the instance based on a confirmed database state, removing the need to "deregister" decorators—which, as noted, is not supported by the Avvio boot sequence.