Skip to content

smith153/Dancer2-Plugin-DomainModel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NAME

Dancer2::Plugin::DomainModel - Combat the anemic domain model

SYNOPSIS

# in config.yml

plugins:
  DomainModel:
    namespace: MyApp::MyModels    # the parent namespace of your model classes

# in your app

use Dancer2;
use Dancer2::Plugin::DomainModel;

get '/' => sub {
  template 'index', { news => model('News')->latest };
};

DESCRIPTION

In the original definition of MVC, the model manages the behavior and data of the application domain. However, in most modern MVC frameworks, a model is typically treated as a thing coupled with some form of ORM and thus each model represents a single table in a database. In most cases, this will lead to including your application and business logic inside of your controllers, causing 'fat controllers' and an anemic domain model.

Having yet another layer between your DAO (data access object) and application framework allows you to decouple your business logic into easily testable modules that will no longer be restricted to the limitations of your DAO or framework implementations. Effectively, your model goes from ISA DAO to HASA DAO

USAGE (via base_class)

DomainModel:
  base_class: ModelBase # your base class or factory
  args:                 # optional args passed to base_class constructor
    dsn: dbname
    user: uname

model

model('<model_name>')->method_name

The model keyword is provided for interfacing with your models.

On initialization, Dancer2::Plugin::DomainModel creates an instance of base_class via base_class->new(app => $self) where app will be the Dancer2 instance itself that was passed to the plugin. This will allow easy access to interfacing with the current Dancer2 instance (such as calling the built in logging methods or other accessible API functions. See Modifying-the-app-at-building-time). Calling model('model_name') will proxy to base_class->get('model_name'). Thus your base_class is responsible for dispatching the correct model object. A simple example is provided in the test files t/lib/Models.pm.

USAGE (via namespace)

DomainModel:
  namespace: MyApp::MyModels        # the parent namespace of your model classes
  DBIC:                             # use the optional DBIC plugin
    dsn: dbi:SQLite:dbname=test.db  # required
    schema_class: MyApp::Schema     # required
  does_roles:                       # ensures each model consumes these roles
    - MyApp::BigRole
    - MyApp::BiggerRole

Configuration via the base_class parameter is nice if you want full control over initialization of your own models. However, a default model driver (AKA model 'factory') is provided and can be enabled by specifying the namespace configuration parameter.

model

model('<model_name>')->method_name

The model keyword functions the same whether Dancer2::Plugin::DomainModel is configured using base_class or namespace.

Namespace configuration parameters

namespace

Obviously, Dancer2::Plugin::DomainModel needs to know where to look for your model classes. namespace should be a parent namespace/directory under which your model classes are located.

DBIC

If set, this will apply the Dancer2::Plugin::DomainModel::RoleDBIC role to all your models. As such, the needed dsn and schema_class need to be specified.

This will add the following callable DBIx::Class methods to your models:

does_roles

On larger projects, you might want to ensure that every model that is loaded consumes a certain role or implements a certain interface. does_roles takes a list of these roles that you may provide.

only_with

only_with:
  - Awesome::Role
  - Awesomer::Role

On even larger projects, you might have classes under your namespace directory that are not even models (yuck!). In that case, you may choose to only load classes that consume a certain role which would denote it as a model class and not something else.

with_logger

with_logger: 1

By default, for every model class under namespace, Dancer2::Plugin::DomainModel will apply the Dancer2::Plugin::DomainModel::RoleLogger role to every model that is loaded. This will give you the following methods callable from your model object:

  • logger

    The raw Dancer2 logger engine object

  • info

    Print a log message of level 'info'

  • warn

    Print a log message of level 'warn'

  • debug

    Print a log message of level 'debug'

If you do not want this role applied, set with_logger to a false value.

make_immutable

make_immutable: 1

By default, for every model class under namespace, Dancer2::Plugin::DomainModel will call ClassName->meta->make_immutable before initialization. If you don't want this, set make_immutable to false.

with_model

with_model: 1

By default, for every model class under namespace, Dancer2::Plugin::DomainModel will apply the Dancer2::Plugin::DomainModel::RoleModel role to every model that is loaded. This will allow you do to $self->model('<model-name>')->my_method_name from your model classes. Roughly the same as the exported model keyword.

CAVEATS

A few gotchas

Configuration conflicts

namespace and base_class are mutually exclusive and will fail on app load.

Moose

Moose is required in order to load the default model driver provided via the namespace parameter. Your model classes should use Moose (Or at least allow applying roles via ClassName->meta semantics).

Immutable Models

Depending on your use case, most models (if they make use of Moose at least) will need to not be set as immutable as model classes might have roles applied to them on initialization. Nonetheless, meta->immutable is called on each loaded class (unless make_immutable is set to 0).

Examples

See the files under the t directory.

  • t/lib/Models.pm

    An example custom model 'driver' class for use with base_class

  • t/lib/Models/News.pm

    An example model for use with a custom model driver.

  • t/lib/Test/MyModels/Weather.pm

    An example of what a model might look like when used with the built in model driver class (enabled via the namespace configuration).

See also

Domain Driven Design

AUTHORS

Samuel Smith [email protected]

BUGS

See http://rt.cpan.org to report and view bugs.

SOURCE

The source code repository for Dancer2::Plugin::DomainModel can be found at https://github.com/smith153/Dancer2-Plugin-DomainModel.

COPYRIGHT

Copyright 2018 by Samuel Smith [email protected].

LICENSE

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

See http://www.perl.com/perl/misc/Artistic.html

About

Combat The Anemic Domain Model

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages