Ankündigung

Einklappen
Keine Ankündigung bisher.

Models/Datenbankabstraktion... wie?

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Models/Datenbankabstraktion... wie?

    Hallo zusammen

    ich habe mal ein seltsames Problem und schiebe es mal unter Einsteiger da es so wenig strukturiert sind. Die Frage schwirrt schon seit Ewigkeiten in meinem Kopf rum und ich versuche sie mal an einem Beispiel zu erklären.

    Nehmen wir an, ich arbeite mit einem beliebigen Framework an einem beliebigen Projekt.

    Hier habe ich ein Problem wenn ich Objekte haben möchte die Daten enthalten und gleichzeitig weitere Funktionen, sie aber trotzdem Kapseln und von der Datenbank fernhalten will. Was man oft sieht sind reine Datenobjekte nach dem Motto es gibt eine "Entity" Tabelle und dann werden automatisch Models und Objekte erzeugt die die Spalten als public Attribute haben. (Active Record wenn ich nicht irre).

    Was ist nun aber wenn ich komplizierte Logik in diesem Objekt haben will? Einige Eigenschaften private ohne getter Methoden die das interne Verhalten des Objekts beeinflussen? Nehmen wir als Beispiel ein komisches Objekt mit einem privaten "typ" Attribut (Löwe/Maus) und einer Methode "saySomething()".

    PHP-Code:
    class Animal
    {
        private 
    $type;
        

        public function 
    saySomething()
        {
            if (
    $this->type == 'Maus') {
                echo 
    'piep!';
            } else {
                echo 
    'ROAR!';
            }
        }

    Dieses Objekt möchte ich nun speichern und wiederherstellen können. Die Tabelle dazu ist "animal" mit einer id und dem typ als varchar. Dazu noch ein paar sinnlose extra Attribute wie Größe oder sowas die ruhig public sein können.

    Im Moment löse ich es so:
    Ich habe eine Model Klasse die statische Methoden zur Verfügung stellt um Objekte zu bekommen. Zum Beispiel "Model_Animal::getById(2)" gibt das Animal Objekt mit den Daten aus ID 2 zurück. Dafür fragt es die Daten aus einer DB ab und erzeugt das Objekt indem es ihm alle Attribute im Konstruktor übergibt. Dann gibt es im Model eine statische "save" Methode dem das zu speichernde Objekt übergeben wird. Im Objekt gibt es eine "exportData" Funktion die einfach alle Attribute als Array ausliefert (damit man auch private Attribute speichern kann). Das finde ich alles widerlich hässlich, grade die exportData Funktion :/ Nur die Frage, gibt es da eine bessere Methode? Ein "Data" Objekt als Attribut der Animal Klasse das wie eine Verbindung zur Datenbank funktioniert? Irgendwie muss man zum speichern ja wieder an private Attribute kommen.

    Mhm, wie man merkt ein unstrukturiertes Problem ohne wirklichen Lösungsansatz von mir. Tut mir leid :/

    Gruß

  • #2
    Schau dir mal http://www.php.de/tutorials/54558-tu...l-und-oop.html an, da sollte einiges er/ge-klärt werden.

    Kommentar


    • #3
      Habs nun nicht überflogen, aber ich denke nicht. Die dort vorgestellten Klassen sind sehr nah an einer Datenbank und haben keine sinnvollen privaten Eigenschaften, keine komplizierte Logik etc. Ich möchte eben keine datenbanknahen Objekte haben sondern logische.

      Kommentar


      • #4
        Sowas: ORM - php bar ?

        Kommentar


        • #5
          Ich denke, hier sollte man die Trennung zwischen Business-Schicht und Domänen-Objekten ziehen. MVC wird hier oft gründlich falsch interpretiert. Models, die direkt auf die Datenbank zugreifen sind bullshit und nur aus Faulheit einen DataMapper zu implementieren entstanden. Active Record ist in OO-Applikationen Mist!

          Auch wenn es thematisch zu deinem Projekt nicht passt, lies dir mal die Artikel Objektorientiertes Design eines Gästebuchs und Objektorientierte Implementierung eines Gästebuchs durch, dort könntest du vielleicht ein paar Anregungen für die Implementierung ziehen.

          Sollte dir das nicht weiterhelfen, können wir gerne nochmal drüber diskutieren. Ich denke, das Thema gehört sogar fast in das Software-Design-Forum. Aber mal sehn, ...
          Viele Grüße,
          Dr.E.

          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          1. Think about software design [B]before[/B] you start to write code!
          2. Discuss and review it together with [B]experts[/B]!
          3. Choose [B]good[/B] tools (-> [URL="http://adventure-php-framework.org/Seite/088-Why-APF"]Adventure PHP Framework (APF)[/URL][URL="http://adventure-php-framework.org"][/URL])!
          4. Write [I][B]clean and reusable[/B][/I] software only!
          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

          Kommentar


          • #6
            Hey, ich habs nicht vergessen. Danke für die Links, lese ich mir bis heut Abend durch und melde mich dann wieder.

            Übrigens nettes neues Design, das alte war doch etwas seltsam .

            Kommentar


            • #7
              Ok, mittlerweile habe ich immerhin Zeit gefunden die Seiten einmal zu überfliegen (bin ziemlich krank). Was ich verstanden habe (oder denke verstanden zu haben) ist, dass du quasi zwei Datenquellen nutzt - die Domainenobjekte in der Business Schicht und eine OR-Datenbank in der Datenschicht - und einen DataMapper der zwischen den Domainenobjekten und der Datenbank "dolmetscht" (also Domainenobjekte aus der Datenbank erstellt und sie wieder in die Datenbank speichert).

              Dabei sind deine Domainenobjekte jedoch wiederrum nur reine Datenhalter ohne viele Funktionen. (Verstehe ich das richtig? Gibt es auf dieser Ebene keine komplexen Funktionen und nur Datenhalter?) Außerdem schreibst du sie besitzen zu jedem (privaten) Attribut getter und setter Methoden. Der generische OR-Mapper wird einmal konfiguriert und funktioniert dann, sehr praktisch. Trotzdem löst das ja leider nicht mein Problem (wie nicht öffentlich sichtbare private Eigenschaften speichern zum Beispiel). Ist die Möglichkeit einen rein internen Zustand zu haben der aber das Verhalten nach außen beeinflusst so abwegig für ein Objekt?

              Mir fällt aber auch kein wirklich gutes Beispiel für mein Problem ein ohne dass man mich nachher noch auslacht wegen meiner aktuellen Lösung. Wobei, wie ist das zum Beispiel mit der ID eines Eintrags? Das ist ja ein komplett sinnfreies Attribut für die Business Logik und nur wichtig in der Datenschicht. Wahrscheinlich steht es auch in dem Tutorial und ich habe es überlesen, aber wo und wie wird die ID mitgeschleppt? Wie kannst du entscheiden ob ein Eintrag den der OR-Mapper speichern soll neu ist oder geupdated wird? Wie würde das GB einen einzelnen Eintrag per ID anzeigen können?

              Kommentar


              • #8
                Hallo cetalian,

                dass du quasi zwei Datenquellen nutzt - die Domainenobjekte in der Business Schicht und eine OR-Datenbank in der Datenschicht
                Datenquellen ist die falsche Bezeichnung. Es sind zwei verschiedene Domänen-Objekt-Modelle, die mit Hilfe eines DataMappers der Anwendung zur Verfügung stehen. In komplexeren Anwendungen ist übrigens usus, die Daten anders abzuspeichern als in der Anwendung zu "präsentieren". Das ist im Wesentlichen auch Kern des Domain-Object-Pattern. Im Fall des Gästebuchs ist das sicher auch anders lösbar, ich wollte jedoch genau an diesem Beispiel aufzeigen, wie das in komplexen Applikationen konzeptionell funktionieren kann.

                Dabei sind deine Domainenobjekte jedoch wiederrum nur reine Datenhalter ohne viele Funktionen. (Verstehe ich das richtig? Gibt es auf dieser Ebene keine komplexen Funktionen und nur Datenhalter?)
                Komplexe Logik hat in Domänen-Objekten nichts verloren! Das muss Teil der Business-Schicht sein, die mit Hilfe der Domänen-Objekte die relevante Geschäfts-Logik abbildet. Sicher kann in einem Domänen-Objekt Benutzer eine Methode getDisplayName() den Namen aus dem Vor- und Nachnamen einer Person zusammensetzen, und falls beide nicht vorhanden sind, die E-Mail-Adresse zurückgeben, das fällt aber nicht unter "komplexe Logik".

                Außerdem schreibst du sie besitzen zu jedem (privaten) Attribut getter und setter Methoden.
                Das ist quasi JAVA Bean Konvention und macht lediglich die API lesbarer gegen über einer getProperty($name)-Methode.

                Der generische OR-Mapper wird einmal konfiguriert und funktioniert dann, sehr praktisch. Trotzdem löst das ja leider nicht mein Problem (wie nicht öffentlich sichtbare private Eigenschaften speichern zum Beispiel). Ist die Möglichkeit einen rein internen Zustand zu haben der aber das Verhalten nach außen beeinflusst so abwegig für ein Objekt?
                Der GORM ist für das Verwalten von Objekten der Datenhaltung zuständig. Deshalb ist er per Konvention darauf angewiesen, dass sich das Domänen-Objekt extern repräsentiert. Im GenericDomainObject ist das generisch darüber gelöst, dass es per getProperties() alle definierten Attribute des Objekts - wie von der Anwendung befüllt - ausliest und gemäß des konfigurierten Daten-Modells persistiert.
                Sofern ein Objekt private und nach Aussen nicht sichtbare Zustände hält, gehören diese entweder in das Model einer Anwendung (=! Domänen-Objekt) oder muss nicht persistiert werden. Sollte es persistiert werden, muss der Mapper darauf Zugriff haben.

                Mir fällt aber auch kein wirklich gutes Beispiel für mein Problem ein ohne dass man mich nachher noch auslacht wegen meiner aktuellen Lösung.
                Hier lacht keiner, dafür sorge ich! Es wäre - um das Thema wirklich sinnvoll zu diskutieren - sogar notwendig, dass du deinen Anwendungs-Fall beschreibst. Ohne diesen kann ich dir nur allgemeine Ansätze näherbringen.

                Wobei, wie ist das zum Beispiel mit der ID eines Eintrags? Das ist ja ein komplett sinnfreies Attribut für die Business Logik und nur wichtig in der Datenschicht. Wahrscheinlich steht es auch in dem Tutorial und ich habe es überlesen, aber wo und wie wird die ID mitgeschleppt?
                Das Attribut wird beispielsweise dann benötigt, wenn du von einer Liste mit Objekten auf eine Detail-Seite verlinken willst. Um hier nicht unnötig ein Mapping zwischen Datenbank-interner Repräsentation und der Repräsentation der Business-Schicht einziehen zu müssen, wird gerne der Einfachheit wegen die DB-Id verwendet. Dagegen spricht IMHO auch nichts, denn es ist nunmal der eindeutigste Identifier eines Objekts.

                Wie kannst du entscheiden ob ein Eintrag den der OR-Mapper speichern soll neu ist oder geupdated wird? Wie würde das GB einen einzelnen Eintrag per ID anzeigen können?
                Hier behelfe ich mir aus Performance-Gründen mit einem einfachen Trick: ist die ID des Objektes vorhanden, so muss ich ein Update durchführen, ist die ID nicht vorhanden, wird das Objekt angelegt. Simpel, aber es hilft.
                Viele Grüße,
                Dr.E.

                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                1. Think about software design [B]before[/B] you start to write code!
                2. Discuss and review it together with [B]experts[/B]!
                3. Choose [B]good[/B] tools (-> [URL="http://adventure-php-framework.org/Seite/088-Why-APF"]Adventure PHP Framework (APF)[/URL][URL="http://adventure-php-framework.org"][/URL])!
                4. Write [I][B]clean and reusable[/B][/I] software only!
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                Kommentar


                • #9
                  Ok, sorry dass ich immer so spät antworte, Zeit ist Geld in den Semesterferien . Gut, bringen wir mal meinen Anwendungsfall hier rein, einen Prototypen für ein Browsergame (hey, lachen verboten! :P).

                  Es geht um den männlichen Kampf, Mensch gegen Zielscheibe, auf Leben und Tod. Dabei gibt es eine "Fight"-Klasse, die quasi die Welt repräsentieren soll. Eine Entity Klasse die beide Kontrahenten darstellt (per privatem Attribut "type" und "enemy" in zwei Lager geteilt). Fight und Entity sind per "participants" (array) bzw "fight" verbunden.

                  Nun habe ich einige Models die diese Objekte erstellen, z.b. Model_Entity mit der statischen Methode "getById". Die Entity und Fight Objekte sind größtenteils Datenhalter, es gibt aber auch Logik wie z.b. eine Entity nutzt einen Skill (wird jede Runde vom Fight Objekt aufgerufen).

                  Meine Probleme damit:
                  a) Diese doppelte Nutzung stört mich. Es sieht seltsam aus in einem Objekt was eine Lebensform beschreiben soll eine "nutzlose" Datenbankid zu sehen. Sollte ich hier vielleicht eher reine Datenhalterobjekte erstellen (für jede Entity ID eines) und die logischen Entities nur auf diese verweisen lassen?

                  b) Das erstellen der Objekte ist auch noch semioptimal. Wenn ich eine Entity per ID auslese wird der passende Kampf nicht automatisch erzeugt. Damit habe ich im "besten" Fall ein Problem wenn 2 gleiche Entities die geändert wurden wieder gespeichert werden (klassisch denke ich) und im schlimmsten Fall fatal errors weil irgendeine Nullreferenz aufgerufen wurde.

                  Ich muss ehrlich sagen ich habe noch ein Problem dein Beispiel mit Domainenobjekten und Datenbank bzw dem ORMapper nachzuvollziehen, bzw auf meinen Anwendungsfall zu passen.

                  Gruß

                  P.S. Ich habe hier noch das relative trockene Buch zu Enterprise Design Patterns von Fowler rumliegen, ich denke ich werde mich darüber auch mal ein wenig hermachen

                  Kommentar


                  • #10
                    Hallo cetalian,

                    Nun habe ich einige Models die diese Objekte erstellen, z.b. Model_Entity mit der statischen Methode "getById". Die Entity und Fight Objekte sind größtenteils Datenhalter, es gibt aber auch Logik wie z.b. eine Entity nutzt einen Skill (wird jede Runde vom Fight Objekt aufgerufen).
                    Wo steckt dann die eigentliche Logik? Ich würde "Fight" auf Anhieb als Business-Komponente sehen, die die relevante Logik abbildet. "Entry" ist IMHO besser als "User" oder "Attendant" (erbt von "User") zu bezeichnen. Entität ist ein technischer Begriff und hat damit nichts in einem Domänen-Objekt zu suchen.

                    a) Diese doppelte Nutzung stört mich. Es sieht seltsam aus in einem Objekt was eine Lebensform beschreiben soll eine "nutzlose" Datenbankid zu sehen. Sollte ich hier vielleicht eher reine Datenhalterobjekte erstellen (für jede Entity ID eines) und die logischen Entities nur auf diese verweisen lassen?
                    Grundsätzlich musst du mal Business-Logik von Domänen-Objekten trennen, Das scheint mir bei dir nicht konsequent genug stattgefunden zu haben. Anschließend solltest du dir genau überlegen, welche Domänen-Objekte du in der Anwendung wirklich hast. Bei einem Spiel ist das sehr komplex - ich diskutiere mit einigen Leuten im APF-Forum schon länger über Datenmodelle von Online-Spielen - deswegen will das gut überlegt sein.
                    Zweitens musst du dir im Klaren darüber sein, dass diese "nutzlose" ID ein Performance-Boost ist. Möchtest du diese ID nicht in den Domänen-Objekten mitführen, so brauchst du eine weitere Mapping-Schicht, die dir Domänen-Objekte in Entity-Objekte der Datenschicht übersetzt. Hier brauchst du eine intelligente Registry, die weiß, wleche Daten wie zusammengehören und entsprechendes Handling in der Datenschicht. Das ist nicht nur aufwändiger, sondern auch Fehler-anfälliger. Ich persönlich würde das nicht tun. Denn: die eineindeutige Repräsentation eines Objekts in der Business-Schicht kannst du "auch nur" über GUIDs korrekt lösen und das ist auch nichts anderes als eine ID.

                    b) Das erstellen der Objekte ist auch noch semioptimal. Wenn ich eine Entity per ID auslese wird der passende Kampf nicht automatisch erzeugt. Damit habe ich im "besten" Fall ein Problem wenn 2 gleiche Entities die geändert wurden wieder gespeichert werden (klassisch denke ich) und im schlimmsten Fall fatal errors weil irgendeine Nullreferenz aufgerufen wurde.
                    An dieser Stelle musst du die Grundregeln der objektorientierten Entwicklung beachten. Ist ein Objekt (z.B. "Fight") auf die Existenz der Gegner angewiesen, müssen diese zum Konstruktions-Zeitpunkt existieren. Ist das nicht der Fall, fliegt eine Exception und der Kampf kann nicht stattfinden. Die Exception kann unterschiedliche Ausprägungen haben: nur ein Gegner "fehlt" oder alle Gegner "fehlen".
                    Es darf IMHO auch keine Kopplung zwischen Auslesen eines Objekts und der Erzeugung des Kampfes geben, das wäre ein Fehler in der Modellierung. Für solche Belange würde ich einen GameManager implementieren, der sich um den Ablauf des Spiels und damit auch im den Ablauf eines Kampfes kümmert. Dabei nimmt er per delegate (kann per DI injiziert werden) verschiedene weietre Komponenten (z.B. "FightManager") zu Hilfe. Dieser regelt dann den Kampf und kann sich zur Beschaffung der Daten (wenn du hier die Zuständigkeit umkehren möchtest, was durchaus sinnvoll ist) einer Daten-Komponetne (DataMapper) bedienen. Diese kann man gerne auch per DI bei der Erzeugung des Service injizieren.

                    Soweit klar?
                    Viele Grüße,
                    Dr.E.

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    1. Think about software design [B]before[/B] you start to write code!
                    2. Discuss and review it together with [B]experts[/B]!
                    3. Choose [B]good[/B] tools (-> [URL="http://adventure-php-framework.org/Seite/088-Why-APF"]Adventure PHP Framework (APF)[/URL][URL="http://adventure-php-framework.org"][/URL])!
                    4. Write [I][B]clean and reusable[/B][/I] software only!
                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                    Kommentar


                    • #11
                      Zitat von dr.e. Beitrag anzeigen
                      Komplexe Logik hat in Domänen-Objekten nichts verloren! Das muss Teil der Business-Schicht sein, die mit Hilfe der Domänen-Objekte die relevante Geschäfts-Logik abbildet.
                      Da krempelt es mir die Fußnägel hoch!

                      Deine Vorstellung von einem Domain Model verletzt wesentliche Prinzipien der sauberen objektorientierten Softwarearchitektur.

                      Deine "Business-Schicht" muss intensiv auf den Zustand der "Domänen-Objekte" zugreifen (vermutlich über getter/setter). Das ist ein schönes Beispiel für den "code smell" feature envy und führt zu viel Redundanz und Abhängigkeit.

                      Oder anders ausgedrückt: Du arbeitest genau entgegengesetzt der Faustregel "tell don't ask":
                      Procedural code gets information then makes decisions. Object-oriented code tells objects to do things.

                      Erst wenn die "Domänen-Objekte" (Entities) selbst den Großteil der Logik enthalten, lassen sie sich vernünftig kapseln. Viel "boilerplate code" für getter und setter fällt weg, und sie lassen sich später erweitern/ändern, ohne dass die "Business-Schicht" (Service Layer) umgekrempelt werden muss.
                      Der Service Layer ist dann nur noch für die großen Zusammenhänge verantwortlich, die den Fokus der Entities übersteigen und stellt ein konsistentes Interface gegenüber der Applikationsschicht zur Verfügung.

                      Martin Fowler hat es hier schön zusammengefasst: MF Bliki: AnemicDomainModel.

                      PS: Dass es oft problematisch ist, ein Domain Model mit "reichen", "aktiven" Entities mit einem ORM nach ActiveRecord-Manier zu vereinen, steht auf einem anderen Blatt...
                      U. a. deshalb bevorzuge ich den DataMapper-Ansatz, der nicht nur die Persistenz für die Entities transparent macht, sondern auch die enge Bindung zwischen DB-Tabelle und Entity aufhebt. Doctrine 2 scheint mir da auf einem guten Weg zu sein (Doctrine 1.x verfolgt den ActiveRecord-Ansatz, Doctrine 2 ist ein kompletter re-write im DataMapper-Schema und aktuell im Alpha-Stadium).

                      Kommentar


                      • #12
                        Hallo literal,

                        Herzlich Willkommen im PHP.de-Forum!

                        Deine Vorstellung von einem Domain Model verletzt wesentliche Prinzipien der sauberen objektorientierten Softwarearchitektur.
                        Da bin ich anderer Meinung.

                        Deine "Business-Schicht" muss intensiv auf den Zustand der "Domänen-Objekte" zugreifen (vermutlich über getter/setter). Das ist ein schönes Beispiel für den "code smell" feature envy und führt zu viel Redundanz und Abhängigkeit.
                        Meine Aussage ist nicht, dass ein Domänen-Objekt dumm sein muss, in vielen Fällen ist es sogar sinnvoll, dass dieses eine gewisse Intelligenz hinsichtlich seiner Natur und Repräsentation besitzt. Nehmen wir an, du modellierst in deiner Applikation zwei Währungen (Euro und Punkte (z.B. für ein Bonus-System)). So sollte beispielswiese die Währung Euro die Intelligenz besitzten in Punkte umgewandelt zu werden oder es muss auf einfache Weise möglich sein, Punkte- und Euro-Werte zusammenzuzählen und anschließend den Wert in Punkten auszugeben.
                        Nicht sinnig ist allerdings die Idee, die Incentivierung eines Bonus-System komplett im Currency-Objekt, das die Bonus-Punkte repräsentiert, abzuhandeln, auch wenn dieses die relevanten Informationen dafür hätte. Weiter ist es ebenfalls nicht vorteilhaft, diese Logik in ein Domänen-Objekt User zu verfrachten, da man von dort bequem auf alle seine Einkäufe zurückgreifen kann.
                        Es hat ferner auch nichts damit zu tun, dass das zwangsläufig zu Redundanz führt, denn eine gut strukturierte Software wird genau das vermeiden. Sofern die Business-Schicht eine Granularität erreicht, die komplexe Berechnung (z.B. die genannte Incentivierung) als granular zu behandelnden Service zur Verfügung stellt, kann diese bequem an den relevaten Stellen der Software verwendet werden.
                        Abhängigkeiten sind IMHO ein Thema, das hier recht wenig Bedeutung hat. Eine Anwendung wird immer eine explizite Abhängigkeit zu seinem Domänen Modell haben. Das ist weder Willkür noch eine zu monierende Tatsache, es ist einfach das Wesen eines solchen Modells und dient der Strukturierung und der starken Typisierung einer Anwendung. Sicher kann man sich über die getter/setter-Geschichte streiten, wenn man lieber C#'s Properties mag oder den puristischen Ansatz verfolgt, dass quasi-öffentliche Attribute auch nicht nach der JAVA-Bean-Definition getter/setter bedürfen. Nicht streitbar ist jedoch, dass diese Vorgehensweise zwangsweise zu unerwünschten Abhängigkeiten führt.

                        Martin Fowler hat es hier schön zusammengefasst: MF Bliki: AnemicDomainModel.
                        Martin Fowler kritisiert den Ansatz der "funktionslosen" Domänen-Objekte - korrekt - jedoch ist der ebenfalls kein Freund von Domänen-Objekte, die sich direkt um die Persistenz desselben kümmern. Das wiederum ist ein ebenso weit verbereitetes "Problem". Nochmal: meine Aussage ist nicht, dass es keine Logik in Domänen-Objekten geben darf, ich möchte lediglich zu Ausdruck bringen, dass sehr wohl zu überlegen ist, wie die Domäne modelliert wird / werden sollte und die Gesamt-Architektur der Software geschnitten ist. Setzt man auf granulare Services, macht die oben beschriebene Art und Weise sicher Sinn.
                        In diesem Thread ging es jedoch um Applikationen, die im Allgemeinen ohnehin relativ wenig echte Domänen-Logik beinhalten, sondern mehr oder weniger eine "Datenbank-Verwaltungs-Oberfläche" mit etwas Präsentations-Logik darstellen. Hier ist es IMHO deshalb sehr schwierig, von falscher Modellierung der Domäne zu sprechen. Vielmehr ist es wichtig, vor zu starker Bindung von Domänen-Objekten zur Datenquelle zu warnen, denn das M des MVC-Pattern wird nur allzuoft missinterpretiert.

                        Sofern du das Thema Domänen-Logik weiter ausführen möchtest - sehr gerne -, kann ich den Thread auch teilen.
                        Viele Grüße,
                        Dr.E.

                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        1. Think about software design [B]before[/B] you start to write code!
                        2. Discuss and review it together with [B]experts[/B]!
                        3. Choose [B]good[/B] tools (-> [URL="http://adventure-php-framework.org/Seite/088-Why-APF"]Adventure PHP Framework (APF)[/URL][URL="http://adventure-php-framework.org"][/URL])!
                        4. Write [I][B]clean and reusable[/B][/I] software only!
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        Kommentar


                        • #13
                          Hallo dr.e.,

                          von mir aus kannst du gern einen neuen Thread abspalten.

                          Man redet bei diesem Thema natürlich gern aneinander vorbei, weil Begriffe wie "Domain Model" in der Literatur unterschiedlich definiert sind und dann noch von jedem Entwickler mit dem Hintergrund konkreter Projekterfahrung eingeordnet und bewertet werden.

                          Hinzu kommt, und darauf können wir uns sicherlich einigen, dass es keine allgemein richtige Anwendungs-Architektur gibt, sondern dass diese immer auch fallabhängig ist. Auch Fowler schreibt ja zum Beispiel, dass er begründete Fälle sieht, auf ein Domain Model (in seinem Sinne) ganz zu verzichten.

                          Wenn du mit diesen beiden Punkten übereinstimmst, wirst du vermutlich auch einräumen, dass deine ursprüngliche Aussage, an der ich mich gestört habe, zumindest in ihrer Absolutheit ("X hat in Y nichts verloren! Das muss ...") suboptimal ist, und evtl. den weniger bewanderten Leser auf die schiefe Bahn bringen könnte.


                          Zitat von dr.e. Beitrag anzeigen
                          Martin Fowler kritisiert den Ansatz der "funktionslosen" Domänen-Objekte - korrekt - jedoch ist der ebenfalls kein Freund von Domänen-Objekte, die sich direkt um die Persistenz desselben kümmern.
                          1. Ich finde es ziemlich eindeutig, dass er auch funktions-arme Domain-Objekte kritisiert.

                          2. Die Persistenz hat damit nichts zu tun. Sie soll natürlich nicht im Domänen-Objekt implementiert sein.
                          Sie ist ja einfach ein anderer Aspekt (im Sinne von Aspektorientierter Programmierung, oder "Concern" wie in "Separation of Concerns"), der die Domänen-Objekte nichts angehen sollte. Deshalb sind ja auch ORMs nach dem Datamapper-Pattern (siehe meinen letzten Post) so charmant, weil sie die Instrumente der AOP nutzen, um non-invasiv die Domänen-Objekte zu persistieren, ohne das diese überhaupt davon wissen müssen.


                          Zitat von dr.e. Beitrag anzeigen
                          Nicht sinnig ist allerdings die Idee, die Incentivierung eines Bonus-System komplett im Currency-Objekt, das die Bonus-Punkte repräsentiert, abzuhandeln, auch wenn dieses die relevanten Informationen dafür hätte. Weiter ist es ebenfalls nicht vorteilhaft, diese Logik in ein Domänen-Objekt User zu verfrachten, da man von dort bequem auf alle seine Einkäufe zurückgreifen kann.
                          Da stimme ich dir zu. Das "Single Responsibility Principle" verbietet dies. Das ist aber überhaupt kein Argument dafür, die Logik des Incentive-Programms in eine übergeordnete Schicht zu packen, die wie ein Gott über die Domänen-Objekte wacht und über getter/setter an deren Implementation gekoppelt ist (und so habe ich deinen Ansatz bisher verstanden).

                          Als ad-hoc Lösung (sicher bei genauer Betrachtung Mist, mal sollte sich für sowas Zeit nehmen): Ein Domänen-Objekt IncentiveBenefit hinzufügen, das der User besitzen kann. Zusätzlich ein Interface IIncentiveProgramm definieren, dessen konkrete Implementationen die Berechnung durchführen. Eine Implementation von IIncentiveProgramm wird an das IncentiveBenefit-Objekt übergeben (Strategy Pattern).
                          Die Bonus-Punkte teilen ihren Wert dem IncentiveBenefit-Objekt mit, und dieses macht damit, was es will.
                          So bleibt die Logik bei den Daten und alles bleibt modular, sprich änderbar und wiederverwendbar.


                          Zitat von dr.e. Beitrag anzeigen
                          Es hat ferner auch nichts damit zu tun, dass das zwangsläufig zu Redundanz führt, denn eine gut strukturierte Software wird genau das vermeiden.
                          Es ist redunant, wenn ein Fremder (irgendein Service, Manager, was-weiß-ich aus der Logik-Schicht) etwas über die Properties eines Domänen-Objekts weiß und mit diesen arbeitet/rechnet. Dann gibt es zwei Orte, an denen das Wissen über das Domänen-Objekt sich manifestiert und auch zwei Stellen, an denen es ggf. geändert werden muss.

                          Nun aber mal ein Beispiel (vereinfacht, bitte nicht an Praxis messen). Nehmen wir einen Online-Shop, weil der einiges an Logik erfordert und nicht nur ein besseres DB-Front-end ist.

                          Zu angebotenen Artikeln sind die Versandkosten zu berechnen.

                          Du würdest es vielleicht so machen:
                          PHP-Code:
                          /**
                           * Teil der "Business-Logik"-Schicht
                           */
                          class ShippingCostCalculator
                          {
                              
                          /**
                               * @var ICarrier
                               */
                              
                          private $carrier;

                              
                          // ...

                              /**
                               * @param IItem Domänen-Objekt
                               */
                              
                          private function calcualteItemShippingCostIItem $item )
                              {
                                  
                          $grossWeight $item->getGrossWeight();
                                  
                          $packageDimensions $item->getPackageDimensions();
                                  return 
                          $this->carrier->calcualteShippingCost($grossWeight$packageDimensions);
                              }
                              
                              
                          // ...

                          * Nun gibt es vielleicht Artikel, für die Verpackungsmaß oder Gewicht nicht hinterlegt sind.

                          * Vielleicht kommt irgendwann die Möglichkeit hinzu, für einzelne Artikel alternativ eine Versandkosten-Pauschale vorzugeben.

                          * Vielleicht geben wir Verpackungsmaß und Gewicht irgendwann ganz auf, weil der Händler, der die Software nutzt, es nicht schafft, diese Werte richtig zu erfassen und zu pflegen.

                          * Vielleicht werden irgendwann auch digitale Güter vertrieben, für die es gar keine Versandkosten, geschweige denn Maß und Gewicht gibt.

                          Dies alles müsste man im ShippingCostCalculator abbilden. Jede interne Änderung bei den Artikeln müsste auch hier nachvollzogen werden. Genau diese Art von Abhängigkeit ist kein Kavaliersdelikt, sondern macht aus Software einen nicht mehr wartbaren Schrotthaufen.

                          Überlässt man jedoch die Berechnung der Versandkosten dem Artikel selbst (der sich ggf. seinerseits an ein Versanddienstleister-Objekt wendet), muss kein Außenstehender mehr wissen, dass ein Artikel Maß und Gewicht hat. Die getter für diese Eigenschaften fallen weg.

                          Digitale und physische Güter könnte man via Polymorphie abbilden. Der digitale Artikel hätte dann in der Tat gar keine Properties für Maß und Gewicht mehr.



                          Zitat von dr.e. Beitrag anzeigen
                          Abhängigkeiten sind IMHO ein Thema, das hier recht wenig Bedeutung hat. Eine Anwendung wird immer eine explizite Abhängigkeit zu seinem Domänen Modell haben.
                          Es ging mir eigentlich um die Abhängigkeiten zwischen Domänen-Objekten und dem, was du Business-Schicht nennst.

                          Die Abhängigkeit der Applikation (ich meine jetzt V und C aus MVC) zum Domain Model (im Fowler-Sinne, also lauter autonome Objekte voller Verhalten) kann man durch einen Service-Layer zwischen den beiden reduzieren. Der Service-Layer stellt - in prozeduraler Manier - Aktionen und Daten aus dem Domain Model zur Verfügung. So wie es z. B. ein Web-Service auch tut, nur eben innerhalb der Anwendung. Wenn man es so konstruiert, stimmt deine Aussage ("Anwendung ... immer ... explizite Abhängigkeit ... Domänen Modell") nicht mehr ganz.


                          Zitat von dr.e. Beitrag anzeigen
                          Sicher kann man sich über die getter/setter-Geschichte streiten, wenn man lieber C#'s Properties mag oder den puristischen Ansatz verfolgt, dass quasi-öffentliche Attribute auch nicht nach der JAVA-Bean-Definition getter/setter bedürfen.
                          Wie man es macht, ist doch egal. Die breite Fraktion der Kritiker richtet sich generell gegen den Zugriff auf die Eingeschaften eines Objekts von außen.
                          Wenn man getter/setter oder Property-Zugriff braucht, heißt das bis auf wenige Ausnahmen, dass man in seiner Architektur ein Problem hat.
                          Falls du das anders siehst, ist das vielleicht noch einen weiteren Thread wert...


                          Zitat von dr.e. Beitrag anzeigen
                          Vielmehr ist es wichtig, vor zu starker Bindung von Domänen-Objekten zur Datenquelle zu warnen, denn das M des MVC-Pattern wird nur allzuoft missinterpretiert.
                          Das kann ich nur unterstreichen. Darum finde ich das Active Record Pattern ja auch so problematisch.

                          Kommentar


                          • #14
                            Hallo Literal,

                            ich hatte leider noch keine Zeit zum antworten, würde die Diskussion aber gerne weiter führen. Du liest von mir...!
                            Viele Grüße,
                            Dr.E.

                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                            1. Think about software design [B]before[/B] you start to write code!
                            2. Discuss and review it together with [B]experts[/B]!
                            3. Choose [B]good[/B] tools (-> [URL="http://adventure-php-framework.org/Seite/088-Why-APF"]Adventure PHP Framework (APF)[/URL][URL="http://adventure-php-framework.org"][/URL])!
                            4. Write [I][B]clean and reusable[/B][/I] software only!
                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                            Kommentar


                            • #15
                              Zitat von dr.e. Beitrag anzeigen
                              ich hatte leider noch keine Zeit zum antworten, würde die Diskussion aber gerne weiter führen. Du liest von mir...!
                              Und? Nach zwei Wochen kann man ja mal eine kleine Erinnerung loslassen...

                              Kommentar

                              Lädt...
                              X