Schrift
[thread]13088[/thread]

Catalyst - DBIx::Class - Model - Diskussion

Leser: 2


<< >> 4 Einträge, 1 Seite
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
pq
 2009-02-01 18:15
#118561 #118561
User since
2003-08-04
12208 Artikel
Admin1
[Homepage]
user image
ist die spalte "user" in deinem modell der primary key? denn dann geht auch einfach
$schema->find($pk), was unabhangig vom namen des PK ist.
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. -- Damian Conway in "Perl Best Practices"
lesen: Wiki:Wie frage ich & perlintro Wiki:brian's Leitfaden für jedes Perl-Problem
pq
 2009-02-01 18:24
#118562 #118562
User since
2003-08-04
12208 Artikel
Admin1
[Homepage]
user image
sid burn+2009-02-01 16:39:04--
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.

verstehe ich nicht. wenn du die spalte in der datenbank änderst, musst du noch lange nicht
den hashkey auch umbenennen. das definiert man ja alles im schema.

und jetzt sag mir mal, wo der unterschied ist (halb pseudo-code):
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
$dbic->find({ user => $user });
vs.
$dein_modell->find_user($user);

$dbic->find({ lastlogin => $date });
vs.
$dein_modell->find_lastlogin( $date);

$dbic->find($userid); # id ist PK
vs.
$dbic>find_id($userid);


das schöne ist ja, dass du sowas ein einziges mal im schema definierst.
nach deinem modell müsste man fur jede spalte eine eigene methode schreiben.
wenn du gerne solche methoden schreibst, hält dich natürlich niemand davon ab,
aber ich bin da eher faul.
ausserdem kannst du im jeweiligen dbic-model auch eigene kurze zugriffsmethoden schreiben,
für oft gebrauchte felder z.b.
die grösste arbeit ist das schreiben des schemas, aber das ist für mich auch gleichzeitig
dokumentation und quelle für das erstellen/ändern der datenbank (mittels sqlt z.b.).
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. -- Damian Conway in "Perl Best Practices"
lesen: Wiki:Wie frage ich & perlintro Wiki:brian's Leitfaden für jedes Perl-Problem
sid burn
 2009-02-21 17:26
#119063 #119063
User since
2006-03-29
1520 Artikel
BenutzerIn

user image
Hi,
erstmal Sorry für die etwas verspätete Antwort. Dachte das würde evtl. mehr Leute Interessieren und der eine oder andere würde dazu noch was schreiben. Nunja eine "Best Bractices" Diskussion scheint die meisten bei Perl ja nicht zu Interessieren. ;)

Quote
ist die spalte "user" in deinem modell der primary key? denn dann geht auch einfach
$schema->find($pk), was unabhangig vom namen des PK ist.

Hmm, jaein.
Das sollte nur ein Beispiel sein. Ich persönlich würde ein VARCHAR Feld sowie nicht als PK nehmen, aber username wohl als UNIQUE Key. Und find() Funktioniert ja auch mit einem UNIQUE Key, wodurch es rein Theoretisch geht. Ein Freund davon bin ich aber trotzdem nicht. Denn wenn die Datenbank erweitert wird kann es ja durchaus vorkommen das man Felder durchsucht die man nicht durchsuchen wollte und es mehr als ein match geben kann.

Ich sage es mal in deinen Worten. Ich bin diesbezüglich faul und tippe es sofort ganz ein um mir mögliche Probleme oder Fehlersuche in der Zukunft zu ersparen.

Oder was passiert z.B wenn ein benutzer als Username "2000" hat und ich langsam auf die 2000 benutzer zu gehe und ich dein weg nutze? (wenn ich ein PK "id" auto_increment habe).

Quote
verstehe ich nicht. wenn du die spalte in der datenbank änderst, musst du noch lange nicht
den hashkey auch umbenennen. das definiert man ja alles im schema.

Du meinst wenn du im Schema bei "add_column(s)" den "accessor" Hash key änderst? Okay das wäre eine Möglichkeit an der ich nicht gedacht habe.

Quote
und jetzt sag mir mal, wo der unterschied ist (halb pseudo-code):

Code: (dl )
1
2
3
$dbic->find({ user => $user });
vs.
$dein_modell->find_user($user);


Im ersten Beispiel gehst du hin und bekommst ein DBIx::Class Objekt zurück und du benutzt direkt das interne Interface von DBIx::Class. Im unteren Beispiel gibt es eine Methode "find_user" die einen Benutzer findet. Wie es diesen aber findet hängt vom Model ab.

Im unteren beispiel kann ich z.B. einfach den Datenspeicher ändern. Die Daten liegen in einer CSV Datei? In einer XML Datei? Oder in ....? Im letzten Beispiel problemlos machbar. Im oberen musst du praktisch alle Funktionalitäten von DBIx::Class nachbauen um sicher zu sein das du nichts kaputt machst, sofern du DBIx::Class wechseln solltest bzw etwas anderes nutzt. Wenn es natürlich nur das find() in diesem Beispiel ist, dann ist das ja nicht allzu kompliziert, nur können search() und andere Routinen von DBIx::Class schon ziemlich kompliziert werden. Und viele Sachen ergeben evtl. bei einem anderen Datenspeicher keinen Sinn. "prefetch" um eine andere tabelle zu laden, oder join Statements auf einer XML Datei?

Das erste Beispiel wäre vergleichbar als wenn du ein Model hast sagen wir mal das auf CSV Basiert und ein "$c->model('User')" würde dir direkt ein Text::CSV_XS Objekt zurück geben. Oder eben ein DBI Objekt oder eben in diesem Fall ein DBIx::Class Objekt. Kurz gesagt, was ist daran ein Model? Absolut gar nichts!

Gehen wir mal zur Komplizierten sachen (Aus der Doku von "DBIx::Class::ResultSet").

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 $rs = $schema->resultset('Person')->search(
undef,
{
alias => 'mother', # alias columns in accordance with "from"
from => [
{ mother => 'person' },
[
[
{ child => 'person' },
[
{ father => 'person' },
{ 'father.person_id' => 'child.father_id' }
]
],
{ 'mother.person_id' => 'child.mother_id' }
],
]
},
);

# Equivalent SQL:
# SELECT mother.* FROM person mother
# JOIN (
# person child
# JOIN person father
# ON ( father.person_id = child.father_id )
# )
# ON ( mother.person_id = child.mother_id )


Wenn du soetwas im Controller hinschreibst was ist den dann noch der Sinn deines Models? Was abstrahiert den dann noch dein Model? Den ganzen Aufruf hiervon steht direkt im Controller, und dadrunter wird ja sogar gezeigt wie es im SQL ausschauen würde. Ob du nun im Controller hingehst und dann dort obiges hinschreibst oder eben ob du das SQL direkt hinschreibst (weil dein Model pures DBI ist) macht keinen großen Unterschied mehr.

Ein echtes Model hast du einfach nicht dadurch. Ein Model das ja eigentlich den Datenzugriff intern machen soll wovon du nichts siehst und das es für dich abstrahiert. Mit dem Sinn dahinter das du das Model ohne Probleme erweitern/austauschen kannst ohne das deine Applikation bricht.

Eine eigene ResultSet Class zu machen wäre schonmal der Anfang, und obrige Sache in eine Methode zu packen die du dann nur noch aufrufst. Das hat dann auch den Vorteil das das geschriebene wiederwendbar ist, und solltest du am Statement irgendwo etwas anfassen müssen änderst du es Zentral an einer Stelle. Nicht in evtl. fünf Stellen in irgendwelche Controller.

Es stellt sich hier nur die Frage. Wieviel musst du letztendlich tun damit du wirklich ein Model hast? Nur bei komplizierten Sachen?

Quote
das schöne ist ja, dass du sowas ein einziges mal im schema definierst.
nach deinem modell müsste man fur jede spalte eine eigene methode schreiben.

Hmm, nein. Ein find() geht ja nur auf PKs oder UNIQUE Keys warum sollte ich dann für jede Spalte eine eigene Methode schreiben? Und auch so macht es kein Sinn, warum sollte ich z.B. ein find_password() hinzufügen? Schonmal einen Eintrag anhand seines Passwortes ausgelesen?

Quote
wenn du gerne solche methoden schreibst, hält dich natürlich niemand davon ab,
aber ich bin da eher faul.

Naja du solltest ja selber Wissen das es zwei arten von Faulheit gibt.

Einmal die Faulheit es vernünftig zu machen und es schnell Quick & Dirty zu machen womit man sich im Moment sehr viel Zeit spart.

Und einmal das Faul sein womit man es gleich von Anfang an richtig macht. Aber sich dann in Zukunft viel Arbeit erspart.

Das letztere Faul sein ist ja eher das was man anstreben sollte, nicht das erste.

Ich möchte hier darüber Diskutieren welcher Weg besser ist. Da ich mir darüber selber noch nicht ganz sicher bin. Den weg den du gehst kommt mir aber nicht toll vor. Mein weg aber auch nicht da es evtl. viel schreibarbeit erfordert und einen hohen aufwand. Die Frage ist halt eher mache ich es "Best Practices" schreibe ich ein Model das wirklich den namen "Model" verdient, oder nutze ich einfach was da ist. Habe es zwar jetzt einfach, und komme schnell zum Ziel, verzichte dann aber darauf wirklich das "Model" austauschen zu können und es nicht mehr groß verändern zu können. Und wenn ich es doch mache was muss ich alles beachten damit ich wirklich ein Model habe?

Der Weg, einfach nur das Schema erzeugen und DBIx::Class direkt zu nutzen ist für mich kein echtes Model.

Vergleichbar hierzu kann man auch sagen. Warum so viel schreibarbeit und ein Model, View Controller voneinander trennen? Hey ich mache alles im View, SQL auslesen, keine Abstraktion und baue dort direkt das HTML auf. Also praktisch PHP. Ist am kürzesten und geht wohl am schnellsten...




Auch eine andere kleine Diskussion die ich mit meinen Arbeitskollegen vor kurzem hatte ist z.B. die Verwendung von HTML::FormFu.

Sicherlich ist es nett, aber die ganze Validierung die es macht ist eigentlich im Controller an der falschen Stelle.

Wenn ich Beispielsweise einen benutzer habe mit "uid", "gid", "email" etc. und nur bestimmte ranges erlaubt sind warum sollten diese in HTML::FormFu stehen? Die gültigkeit der Werte gehört doch eigentlich in das Model. Das Model selber muss Wissen welche Werte gültig sind, und nicht irgendeine Controller Methode.

Das hat dann auch den Vorteil wenn sie im Model sind das man sie nicht "vergessen" kann.

Zumal bei manchen Validierungen eh das Model befragt werden muss. z.B. "Gibt es den benutzer schon?" oder ob Felder wirklich "Unique" sind etc.

So wie es eigentlich sein müsste ist man bekommt die Daten vom benutzer, fügt sie mithilfe des Modells hinzu, und das klappt dann oder es gibt zurück was nicht klappte und warum, und man präsentiert dies im Fehlerfall dem benutzer.


Wie ich letzteres aber umsetze weiß ich auch noch nicht genau. Alle Methoden zum Setzen/Lesen überschreiben? Die "Inflate/Defalte" sachen nutzen die DBIx::Class bietet? Oder hat DBIx::Class noch mehr möglichkeiten, muss ich mich damit mal mehr auseinander setzen.
Nicht mehr aktiv. Bei Kontakt: ICQ: 404181669 E-Mail: perl@david-raab.de
<< >> 4 Einträge, 1 Seite



View all threads created 2009-02-01 17:39.