Repository navigation
Lack of a simple approach for "Database per Service" #360
Description
Proposed new feature or change
Hi, I’d like to use your package with Symfony because I prefer it specifically due to the absence of DBAL and the general decision to forgo ORM-based auto-migration.
However, I recently ran into an issue. Here’s the situation: I use a "Database per Service" approach within a monolith because it makes it much easier to semantically separate database purposes and implement loosely coupled services.
For instance, I have a main database storing all business entities including a list of metrics with around 200 million records. But I also have a separate database containing dozens of tables with an aggregated structure; I write aggregated records there via cron jobs covering a specific number of days. This allows me to avoid the high cost of rebuilding materialized views and keeps the load within reasonable limits.
However, when I tried to migrate code like this:
$builder = new MigrationBuilder(new YiiDbConnectionFactory('maps'));I realized there was no clean way to avoid having to manually instantiate the builder and the connector.
The downside of this approach is that the record of whether the migration was applied to Database #2 ends up being stored in the main migration itself something I’d rather avoid. Right now, I have a set of connections defined in a separate services file like this:
services:
yii3.connections.default:
class: Yiisoft\Db\Connection\ConnectionInterface
factory: ['@App\Shared\Outbounding\YiiDbConnectionFactory', 'create']
arguments:
- 'default'
public: true
Yiisoft\Db\Connection\ConnectionInterface $defaultConnection: '@yii3.connections.default'
Yiisoft\Db\Connection\ConnectionInterface: '@yii3.connections.default'
yii3.connections.maps:
class: Yiisoft\Db\Connection\ConnectionInterface
factory: ['@App\Shared\Outbounding\YiiDbConnectionFactory', 'create']
arguments: ['maps']
public: trueHere is how I envision the implementation: In a configuration file, one could specify human-readable database names—such as "Default," "Maps," "Analytics," etc. and map them to a specific connection implementation or a name for the MigrationBuilder. This would allow autowiring to automatically inject the correct connection without altering other behaviors, such as how migration versions are stored.
When running a command like create:migration, I could pass a flag (e.g., --for) with that human-readable name. This would generate a migration class extending the appropriate base class, allowing the runner to establish the correct connection based on that choice.
For instance, I wouldn't mind creating "stub" classes for each specific database case; these would extend MigrationBuilder and override only the database connection, leaving everything else unchanged.
I am also open to your suggestions, as I am not entirely sure about this approach it is possible I simply missed a solution already present in the documentation.
Activity
samdark commentedon Sep 29, 2026
Do you need one command to apply migrations across all databases, or would separate runs per database work?
samdark commentedon Sep 29, 2026
You want history to be in the database that the migration itself works with, right?
samdark commentedon Sep 29, 2026
Do you want to store migrations for each database (set) at a separate path?
DisboardTetta commentedon Sep 30, 2026
I think this should:
That is correct, given the need to apply a transfer to Database No. 2, Database No. 1 is required.
Yes, the ideal setup is
config/migrations/{default, maps, analytics, ...}. With this arrangement, it is much easier to track all the migrations than if they were all lumped together in a single migrations directory.samdark commentedon Sep 30, 2026
Makes sense overall. I think the following will be backwards compatible:
migrate:up,migrate:new,migrate:historywithout--dbiterate through all sets one by one (default first, then config order), stopping on the first failure.--db=orderslimits to one set.migrate:createdefaults to the default set.migrate:downandmigrate:redorequire--dbonly when more than one set is configured.samdark commentedon Sep 30, 2026
@DisboardTetta would you please try/check #361?
DisboardTetta commentedon Sep 30, 2026
@samdark
Hi, I tried it, and yes, this is exactly what I wanted, thank you very much.
However, some problems arose when integrating this solution. In fact, for Symfony autowiring to work correctly, I had to change this part:
I don't know why, but DI overwrites my register and injects without the DB set.
Also, I don't understand why the default behavior can't be overridden; this is somewhat illogical. I would like to specify a separate directory for it as well, and possibly even a quite specific connection.
Could you clarify whether I need to open an issue for a documentation update in the yii-docs repository?
But, anyway, thank you very much.
samdark commentedon Oct 1, 2026
No, I've updated the pull request addressing both.
DisboardTetta commentedon Oct 1, 2026
Well, a full day of work has passed, and I can confidently tell you that everything is excellent. Thank you very much. I can't wait for the release to the main branch.
samdark commentedon Oct 2, 2026
Great. Will proceed with another self-checking of code and then will merge it.
Update package configuration from #360 (#366)