Thread Catalyst - DBIx::Class - Model - Diskussion (3 answers)
Opened by sid burn at 2009-02-01 17:39

sid burn
 2009-02-01 17:39
#118560 #118560
User since
2006-03-29
1520 Artikel
BenutzerIn

user image
Hi,
ich setze mich derzeit verstärkt mit Catalyst und natürlich auch DBIx::Class
auseinander. Wenn ich aber an das MVC Kozept denke, dann denke ich mir
das DBIx::Class und so wie es oft verwendet wird irgendwie nicht ganz
in das Konzept passt, diesen Punkt werde ich jetzt etwas genauer erklären.
Ansonsten geht es in meinen Beispiel jetzt ums Prinzip, also nicht sagen
das Hsitorisch gesehen dieses oder jenes Modul nicht gab etc. Danke.

Nehmen wir an wir haben ein Programmierer der bereits vor 20 Jahren
eine Webanwendung nach dem MVC Konzept geschrieben hat.

Da früher Datenbanken noch nicht so weit verbreitet waren und es noch wenige
Alternativen gab, entschied er sich als Datenspeicher eine simple CSV Datei
zu nehmen. Da zu dem Zeitpunkt auch nicht mit so einem hohen Datensatz
aufkommen zu rechnen war, reichte das ganze auch noch aus. In diesem
Beispiel speichert er einfach ein paar Informationen zu einem Benutzer.
Sein Model sah dann ungefähr so aus.

Code: (dl )
1
2
3
4
5
6
7
8
9
package Model::CSV;
sub new {
# konstruktor der die CSV Datei öffnet und alles abspeichert
}
sub find_user {
my ( $self, $user ) = @_;
# Sucht den angegebene Benutzer $user aus der CSV Datei heraus und
# liefert ein Schema::User zurück.
}


Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
package Schema::User;
sub new {
# konstruktor der einen benutzer darstellt und auch Model::CSV
# enthält damit das Model Daten verändern kann.
}

sub user {
my ( $user ) = @_;
# Mutator - Gibt den namen des benutzers zurück oder liest ihn aus.
}

# weitere mutator für weitere attribute...



Seine Webapplikation läuft nun ein paar Jahre, in seinen Controllern finden
sich ein haufen aufrufe von "find_user($user) etc.

Da die CSV Datei immer größer geworden ist, das Auslesen und setzen immer
länger dauert und da mit den Jahren immer mehr freie Datenbanken erschienen
sind, entscheidet er sich DBI mit einer Datenbank zu nutzen. Durch
die aufteilung des MVC ist er jetzt in der Lage diese Umstellung zu machen
find_user() könnte z.B. so aussehen.

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sub find_user {
my ( $self, $user ) = @_;

my $stmt = q{
SELECT *
FROM user
WHERE user = ?
};
my @bind = ( $user );

$self->dbh->prepare($stmt);
$self->exec(@bind);

# u.s.w.
}


Was er letztendlich getan hat ist eine komplette Umstellung der CSV Basierten
Speicherung auf eine Datenbank. Was musste er im Controller anpassen?
Absolut gar nichts. Den die Controller haben das Abstrakte Model genutzt
das den Zugriff intern regeln. Er hat also den Daten zugriff geändert und nur
das Model verändert. So wie es auch sein sollte. Den das Model soll ja auch
eine Abstraktion für den Datenzugriff darstellen.

Es ist sogar möglich das er später einmal auf Class::DBI umstellt und später
dann auf DBIx::Class. Er passt dafür immer nur das Model an, und das war es.
In DBIx::Class könnte der Zugriff dann so ausschauen.

Code: (dl )
1
2
3
4
5
6
7
sub find_user {
my ( $self, $user ) = @_;

$self->find({ user => $user });

# und wieder alles aufbereiten für das eigene Schema::*
}




Jetzt zurück zur Gegenwart.

In Catalyst nutzen wir im Model ein Schema das den Datenzugriff darstellt.
Innerhalb von Catalyst könnten wir z.B. mit
Code: (dl )
$c->model('DB::User')->find({ user => $user });

unseren Benutzer bekommen. In der Regel wird das auch so gemacht, mitten
im Controller.


Und hier ist bereits das Problem. Wir haben keine Datenabstraktion mehr was
uns das Model eigentlich bieten sollte. Mit "$c->model('DB::User')" bekommen
wir einen ResultSet zurück und wir benutzen das Interface direkt!

Das wäre vergleichbar gewesen als wenn unser Programmierer der von 20 Jahren
sein Model geschrieben hat nicht eine neue Klasse für den Zugriff bereit
stellt sondern uns im Controller einfach nur das CSV Modul bereit
gestellt hätte und wir hätten das CSV Modul direkt genutzt um auf die Daten
zuzugreifen und um unsere Einträge zu ändern. Was wäre die Folge gewesen?

Er hätte seine änderungen nicht mehr so machen können wie ich es beschrieben
habe. Den seine Controller nutzen direkt das "dadrunterliegende" Interface
um die Daten auszulesen/ändern/erstellen. Ein wechsel zu DBI hätte bedeutet
er hätte alle Controller anpassen müssen die sein Datenmodel nutzen und
anstatt dort CSV Zugriffe zu nutzen hätte er direkt im Controller
DBI genutzt und dort SQL hineingeschrieben.

Kurz gesagt, das was das "Model" eigentlich liefern sollte wurde komplett
verfehlt. Es ist absolut gar keine Abstraktion vorhanden. Änderungen
im Model bedeuten gleichzeitig auch das änderungen im Controller geschehen
müssen.

Selbst das simple DBIx::Class Beispiel verstößt dort schon dagegen.
Code: (dl )
$c->model('DB::User')->find({ user => $user });

Den in diesem Beispiel sagen wir ja schon das wir einen benutzer haben wollen
dessen "user" Spalte in der Datenbank $user ist. Sollte sich unser
Model ändern, z.B. entscheiden wir uns "username" im Model zu verwenden
dann sind alle Zugriffe im Controller kaputt. Eigentlich sollte uns
die Abstraktion als Model uns vor soetwas ja bewahren.

Hätte man ein eigenes ResultSet genommen und eine eigene Methode
"find_user" eingefügt und hätte diese dann genommen anstatt die
"find" Methode zu nutzen die das ResultSet anbietet hätten wir diese
Änderungen machen können. Im Controller würde dann soetwas stehen wir
folgendes.
Code: (dl )
$c->model('DB::User')->find_user($user);


und im ResultSet dann folgende Methode.
Code: (dl )
1
2
3
4
sub find_user {
my ( $self, $user ) = @_;
$self->find({ user => $user });
}


Zwar ist das wenig Code das eine Abstraktion schon fast nicht wert ist
(so denkt man), aber wenn wir unsere Datenbank wirklich anpassen sollten und
aus dem Feld "user" ein "username" machen würden, müssten wir nur das Model
anpassen. Die Controller die unser Model nutzen müssen nicht angepasst
werden.

Ansonsten ist das ja noch ein simples beispiel. komplexe find, search
abfragen wo mit prefetch etc. gearbeitet wird schreiben wohl auch die meisten
direkt im Controller. Die komplette Arbeitsweise so wird einem ja auch
sogar gleich so im Catalyst Tutorial beigebracht.


Und jetzt halt die endgültige Frage. Wie steht ihr dazu?
Wie seht ihr das ganze?

Letztendlich ist eine echte Abstraktion des Model so nicht erreicht. Sollte
sich am Model oder der Datenbank etwas ändern müssen evtl. die Zeilen
im Controller angepasst werden.

Schreibt man solche Zeilen im Controller sind sie nicht wiederbenutzbar.

Das Model ist nicht mehr veränderbar. Hätte man vor paar Jahren noch
Class::DBI genutzt und das Interface genau so benutzt wäre ein Umstieg
zu DBIx::Class wohl nicht mehr möglich gewesen (oder erschwert gewesen).
Auser es wird das komplett gleiche Interface angeboten (was in diesem
fall glaube ich sogar der fall war).

Letztendlich ist solch eine Benutzung derzeit nur eins. Eine vereinfachung
gegenüber der verwendung von z.B. DBI. Einen großen Unterschied
macht es nicht ob wir im Controller direkt DBIx::Class nutzen oder
ob wir dort direkt das DBI Interface nutzen und unser SQL dort direkt
hereinschreiben.

Wir haben dadurch keine Abstraktion gewonnen, sollte ein anderes Modul auf
unsere daten zugreifen muss es wieder das gleiche SQL nutzen nur steht es dann
an zwei stellen, anstatt etwas bereits vorhandenes einfach wieder benutzt
wird, weil man es in eine Subroutine ausgelagert hatte.


Also wie steht ihr zu dem ganzen?
Habt ihr daran bisher noch nicht gedacht?
Ist es egal da ihr meint das ihr sowieso nie das Model ändert?
Zu komplex/großer aufwand das ganze nochmals zu abstrahieren?

Oder habt ihr eine gute Idee wie man das ganze besser Abstrahieren kann?
Nicht mehr aktiv. Bei Kontakt: ICQ: 404181669 E-Mail: perl@david-raab.de

View full thread Catalyst - DBIx::Class - Model - Diskussion