A shared database with a single application user has no boundaries at all, whatever the code layout suggests.
CREATE USER 'fulfilment'@'%' IDENTIFIED BY '...';
GRANT SELECT, INSERT, UPDATE, DELETE
ON shop.orders TO 'fulfilment'@'%';
GRANT SELECT ON shop.catalogue_products_v1 TO 'fulfilment'@'%';
-- and the migration user, which is separate and is not
-- what the application connects as:
GRANT ALTER, CREATE, DROP, INDEX ON shop.* TO 'migrator'@'%';
Separating the migration user from the application user is the part that is skipped most often and is the one that prevents an application bug from dropping a table. The failure mode of getting a grant wrong is loud — a query fails with a permission error — which makes this much safer to introduce than it sounds.