Donnerstag, 1. August 2013

Was ist eine Entität?

Im Zuge meiner Beschäftigung mit CQRS und Event Sourcing habe ich mir wieder die Frage gestellt, was denn eigentlich eine DDD Entität sei?

Nun hab ich endlich für mich eine ganz einfache Definition gefunden: Eine Entität ist ein Dictionary<string,string> mit unwandelbarer Id [1]. Eine Entität ist ein Sammlung von Schlüssel-Wert-Paar als Zeichenfolgen mit unwandelbarer Id [1].

Jup. Das isses. Mehr nicht [2].

Den Inhalt einer Entität kann man wandeln, die Id aber nicht. Und der Inhalt einer Entität ist strukturiert. Gerade die Annahme, dass Keys Pfade sein können [2], macht mich zufrieden. Nicht nur “Vorname” oder “Mindestgebot” oder “Lagerplatz” sind also Keys, sondern ebenfalls “Wohnsitz/PLZ” oder “Telefonnummern/3” oder “Positionen/2/Menge” [3].

So ein Dictionary ist ein Dokument, d.h. es ist selbstgenügsam (self-contained). Alles, was dazugehört, steht drin. Was nicht drin steht, gehört zu einer anderen Entität. So einfach ist das.

Was Sie als Daten bei einem Key hinterlegen, ist Ihre Sache. Das kann so umfangreich und strukturiert sein, wie es will. Per definitionem ist das aber keine Entität, weil es eben ein Wert in einer Entität ist. Werte als string [4] und nicht als object zu definieren ist mir deshalb sehr wichtig. Dadurch ist ausgeschlossen, aus einer Entität Objekte zu referenzieren.

Entitäten dürfen allerdings Ids anderer enthalten. Solche Entitätsreferenzen sind ok, weil sie sich auf das wahrhaft Unwandelbare beziehen [5]. Beispiel:

var frau = new Dictionary<string,string>{{$$id, "10"}, {"Vorname", "Eva"}, ...};
var mann = new Dictionary<string,string>{{$$id, "0815"}, {"Vorname", "Adam"}, ...};

frau["partner"] = mann["$$id"];
mann["partner"] = frau["$$id"];

Mit der Definition “Eine Entität ist ein Dictionary<string,string>” ist es nun ganz einfach herauszufinden, ob ein zusammengesetzter Wert eine Entität ist oder nicht. Fragen Sie sich einfach, ob Sie den Wert einem oder mehreren Keys in einer anderen Entität zuweisen würden. Beispiel:

Ist der zusammengesetzte Wert

Person("Vorname": "Peter", "GebDat": "1967-05-12", "Geschlecht": "m")

eine Entität? In manchen Zusammenhängen mag das so sein:

var person = new Dictionary<string,string>{{"$$id", "7654}, {"Vorname", "Peter"}, ...};
var einwohner = new Dictionary<string,string>{{"$$id", "0192"}, {"Person", "7654"}};

In anderen wieder nicht. Dann ist er Teil einer umfassenden Entität, z.B.

var einwohner = new Dictionary<string,string>{...};
einwohner["Person"] = "{\"Vorname\": \"Peter\",
                        \"GebDat\": \"1967-05-12\",
                        \"Geschlecht\": \"m\"}";

oder

einwohner["Person/Vorname"] = "Peter";
einwohner["Person/GebDat"] = "1967-05-12";
einwohner["Person/Geschlecht"] = "m";

Ich denke, das macht die Entscheidung einfacher. Doch auch wenn die mal daneben geht, ist es nicht so schlimm. Entitäten in dieser Form kann man auch leicht refaktorisieren. “Inline entity” und “extract entity” scheinen mir genauso probat wie “inline method” und “extract method”. Also keine Angst vor BDUF!

Außerdem lassen sich Entitäten dieser Form sehr leicht verteilt über Ereignisse denken, z.B.

Zugezogen("$$id": "9384", "Nachname": "Müller", "Straße": "Isestr. 2", ...)
Umgezogen("$$id": "9384", "Straße": "Richterstr. 17", "PLZ": "22085", ...)
Geheiratet("$$id": "9384", "Ehepartner": "0815", "Nachname": "Schmied", ...)
Geheiratet("$$id": "0815", "Ehepartner": "9384", ...)

In jedem Ereignis wird die Entität angegeben und danach nur eine Liste von Keys mit neuen Werten. Der aktuelle Stand einer Entität ergibt sich dann aus dem Summe ihrer Ereignisse.

Endnoten

[1] Ob die Id ein Eintrag mit speziellem Key in so einem Dictionary ist oder separat gehalten wird, ist mir erstmal egal.

[2] Noch einfacher könnte man natürlich sagen, eine Entität sei ein strukturierter Text, z.B. ein Json- oder XML-Dokument. Wird letztlich bei NoSql auch so gemacht. Aber das ist mir grad zu wenig griffig, wenn ich an den Umgang damit im Code denke. Mit einem Dictionary fühle ich mich etwas wohler.

Ein geschachteltes Dokument wie

<Person id="1234">
  <Vorname>Peter</Vorname>
  <Anschrift>
    <PLZ>22085</PLZ>
  </Anschrift>
</Person>

sieht mir auch komplizierter aus als eine Liste von Key-Value-Paaren:

$$id:1234
Vorname:Peter
Anschrift/PLZ:22085

Das eine kann in das andere übersetzt werden. Das reicht mir.

[3] Wem da Metadaten fehlen, der kann sie sich gern dazudenken, z.B. “Vorname:string” oder “Positionen/2/Menge:int”. Am Ende ist jedenfalls jeder Wert auf eine Zeichenkette abbildbar.

Wie solche Pfade aussehen, ist hier ebenfalls nicht wichtig. Mit der Syntax, die ich hier benutze, sind Sie ja aber vertraut.

[4] Binärdarstellungen sind nur eine Optimierung. Mit textuellen Repräsentationen von Daten kommen wir weiter. Sie sind ausreichend, bis ein konkreter Use Case etwas anderes fordert.

[5] Allerdings hadere ich hier noch mit mir. Manchmal denke ich, dass Entitäten gar keine Referenzen enthalten sollten. Wenn sie zu anderen in Beziehung stehen, dann nur innerhalb eines Kontextes - und der wird dann von einem Aggregat aufgespannt.

Ein Aggregat ist dann eine Entität, deren Zweck es ist, andere zu verbinden. Es ist ein Beziehungsraum.

So ganz traue ich mich aber noch nicht, das konsequent zu denken. Ich habe es zwar schon einmal in der dotnetpro so beschrieben - doch im Moment bin ich wieder ein wenig im Zweifel… Hm… warum eigentlich? Dazu mach ich mir am besten ein andermal weitere Gedanken.

24 Kommentare:

Anonym hat gesagt…

Eine Entität ist ein Dictionary mit unwandelbarer Id [1].

Wenn schon, denn schon; dann aber schon full monty und unabhängig:
Eine Entität ist ein Sammlung von Schlüssel-Wert-Paaren als Zeichenfolge mit unwandelbarer Id.

Somit lässt sich der Gedanke auf jede Sprache abbilden, die eine entsprechende Sammlung zulässt.

Bezüglich der Referenzen: Diese Vorgehensweise hat den Vorteil, dass auch zu einem späteren Zeitpunkt statt einem Wert eine Referenz hinterlegt werden kann, ohne an der Entität zu fummeln.

Die Verweise/Referenzen werden woanders abgehandelt werden. DCI grüßt (mal wieder :)

Insofern stimme ich dem Speichern als String zu... obwohl ich mir vielleicht auch einen int, boolean usw. vorstellen könnte. Hatte so etwas schon mal implementiert.

Ralf Westphal - One Man Think Tank hat gesagt…

Danke für die full monty Definition :-) Die gefällt mir gut. Beim Dictionary war mir auch nicht 100% wohl, weil in dem Begriff etwas Plattform steckt.

Eine solche Sammlung ist natürlich in jeder Sprache möglich, die Felder bietet. Eine solche Key-Value-Sammlung lässt sich mit einem string-Array implementieren.

int, bool etc. sind nur Optimierungen. Wer das will, kann es machen. Aber dann gleitet man schnell ab und will auch object. Und dann kommen die Referenzen wieder ins Spiel.

Anonym hat gesagt…

int, bool etc. sind nur Optimierungen. Wer das will, kann es machen. Aber dann gleitet man schnell ab und will auch object. Und dann kommen die Referenzen wieder ins Spiel.

Slippery road... ist richtig; eher dort auf den "richtigen Typ" umwandeln, wo's wichtig ist... da war jemand schon mal so schlau :D

Thomas hat gesagt…

Hallo Ralf,
für mich ist eine Entität, die reale , oder auch abstrakte Ausprägung innerhalb der Umwelt (Domäne), die in einem System abgebildet werden soll.
Deshalb haben natürlich Objekte (im Sinne von Softwareentwicklung) die Entitäten abbilden meist einen eindeutigen Identifikator (Id)
Gruß Thomas

Mike Bild hat gesagt…

Absolut! Jede "reine" Datenstruktur sind key-value-paare. Ob im Value Werte oder eine Referenz auf andere Key-Value-Paare stehen ist Desgin. Aggregates ist eine spezielle Entität. Ich verwende Aggregates als "zusammengesetzte" (z.B. projektion) Dokumente. Dabei können die Projektionen, z.B. durch Live-Projektionen oder auch als Background-Task bei Änderungen in den Entitäten) Referenzen natürlich auch gegen Ihren Value (andere Entität) "auflösen". Entitäten haben meist keine weiteren Beziehungen. Aus eine Entität kann natürlich irgendwann auch ein Aggregate werden. Auch das ist Desgin. Aggregates sowie Entitäten haben IDs. Diese können natürlich zusammengesetzt sein.( bsw. Vorname + Nachname). Ist also auch Design.

Mike Bild hat gesagt…

Ein wichter Aspekt sind natürlich die nicht genannten Value Objects. Damit wird das Bild dann auch erst komplett.

Ralf Westphal - One Man Think Tank hat gesagt…

Value Objects halte ich für überbewertet. Da ergeben sich viel zu schnell Fragen um Implementationsdetails. Und die FP erhebt ihr Haupt.

Entscheidend für mich Zusammenhänge von Daten mit eigener Identität. Wobei diese Identität eine konzeptionelle/logische ist und nichts mit Speicherplatz oder Persistenzinfrastruktur zu tun hat. Sie ist eben völlig neutral, sozusagen abstrakt.

Identität ist unwandelbar, neutral und einzigartig.

Inwiefern eine Kombination Vorname+Nachname diese Bedingung erfüllen kann, lasse ich mal dahingestellt.

Insgesamt höre ich aus deinem Kommentar auch noch für meinen Geschmack zuviel heraus, "was geht"". Damit sind wir aber auf dem Holzweg. Die Frage ist weniger "was geht?", "was ist möglich?", sondern "was soll?".

Wir brauchen weniger Möglichkeiten und mehr Grenzen und Guidelines. Das ist nicht anders als weiland beim Goto. Goto geht - aber Goto soll nicht sein. Wir haben uns mit Kontrollstrukturen wie If oder While bewusst Schranken gesetzt. Wir tun nicht mehr als das, was möglich ist. Wir haben erkannt, dass wir uns sonst verzetteln. Spaghetticode droht.

Deshalb geben wir auch nicht jedem eine Waffe in die Hand. Möglich ist das, wie die USA zeigen. Aber dann gibt es eben jedes Jahr Tausende Tote. So haben wir uns bewusst gegen das entschieden, was möglich ist.

Und so sehe ich es auch bei den Daten. Die Frage ist nicht, was möglich ist. Sie ist, was wir tun sollten, um uns nicht zu verzetteln.

In dieser Hinsicht meine ich meine Beschreibung einer Entität.

Und in dieser Hinsicht sollte die Frage beantwortet werden, ob eine Entität sich auf andere beziehen sollte oder nicht. Dass sie es technisch kann, ist unzweifelhaft. Aber sollte sie? Oder sollten Zusammenhänge von Entitäten nur in einem Aggregat hergestellt werden? Ist das der Zweck von Aggregaten?

Es geht um "Freiwillige Selbstbeschränkung" bei den Daten. Damit wir schneller zu Lösungen kommen. Weniger im Rätselraten und Abwägen verzetteln.

Klar, damit soll die Effizienz der Lösungsfindung steigen und gleichzeitig die Effizienz der Strukturen gesenkt werden - zugunsten größerer Flexibilität. Das ist auch das Thema des Event Sourcing.

Mike Bild hat gesagt…

Value Objects - Halte ich für ein sehr wichtiges Konzept um die Frage von Datem-Beziehungen zu klären. FP und "immutable" spielen in diesem Kontext für mich eine untergeordnete Rolle. Implementierung ist dabei auch nebensächlich, da das vom Konzept her mit jeder Sprache umsetzbar ist. Entscheidend ist nur das es sich um "Dinge" ohne ID handelt. Hier ist der "Wert" von Bedeutung.


@Entität: Ja - "Dinge" mit ID. Eindeutig, neutral und einzigartig - Systemweit.

@Sollten Entitäten untereinander Beziehungen haben? Meiner Meinung nach nicht. Dafür sind Aggregates als "besondere" Entitäten da. Das sind hier Zweck. -> Ein bisschen vereinfacht: Aggregates = Consistency Boundary!

@Sollten Entitäten "Beziehungen" mit Value Objects haben? Ja klaro! Genau dafür sind IMHO sie so wichtig.


Es geht mir auch nicht um "Was geht?" Mir geht es ebenfalls um das Konzept mit seinen Pro und Kontra.

@"Freiwillige Selbstbeschränkung" - Da bin ich ganz bei Dir. Ja einfacher desto besser. So einfach wie möglich - nur nicht einfacher. ;-)

@Flexibilität: Auch hier - Klaro - Deshalb Events + Event-Sourcing + Projections = Aggregates = Documents.

Wie Du auch bemerkst ist es mit dem DDD Ansatz (ähnlich OOA/OOP) doch etwas umfangreicher im Design und dann letztlich in der Umsetzung. DDD (und IMHO auch OOA) setzt eine "stabile" Domäne voraus. Vielleicht klappt das gut mit 20 Jahre "alten" Systemen (Healthcare, Automative, Flugzeugbau) in denen Anforderungen klar sind und halbwegs "konstant" bleiben. Für meine häufigsten Anwendungsfälle funktioniert das nur zum Teil gut und bleibt eher ein Kommunikations- / Analysemittel.

PS: Ich hab nix gegen GOTO ;). Vorsichtig eingesetzt ist das für mich gewinnbringend.

Ralf Westphal - One Man Think Tank hat gesagt…

Ich hab nix gegen Messer oder Gewehr. Vorsichtig eingesetzt sind das sehr hilfreiche Werkzeuge. Muss man nur erstens beherrschen. Und zweitens braucht es drumherum ein Wertesystem, um den Einsatz zu begrenzen. Nicht alles, was möglich ist, sollte damit getan werden. Und nicht von jedem.

Dito bei Goto und den technischen Mitteln der Objektorientierung.

Aber da sind wir wohl einer Meinung.

Aggregate als Beziehungsraum für Entitäten? Ja, damit kann ich etwas anfangen. Ich habe das auch mal genau so in der dotnetpro dargestellt.

Das bedeutet - entgegen dem, was du geschrieben hast -, dass Entitäten eben keine (!) Referenzen auf andere haben dürfen. Wieder geht es um die Begrenzung des Möglichen. Möglich wäre es, aber wir tun es nicht.

Beziehungen zwischen Entitäten werden dann einzig innerhalb eines Aggregats hergestellt.

Und daraus folgt auch, dass Entitäten Aggregate nicht kennen. Sondern nur Aggregate kennen "ihre" Entitäten.

@Value Objects: Genau wegen deiner Formulierung finde ich VOs so hinderlich. Da fliegt nämlich Konzept und technische Objektorientierung durcheinander. Da sollten wir nicht auf Evans hören. Darin liegt nicht seine Stärke.

Konzeptionell betrachtet gibt es keine VOs. Es gibt nur Entitäten mit Werten. Was du in einen Wert reinsteckst, ist deine Sache. Es ist aber immer ein Wert. Dazu hat die Entität keine Beziehung, sondern sie enthält (!) diesen Wert.

Das ist vielleicht ein feiner, aber für mich wichtiger Unterschied.

Werte sind Werte. Werte sind keine Objects. Da hilft dann auch kein "Value" als Qualifizierung. Value Object ist ein technischer Begriff. Der hat in einem Metamodell wie DDD nichts zu suchen.

Es gibt Daten: 101001101
Es gibt Werte: "Vorname":"Verena"
Es gibt zusammengesetzte Werte: Gast("Vorname":"Verena", "Alter":13)
Es gibt Entitäten: Party("$$id":"abcd", "Gäste/0/Vorname":"Verena", "Gäste/0/Alter":13, ...)

Daten sind einfach nur Bitfolgen. Werte sind Bitfolgen mit einer Bedeutung. Und Entitäten sind Werte mit einer Id.

Für den irreführenden Begriff "Value Object" sehe ich gar keinen Bedarf. Mindestens mal, weil es hier überhaupt nicht um Objekte geht.

Es geht um Daten, um Werte, um zusammengesetzte Werte, um identitätsbehaftete Werte und schließlich um Beziehungsräume für identitätsbehaftete Werte.

Kann man denn dann die Daten eines Wertes verändern? Selbstverständlich. Warum nicht?

Wer ("Vorname":"Verena") in der Hand hat, der kann daraus ("Vorname":"Maria") machen. Was das technisch bedeutet, ob dahinter ein C# struct oder eine C# class steckt... Das ist mir doch egal.

Wer auf dieser Ebene schon gleich immutability denken muss, der vermischt Belange. Immutability mag wichtig sein - nur tut sie nix zur Sache bei der Datenmodellierung.

Mike Bild hat gesagt…

Richtig Aggregates sind einer Untermenge der Entitäten. Ich erinnere mich *nicht* geschrieben zu haben, dass Entitäten auf andere eine Referenz haben dürfen.

@Value Objects: Kein Bedarf - stimmt schon zum Teil. In der Kommunikation finde ich dennoch hilfreich explizit unterscheiden zu können. Vektor, Geoposition, Tuple ... lieber VO. So bekommt ein zusammengesetzter Datentyp eben einen allgemeinen Namen. Ich finde das hilfreich.


Werte/Daten vs. Objects: Absolut! Hier wird über den Wert(e) und nicht über die ID verglichen. Ist eben ein Wert, keine Identität.




Ralf Westphal - One Man Think Tank hat gesagt…

Das Problem mit dem Begriff "Value Object" ist, dass "Object" Identität impliziert.

Und wie gesagt, die Frage nach der Immutability halte ich für orthogonal zu DDD.

Anonym hat gesagt…

Hallo Ralf,

bin per Zufall über diesen Blog gestolpert und ehrlich gesagt etwas erstaunt über die Sichtweisen. Vielleicht verstehe ich auch den Sinn des Artikels einfach nur nicht.

Eine Entität ist ein Sammlung von Schlüssel-Wert-Paar als Zeichenfolgen mit unwandelbarer Id [...] Jup. Das isses. Mehr nicht

Wir haben doch mit der Objektorientierung bereits eine Abstraktion. Wozu eine weitere?

Abgesehen davon ist diese Definition rein datenorientiert. Ihr fehlt der Typ und das zumindest in der objektorientierten Welt damit verbundene Verhalten.

Dabei sehe ich die Dinge wesentlich einfacher: Die Aufgabe besteht darin, fachliche Konzepte mit Hilfe eines Objektmodells zu beschreiben. Objektidentität ist der fachlichen Identität jedoch nicht gleichzusetzen. Unter fachlicher Identität soll hier die Identität verstanden werden, die seitens der Domäne gefordert wird. Den hierfür von Evans verwendeten Begriff "konzeptionelle Identität" habe ich hier vermieden, da Du in Deinem Artikel "konzeptionell" anders verwendest.

Die "Entity" (DDD) ist also nur die Bezeichnung für ein (Fach-)Objekt mit fachlicher Identität. Das "Value Object" analog die Bezeichnung für ein Objekt ohne fachliche Identität. So einfach wäre das:keine Zeichenketten, keine Schlüssel-Wert-Paare/Dictionaries.

Für den irreführenden Begriff "Value Object" sehe ich gar keinen Bedarf. Mindestens mal, weil es hier überhaupt nicht um Objekte geht.

Wie kann es sein, dass es im OO-Entwurf überhaupt nicht um Objekte geht? "Value Object" ist für mich ein Objekt, das einen Wert repräsentiert. Irreführung?

Das Problem mit dem Begriff "Value Object" ist, dass "Object" Identität impliziert.

Es ist doch nicht ungewöhnlich, nicht identische aber gleich(wertig)e Objekte zu haben.

Wer ("Vorname":"Verena") in der Hand hat, der kann daraus ("Vorname":"Maria") machen. Was das technisch bedeutet, ob dahinter ein C# struct oder eine C# class steckt... Das ist mir doch egal.

Wer ein Objekt mit dem Attribut Vorname und dem Attributwert "Verena" hat, kann den Attributwert durch "Maria" ersetzen. Wie das technisch in einer Programmiersprache umgesetzt wird... Das ist mir doch egal. :-)

Aber: der Wert selbst wird nicht geändert. Er wird durch einen anderen ersetzt. Würde der Wert selbst geändert werden, würden alle "Verena"s zu "Maria"s werden. Und schon sind wir an dem Punkt, warum die Unveränderlichkeit von Value Objects ganz erheblichen Einfluss hat.

Michael

Anonym hat gesagt…

Hallo Ralf,

nachdem der Kommentar nicht länger sein durfte, muss ich ihn aufteilen. Hier also der zweite Teil:

Was ist ein Aggregat? Ich kann und will hier keine Definition geben. Aber ich sehe ein DDD-Aggregat im Wesentlichen als Objektgraphen an, der Ganzes-Teile-Beziehungen darstellt, der als Einheit betrachtet wird und bei dem es eine bestimmte, als Wurzel bezeichnete Entity gibt, von der aus alle anderen Objekte des Graphen direkt der indirekt erreichbar sind. Sinnvollerweise kann man sich noch zur Auflage machen, dass der Graph ein Baum sein muss.

Selbstverständlich dürfen Entities andere referenzieren. Allerdings tauchen dabei zwei Fragen auf. Erstens, welche Entitäten außerhalb des eigenen Aggregats dürfen referenziert werden? Zweitens, welche ID verwenden?

Nach dem Prinzip "spiele nicht in den Hosentaschen fremder Leute" darf nur die Wurzel eines Aggregats (dauerhaft) referenziert werden. Für die zweite Frage gibt es zwei Möglichkeiten: fachliche ID oder Objekt-ID (=Objektreferenz)? Kopplung und Kohäsion sowie äußere Zwänge machn die Angabe eines Patentrezepts schwer.

Z.B.: Studenten-Kurs-Belegungen. Kurse sind Entitäten, Studenten sind Entitäten. Sie bilden je ein Aggregate Root. Die Belegung enthält nun Objektreferenzen zum Kurs und zum Studenten. Ob die Belegung nun als Entity oder VO modelliert wurde, spielt keine Rolle. Sie wird sich in jedem Fall außerhalb von mindestens einem der beiden Aggregate befinden. Kein Problem.

OR-Mapper, die Objektreferenzen nur innerhalb einer Datenbank beherrschen können uns beispielsweise dazu zwingen, Referenz per fachlicher Identität zurückzugreifen. Das sind Realisierungsdetails.

Michael

Mike Bild hat gesagt…

Hallo Michael,

vielen Dank für Dein Beispiel. Ich komme gleich darauf zu sprechen.

"Abgesehen davon ist diese Definition rein datenorientiert. Ihr fehlt der Typ und das zumindest in der objektorientierten Welt damit verbundene Verhalten."
...
"Kopplung und Kohäsion"...

Berücksichtigung und Design von Kopplung, Kohäsion bzw. "richtige Platzierung" von Verhalten führen ohne sehr genaues Studium von Anforderungen der Fachdomäne zwangsläufig zu Irrtümern und Kompromissen. Ein komplexes Geflecht von Abhängigkeiten entsteht. Das zeigt die Praxis - spätestens in der Weiterentwicklung.

In deinem ersten Kommentar ist Key/Value "nur" datenorientiert. Im zweiten Kommentar verwendest du Aggregates als "datenorientiert". Nun offensichtlich quält immer die Frage - Wohin mit dem Verhalten?

Zu Deinem Beispiel:
Stimmt, sehr einfach. Doch wohin mit dem Verhalten? Davon sprichst Du ja wenn du die Fahne der OO A/P hochhalten möchtest.

Nehmen wir, Deine Studenten-Kurs-Belegungen, benötigt Verhalten, d.h. nicht nur Datenbeziehungen.

Nehmen wir einfach mal - Ein Kurs darf maximal 20 Teilnehmer haben. Ein Student darf maximal 5 Kurse belegen. Keine Kurse dürfen sich "überschneiden". Wohin damit? Wie bewerkstelligen mit Class -Student, Kurs und Belegung?

Jetzt buchen natürlich alle Studenten gleichzeitig am Semesterbeginn Ihren Kurs ;). Wie verhält es sich mit *Konsistenz*? Wie verhält sich dieses System in einem Multi-Threaded/Process Szenario? Wo, Wann und Welche Art Transaktionen unter Berücksichtigung der genannten Business Rules mit Deinen 2 Entitäten + Relations-(table/object/was auch immer)?

Eine nicht ganz triviale Aufgabe wie ich finde. Der Wunsch ist nun - Entwicklung nur wenn nötig, flexibel aber einfach - möglichst ohne gaaaaaaaanz genaues Studium der Fachdomäne mit allen seinen "könnte, würde, hätte".

Grüße, Mike

Ralf Westphal - One Man Think Tank hat gesagt…

@Michael: Ich finde deine Einwände verständlich, wenn ich als Prämisse "traditionelles" OO-Denken dahinter vermute.

Aber genau darum geht es mir eben nicht. Deshalb bin ich auch kein Freund von Value Objects.

DDD hat nichts mit OO zu tun. Es wird nur immer so ausgelegt. Klar, Evans hat auch nicht viel getan, da ganz klar zu trennen. Aber deshalb ist genau dieser Teil seines Buches eben auch der schwächere.

Eine Entität ist kein Objekt im OO-Sinne. Man kann sie damit realisieren - aber genau das führt schnell in die Irre.

Aus dem Grunde habe ich Entitäten als identitätsbehaftete Key-Value Listen (Dictionary) beschrieben. Dann fällt die Trennung leichter zwischen OO-Implementationsdetail und Nachdenken über Lösungsansätze für Anforderungen.

DDD ist "Design", Entwurf, also eben nicht (!) Implementation. Ruby, JS, Java, C#: das ist Implementation.

Es muss ein Mapping geben von DDD Entwurfskonzepten nach konkreter Sprache. Aber konkrete Sprache hat nichts in Konzepten zu tun. Das ist ja schon das Problem bei UML, wo Javas Pakete in eine universelle Sprache gewandert sind - ohne dann Mappings von Paket nach C, Python oder JS zu liefern.

Du schreibst selbst:

Wer ein Objekt mit dem Attribut Vorname und dem Attributwert "Verena" hat, kann den Attributwert durch "Maria" ersetzen.

Da benutzt du Objekt synonym mit Entität. Denn genau das kann man mit einer Entität machen.

Und gleichzeitig erzeugst du Dissonanz, weil genau das eben bei einem Value Object nicht erlaubt ist.

Und was interessieren mich O/R Mapper? Die Technologie muss sich dem Konzept beugen. Dito die technische Objektorientierung. Sie ist Diener, nicht Herr.

Die Frage ist nicht, ob technische Objekte einander referenzieren können, sondern welche Arten von Referenzen ein Konzept erlauben sollte.

Und auch hier stehe ich zu meiner Dictionary-Darstellung. Da fehlt es an Referenzen im technischen Sinn. Ganz bewusst. Die Implementation einer Entität nach meinem Bild kann kein irgendwie geartetes Objekt (technische Objektorientierung) referenzieren. Gut so!

Wenn wir meinen, sie soll auch nicht "soft" per Id andere Entitäten referenzieren, dann ist das leider nur eine Regel. Physisch lässt sich da wohl nichts machen. Deshalb sollte die Regel klar und einfach sein. Ich lasse mich drauf ein: Entitäten enthalten überhaupt keine Referenzen. Das tun nur Aggregate.

Aggregate sind (!) Entitäten. Deshalb brauchen sie auch nicht - in Abweichung zu Evans - eine Root Entity. Und diese Entitäten enthalten ausschließlich Beziehungen zu und zwischen Entitäten.

Immer noch sprechen wir aber von Daten. Nur Daten. Dictionaries. Maps.

Wenn du Funktionalität willst... Dann hat die jenseits physischer Konsistenzsicherung nichts darauf zu suchen. Jedenfalls nicht bei einer technischen Umsetzung mit üblichen OO-Mitteln. Das wäre ein Widerspruch zu den Prinzipien SRP und SoC.

Anonym hat gesagt…

Hallo Mike, hallo Ralf,

in der Tat bin ich von der üblichen objektorientierten Anwendung von DDD ausgegangen. Nachdem es aber darum gar nicht ging, bin ich schon raus "aus der Nummer".

Trotzdem noch ein paar Anmerkungen:

Eine Entität ist kein Objekt im OO-Sinne.

Eine Entität soll also eine Sammlung von Schlüssel-Wert-Paaren von Zeichenketten sein aber kein OO-Objekt, denn letzteres wäre nur eine Realisierung, ersteres aber nicht? Jetzt bin ich in der Tat verwirrt. Ist es denn nicht in erster Linie das "Verhalten", das im Allgemeinen ein Objekt von der Schlüssel-Wert-Paare-Sammlung unterscheidet?

Da benutzt du Objekt synonym mit Entität. Denn genau das kann man mit einer Entität machen.

Wie Du schon geschrieben hast, kann ich Entitäten mit Hilfe von OO-Objekten realisieren...

Und gleichzeitig erzeugst du Dissonanz, weil genau das eben bei einem Value Object nicht erlaubt ist.

... und genauso kann ich "Value Object" mit Hilfe von OO-Objekten darstellen. Das hat nichts mit Dissonanz zu tun. Dass OO-Objekte technisch auch alle Eigenschaften von Entitäten besitzen, ist eine andere Sache.

Die Technologie muss sich dem Konzept beugen.

Ideologisch stimme ich 100 %-ig zu. Wir agieren leider zum einen meist nicht auf der grünen Wiese und zum anderen in erster Linie in einer Welt, in der Geld, Konkurrenz und Politik die gewichtigeren Entscheidungsfaktoren sind. Daher habe ich auch von "äußeren Zwängen" geschrieben. Wenn der Kunde partout eine Technologie X fordert, zieht das Argument, dass die Technologie Diener sein muss und nicht Herr, nicht wirklich. Leider.

Aggregate sind (!) Entitäten. Deshalb brauchen sie auch nicht - in Abweichung zu Evans - eine Root Entity.

Ersteres ist in erster Linie Ansichtssache. Und ja, formal benötigen wir den Begriff "Root Entity" natürlich nicht (genauso wenig wie "Aggregat"). Mir gefällt die Trennung von Evans besser, eben weil zwischen Aggregat und Root Entity unterschieden wird. Für mich entspricht es auch eher dem Prinzip divide et impera.

Wenn du Funktionalität willst... Dann hat die jenseits physischer Konsistenzsicherung nichts darauf zu suchen. Jedenfalls nicht bei einer technischen Umsetzung mit üblichen OO-Mitteln. Das wäre ein Widerspruch zu den Prinzipien SRP und SoC.

Inwiefern?

Danke

Michael

Ralf Westphal - One Man Think Tank hat gesagt…

@Michael:

Eine Liste von Schlüssel-Wert-Paaren ist kein Objekt. Es ist nur eine Liste von Schlüssel-Wert-Paaren. Die kann man auch auf einer Karteikarte halten. Voilà, Entität :-)

Ich habe nichts dagegen, bei der softwaretechnischen Umsetzung solch einer Sammlung Funktionalität mitzugeben. Aber ich plädiere dafür, sie zunächst ohne zu denken. Und wenn Funktionalität, dann nur ganz bestimmte: eben nur solche, die der physischen (!) Konsistenz dient.

Wie gesagt: technisch geht es anders. Aber wir sollten uns Regeln geben, wie wir mit dem technisch Machbaren umgehen. Das ist so eine Regel.

Genauso mit den "Value Objects": Das, was da gewollt wird - ein Container, dessen Inhalt man nach Befüllung nicht mehr ändern kann -, sollte unabhängig von OO Jargon benannt werden.

Klar, technisch umsetzen kann man es mit OO Mitteln. Aber eben auch anders.

Dazu kommt, dass dem Begriff "Objekt" von Hause aus die Veränderbarkeit anhaftet. Diese Eigenschaft durch den Zusatz "Value" zu subtrahieren, halte ich für sprachlich völlig kontraproduktiv. Quasi Oxymoron oder contradictio in adiecto.

Du gehörst auch der Fraktion an, die einem Kunden immer alles gibt, was er sich wünscht? Der sagt C# und du gibst ihm dann traditionelle OOP? Aus welchem Jahr? Vor 1995?
Ne, tut mir leid, das Argument halte ich für völlig irrelevant. Damit kannst du am Ende alles rechtfertigen. "Der Kunde wollte aber, dass wir nur mit Active Record statt mit Domänenmodellen arbeiten." - Na, dann hast du ja dein Lehrbuch. Das schreibt der Kunde. Dann müssen wir hier nicht diskutieren.

Du findest die Trennung bei Evans besser? Aber welche Trennung denn? Da gibt es nur Entitäten - und irgendwie ein Aggregat, dass eine Root Entität hat und selbst deshalb anscheinend nichts tut?
Hm... Das finde ich konzeptionell wiederum zu schwach differenziert.
Ich gebe dem Aggregat selbst mehr Verantwortung.

Alle nehmen es doch so wichtig. Dann sollte sich das auch in der Verantwortung ausdrücken. Anders kann man seine Bedeutung als transaction boundary auch nicht ausdrücken, finde ich.

Ein Stack ist ein schönes Beispiel, wo traditionelle Objektorientierung Sinn macht: Es gibt ein Interface, das in Bezug auf Daten gewisse Regeln aufstellt. ADT. Wie die Daten konkret gehalten werden, ist egal (Liste, Array...). Einzig wichtig, dass man darauf LIFO zugreifen kann. Die Funktionalität "auf den Daten" sorgt dafür, dass in Bezug darauf Konsistenz herrscht.

Und nun nicht ein Stack, sondern Daten einer Rechnung. Ich meine, die sollten wir auch nur wie eine ADT behandeln. Auf ein Rechnungsobjekt gehören also nur Methoden zur Sicherung der physischen Konsistenz. Beispiele: Es muss immer eine Rechnungsnummer und einen Rechnungsempfänger geben. Für jedes Produkt gibt es nur eine Rechnungsposition. (Falls ein Produkt mehrfach hinzugefügt wird, werden die Mengen addiert.)

Das sind Regeln, die mit Code durchgesetzt werden müssen. Dieser Code gehört direkt auf das Rechnungsobjekt. Er macht aus ihr quasi einen ADT. Mehr aber nicht. Er weiß eigentlich nicht, um welches Business es geht. Er konzentriert sich auf eine Form. Und genau das ist eine sehr klare Verantwortlichkeit. Dabei sollte es bleiben.

Weitere sinnige Funktionalität im Zusammenhang mit Rechnungen, gehört dann nicht mehr auf das Objekt, das die Rechnungsdaten direkt hält. Beispiele: Wenn der Rechnungsbetrag eine Schwelle überschreitet, verlängert sich das Zahlungsziel. Wenn Produkte einer bestimmten Kategorie enthalten sind, entfallen die Versandkosten. Es gibt einen Rabatt auf bestimmte Artikel für Rechnungsempfänger aus einem PLZ-Gebiet.

Das ist natürlich uferlos. Und genau deshalb ist es wichtig, eine klare Grenze zu ziehen. Auf das Objekt mit den Daten gehören nur sehr wenige Methoden. Alles andere muss woanders untergebracht werden.

Daten sind Daten sind Daten. Und nur weil wir technisch Funktionalität direkt mit Daten verbandeln können, sollten wir das nicht ausufern lassen. Wo aber ist die Grenze? Ich habe eine gezogen.

Mike Bild hat gesagt…

Jepp!

Interface mit Funktionen - nur Funktionen/Methoden! Zustand wird nicht veröffentlicht. Zustand sind im einfachsten Falle Key/Values. Diese haben mit (Konsistenz)regeln und Verarbeitungsprozessen nichts zu tun.

Was bleibt im einfachsten Falle?
Funktionen auf Key/Values

Wie machen?
Meiner Meinung nach sind Funktionen auf/mit Kontext (Monaden) oder auch Projektionen auf Event-Logs (Event Sourcing) in der Early Stage ein guter Weg. OO (A/P) und auch DDD braucht meiner Meinung nach mehr Stabilität in der Domäne und etwas mehr Vorbereitung, Analyse und Design.

m2c,
Mike

Anonym hat gesagt…

Hallo Ralf, hallo Mike,

um zu überprüfen, ob ich Euch richtig verstanden habe, ein Beispiel: Sagen wir einmal, ich möchte simple Telefonabrechnungen realisieren.

Dabei habe ich es mit verschiedenen Gesprächstarifen zu tun. Wir machen es ganz einfach und gehen einmal davon aus, dass es sich rein um Minutenpreise handelt.

Wenn ich Euch jetzt richtig verstanden habe, ist die Denke die folgende:

Erstmal geht es darum, Preise zu berechnen. Das macht eine Funktion. Was brauche ich dazu? Gut, den Tarif und die Gesprächsdauer. Hierfür gibt es dann ein Interface:

--> Interface IErrechneGeschprächspreis: errechneGesprächspreis(tarif, dauer):Betrag

Ein Tarif ist eine Entity. Diese wird definiert durch Schlüssel-Werte-Paare. Daraus folgt:

--> Entitätstyp Tarif: id, name, minutenpreis

Das ganze soll jetzt per OO-Methoden umgesetzt werden. Funktionalität auf der Entity nur zur Konsistenzsicherung, d. h. die Entity wird zum OO-Objekt, indem dieser ein entsprechender Konstruktor spendiert wird und die Eigenschaften gekapselt werden durch Getter/Setter. Name und Minutenpreis müssen vorhanden sein, Minutenpreis muss positiv sein.

Das Interface muss auch noch implementiert werden. Dazu führen wir mal eine Klasse Gesprächspreisrechner ein. Diese enthält dann eine Methode, die einen Tarif und die Dauer des Gesprächs bekommt und mit den Betrag liefert.

Sehe ich das richtig?

Danke und Gruß

Michael

Ralf Westphal - One Man Think Tank hat gesagt…

Eine Entität Tarif(id, name, minutenpreis) klingt ok. Denn derselbe Tarif kann zu unterschiedlichen Zeiten ja verschiedene Minutenpreise haben.

Auf logischer/konzeptioneller Ebene ist das nur eine Liste von Name-Wert-Paaren.

Wenn wir das aber codieren, dann können wir etwas anderes draus machen als ein Dictionary. Es darf auch eine richtige ;-) Klasse sein.

Nur eben Vorsicht, was darauf für Funktionalität liegt. Auch eine Klasse Tarifentität{} hätte keine Methode Gesprächspreis_berechnen()! Das ist ja Logik. Das hat nichts mit der Konsistenz der Daten in der Entität zu tun.

Konsistenzfunktionalität könnte zum Beispiel darauf achten, dass der Minutenpreis nicht <0 ist.

Aber wer berechnet nun die Gesprächsdauer? Hm...

Das könnte ein Service sein (um in DDD Speak zu bleiben). Du hast dafür ein Interface definiert, das nur eine Funktion enthält. Kann man machen. Ist für den Punkt, um den es geht, aber egal. Entscheidend ist, dass es eben ein anderes Objekt ist als die Entität.

Insofern kann es auch ein zustandsbehaftetes Objekt sein. Womit ich zu einer Erweiterung dessen komme, was ich beschrieben habe. Es gibt nämlich zweierlei Entitäten :-)

Für mich gibt es Datenentitäten (entity data objects). Das sind die Entitäten, über die ich im Artikel gesprochen habe. Die sind (!) Daten und haben Konsistenzfunktionalität.

Und dann gibt es Logikentitäten (entity logic objects). Die kapseln eine Datenentität. Sie haben (!) also Daten - die man nach außen nicht sieht.

Eine Datenentität zeigt all ihre Daten nach außen. Das ist ihr Job: Datenstruktur.

Eine Logikentität hingegen zeigt keine Daten, sondern verbirgt sie. Darauf kann man nur mit Methoden zugreifen, Tell don´t ask. Wie die Logikentität seine Daten hält, ist der Umwelt egal. Nix Properties, keine Train wrecks (LoD).

Aber Logikentitäten können Daten produzieren, sozusagen Views auf ihre Datenentität.

So eine Logikentität könnte nun eine Tarifdatenentität enthalten und die Gesprächskostenberechnung durchführen.

Warum diese Trennung? SRP, SoC.

Anonym hat gesagt…

Hallo Ralf,

nachdem wir jetzt übereinstimmen, dass DDD-Entities nicht nur Daten sind... ;-)

"So eine Logikentität könnte nun eine Tarifdatenentität enthalten und die Gesprächskostenberechnung durchführen."

Gut, wir können außerdem noch davon ausgehen, dass jedem Gespräch exakt ein Tarif zuordenbar ist. Dazu lässt sich der Einfachheit halber annehmen, dass in der Gesprächs-Entität ein Feld zur Referenzierung des Tarifs vorhanden sei.

Bleibt die Frage, woher das Programm weiß, welche Gesprächskostenberechnung für welchen Tarif anzuwenden ist.

Eine Möglichkeit wäre es, ein Gesprächskostenberechnung-Repository zu bemühen. Möglich wäre es und in der Tat könnte dies das Problem lösen. Gleichzeitig führt es aber ein neues ein: zusätzliche Komplexität. Man gibt bei Gesprächsdaten den Tarif an, muss dann über ein Repository die zugehörige Berechnungs-Entity herausfinden, die ihrerseits den selben Tarif referenziert.

Eie andere Möglichkeit bestünde darin, die Referenz vom Gespräch zum Tarif durch eine Referenz zur Gesprächskostenberechnung zu ersetzen. Allerdings haben wir jetzt ein Modell das zwar ein Problem löst, dieses aber nicht darstellt: der Problembeschreibung nach gibt es eine Relation von Gespräch und Tarif, nicht aber von Gesprächskostenberechnungen...

Wir stecken jetzt in einer Konfliktsituation. Letztlich führen beide Fälle zu ein und demselben Problem: anhand des Tarifs muss die Gesprächskostenberechnung ermittelt werden. Bei der ersten Möglichkeit muss dies während der Abrechnung geschehen, bei der zweiten im Rahmen der Verwaltung der Gesprächsentity.

Diesem Konflikt liegt meiner Ansicht nach ein Fehler zugrunde und dieser Fehler ist Folge eines datenorientierten Denkansatzes oder zumindest der Übertragung dessen auf die Objektorientierung. Die Frage, was ein Tarif ist, wird dabei falsch beantwortet. Der Tarif ist nämlich eben keine Datensammlung sondern der Tarif ist die Gesprächskostenberechnung. Die Ermittlung von Gesprächskosten ist sein einziger Zweck. Ob (bzw. dass) hierfür neben der im Beispiel gewählten Gesprächsdauer weitere Daten benötigt werden, steht auf einem anderen Blatt.

Gruß
Michael

Mike Bild hat gesagt…

Hallo Michael,

jetzt hast Du so viel drum herum geredet und design, dass es irgendwie passt bzw. nicht passt. Genau das ist in meinen Augen der Beweis für diese z.T. unnötige (OO/DDD) Herangehensweise. Ein einfaches Problem und Du sprichst von Tarif, Gesprächskostenberechnung, Gesprächskostenberechnung-Repository, Gesprächs-Entität und wo jetzt hin mit der ganzen Funktionalität. Genau das ist das geschilderte Strukturproblem mit einem ausgesprochenen Lösungsansatz. Die Anforderung mit aller Macht auf Klassen, Objekten, Referenzen, OO und DDD zu "mappen" finde ich ausgesprochen merkwürdig.

Analyse, Design, Naming - alles gut und sicher im Sinne der Kommunikation richtig. Aus meiner Sicht hat das jedoch nicht unbedingt etwas mit der Implementierung zu tun. Das kann auch ganz anders gehen. Diese Anforderungen lässt sich bsw. auch sehr gut innerhalb einer relationalen Datenbank mit Tabellen und Stored Procedures, oder per Map/Reduce in einer einer NoSQL, oder mit Projektionen auf Event Streams lösen. Es ist einfache Datenverarbeitung -OO/DDD ist dafür nicht zwangsweise nötig.

Vorrangig geht es bei Deinen geschilderten Anforderungen um Daten - Tarif = Stammdaten, Gespräch = Verlaufdaten. Jetzt folgt eine wie auch immer geartete Datenverarbeitung - Gesprächskostenberechnung. Diese braucht natürlich Daten um etwas zu berechnen. Braucht diese jetzt irgendeine Art von Relationen? Nein! Sie braucht die Daten mit einer irgendwie erstellten Projektion. Danach kann sie rechnen wie der Teufel und das Ergebnis zurückgeben oder wieder irgendwo speichern. Das war's. Entitäten - ja - als Datenstruktur. Aggregates - ja - als Funktionen die Projektionen, Aggregierung und Fallunterscheidungen machen.

Beste Grüße,
Mike

Anonym hat gesagt…

Hallo Mike,

Deinen Ausführungen stimme ich grundsätzlich zu. Wenn ich das tatsächlich realisieren wollte, würde ich einfach Excel nehmen :-)

Darum ging es hier aber nicht.

Gruß
Michael

Ralf Westphal - One Man Think Tank hat gesagt…

@Michael: Ich sehe es ähnlich. Was soll das ganze Rumgeeiere und Buzzword-Bingo.

Lass uns anschauen, was es gibt: Um Telefongespräche abzurechnen braucht es:

1. Tarifdaten: Die Tarifdaten sind eine Reihe von Preisen pro Minute oder Sekunde, z.B. "10c pro Minute zwischen 6-18h, 12c pro Minute zwischen 18-6h".

2. Gesprächsdaten: Gesprächsdauer (von..bis), Gesprächspartner und Kunde.

3. Berechnung: Die Berechnung kombiniert Gesprächsdauer mit Tarif und kommt zu einem Gesprächspreis für das Telefonat des Kunden.

4. Tarifbestimmung: Mindestens für die Kombination Gesprächspartner+Kunde muss ein Tarif bestimmt werden.

5. Speicher: Tarifdaten und Kundendaten müssen irgendwo gespeichert sein. (Kundendaten sind nötig, um darüber an Tarifdaten zu kommen.)

Das sind für mich die Aspekte der Domäne. Und die gilt es zu trennen im Code. Deutlich. Dafür sprechen mindestens SRP und SoC.

Was das mit DDD zu tun hat? Erstmal nix.

Jetzt kannst du aber die DDD Brille aufsetzen und streng da durch schauen, um die Aspekte schön auf das Vokabular zu mappen. Da suchst du dann dringend Aggregate, Entitäten, VOs, Repos, Services usw.

Alles gut und schön. Ich hab nix dagegen. Aber das kann eben schnell auch ausarten. Dann machst du DDD um DDD Willen und nicht, weil es dadurch verständlicher wird.

Was ich hier beschreiben wollte ist eine sehr dünne DDD Brille, sozusagen eine minimale Brille. Die schaut auf die Daten. Und da findet sie zum Beispiel eine Tarifdatenentität. Die enthält Daten wie oben beschrieben, die gewisse formale Konsistenzkriterien erfüllen müssen, z.B. dürfen die Urzeiten für die Minutenpreise nicht überlappen. Aber das war es dann schon. Keine Berechnungsvorschrift, wie mit den Daten umzugehen wäre.

Das ist einfach ein anderer Aspekt. Und ich meine, das liegt auf der Hand - bzw. ich möchte erreichen, dass man es als auf der Hand liegend ansieht. Dem gilt das Plädoyer in meinem Artikel.

Man kann es auch anders sehen und "alles" in so eine Entität reinstopfen. Kann man. Wird meist gemacht. Und dann wundert man sich, dass Software unwartbar wird.

Das machen auch Leute, die sonst so viel auf SOLID geben. Ich finde das merkwürdig.

Aber naja... Es gibt ja auch Leute, die schwören auf Fitness - und dann rauchen sie und laben sich nach dem Sport bei 0,5 Liter Hefeweizen. Kann man machen - nur darf man sich dann nicht wundern, wenn es irgendwann trotz Fitnesstraining zu allen möglichen negativen körperlichen Erscheinungen führt. Das ist schlicht nicht nachhaltig.

Es gibt ja wenig Konsenz in der Branche. Aber eine Prinzip sehe ich ganz vorne in der Akzeptanz. Über alle Fraktionen hinweg:

Strebe nach geringer Kopplung und hoher Kohäsion.

SRP, SoC, SLA, ISP sind dazu nur Kommentare.

Der Trick nun: Man muss das Prinzip aber auch leben. Echt leben. Wirklich dran glauben und es tun. Sich immer wieder fragen, wo denn wirklich hohe Kohäsion ist und wo nicht.

Das (!) geschieht nur ansatzweise. Und deshalb haben wir immer noch die Codeverhaue, die wir haben.

Daten und Logik zusammen zu legen widerspricht diesem Prinzip. Da ist mir die ganze traditionelle Objektorientierung egal. Und auch DDD-Proponenten wie Jimmy Nilsson können erzählen, was sie wollen. Solange es im Widerspruch zu so einem fundamentalen und akzeptierten Prinzip steht, bin ich nicht dabei.

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.