NAME

Mojolicious::Plugin::Fondation::Model::DBIx::Async - Fondation plugin exposing DBIx::Class::Async natively —

VERSION

version 0.01

SYNOPSIS

# myapp.conf
{
    'Fondation' => {
        dependencies => [
            { 'Fondation::Model::DBIx::Async' => {
                backends => [
                    main => {
                        dsn          => 'dbi:SQLite:dbname=data/app.db',
                        schema_class => 'MySchema',
                        workers      => 2,
                    },
                ],
                models => {
                    user => { source => 'users' },
                },
            }},
        ],
    },
}

# In a controller
sub list ($self) {
    $self->render_later;
    $self->model('user')->search({ active => 1 })->all
        ->on_done(sub {
            my @users = @_;
            $self->render(json => [ map { $_->get_columns } @users ]);
        })
        ->on_fail(sub { $self->reply->exception(shift) })
        ->retain;
}

DESCRIPTION

Mojolicious::Plugin::Fondation::Model::DBIx::Async is a Fondation plugin that exposes DBIx::Class::Async natively — no hashref CRUD wrapper, no Future-to-Mojo::Promise conversion. Every call goes through a background worker pool, keeping the Mojolicious event loop responsive.

Architecture

┌─────────────────────────────────────────────────┐
│  Mojolicious Application                        │
│  $c->model('user') → ResultSet                  │
│  $c->schema         → DBIx::Class::Async::Schema│
└──────────────┬──────────────────────────────────┘
               │ IO::Async::Loop::Mojo
┌──────────────▼──────────────────────────────────┐
│  Worker Pool (forked processes)                 │
│  ┌────────┐ ┌────────┐ ┌────────┐              │
│  │Worker 1│ │Worker 2│ │Worker N│              │
│  └────────┘ └────────┘ └────────┘              │
└──────────────┬──────────────────────────────────┘
               │ DBI
┌──────────────▼──────────────────────────────────┐
│  Database                                       │
└─────────────────────────────────────────────────┘

Each backend is a separate DBIx::Class::Async::Schema with its own IO::Async::Loop::Mojo and worker pool. Workers are forked lazily on the first schema access and are automatically stopped on process exit.

Model discovery

During fondation_finalyze, the plugin scans every loaded Fondation plugin for a models key in their configuration. Models declared by dependency plugins are merged, with the application configuration taking priority. Each model is validated to have a resolvable backend.

Source registration

The DBIx action (Mojolicious::Plugin::Fondation::Model::DBIx::Async::Action::DBIx) auto-discovers Result and ResultSet classes under each plugin's Schema::Result::* and Schema::ResultSet::* namespaces and registers them on the native schema class before workers are forked.

Shutdown

An END block disconnects all schemas on clean process exit (Ctrl-C, kill, systemctl stop), calling "disconnect" in DBIx::Class::Async to gracefully stop worker processes. The before_server_stop hook is also emitted so other code can react to shutdown. Only SIGKILL bypasses this — no hook can help there.

CONFIGURATION

'Fondation::Model::DBIx::Async' => {
    backends => [
        main => {
            dsn          => 'dbi:SQLite:dbname=data/app.db',
            schema_class => 'MySchema',
            user         => '',           # optional
            pass         => '',           # optional
            workers      => 2,            # default: 2
            dbi_attrs    => {},           # optional
        },
        logs => {
            dsn          => 'dbi:SQLite:dbname=data/logs.db',
            schema_class => 'MyLogSchema',
            workers      => 1,
        },
    ],
    default_backend => 'main',            # optional
    models => {
        user    => { source => 'users' },
        article => { source => 'articles', backend => 'main' },
        log     => { source => 'logs',    backend => 'logs' },
    },
},

backends

Array of name/config pairs (ordered). Each pair provides a backend name followed by its configuration hash. Each backend requires dsn and schema_class. Names are used by models and other plugins to reference a specific connection.

Plain DSN strings are accepted as a shorthand and normalized to { dsn => $dsn }.

default_backend

Name of the default backend. When omitted, the first backend in the backends array is used. Models without an explicit backend fall back to this.

models

Hash of model definitions. Each model maps a name to a database source (table name). The backend key is optional — it defaults to default_backend or the first configured backend.

HELPERS

All helpers are available on the controller object ($c).

schema_class

my $class = $c->schema_class;              # from first backend with schema_class
my $class = $c->schema_class('backend');   # from a specific backend

Returns the schema class name string, without connecting.

schema

my $schema = $c->schema;                   # first backend
my $schema = $c->schema('backend');        # specific backend

Returns a DBIx::Class::Async::Schema instance. Creates it on first access (lazy, cached per backend). Workers are forked only when the schema is first connected.

model

my $rs = $c->model('user');                # DBIx::Class::Async::ResultSet

Returns a DBIx::Class::Async::ResultSet for the named model. The model must be declared in configuration. All DML (search, create, update, delete, find) goes through the worker pool and returns Future objects.

# Search
$c->model('user')->search({ active => 1 })->all
    ->on_done(sub { my @users = @_; ... })
    ->on_fail(sub { my $err = shift; ... })
    ->retain;

Always end Future chains with ->retain to prevent garbage collection before the async worker responds.

model_config

my $cfg = $c->model_config('user');
# { name => 'user', source => 'users', backend => 'main' }

Returns model metadata.

model_list

my $names = $c->model_list;                # ['article', 'user']

Returns a sorted arrayref of configured model names.

backend_config

my $cfg = $c->backend_config;              # first backend
my $cfg = $c->backend_config('main');
# { dsn => '...', schema_class => '...', name => 'main', ... }

Returns the full backend configuration hash (including the name key). Used by other plugins (MigrationDBIx, OpenAPI) to discover connection details.

default_backend_name

my $name = $c->default_backend_name;       # 'main'
my $name = $c->default_backend_name('logs');   # explicit wins

Cascade: explicit parameter → default_backend config → first backend → undef.

PLUGIN INTEGRATION

Plugins that provide DBIC Result classes must declare their fondation_meta:

sub fondation_meta {
    return {
        dependencies => ['Fondation::Model::DBIx::Async'],
        defaults => {
            models => {
                user => {
                    source  => 'users',
                    backend => undef,   # resolves to default
                },
            },
        },
    };
}

The DBIx action will then auto-discover Schema::Result::* and Schema::ResultSet::* classes under the plugin's namespace and register them on the schema.

SEE ALSO

AUTHOR

Daniel Brosseau dab@cpan.org

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Daniel Brosseau.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.