Catalyst: Framework web para Perl

Justo cuando colgaba la encuesta sobre: Si tuvieses que hacer un sitio web dinámico, ¿en qué tecnología lo harías?; busqué un framework en los lenguajes más populares de desarrollo web y, topé con Catalyst, un framework en Perl para el desarrollo de aplicaciones web.

Después de un recorrido por Symfony (PHP), Rails (Ruby) y Django (Python), he decidido echarle un vistazo a Catalyst (Perl) para ver qué novedades integra y en qué mejora, tanto a nivel de rendimiento, como a nivel de desarrollo, la generación de sitios web dinámicos.

Perspectiva MVC

Cada entorno integra MVC de una forma diferente, incluso lo que se entiende por MVC, desde los primeros libros a finales de los 70 y principios de los 80, es un esquema en el que la Vista puede interactuar con el modelo, al igual que el controlador con la vista y con el modelo.

En Rails, se presenta el fat model, donde la codificación referente a base de datos se realiza en estos, y el flujo de la aplicación recae en los controladores, que deciden qué tipo de vista o presentación de la información emplear, dando facilidades para la presentación en HTML, JSON, XML… e incluso vía email.

En cambio, Django, no dispone de controlador como tal, ya que lo nombran por defecto como views, siendo esta la entidad que controla el flujo y la presentación, ayudada de la plantillas usando DTL. En este caso, la parte de MVC queda más difusa y ellos mismos explican en su web, que se podría decir más bien que Django es MTV (Model-Template-View).

Catalyst, sin embargo, tiene más semejanza a Rails en este sentido. Tiene un parte para los controladores, una parte para los modelos y otra parte para las vistas. Así mismo, las vistas se desarrollan, al igual que Django y Rails, con un sistema de plantillas (templates).

Al crear un proyecto, tan solo ejecutando el siguiente comando, se genera la estructura de ficheros que podemos ver a continuación:

$ catalyst.pl miproject
created "miproject"
created "miproject/script"
created "miproject/lib"
created "miproject/root"
created "miproject/root/static"
created "miproject/root/static/images"
[...]
$ tree
.
├── Changes
├── lib
│   ├── miproject
│   │   ├── Controller
│   │   │   └── Root.pm
│   │   ├── Model
│   │   └── View
│   └── miproject.pm
├── Makefile.PL
├── miproject.conf
├── miproject.psgi
├── README
├── root
│   ├── favicon.ico
│   └── static
│       └── images
│           ├── btn_120x50_built.png
│           ├── btn_120x50_built_shadow.png
│           ├── btn_120x50_powered.png
│           ├── btn_120x50_powered_shadow.png
│           ├── btn_88x31_built.png
│           ├── btn_88x31_built_shadow.png
│           ├── btn_88x31_powered.png
│           ├── btn_88x31_powered_shadow.png
│           └── catalyst_logo.png
├── script
│   ├── miproject_cgi.pl
│   ├── miproject_create.pl
│   ├── miproject_fastcgi.pl
│   ├── miproject_server.pl
│   └── miproject_test.pl
└── t
    ├── 01app.t
    ├── 02pod.t
    └── 03podcoverage.t

10 directories, 25 files

En el directorio lib/miproject es donde está el código principal.

Los controladores

A diferencia de otros entornos, las URLs o rutas para acceso a los métodos de Catalyst son implícitas a las subrutinas de cada paquete que está en el directorio (o paquete) de controladores. Por ejemplo, si editamos el fichero lib/miproject/Controller/Root.pm y agregamos el siguiente código:

sub libros :Global {
    my ($self, $c) = @_;
    $c->response->body ( "listado de libros!" );
}

Al ejecutar el servidor de pruebas: script/miproject_server.pl; podremos ver la siguiente tabla:

[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
+----------------------+--------------------------------------+--------------+
'----------------------+--------------------------------------+--------------'

Al cargar la página http://localhost:3000/libros podemos ver el resultado de la ejecución de la subrutina escrita.

La vista

Hemos realizado antes una salida directa desde el controlador, pero lo suyo es crear una vista. El sistema de plantillas de Perl se denomina TT. Es parecido al que se usa en JSP e incluso al que usa la DTL (Django Template Library), pero difiere bastante. Vamos a generar una vista primero:

$ script/miproject_create.pl view HTML TT

Con esto nuestro directorio de View se completa con la generación de un módulo de Perl HTML.pm. Ahora creamos una vista con la siguiente información en la ruta root con el nombre listado.tt:

<h1>Hola mundo!</h1>
 
<p>[% libros %]</p>

Para lo que habrá que cambiar la subrutina escrita anteriormente a la siguiente (también se puede cambiar el nombre y agregar como una completamente nueva):

sub libros :Global {
    my ($self, $c) = @_;
    $c->stash(template => "listado.tt");
    $c->stash(libros => "no hay libros");
}

Los modelos

Tanto Django como Rails, e incluso Doctrine, tienen sistemas de migraciones o sincronización con base de datos. Catalyst no dispone de estos mecanismos. Puede generar sus modelos de una conexión de base de datos con tablas ya creadas, pero no al contrario.

No obstante, su ORM es bastante potente. Por ejemplo, pongamos que creamos una base de datos SQLite (supongamos que en /tmp/libros.db):

PRAGMA foreign_keys = ON;
CREATE TABLE libros (
     id INTEGER PRIMARY KEY,
     titulo TEXT
);
 
CREATE TABLE autores (
     id INTEGER PRIMARY KEY,
     nombre TEXT,
     apellidos TEXT
);
 
CREATE TABLE libros_autores (
     libro_id INTEGER REFERENCES libros(id) ON DELETE CASCADE ON UPDATE CASCADE,
     autor_id INTEGER REFERENCES autores(id) ON DELETE CASCADE ON UPDATE CASCADE,
     PRIMARY KEY (libro_id, autor_id)
);
 
INSERT INTO libros VALUES (1, 'Diseño Ágil con TDD');
INSERT INTO libros VALUES (2, 'No me hagas pensar');
INSERT INTO libros VALUES (3, 'Hacking Etico');
INSERT INTO libros VALUES (4, 'Perl Best Practices');
 
INSERT INTO autores VALUES (1, 'Carlos', 'Ble');
INSERT INTO autores VALUES (2, 'Steve', 'Krug');
INSERT INTO autores VALUES (3, 'Carlos', 'Tori');
INSERT INTO autores VALUES (4, 'Damian', 'Conway');
 
INSERT INTO libros_autores VALUES (1, 1);
INSERT INTO libros_autores VALUES (2, 2);
INSERT INTO libros_autores VALUES (3, 3);
INSERT INTO libros_autores VALUES (4, 4);

Generamos nuestros modelos con el siguiente comando:

$ script/miproject_create.pl model  DB DBIC::Schema \
     MiProject::Schema create=static \
     dbi:SQLite:/tmp/libros.sqlite \
     on_connect_do="PRAGMA foreign_keys = ON"

Con esto nos genera un fichero de esquema en el directorio Model, con lo que tendremos el acceso a la base de datos configurada. Si quisieramos probar, tan solo necesitaríamos ejecutar lo siguiente. Modificamos la subrutina de nuevo:

sub libros :Global {
    my ($self, $c) = @_;
    $c->stash(template => "listado.tt");
    $c->stash(libros => [$c->model('DB::Libro')->all]);
}

Y también el fichero de plantilla por lo siguiente:

<h1>Hola mundo!</h1>
 
[% FOREACH libro IN libros -%]
[% tt_autores = []; tt_autores.push(autor.apellidos) FOREACH autor = libro.autores %]
<p>[% libro.titulo %] - [% tt_autores.join(', ') | html %]</p>
[% END -%]

Por último, agregamos las referencias necesarias en Libro.pm y Autore.pm (es lo malo del pluralize del inglés cuando se usan palabras castellanas):

__PACKAGE__->many_to_many(libros => 'libros_autores', 'libro');

Y esto para Autore.pm:

__PACKAGE__->many_to_many(autores => 'libros_autores', 'autor');

Con esto ya tendríamos un listado muy simple en funcionamiento. Puedes ver el tutorial completo (con un CRUD de ejemplo y más cosas aún) en la siguiente dirección (en inglés): Tutorial.

Conclusiones

El entorno proporciona las bases que proporcionan el resto de frameworks, su simpleza en las rutas y su formato de ORM hace que la programación de elementos sea bastante simple y rápida, no obstante, hay ciertos elementos con los que hay que tratar y revisarlos bien, leer bien la documentación y tratar de entenderlos, si no queremos toparnos con ningún problema a la hora de realizar cualquier entorno.

Personalmente, me ha gustado la experiencia y el entorno en sí, la programación con los módulos de Perl, lo documentado que queda todo si se siguen las líneas y el hecho de que no hace falta escribir mucho código para conseguir que el sistema funcione bien, hacen que sea un framework muy cómodo de utilizar.