Skip to content

Lack of a simple approach for "Database per Service" #360

Closed

Description

@DisboardTetta

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: true

Here 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

  1. samdark commented on Sep 29, 2026

    @samdark
    Member

    Do you need one command to apply migrations across all databases, or would separate runs per database work?

    migrate:up --db=xxx # single set of migrations
    migrate:up # what should it do by default?
    migrate:up --all-dbs # apply all sets defined one by one?
  2. samdark commented on Sep 29, 2026

    @samdark
    Member

    You want history to be in the database that the migration itself works with, right?

  3. samdark commented on Sep 29, 2026

    @samdark
    Member

    Do you want to store migrations for each database (set) at a separate path?

  4. DisboardTetta commented on Sep 30, 2026

    @DisboardTetta
    Author

    migrate:up # what should it do by default?

    I think this should:

    migrate:up --all-dbs # apply all sets defined one by one?


    You want history to be in the database that the migration itself works with, right?

    That is correct, given the need to apply a transfer to Database No. 2, Database No. 1 is required.


    Do you want to store migrations for each database (set) at a separate path?

    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.

  5. samdark commented on Sep 30, 2026

    @samdark
    Member

    Makes sense overall. I think the following will be backwards compatible:

    1. Each "database" is a named set with its own connection, migration directory, and history table stored under that connection.
    2. migrate:up, migrate:new, migrate:history without --db iterate through all sets one by one (default first, then config order), stopping on the first failure. --db=orders limits to one set.
    3. migrate:create defaults to the default set.
    4. migrate:down and migrate:redo require --db only when more than one set is configured.
  6. self-assigned this
    on Sep 30, 2026
  7. samdark commented on Sep 30, 2026

    @samdark
    Member

    @DisboardTetta would you please try/check #361?

  8. DisboardTetta commented on Sep 30, 2026

    @DisboardTetta
    Author

    @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:

      Yiisoft\Db\Migration\:
        resource: "../vendor/yiisoft/db-migration/src/"
        exclude:
          - '../vendor/yiisoft/db-migration/src/DatabaseSetRegistry.php'
    

    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.

  9. samdark commented on Oct 1, 2026

    @samdark
    Member

    No, I've updated the pull request addressing both.

  10. DisboardTetta commented on Oct 1, 2026

    @DisboardTetta
    Author

    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.

  11. samdark commented on Oct 2, 2026

    @samdark
    Member

    Great. Will proceed with another self-checking of code and then will merge it.

  12. added a commit that references this issue on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions