Utility methods in Entity of niet?

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
Hoi,

Bij het ontwerpen van entity classes hou ik zelf altijd de regel aan dat entities alleen de persistent entity data bevatten en niets anders. Eventuele utility methods plaats ik dan in een utility class. B.v.

Java:
1
2
3
4
5
6
7
8
package somecompany.models;

public @Entity class Person {

   Long @Id id;
   String name;
   Date dateOfBirth;  
}


Java:
1
2
3
4
5
6
7
8
9
package somecompany.utils;

public class Persons {

   public static boolean isTodayBirthDay(Person person) {
       return ...
   }  

}


Nu kom ik echter af en toe mensen tegen die dergelijke utility methods, alsmede dingen als render hints of styles direct in de entity willen stoppen. B.v.

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package somecompany.models;

public @Entity class Person {

   Long @Id id;
   String name;
   Date dateOfBirth;

   transient String cssClass; // when rendering in web environment

   public static boolean isTodayBirthDay(Person person) {
       return ...
   }  
  
}


Did is natuurlijk een klein voorbeeld, maar 'in het echt' gaat het dan om meerdere van dergelijke static methods en transient fields op de entity class.

Mijn vraag is, wat zijn nu de meningen over dergelijke praktijken? Aan de ene kant, transient properties (of in geval van JPA, de @Transient annotation) bestaat niet zonder reden en static methods zijn ook toegestaan. Ik vind zelf dat je data die puur gerelateerd is aan de entity niet moet mixen met andere data die toevallig ergens in een bepaalde omgeving handig is.

De utility methods zijn moeilijker. Zolang ze maar geen dingen doen als naar een DB connecten of iets dergelijks, maar puur op de eigen data werken, zou het in principe wel kunnen. Toch twijfel ik er over...

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:07
Wat noem je een utility method ....

Zoals jij het doet, bevat je entity dus enkel data ? Geen logica ?
Bij mij bevatten mijn entities wel methods die uitgevoerd worden op de data van die entity. Bv, die IsTodayBirthday method uit jouw voorbeeld zou bij mij gewoon een method in de entity worden.
Zelfs als die methods een repository accessen om data-access te doen, dan nog zou ik deze in de entity zelf durven steken. (De data-access is dan wel ge-abstraheerd door de repository).


Dingen die dan weer aan de UI gerelateerd zijn, of eigenlijk helemaal niets met de entity zelf te maken hebben, ga ik dan weer zeker niet in de entity steken. (Zoals die cssClass string bv).

In jouw geval is een entity dus een domme data-holder , en niets meer ? Wat vind je dan het voordeel om de methods die het gedrag van die entity bevatten, in een aparte class te gaan steken ? En hoe zorg je er dan bv voor dat bepaalde gegevens niet 'direct' mogen gewijzigd worden ? (Bv een property die wel een getter heeft, maar die property mag niet zomaar gewijzgigd worden)

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
whoami schreef op zaterdag 22 november 2008 @ 19:07:
Wat noem je een utility method ....

Zoals jij het doet, bevat je entity dus enkel data ? Geen logica ?
Enkel data, waarbij de methods voornamelijk getters/setters zijn of methods die eenvoudige derived data terug geven, mits deze data een betekenis heeft in het oorspronkelijke problem domain.

Een instance method isTodayBirthday() zou ik dan misschien ook nog net doen, maar static methods die van toepassing zijn op andere entities dan weer niet. Het blijft natuurlijk een beetje moeilijk om te bepalen wat wel en wat niet, vandaar dus ook dit topic ;)

Een static method als de volgende zou ik b.v. liever niet in m'n entity hebben:

Java:
1
public static List<Person> getAllBirthDaysOfToday(List<Person> persons);
Zelfs als die methods een repository accessen om data-access te doen, dan nog zou ik deze in de entity zelf durven steken. (De data-access is dan wel ge-abstraheerd door de repository).
Dat zou ik dus niet zo snel doen, behalve als de abstractie echt goed gedaan is, en het voor de gebruiker duidelijk is wanneer de entity deze data access zou kunnen gaan doen. Niets is vervelender als een lijst van entities die opeens per entity contact gaat op zitten te nemen met een data base op plekken dat je dat totaal niet verwacht (b.v. tijdens het renderen in je uiterste front-end layer, of wanneer overgezonden (via RMI ofzo) naar een client application.

JPA doet dat b.v. automatisch; een attached entity kan via lazy loading nog 'contact opnemen' met de plek waar deze vandaan komt. Maar eens detached heb je de garantie dat ie dat nooit meer gaat (of kan!) doen. Het mechanisme via een dynamische proxy zorgt dan ook voor een mooie abstractie.

Maar zelf, direct in een method, zou ik dat dus nooit doen.
In jouw geval is een entity dus een domme data-holder , en niets meer ? Wat vind je dan het voordeel om de methods die het gedrag van die entity bevatten, in een aparte class te gaan steken ?
Het voordeel van de entity alleen data laten te bevatten is (IMO) dat het duidelijk de 'zelfstandige naamwoorden' van mijn domain aangeeft. Door naar de entity te kijken, zou een niet technisch persoon in principe meteen moeten kunnen begrijpen wat een entity is en of deze overeenkomt met het domain.

Allerlei utility methods, die in verschillende contexten wel of niet handig zijn, vertroebelen dat beeld een beetje. Er zijn veel meer van dit soort methodes te bedenken. Wat denk je b.v. van:

Java:
1
2
3
4
public static List<Person> getOldestPerson(List<Person> persons);
public static List<Person> getYoungestPerson(List<Person> persons);
public static List<Person> getAverageAgePersons(List<Person> persons);
public static List<Person> getPersonsHavingDoneOrder(List<Person> persons, List<Order> order);


Het zijn dan geen dingen die het 'gedrag' van een entity bepalen, maar hulp functies die in een bepaalde context ooit eens handig waren. B.v. iemand had ooit eens een functie nodig om uit een lijst personen de oudste te filteren en stopt deze maar meteen in de entity erbij.

Ik zou dan zeggen, als 1 client app deze functie nodig heeft, stop die dan in een utility method die bij die app hoort.

Voor de 'werkwoorden' van mijn applicatie gebruik ik overigens (stateless) session beans. Die bevatten meer methods die 'over' een entity gaan, op een wat hoger niveau. B.v. iets als subscribePerson(Person person) is bij mij een method in een session bean en niet een method in de entity zelf.
En hoe zorg je er dan bv voor dat bepaalde gegevens niet 'direct' mogen gewijzigd worden ? (Bv een property die wel een getter heeft, maar die property mag niet zomaar gewijzgigd worden)
Ik snap dit niet 100%. Als een propery een getter heeft is deze read-only en kan je hem niet wijzigen (aangenomen dat het bijbehorende field gewoon private is en afgezien van reflection hacks).

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:07
flowerp schreef op zaterdag 22 november 2008 @ 19:41:
[...]


Een static method als de volgende zou ik b.v. liever niet in m'n entity hebben:

Java:
1
public static List<Person> getAllBirthDaysOfToday(List<Person> persons);
Dat hoort idd niet in je entity zelf, maar dat hoort dan weer in de Persons-repository/dao. (En het hoeft ook geen static method te zijn dan).
Dat zou ik dus niet zo snel doen, behalve als de abstractie echt goed gedaan is, en het voor de gebruiker duidelijk is wanneer de entity deze data access zou kunnen gaan doen. Niets is vervelender als een lijst van entities die opeens per entity contact gaat op zitten te nemen met een data base op plekken dat je dat totaal niet verwacht (b.v. tijdens het renderen in je uiterste front-end layer, of wanneer overgezonden (via RMI ofzo) naar een client application.
Het is natuurlijk aan jou om ervoor te zorgen dat die method, die een webservice of database gaat gaan aanspreken, dat op een goede manier doet.
Allerlei utility methods, die in verschillende contexten wel of niet handig zijn, vertroebelen dat beeld een beetje. Er zijn veel meer van dit soort methodes te bedenken. Wat denk je b.v. van:

Java:
1
2
3
4
public static List<Person> getOldestPerson(List<Person> persons);
public static List<Person> getYoungestPerson(List<Person> persons);
public static List<Person> getAverageAgePersons(List<Person> persons);
public static List<Person> getPersonsHavingDoneOrder(List<Person> persons, List<Order> order);
Tja, ....
In sommige gevallen zou ik hiervoor een static method in de entity durven maken, in andere gevallen maak ik hier een aparte 'specification' class voor die deze methods bevat ...
Ik snap dit niet 100%. Als een propery een getter heeft is deze read-only en kan je hem niet wijzigen (aangenomen dat het bijbehorende field gewoon private is en afgezien van reflection hacks).
Idd, je kan die property van buitenaf niet wijzigen.
Echter, wat als jij 'm wel -via een 'gecontroleerde manier'- wilt wijzigen ?
Bv, stel een situatie waar je property A alleen kunt wijzigen als ook property B gewijzigd wordt.
Om dit af te dwingen, ga je voor A & B enkel een getter voorzien, en geen setter, en je zult een method maken waarbij de 2 properties gewijzigd kunnen worden (door de achterliggende members te gaan wijzigen).
Als je een dergelijke method niet in je entity zelf wilt, dan kan je dit nooit verwezenlijken.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
whoami schreef op zaterdag 22 november 2008 @ 22:01:
Bv, stel een situatie waar je property A alleen kunt wijzigen als ook property B gewijzigd wordt.
[...]
Als je een dergelijke method niet in je entity zelf wilt, dan kan je dit nooit verwezenlijken.
Ik snap wat je bedoeld. Zoiets zou je ook wel degelijk dan als onderdeel van de business entiteit zien en dus wel degelijk gewoon opnemen in je entity. Als A en B echt perse simultaan gewijzigd moeten worden, dan moet die constraint ook wel in je problem domain voorkomen en dat 'rechtvaardigt' dus (IMHO) een plek in de entity.

Sterker nog, als A en B echt perse simultaan gezet moeten worden zou ik er heel hard over nadenken om het gewoon samen een complex type te maken in plaats van een aparte A en B (indien mogelijk natuurlijk).

Validatie methoden zijn daarbij een verhaal apart. In hoeverre moet een entiteit zich zelf valideren? Je wilt waarschijnlijk dingen als "postive integer", "not null", "max length 56" wel in je entity hebben, maar de entity moet natuurlijk geen complete validatie routines gaan bevatten inclusief i18n lookups en het zetten van Faces messages (voor JSF) in de Faces context.

Kijk, het probleem waar ik in essentie mee zat is dat veelal onervaren programmeurs alles wat maar met een entity te maken kan hebben er in gingen zitten stoppen. Dan werd een entity dus zijn eigen DAO (bv, person.save(), person.load()), mocht een entity zelf de authenticatie gaan doen (b.v. person.login(name, password)), en ging de entity z'n eigen subscription regelen (b.v. person.subscribe(name, age, ...). Dan kreeg je 'entities' van 100'en, soms 1000'en regels code. Totaal onbeheersbaar.

Een entity alleen data laten bevatten en hooguit wat methods voor afgeleide data (b.v. een method getFullName( return firstNname + lastName ); helpen aardig om dat tegen te gaan. Bepalen wat 'afgeleide' data is, is helaas niet altijd meteen duidelijk.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • MisterBlue
  • Registratie: Mei 2002
  • Laatst online: 10-09 20:13
Ik ben waarschijnlijk van een andere stroming. Ik gebruik geen Entity objecten, maar gewoon Domain objecten die wel of niet persistent zijn. Ik zie objecten als levende dingen met gedrag. Interactie met de objecten gebeurd met doeDit, doeDat methodes op de objecten. Een aantal van deze methodes zullen dan de utility methodes zijn waar jij op doelt.

Het probleem van de onervaren programmeurs los je in mijn ogen niet op door het ontwerp daarvoor aan te passen. Pair programming met seniors en code reviews, zijn betere methodes om de code kwaliteit te bewaken en de onervaren programmeurs naar een hoger nivo te tillen.

Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
MisterBlue schreef op maandag 24 november 2008 @ 11:01:
Ik ben waarschijnlijk van een andere stroming. Ik gebruik geen Entity objecten, maar gewoon Domain objecten die wel of niet persistent zijn.
Wat is precies het verschil tussen een (business) domain object en een (business) entity object?
Ik zie objecten als levende dingen met gedrag. Interactie met de objecten gebeurd met doeDit, doeDat methodes op de objecten. Een aantal van deze methodes zullen dan de utility methodes zijn waar jij op doelt.
Ik heb veel entity objecten als b.v. "Huis" of "Boot". Dat zijn vrij levenloze objecten, die eigenlijk alleen maar beschrijven wat het nu is om een "Huis" te zijn. B.v. een huis heeft kamers, ramen en een dak, maar in dit geval geen of heel erg weinig gedrag. Als een huis gerepareerd moet worden, doet een reperateur dat (die heeft dus wel gedrag) -> reperateur.repareer(huis) en niet, huis.repareer(). Maar eigenlijk geeft mijn code helemaal niet een individuele reperateur opdracht om mijn huis te repareren. Ik werk namelijk via een 'bureau' die ik opbel met de opdracht. Dat het bureau dan een individuele reperateur stuurt, heb ik weinig mee te maken -> reperatieService.repareer(huis, ik, maximaal_bedrag, ...);

De utility methods waar ik op doel zijn vooornamelijk dingen die niet op de object instance werken, maar die generiek op een verzameling of extern object werken (de public static methods).

Dingen die echt afgeleid zijn en bij het object zelf horen plaats ik dan wel weer in een entity.

B.v. stel een Huis heeft alleen kamers als properties en niet direct een veld "totale oppervlakte"

Dan zou, huis.getTotaleOpervlakte(); // som van alle opervlaktes van alle kamers

dan wel in mijn entity komen, maar een functie om het gunstigste huis qua prijs per m2 uit een verzameling huisen te pikken weer niet.
Het probleem van de onervaren programmeurs los je in mijn ogen niet op door het ontwerp daarvoor aan te passen. Pair programming met seniors en code reviews, zijn betere methodes om de code kwaliteit te bewaken en de onervaren programmeurs naar een hoger nivo te tillen.
Das natuurlijk waar, het ontwerp moet geen echt rare dingen gaan voorschrijven, maar sommige strakke regels om misbruik te voorkomen is misschien zo gek nog niet. B.v. ik heb ook een harde regel dat er geen scriptlets op JSP pages gebruikt mogen worden. Een goede ervaren programmeur zou dit absoluut op een valide manier kunnen gebruiken, maar in de praktijk wordt het gewoon te vaak misbruikt. Ik kan niet elke regel code van de juniors waar ik verantwoordelijk voor ben (5 totaal) gaan babysitten.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • MisterBlue
  • Registratie: Mei 2002
  • Laatst online: 10-09 20:13
Wat is precies het verschil tussen een (business) domain object en een (business) entity object?
Er is geen verschil, maar bij Entity objecten krijg ik sterk het gevoel dat er een tabel in de database is terwijl ik dat bij Domain objecten niet heb. Kwestie van terminilogie afspreken, meer niet.
Ik heb veel entity objecten als b.v. "Huis" of "Boot". Dat zijn vrij levenloze objecten, die eigenlijk alleen maar beschrijven wat het nu is om een "Huis" te zijn. B.v. een huis heeft kamers, ramen en een dak, maar in dit geval geen of heel erg weinig gedrag. Als een huis gerepareerd moet worden, doet een reperateur dat (die heeft dus wel gedrag) -> reperateur.repareer(huis) en niet, huis.repareer(). Maar eigenlijk geeft mijn code helemaal niet een individuele reperateur opdracht om mijn huis te repareren. Ik werk namelijk via een 'bureau' die ik opbel met de opdracht. Dat het bureau dan een individuele reperateur stuurt, heb ik weinig mee te maken -> reperatieService.repareer(huis, ik, maximaal_bedrag, ...);
Ik ben dus inderdaad van een andere stroming. Levensloos in de echte wereld hoeft niet te betekenen levensloos in software. Ik heb geen problemen met de huis.repareer(). Het huis zal immers zelf weten wat kapot is aanzichzelf. Een reperateur of service moet eerst de informatie uit het huis halen en dan krijg je weer een tight coupling tussen reperateur/service en huis. Maar dat is een andere discussie.

Bij utility methods die niet van toepassing zijn op een instantie van een object, probeer ik te kijken of ze misschien wel op een instantie van een ander object van toepassing zijn, zoals bijvoorbeeld de reeds genoemde repository en als het echt niet anders kan gebruik ik wel een utility class.

Het nadeel van utility classes met static methods vind ik de verleiding om procedureel te programmeren vrij groot wordt. Juist bij de onervaren programmeurs, waardoor het weer langer duurt voordat ze echt OO leren denken.
Ik kan niet elke regel code van de juniors waar ik verantwoordelijk voor ben (5 totaal) gaan babysitten.
Ik begrijp wel dat dit niet te doen is, maar je kunt ook peer reviews toepassen. Als men al weet dat elk stukje code die ze schrijven door een ander nog gelezen gaat worden gaan ze al wat beter nadenken voordat ze wat opschrijven en men leert ook van elkaar.

Acties:
  • 0 Henk 'm!

  • JaWi
  • Registratie: Maart 2003
  • Laatst online: 26-08 22:41

JaWi

maak het maar stuk hoor...

Als ik met zo'n zelfde soort probleem zit, dan probeer ik altijd of de GRAS(P) patronen werken, meestal helpen deze nl. erg goed.

Statistics are like bikinis. What they reveal is suggestive, but what they hide is vital.


Acties:
  • 0 Henk 'm!

Verwijderd

Ik ga meestal uit van hoe de bestaande boeken en turtorials JPA toepassen. In de meeste gevallen laten die entities een vrij directe representatie zijn van de tabel waar ze uiteindelijk heen persist worden. Dus wel eenvoudige afgeleide data, maar niet complexe business dingen.

Acties:
  • 0 Henk 'm!

Verwijderd

Bij ons pakken we het als volgt aan.
a) op basis van de database worden domme Entities gegenereerd.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FooEntityBase : EntityBase
{
    // programmeur MOET van deze entitybase classes afblijven
    // 1-op-1 mapping met de database
    // alleen properties voor toegang tot de attributen
    // dus GEEN utility
}

public class FooEntity : FooEntityBase
{
     // hier mag de programmeur aanzitten
     // normaal gesproken geen Utility
     // maar extra velden om, bijvoorbeeld, en description bij een id te laden (Foreign relatie
}


Utility vindt plaats in de managerclasses, methodes hierin worden door bepaalde interfaces aan andere assemblies aangeboden:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// interface/contract
public Interface IFooFace : IInterfaceBase
{
     void DoeIets(Bar fooBar);
}

//benaderbare tussenlaag, waarin extra controle zit
public class FooFace : InterfaceBase<FooFaceManager>, IFooFace
{
     public void DoeIets(Bar fooBar)
     {
         //hier nog wat controle spulletjes
         Manager.DoeIets(fooBar);
     }
}

// business logic
public class FooFaceManager : ManagerBase<FooEntity>
{
     public void DoeIets(Bar fooBar)
     {
          // doe allerlei magische dingen
     }
}


In de entity (niet entitybase) geven we overigens wel bepaalde attributen mee, zoals foreign relaties met andere entiteiten.

[ Voor 4% gewijzigd door Verwijderd op 26-11-2008 15:45 ]


  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
flowerp schreef op maandag 24 november 2008 @ 11:22:
Ik heb veel entity objecten als b.v. "Huis" of "Boot". Dat zijn vrij levenloze objecten, die eigenlijk alleen maar beschrijven wat het nu is om een "Huis" te zijn. B.v. een huis heeft kamers, ramen en een dak, maar in dit geval geen of heel erg weinig gedrag. Als een huis gerepareerd moet worden, doet een reperateur dat (die heeft dus wel gedrag) -> reperateur.repareer(huis) en niet, huis.repareer(). Maar eigenlijk geeft mijn code helemaal niet een individuele reperateur opdracht om mijn huis te repareren. Ik werk namelijk via een 'bureau' die ik opbel met de opdracht. Dat het bureau dan een individuele reperateur stuurt, heb ik weinig mee te maken -> reperatieService.repareer(huis, ik, maximaal_bedrag, ...);
Wat nu als je van een huis het aantal kamers wilt weten (en dit niet een bekend gegeven is maar iets dat je moet tellen)? Is dat dan huis.telKamers() of KamerTeller.telKamers(huis)? Ik vind dat laaste, tja...ranzig. IMHO kun je, als je deze entities puur als datacontainers ziet, bijna net zo goed met arrays werken. Ik ben zelf een groot voorstander voor het stoppen van logica in de objecten die de informatie/state kennen/leveren.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
Hydra schreef op donderdag 27 november 2008 @ 17:13:
[...]

Wat nu als je van een huis het aantal kamers wilt weten (en dit niet een bekend gegeven is maar iets dat je moet tellen)?
house.getRooms().getSize(); ? :P

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Precies, en dan heb je dus geen externe 'utility' class :P

https://niels.nu


Acties:
  • 0 Henk 'm!

Verwijderd

Eigenlijk is getRooms() een slecht voorbeeld, want Rooms is een member van huis, dus zitten de getter en setter van Rooms automagisch in House (en komt er dus geen utility bij kijken, want je vraagt alleen de waarde van een member op).
Wanneer je bewerkingen op deze member plaats zou laten vinden in House, ben je (natuurlijk, subjectief) fout bezig, want dan plaats je wel degelijk utility in de entity.

Het blijft, denk ik, een kwestie van smaak, maar behalve getters en setters (properties in mijn geval aangezien ik mezelf op .NET/C# richt) wíl ik niets zien in mijn entities behalve een 1-op-1 mapping met de bewuste database tabel. Op die manier houdt je je datamodel mooi gescheiden van je business logic, mocht er iets veranderen in je datamodel, genereer je je entity classes opnieuw, zonder dat je bang hoeft te zijn logica te verliezen. Wat dat betreft ben ik, persoonlijk, een groot voorstander van een strikt gescheiden lagen-model.

[ Voor 66% gewijzigd door Verwijderd op 02-12-2008 18:46 ]


Acties:
  • 0 Henk 'm!

  • muksie
  • Registratie: Mei 2005
  • Laatst online: 03-09 18:35
Verwijderd schreef op dinsdag 02 december 2008 @ 18:33:
Het blijft, denk ik, een kwestie van smaak, maar behalve getters en setters (properties in mijn geval aangezien ik mezelf op .NET/C# richt) wíl ik niets zien in mijn entities behalve een 1-op-1 mapping met de bewuste database tabel. Op die manier houdt je je datamodel mooi gescheiden van je business logic, mocht er iets veranderen in je datamodel, genereer je je entity classes opnieuw, zonder dat je bang hoeft te zijn logica te verliezen. Wat dat betreft ben ik, persoonlijk, een groot voorstander van een strikt gescheiden lagen-model.
Zoals je het hier vertelt juist niet, of het moet zijn dat je nog een laag ertussen hebt zitten. Immers, als je datamodel verandert en je regenereert a.d.h.v. het nieuwe datamodel je entities, dan verandert ook de interface van je entity-laag. Als je je business logic rechtstreeks bovenop deze laag hebt gebouwd, zul je deze hier ook aan moeten aanpassen. Als je nog een laag tussen je entity-laag en je business logic hebt zitten zul je deze moeten aanpassen en je business logic niet.

Acties:
  • 0 Henk 'm!

  • Confusion
  • Registratie: April 2001
  • Laatst online: 01-03-2024

Confusion

Fallen from grace

flowerp schreef op zaterdag 22 november 2008 @ 18:42:
Bij het ontwerpen van entity classes hou ik zelf altijd de regel aan dat entities alleen de persistent entity data bevatten en niets anders. Eventuele utility methods plaats ik dan in een utility class. B.v.
[..]
Did is natuurlijk een klein voorbeeld, maar 'in het echt' gaat het dan om meerdere van dergelijke static methods en transient fields op de entity class.
[..]
De utility methods zijn moeilijker. Zolang ze maar geen dingen doen als naar een DB connecten of iets dergelijks, maar puur op de eigen data werken, zou het in principe wel kunnen. Toch twijfel ik er over...
Ik denk dat je er geen principieel standpunt over in kunt nemen, maar dat je het moeten laten afhangen van de combinatie van voordehandliggendheid en leesbaarheid van de resulterende code.

Een methode als 'isTodayBirthday' zou ik zonder te twijfelen in de Person class hangen, als instance method. Het bepaald iets dat inherent aan het Person object is.
Java:
1
2
3
if (person.isBirthdayToday()) {
    sendPostcard(person);
}

vind ik bijzonder voor de hand liggend en leesbaar. Beter dan
Java:
1
2
3
if (Person.isBirthdayToday(person)) {
    sendPostcard(person);
}

Dat komt gewoon redundant op me over. Dan geef ik gevoelsmatig nog eerder de voorkeur aan:
Java:
1
2
3
if (PersonUtils.isBirthdayToday(person)) {
    sendPostcard(person);
}


Een methode als 'getAllBirthDaysOfToday' zou ik daarom ook in een PersonUtils class hangen: het is een method die enkel informatie over Persons nodig heeft en gebruikt.
Java:
1
2
3
4
persons = PersonUtils.getAllBirthDaysOfToday(persons);
for (Person person: persons) {
    sendPostcard(person);
}

vind ik weer typisch beter te volgen dan
Java:
1
2
3
4
persons = Person.getAllBirthDaysOfToday(persons);
for (Person person: persons) {
    sendPostcard(person);
}

Want dit roept de vraag in me op "Huh, Person enkelvoud, waar komt die vandaan? Oh ja wacht, dat is gewoon de entity class." Kost misschien 100 ms, maar welke elke keer dat je het leest.

Natuurlijk heeft het de voorkeur alle soort-van-gerelateerde utility methoden bij elkaar te hebben en natuurlijk voorkom je daarmee discussie over twijfelgevallen, maar person.isBirthdayToday() komt gewoon zo natuurlijk over dat ik niet denk dat je het moet laten.

[ Voor 5% gewijzigd door Confusion op 02-12-2008 20:21 ]

Wie trösten wir uns, die Mörder aller Mörder?


Acties:
  • 0 Henk 'm!

Verwijderd

Confusion schreef op dinsdag 02 december 2008 @ 20:19:
[...]

Ik denk dat je er geen principieel standpunt over in kunt nemen, maar dat je het moeten laten afhangen van de combinatie van voordehandliggendheid en leesbaarheid van de resulterende code.

Een methode als 'isTodayBirthday' zou ik zonder te twijfelen in de Person class hangen, als instance method. Het bepaald iets dat inherent aan het Person object is.
Java:
1
2
3
if (person.isBirthdayToday()) {
    sendPostcard(person);
}


vind ik bijzonder voor de hand liggend en leesbaar. Beter dan
Java:
1
2
3
if (Person.isBirthdayToday(person)) {
    sendPostcard(person);
}

Dat komt gewoon redundant op me over. Dan geef ik gevoelsmatig nog eerder de voorkeur aan:
Java:
1
2
3
if (PersonUtils.isBirthdayToday(person)) {
    sendPostcard(person);
}


Een methode als 'getAllBirthDaysOfToday' zou ik daarom ook in een PersonUtils class hangen: het is een method die enkel informatie over Persons nodig heeft en gebruikt.
Java:
1
2
3
4
persons = PersonUtils.getAllBirthDaysOfToday(persons);
for (Person person: persons) {
    sendPostcard(person);
}

vind ik weer typisch beter te volgen dan
Java:
1
2
3
4
persons = Person.getAllBirthDaysOfToday(persons);
for (Person person: persons) {
    sendPostcard(person);
}

Want dit roept de vraag in me op "Huh, Person enkelvoud, waar komt die vandaan? Oh ja wacht, dat is gewoon de entity class." Kost misschien 100 ms, maar welke elke keer dat je het leest.

Natuurlijk heeft het de voorkeur alle soort-van-gerelateerde utility methoden bij elkaar te hebben en natuurlijk voorkom je daarmee discussie over twijfelgevallen, maar person.isBirthdayToday() komt gewoon zo natuurlijk over dat ik niet denk dat je het moet laten.
Het ophalen van alle personen die vandaag jarig zijn hoort op je Repository te staan. Of een gebruiker vandaag jarig is, hoort in mijn ogen gewoon op het Person object te staan;

PHP:
1
2
3
4
5
6
7
$personRepository = PersonRepositoryFactory::getInstance();
$perons = $personRepository->getPersonsWhoAreHavingTheirBirthdays();

$person = $personRepository->getPersonById(1);
if ($person->isHavingHisBirthdays()) {
  $person->sendCard(new Card('Feli...'));
}


Zelf snap ik *niet* waarom mensen hun domein objecten zo kaal mogelijk houden. Iedereen die gedrag *bewust* verplaatst naar 'Helper' of 'Utility' klassen zou eens het artikel van Fowler moeten lezen over Anemic Domain Models.

Wanneer je domein object klassen alleen getters en setters bevatten, ben je gewoon een datastructuur aan het bouwen om je resultset. Imho i-di-oot. Een goed domein model heeft in mijn ogen *juist* methoden die logisch zijn voor het gedrag van de applicatie.

Wanneer je dus een applicatie schrijft om mensen een bericht te sturen als ze jarig zijn, dan hoort er gewoon een sendCard methode op het Person object.

Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
Confusion schreef op dinsdag 02 december 2008 @ 20:19:
[...]
Een methode als 'isTodayBirthday' zou ik zonder te twijfelen in de Person class hangen, als instance method. Het bepaald iets dat inherent aan het Person object is.
Java:
1
2
3
if (person.isBirthdayToday()) {
    sendPostcard(person);
}
Ik denk dat dit ook inderdaad een goed voorbeeld is. Dit hier gaat om een instance method die gewoon data van het object gebruikt. Lijkt me prima in de entity zelf. Wat ik eerder bedoelde wat ik er niet, of minder in vind passen, zijn static methodes die dus niets met een instance zelf doen.
Wanneer je domein object klassen alleen getters en setters bevatten, ben je gewoon een datastructuur aan het bouwen om je resultset. Imho i-di-oot. Een goed domein model heeft in mijn ogen *juist* methoden die logisch zijn voor het gedrag van de applicatie.
Het punt is dat veel functies die het gedrag van je applicatie modelleren dikwijls iets zeggen over meerdere entities. Dat gedrag kun je dus logischerwijs niet in een enkele entity plaatsen, want het gedrag gaat over meerdere en er is niet 1 entity die absoluut dominant is.

Je krijgt daarna ook erg practische problemen. Als mijn entities gewone objecten zijn die primair data representeren en voor hun (eenvoudige) gedrag slechts gebruik maken van hun eigen data fields, dan kan ik deze entities zeer makkelijk tussen alle lagen van mijn applicatie transporteren.

Stel je b.v. een Reservation entity voor. Als deze zijn eigen makeReservation() method zou bevatten (die intern andere entities aanmaakt en raadplaagt, dingen doet als externe systemen raadplegen voor beschikbaarheid om voor het chargen van een credit card, etc), dan kan ik zo'n object al geneens doorsturen vanuit mijn enterprise laag naar b.v. mijn web laag (in een web applicatie). De web laag mag namelijk helemaal geen toegang hebben tot dergelijke functionaliteit.

Nog duidelijker wordt het als je enterprise laag niet alleen een web client heeft, maar ook b.v. een remote Swing client. Een entity zonder dependencies op complexe andere objecten kan ik gewoon zo naar die remote client toesturen. Zo'n makeReservation() method erin zou de boel alleen maar erg onduidelijk maken. Hoe had je dat dan voor je gezien?


Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Reservation {

   // May only be called from the enterprise layer. DO NOT CALL FROM ANYWHERE ELSE
   public Reservation makeReservation(int roomNr, BigDecimal cost, Period period, String CC) {

   }

   // Creates a JPanel representing this Reservation.
   // DO NOT CALL ON THE SERVER!!! ONLY CALL THIS ON THE CLIENT!
   public JPanel renderSwing() {

   }
}


Dat gaat toch niet werken joh? |:(

Een oplossing waar met vroeger wel eens mee kwam was een tussen object; een DTO. In het verleden heb ik deze dikwijls gebruikt, maar het is een hoop dubbel gedoe (zeker als je entities meer dan een paar fields hebben), dit terwijl je entity zelf deze rol net zo goed kan vervullen.

Je hebt ook niet voor niets zoiets als session beans naast entity beans. Session beans kun je perfect gebruiken voor service/work flow/task flow achtige dingen, terwijl entity beans juist perfect de data vertegenwoordigen en het werkelijk-universele-en-niet-afhankelijk-van-andere-dingen gedrag.
Wanneer je dus een applicatie schrijft om mensen een bericht te sturen als ze jarig zijn, dan hoort er gewoon een sendCard methode op het Person object.
:|

Je bedoelt net zoals in het problem domain zelf, waarbij mensen allemaal hun eigen methode hebben om zichzelf een bericht te laten sturen?

Volgens jouw methode kan een applicatie dan alleen die sendCard implementatie gebruiken die het person object zelf intern biedt. Handig als je ook nog andere dingen hebt waar je berichten heen kunt sturen en al die dingen implementeren intern deze, of een gelijke methode. Als je van messaging system wilt veranderen, of deze wilt uitbreiden (b.v. je wilt SMS naast mail gaan doen), moet je dus al je individuele classen gaan aanpassen? Als je dan ook nog eens dingen wilt gaan aanbieden als het kunnen uitschakelen van berichten, dan komt al die code om b.v. de voorkeuren hiervoor te checken allemaal in de person class?

En dat doe je dan ook voor alle andere X dingen die je met een Person kunt doen? :X

Een aanpak die voor veel betere 'low-coupling, high-cohesion' zorgt is gewoon een Person een Person te laten zijn, daarnaast een generieke MessagingService te hebben die alle details over messaging kent, en daarna een BirthdayMessageJob te hebben draaien, die checked wie er op een dag jarig zijn, een message daarvoor aanmaakt, en die verzend via de MessagingService van jouw app.

Elk stukje code concentreerd zich dan op 1 ding en je krijgt dan geen idiote 'high-coupling, low-cohesion' gevallen als een Person die: zichzelf berichten stuurt, zichzelf subscribed, zichzelf unsubscribed, zichzelf een huis laat bouwen, zichzelf een reservering voor de film laat maken, zichzelf...

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:07
flowerp schreef op zaterdag 06 december 2008 @ 01:02:

Het punt is dat veel functies die het gedrag van je applicatie modelleren dikwijls iets zeggen over meerdere entities. Dat gedrag kun je dus logischerwijs niet in een enkele entity plaatsen, want het gedrag gaat over meerdere en er is niet 1 entity die absoluut dominant is.
Dat hangt ervan af. Soms heb je instance methods van een entity die een ander type als argument kunnen nemen.
Je kan bepaalde logica ook kwijt in Services of Specifications. Hangt allemaal van de situatie af.
Je krijgt daarna ook erg practische problemen. Als mijn entities gewone objecten zijn die primair data representeren en voor hun (eenvoudige) gedrag slechts gebruik maken van hun eigen data fields, dan kan ik deze entities zeer makkelijk tussen alle lagen van mijn applicatie transporteren.
Wat is het voordeel van een dergelijk 'dom' object ? Als het gewoon een 1 op 1 mapping is met je DB, dan heeft dit toch geen enkele toegevoegde waarde, en dan kan je beter gebruik gaan maken van -als je in .NET werkt bv- de DataSet/DataTable classes.
Stel je b.v. een Reservation entity voor. Als deze zijn eigen makeReservation() method zou bevatten

(die intern andere entities aanmaakt en raadplaagt, dingen doet als externe systemen raadplegen voor beschikbaarheid om voor het chargen van een credit card, etc), dan kan ik zo'n object al geneens doorsturen vanuit mijn enterprise laag naar b.v. mijn web laag (in een web applicatie).
Slecht voorbeeld imho. :)
Een Reservation entity moet geen 'MakeReservation' method hebben, want een Reservatie wordt niet door een reservatie gecreeërd. Een Person / Customer object kan bv wel een MakeReservation object hebben, die een Reservation object retourneerd.
En wat is het probleem als die MakeReservation method andere (externe) bronnen raadpleegt ?
Je kan desnoods een (of meerdere) interfaces als argument van die MakeReservation method definieren. Als je die method aanroept, geef je dan gewoon de juiste implementatie mee. (Of je gebruikt een IoC Container).
De web laag mag namelijk helemaal geen toegang hebben tot dergelijke functionaliteit.
Waarom niet ? Wie bepaald dat ?
Nog duidelijker wordt het als je enterprise laag niet alleen een web client heeft, maar ook b.v. een remote Swing client. Een entity zonder dependencies op complexe andere objecten kan ik gewoon zo naar die remote client toesturen. Zo'n makeReservation() method erin zou de boel alleen maar erg onduidelijk maken. Hoe had je dat dan voor je gezien?
Remote app's hebben behoefte aan een andere oplossing; je moet er nl. ook voor zorgen dat je geen 'chatty' applicatie hebt, want remote calls zijn duur.
In een dergelijk geval ga je IMHO eerder met een remote service layer gaan werken, die de use-cases van je domain bevat.
Je remote client doet calls naar die service layer, die de domain objecten aanspreekt, en geeft je evt. resultaten terug mbhv DTO's.
Een oplossing waar met vroeger wel eens mee kwam was een tussen object; een DTO. In het verleden heb ik deze dikwijls gebruikt, maar het is een hoop dubbel gedoe (zeker als je entities meer dan een paar fields hebben), dit terwijl je entity zelf deze rol net zo goed kan vervullen.
Het is een heel gedoe, maar wat als je je domain layer refactored bv. Je zorgt niet voor nieuwe functionaliteit, maar bepaalde classes ga je herstructureren.
Als jij domain entities naar je client stuurt, dan ga je ook je client opnieuw moeten deployen.

En wat als je bv (N)Hibernate gebruikt als O/R mapper ? (N)Hibernate zorgt zelf voor de dirty tracking van objecten. Hoe ga je dat oplossen als je je entities (die door NHibernate gemanaged worden), naar de client over en weer stuurt ?
Volgens jouw methode kan een applicatie dan alleen die sendCard implementatie gebruiken die het person object zelf intern biedt. Handig als je ook nog andere dingen hebt waar je berichten heen kunt sturen en al die dingen implementeren intern deze, of een gelijke methode. Als je van messaging system wilt veranderen, of deze wilt uitbreiden (b.v. je wilt SMS naast mail gaan doen), moet je dus al je individuele classen gaan aanpassen? Als je dan ook nog eens dingen wilt gaan aanbieden als het kunnen uitschakelen van berichten, dan komt al die code om b.v. de voorkeuren hiervoor te checken allemaal in de person class?
Niet noodzakelijk.
Die SendCard method (ik zou er eerlijk gezegd ook een service van maken), kan evengoed een ioc container aanspreken, die het type ophaalt dat verantwoordelijk is om een kaart te versturen (die service bv), en dan die service gebruikt om een kaart te versturen naar de huidige persoon.

Maar, eerlijk gezegd, ik zou het ook zo niet doen. Het is niet de 'verantwoordelijkheid' van een Persoon om een kaart te versturen. Die verantwoordelijkheid ligt ergens anders; ik zou dus ook voor een 'SendxxService' gaan.
Een aanpak die voor veel betere 'low-coupling, high-cohesion' zorgt is gewoon een Person een Person te laten zijn, daarnaast een generieke MessagingService te hebben die alle details over messaging kent, en daarna een BirthdayMessageJob te hebben draaien, die checked wie er op een dag jarig zijn, een message daarvoor aanmaakt, en die verzend via de MessagingService van jouw app.
Zoiets, ja. :)

Trouwens, het is niet nodig om al die 'negatieve' smilies te gaan gebruiken ...
Verwijderd schreef op vrijdag 05 december 2008 @ 23:50:
[...]

Zelf snap ik *niet* waarom mensen hun domein objecten zo kaal mogelijk houden. Iedereen die gedrag *bewust* verplaatst naar 'Helper' of 'Utility' klassen zou eens het artikel van Fowler moeten lezen over Anemic Domain Models.

Wanneer je domein object klassen alleen getters en setters bevatten, ben je gewoon een datastructuur aan het bouwen om je resultset. Imho i-di-oot. Een goed domein model heeft in mijn ogen *juist* methoden die logisch zijn voor het gedrag van de applicatie.
Eens. :)
Wanneer je dus een applicatie schrijft om mensen een bericht te sturen als ze jarig zijn, dan hoort er gewoon een sendCard methode op het Person object.
Niet mee eens dus. :) zie hierboven

[ Voor 24% gewijzigd door whoami op 06-12-2008 09:50 ]

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

Verwijderd

flowerp schreef op zaterdag 06 december 2008 @ 01:02:
[...]

Dat gaat toch niet werken joh? |:(
Nee, dat gaat niet werken, dat stelde ik volgens mij ook helemaal niet voor. Zo'n render methode heeft daar niets te zoeken. Die makeReservation trouwens ook niet. Dat zou eeder op een Person moeten staan. Zo'n makeReservation zou ik vervolgens aanroepen vanuit een Service die transaction boundaries regelt en toegangscontrole uitvoert.
flowerp schreef op zaterdag 06 december 2008 @ 01:02:
[...]
Je bedoelt net zoals in het problem domain zelf, waarbij mensen allemaal hun eigen methode hebben om zichzelf een bericht te laten sturen?

Volgens jouw methode kan een applicatie dan alleen die sendCard implementatie gebruiken die het person object zelf intern biedt. Handig als je ook nog andere dingen hebt waar je berichten heen kunt sturen en al die dingen implementeren intern deze, of een gelijke methode. Als je van messaging system wilt veranderen, of deze wilt uitbreiden (b.v. je wilt SMS naast mail gaan doen), moet je dus al je individuele classen gaan aanpassen? Als je dan ook nog eens dingen wilt gaan aanbieden als het kunnen uitschakelen van berichten, dan komt al die code om b.v. de voorkeuren hiervoor te checken allemaal in de person class?
Wellicht was m'n methode naam een beetje verkeerd, want ik bedoelde er mee dat een Person object een kaart kan ontvangen. Soort van $person->receiveCard(new Card());

Software ontwikkeling speelt zich altijd af op de dunne scheidslijn tussen pragmatisch werken en utopia. Mocht het tot de mogelijkheden behoren dat al je 'als als als' zaken realiteit worden, dan zou ik de methode niet receiveCard() noemen, maar dan zou ik een notify methode op de person zetten. Vervolgens kan je dan verschillende notify messages naar een persoon sturen; sms, email, intern bericht.

Volgens mij wil je wel zo'n notify methode op je domein object hebben aangezien je het desbetreffende Person object een bericht wilt sturen. Daarom hoort de methode in mijn ogen ook thuis op het Person object.

Zo'n notify is immers een klein atomair blokje dat herbruikt kan worden in een groter geheel. Stel je voor dat het om een spel gaat waarbij een Person een Item kan kopen.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person
{
    public function buy(Item $item) {
        $seller = $item->getOwner();
        $item->setOwner($this);
        ItemRepositoryFactory::getInstance()->save($item);
        $seller->notify(new Message($this->getUsername() . ' heeft je ' . $item->getName() . ' gekocht.'));
    }

    public function notify(Message $message) {
        $notification = new Notification ($title, $message);
        
        // wil je berichtenverkeer uitzetten, dan kan je in je bootstrap een 
        // StubNotificationRepository registreren in je factory.
        $notificationRepository = NotificationRepositoryFactory::getInstance();
        $notificationRepository->save ($this, $notification);
    }
}


Hier zie je dat zo'n notify een klein atomair blokje is in een groter geheel; de buy methode. Zo'n buy methode kan weer een atomair blokje zijn in een nog groter geheel. Alles wat in een transactie moet gebeuren, zou ik in een Service zetten. Vanuit je webapp wil je in je Action het liefst zo'n Service aanspreken. In de service regel je transacties en toegangscontrole.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TransferServiceImpl implements TransferService
{
    public function buyItem(Person $person, Item $item) {
        $this->database->startTransaction();
        $person->buy($item);
        $this->database->commit();
    }
}

class MessageServiceImpl implements MessageService
{
    public function send(Person $person, Message $message) {
        $this->database->startTransaction();
        $person->notify($message);
        $this->database->commit();
    }
}


In het eerder genoemde voorbeeld zou ik dus op de Person gewoon een notify implementeren, en ook een service maken die de notify methode van de Person aanspreekt. Vanuit je webapp Action waar je een Person een bericht wilt sturen roep je de MessageService aan, waar je een Item wil kopen, spreek je de TransferService aan (arbitrair of je dit zo moet modellen, maar het gaat om het idee).

Kortom: ik ben heel erg benieuwd of jullie dit ontwerp verwerpen, en zo ja, waarom. En ik vind die denigrerende smileys best vervelend. Ik neem aan dat wij beiden hier zitten om van elkaar te leren en dat er geen heilige graal is op het gebied van software ontwikkeling.
flowerp schreef op zaterdag 06 december 2008 @ 01:02:
[...]

En dat doe je dan ook voor alle andere X dingen die je met een Person kunt doen? :X

Een aanpak die voor veel betere 'low-coupling, high-cohesion' zorgt is gewoon een Person een Person te laten zijn, daarnaast een generieke MessagingService te hebben die alle details over messaging kent, en daarna een BirthdayMessageJob te hebben draaien, die checked wie er op een dag jarig zijn, een message daarvoor aanmaakt, en die verzend via de MessagingService van jouw app.

Elk stukje code concentreerd zich dan op 1 ding en je krijgt dan geen idiote 'high-coupling, low-cohesion' gevallen als een Person die: zichzelf berichten stuurt, zichzelf subscribed, zichzelf unsubscribed, zichzelf een huis laat bouwen, zichzelf een reservering voor de film laat maken, zichzelf...
Ik denk dat we elkaar niet goed begrepen hebben. Dit is inderdaad niet hoe je het wilt hebben.

[ Voor 3% gewijzigd door Verwijderd op 06-12-2008 11:13 ]


Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
whoami schreef op zaterdag 06 december 2008 @ 09:45:
Wat is het voordeel van een dergelijk 'dom' object ? Als het gewoon een 1 op 1 mapping is met je DB, dan heeft dit toch geen enkele toegevoegde waarde, en dan kan je beter gebruik gaan maken van -als je in .NET werkt bv- de DataSet/DataTable classes.
Nou, het voordeel is natuurlijk naming, typing en overall duidelijkheid. Een DataSet heet ten eerste al DataSet en niet Reservation. Bij het gebruik van DataSet in je code, zie je dus overal die naam staan, wat je code niet duidelijker maakt.

Daarnaast zijn alle columns in een DataSet (ik neem aan ook in .NET), van het zelfde -declared- type. In mijn Reservation entity kan ik b.v. een int hebben voor het aantal plaatsen, een BigDecimal voor het bedrag en een String voor de naam van de voorstelling. Met alleen een DataSet, moet ik dan in code telkens onthouden welk type een column was: getInt("seats"), getBigDecimal("price"), getString("name"), etc. Nog leuker wordt het als ik er ook custom objects in heb. Zeg dat een Reservation een List<Customer> bevat. Dan moet ik ook nog eens overal gaan casten: (List<Customer>)getObject("customers"). De generieke DataSet kent natuurlijk niet mijn type. Dit alles lijkt me niet echt een type-safe en solide manier van werken.

Het wordt nog erger. Bij alleen gebruik maken van een DataSet, heb ik de field names dus alleen in een String. Ik kan snel iets verkeerd schrijven: getString("nqme") ipv "getString("name"). Geen compiler die dat ontdekt voor me.

Tevens is een DataSet altijd per definitie een ding dat meerdere rows kan bevatten. Als de code overal een DataSet doorgeeft, dan moet de code die deze doorkrijgt altijd rekening houden met het feit dat er meerdere in kunnen zitten, of, als dat echt niet mag, continu checken dat de DataSet maar 1 row heeft.

Het grappige is, toen ik pas met ResultSets in het algemeen te maken kreeg (destijds nog in C++), heb ik dit inderdaad wel overwogen en heel vroeger ook wel eens gedaan, maar het maakt je code alleen onduidelijker.

Een entity, zelfs als deze nagenoeg een 1:1 mapping is, biedt enorm veel voordelen. Een typesafe, object graph druk je er zo mee uit, dat terwijl een DataSet gewoon een tabel blijft.
Slecht voorbeeld imho. :)
Een Reservation entity moet geen 'MakeReservation' method hebben, want een Reservatie wordt niet door een reservatie gecreeërd.
Bedoelen we hier niet hetzelde? Want dit is ook precies mijn punt ;)
Waarom niet ? Wie bepaald dat ?
In mijn project, ik :+

Het was natuurlijk een voorbeeld, niet dat in het algemeen een web laag zoiets niet zou mogen. Het punt is, toegang tot services en resources wil je dikwijls beperken. Dat is ook het voordeel van een gelaagd model. In een OS geldt dat net zo. De low level kernel mag bij de hardware, de services layer mag bij de low level kernel, en de userland software mag bij de services. In grote systemen maak je een scheiding aan tussen dingen, zodat je bepaalde garanties hebt in elke laag. Als software dan dwars door alle lagen van alles gaat lopen aan roepen, neemt de complexiteit en de beheersbaarheid enorm toe,

Natuurlijk, in kleine web applicaties van een 10-tal pagina's mag de web layer best direct naar de DB toe, maar in grotere (enterprise) applicaties wil je dat niet altijd. Het voorbeeld met de remote Swing client (neem i.p.v. Swing gerust een remote win32 client), was misschien al een stuk duidelijker. Je wilt een client natuurlijk niet direct toegang geven tot je interne server side resources.

Echter, entity objecten gaan juist wel door al je lagen heen, want deze defineren je business domain. Als mijn enterprise laag een Reservation aanmaakt, dan kan deze gewoon zo naar de remote Swing client gestuurt worden. Deze kan hier dan mooi een rendering van maken en ik hoef niet bang te zijn dat die client toch bij mijn interne resources zal kunnen vanwege bepaalde methods in die Reservation.
Je remote client doet calls naar die service layer, die de domain objecten aanspreekt, en geeft je evt. resultaten terug mbhv DTO's.
Klopt, en dat is ook een bekende andere manier van werken. Ik heb zelf een lange tijd op die manier gewerkt. Als je echter je entities eenvoudig houdt dan -zijn- dit al je DTO's. Dat scheelt een hele hoop zaai omzet werk overal. De remote client is natuurlijk een heel duidelijk voorbeeld, maar dezelfde mate van "separation of concerns" wil je ook dikwijls binnen je local boundaries (aka je 'layers') al hebben.
Het is een heel gedoe, maar wat als je je domain layer refactored bv. Je zorgt niet voor nieuwe functionaliteit, maar bepaalde classes ga je herstructureren.
Als jij domain entities naar je client stuurt, dan ga je ook je client opnieuw moeten deployen.
Klopt, maar dat geldt voor nagenoeg alle onderdelen van je code, als mijn app nog redelijk simpel op 1 VM draait, met 2 modules; een enterprise module en een web module. Als ik dan types in de enterprise module verander die opgevraagt worden door de web module, moet ik natuurlijk beiden wijzigen en deployen. Omdat ze echter in de regel toch al via 1 archive file gedeployed worden is dat geen probleem. Voor de remote client heb je in het geval van Java een mechanisme dat Javawebstart heet, waarmee je vrij makkelijk remote clients kunt updaten. Ik weet niet helemaal of dat is waar je boven op doelt?
En wat als je bv (N)Hibernate gebruikt als O/R mapper ? (N)Hibernate zorgt zelf voor de dirty tracking van objecten. Hoe ga je dat oplossen als je je entities (die door NHibernate gemanaged worden), naar de client over en weer stuurt ?
Maar lieve schat, dat is toch juist het hele mechanisme in Hibernate met attached en detached entities? ;)

Kort weg, je entities zijn in Hibernate (en JPA) onderdeel van een persistence context. Deze persistence context heeft een scope, en die is meestal (maar niet altijd) gelinked aan je transaction scope. Zodra je deze scope closed, is je entity niet meer 'attached'. Operaties die lazy loading waren (b.v. mogelijk het ophalen van die customer list in mijn Reservation voorbeeld boven), gooien in die staat dan een lazy initialization exception. Specifiek voor het dirty tracking (-> updaten fields -> wordt automatisch op een bepaald moment persisted) werkt dat gewoon niet meer op een detached entity. Dat is per design.

Ik kan dus in Hibernate binnen mijn transaction een entity ophalen en deze terug geven aan een (remote) client. Als ik dat doe via een (stateless) session bean, dan is de method die mijn remote client oproept per default precies 1 transaction en dus 1 persistence context. Zodra ik de entity return is deze dan detached.

De client ziet nu een gewone POJO, en kan alle fields opvragen en veranderen (als ik lazy initialization exceptions wil voorkomen, liet ik mijn entity in mijn session bean als eager loaden). Als de client klaar is met dit object kan hij deze weer aan een methode in mijn session bean terug geven. Aanvankelijk is dit object, ook binnen de transaction van deze methode, nog steeds een detached entity. De entity manager (standaard ding in Java), heeft echter een method "merge" die een detached entity weer aan de persistence context toevoegt.

Ik kan me nu inderdaad voorstellen dat als je het attached/detached mechanisme van 'Hibernate/JPA/ongetwijfeld andere ORMs' niet kent, dat je je dan afvraagt waar ik het nu boven overhad. Inderdaad, zonder zo'n mechanisme is een DTO logisch. Feitelijk hebben Hibernate en consorten het DTO mechanisme geautomatiseerd.
Trouwens, het is niet nodig om al die 'negatieve' smilies te gaan gebruiken ...
ok, ok, maar ze gaven wel mooi mijn echte reactie weer toen ik het las en got biedt die smileys wel aan ;)

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:07
flowerp schreef op zaterdag 06 december 2008 @ 11:56:
[...]


Nou, het voordeel is natuurlijk naming, typing en overall duidelijkheid. Een DataSet heet ten eerste al DataSet en niet Reservation. Bij het gebruik van DataSet in je code, zie je dus overal die naam staan, wat je code niet duidelijker maakt.
typed datasets. :)
Daarnaast zijn alle columns in een DataSet (ik neem aan ook in .NET), van het zelfde -declared- type. In mijn Reservation entity kan ik b.v. een int hebben voor het aantal plaatsen, een BigDecimal voor het bedrag en een String voor de naam van de voorstelling. Met alleen een DataSet, moet ik dan in code telkens onthouden welk type een column was: getInt("seats"), getBigDecimal("price"), getString("name"), etc. Nog leuker wordt het als ik er ook custom objects in heb. Zeg dat een Reservation een List<Customer> bevat. Dan moet ik ook nog eens overal gaan casten: (List<Customer>)getObject("customers"). De generieke DataSet kent natuurlijk niet mijn type. Dit alles lijkt me niet echt een type-safe en solide manier van werken.
Lieve schat, dat heb je niet met typed datasets.
Met een typed dataset heb je controle over de naam van je getypeerde dataset, over de datatables die erin zitten, en de types en namen van de velden.

Volgens mij kan je zelfs afdwingen dat een bepaalde table bv slechts 1 row mag hebben.

Nu wil ik hier hoegenaamd geen lans breken voor het gebruik van (typed) datasets; ik heb er ook mee gewerkt, en er kleven een paar nadelen aan. Zo is het moeilijk om je domein mbhv datasets te gaan modelleren. Daar zijn ze natuurlijk ook niet voor bedoeld.
Bedoelen we hier niet hetzelde? Want dit is ook precies mijn punt ;)
Precies. :)
In mijn project, ik :+
Ik bedoelde meer: wat is de achterliggende reden ? Waarom ?
Het was natuurlijk een voorbeeld, niet dat in het algemeen een web laag zoiets niet zou mogen. Het punt is, toegang tot services en resources wil je dikwijls beperken. Dat is ook het voordeel van een gelaagd model. In een OS geldt dat net zo. De low level kernel mag bij de hardware, de services layer mag bij de low level kernel, en de userland software mag bij de services. In grote systemen maak je een scheiding aan tussen dingen, zodat je bepaalde garanties hebt in elke laag. Als software dan dwars door alle lagen van alles gaat lopen aan roepen, neemt de complexiteit en de beheersbaarheid enorm toe,

Natuurlijk, in kleine web applicaties van een 10-tal pagina's mag de web layer best direct naar de DB toe, maar in grotere (enterprise) applicaties wil je dat niet altijd. Het voorbeeld met de remote Swing client (neem i.p.v. Swing gerust een remote win32 client), was misschien al een stuk duidelijker. Je wilt een client natuurlijk niet direct toegang geven tot je interne server side resources.
Helemaal mee eens.
Echter, entity objecten gaan juist wel door al je lagen heen, want deze defineren je business domain. Als mijn enterprise laag een Reservation aanmaakt, dan kan deze gewoon zo naar de remote Swing client gestuurt worden. Deze kan hier dan mooi een rendering van maken en ik hoef niet bang te zijn dat die client toch bij mijn interne resources zal kunnen vanwege bepaalde methods in die Reservation.
Tja, hangt er totaal van af hoe je het bekijkt... Zoals eerder al aangehaald door Quist: er bestaat geen silver bullet.
Maar lieve schat, dat is toch juist het hele mechanisme in Hibernate met attached en detached entities? ;)

Kort weg, je entities zijn in Hibernate (en JPA) onderdeel van een persistence context. Deze persistence context heeft een scope, en die is meestal (maar niet altijd) gelinked aan je transaction scope. Zodra je deze scope closed, is je entity niet meer 'attached'. Operaties die lazy loading waren (b.v. mogelijk het ophalen van die customer list in mijn Reservation voorbeeld boven), gooien in die staat dan een lazy initialization exception. Specifiek voor het dirty tracking (-> updaten fields -> wordt automatisch op een bepaald moment persisted) werkt dat gewoon niet meer op een detached entity. Dat is per design.

Ik kan dus in Hibernate binnen mijn transaction een entity ophalen en deze terug geven aan een (remote) client. Als ik dat doe via een (stateless) session bean, dan is de method die mijn remote client oproept per default precies 1 transaction en dus 1 persistence context. Zodra ik de entity return is deze dan detached.

De client ziet nu een gewone POJO, en kan alle fields opvragen en veranderen (als ik lazy initialization exceptions wil voorkomen, liet ik mijn entity in mijn session bean als eager loaden). Als de client klaar is met dit object kan hij deze weer aan een methode in mijn session bean terug geven. Aanvankelijk is dit object, ook binnen de transaction van deze methode, nog steeds een detached entity. De entity manager (standaard ding in Java), heeft echter een method "merge" die een detached entity weer aan de persistence context toevoegt.
Uhu, maar, je detached entity moet, als je 'm wilt updaten opnieuw aan een sessie gekoppeld worden.
En daar wringt het voor mij een beetje; je moet dan Hibernate eerst een select laten uitvoeren, zodanig dat hij kan weten wat er aan je entity gewijzigd is, alvorens je 'm kan updaten.
ok, ok, maar ze gaven wel mooi mijn echte reactie weer toen ik het las en got biedt die smileys wel aan ;)
GoT biedt die smilies wel aan , maar dan nog is het aan jou om er verstandig mee om te springen. Als je dergelijke smilies teveel 'op de man' gebruikt, dan komt dit denigrerend over, en verpest het de sfeer.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
whoami schreef op zaterdag 06 december 2008 @ 12:11:
[...]
Lieve schat, dat heb je niet met typed datasets.
lol, 1-1 ;) Inderdaad, het concept typed dataset kende ik nog niet. Dit zit vrij dicht aan tegen wat je krijgt als je een simpele pojo met alleen instance variables (fields) annotate met alleen de vereiste annotations. De MSDN library laat dit kort zien met VB syntax: http://msdn.microsoft.com/en-us/library/esbykkzb(VS.71).aspx

In dat geval lijkt .NET dus in zekere zin als een soort type generator op te treden. Een klein stapje verder is het dan om maar meteen gewoon een simpele class te generaten. Sommige tools kunnen dit inderdaad doen gegeven een bestaand relationeel schema en een .xml mapping file.

Om nog even het allereerste voorbeeld te herhalen uit de OP:

Java:
1
2
3
4
5
6
7
8
package somecompany.models; 

public @Entity class Person { 

   Long @Id id; 
   String name; 
   Date dateOfBirth;   
}


Dit geeft ook al een compleet gemapte entity aan en hier heb je gewoon echt 100% een normale class. Inderdaad, zo'n typed DataSet heeft nagenoeg alle voordelen die een gewone class ook biedt, maar stylistisch gezien is het het misschien 'net niet' (afhankelijk van voorkeuren misschien). Ik kan me voorstellen dat als het veel makkelijker is om even een typed DataSet te mappen dan om een gehele class te defineren, dat je dan soms wel even snel voor een typed DataSet gaat. Echter, als het mappen van een gewone class net zo makkelijk is, of zelfs nog makkelijker, dan wordt het nut van een typed DataSet al minder. Tenzij die laatste natuurlijk nog allemaal extra handige truukjes heeft:
As such, it inherits all the methods, events, and properties of a DataSet
Tja, hangt er totaal van af hoe je het bekijkt... Zoals eerder al aangehaald door Quist: er bestaat geen silver bullet.
Natuurlijk. Als ik er van overtuigd was dat ik die bullet in mijn pistool al had zitten, had ik deze topic niet hoeven te beginnen ;)

En inzichten veranderen nog al eens. Ergens begin jaren '90 was ik er van overtuigd dat gewoon overal ResultSets doorsturen een goede oplossing was. Dat die dingen ook nog eens geclosed moeten worden had ik toen nog niet helemaal door. :X ;)

En in het begin van de jaren '00 vond ik DTOs in combinatie met fat entities erg voor de hand liggen, terwijl ik nu meer neig naar 'slim entities' als zelfstandige naamwoorden in combinatie met services als werkwoorden zeg maar. Mijn entities zwerven door de hele app heen, terwijl services een duidelijke plek hebben.

Het blijft natuurlijk een beetje aftasten. Ik sluit niet uit dat we er allemaal over nogmaals 10 jaar weer heel anders over denken.
Uhu, maar, je detached entity moet, als je 'm wilt updaten opnieuw aan een sessie gekoppeld worden.
En daar wringt het voor mij een beetje; je moet dan Hibernate eerst een select laten uitvoeren, zodanig dat hij kan weten wat er aan je entity gewijzigd is, alvorens je 'm kan updaten.
Hoeft natuurlijk niet perse. Alle operaties zijn uitdrukkelijk niet 1:1, maar worden mogelijk verzameled, geoptimaliseerd en op een later moment uitgevoerd (er zijn daarnaast wel speciale methods om iets ogenblikkelijk te laten plaatsvinden).

Speciaal voor entities die niet heel frequent worden geupdate is het waarschijnlijk dat je er de @Cache annotation op hebt gezet. Dat wil zeggen dat Hibernate die entity, by ID, onthoudt en dus ook op de hoogte is van veranderingen. Als jij dus met een detached entity komt aanzetten (met veranderingen sinds de detach), dan kan Hibernate in de regel heel snel zien, zonder een select query te hoeven doen, of deze is veranderd.

Een zelfde soort mechanisme wordt ook gebruikt als je binnen 1 persistence context dezelfde entity meerdere keren veranderd (hetzij direct, hetzij via een detach-verander-merge). Hibernate gaat niet elke verandering direct naar de DB schrijven. Pas als de persistence context afgesloten wordt OF als je handmatige een flush aanroept, worden de verzamelde veranderingen doorgevoerd.

Zo'n cache annotation ziet er overigens zo uit:

Java:
1
2
3
4
5
6
7
8
9
package somecompany.models; 

@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public @Entity class Person { 

   Long @Id id; 
   String name; 
   Date dateOfBirth;   
}

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

Verwijderd

flowerp schreef op zaterdag 06 december 2008 @ 01:02:
[...]

...

Een aanpak die voor veel betere 'low-coupling, high-cohesion' zorgt is gewoon een Person een Person te laten zijn, daarnaast een generieke MessagingService te hebben die alle details over messaging kent, en daarna een BirthdayMessageJob te hebben draaien, die checked wie er op een dag jarig zijn, een message daarvoor aanmaakt, en die verzend via de MessagingService van jouw app.

...
Een Service mag je alleen vanuit een hogere laag aanroepen en kan je niet aanroepen vanuit je domein objecten. Natuurlijk kan zo'n generieke MessagingService handig zijn, maar het is verstandig om die MessagingService zo kaal mogelijk te laten.

PHP:
1
2
3
4
5
6
7
8
9
10
class MessageService {
    public function notifyPersonsWhoAreHavingTheirBirthdays() {
        $this->database->startTransaction();
        $persons = PersonRepositoryFactory::getInstance()->getPersonsWhoAreHavingTheirBirthdays();
        foreach ($persons as $person) {
            $person->notify(new Message('Gefeliciteerd'));
        }
        $this->database->commit();
    }
}


Als je in bovenstaand voorbeeld niet de notify methode op de person zet, maar direct een MessageRepository aanspreekt, ben je eigenlijk een Transaction Script aan het schrijven, waardoor de herbruikbaarheid van het aspect "stel een persoon op de hoogte van iets" uit je Domein Model verdwijnt. Door juist *wel* een notify methode op je Person object te houden, is deze kleine atomaire taak overal beschikbaar. Denk aan het voorbeeld wat ik eerder gaf met betrekking tot een spel waarbij een Item gekocht kan worden, waarna de verkoper een bericht moet krijgen.

De service laag moet in mijn ogen zo dun mogelijk zijn als kan. Je Domein Model moet zo dik mogelijk als maar kan. Daarom snap ik nog steeds niet waarom jullie zo tegen een notify methode op je Person klasse zijn. Vanuit je Domein Model kan je immers geen Service aanspreken, terwijl er wel methoden te bedenken zijn op je Person object ($person->buy($item)) die zelf weer een bericht moeten sturen aan een persoon.

Wanneer je van mening bent dat zo'n buy methode *ook* niet op je Person object haalt, dan wil ik nogmaals herhalen dat je anders verzandt in Anemic Domain Modelling waarbij je Services in feite Transaction Scripts (P of EAA:110) zijn en je domein objecten droge datastructuren zijn om je ResultSet. Fowler schrijft over Transaction Scripts: "As the business logic gets more complicated, however, it gets progressively harder to keep it in a well-designed state. One particular problem to watch for is its duplication between transactions. Since the whole point is to handle one transaction, any common code tends to be duplicated".

Ik denk dat hij hiermee bedoelt dat wanneer je een dun Domein Model hebt, en een dikke Service laag de verschillende methoden in je Service laag in feite Transaction Scripts worden waarbij dus het gevaar ontstaat dat je veel taken zit te herhalen. In een TransferService buy methode waarbij je een Item koopt moet je dezelfde messaging code aanroepen die je ook moet aanroepen als je in een MessageService methode een bericht wilt sturen aan een Person. In dat geval loop je jezelf te herhalen, en daarom ben ikzelf terughoudend met Transaction Scripts.

Transaction Scripts zijn in mijn ogen handig wanneer je een kleine actie wil schrijven die periodiek een bepaalde taak in een Batch moet doen, of wanneer je bijvoorbeeld snel iets moet hebben om data te importeren of te converteren.

Ik ben gewoon echt heel erg benieuwd hoe jullie de functie van de Service laag zien, wat daar wel in thuis hoort en wat niet, en wat in je Domein Model thuishoort en wat niet.

Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
Verwijderd schreef op zaterdag 06 december 2008 @ 13:51:
Wanneer je van mening bent dat zo'n buy methode *ook* niet op je Person object haalt, dan wil ik nogmaals herhalen dat je anders verzandt in Anemic Domain Modelling waarbij je Services in feite Transaction Scripts (P of EAA:110) zijn en je domein objecten droge datastructuren zijn om je ResultSet.
Een wijs man hier of got zei ooit het volgende:
EfBe schreef op vrijdag 08 augustus 2008 @ 12:43:
Dat terzijde, het wordt tijd dat meneer Fowler minder belangrijk gevonden gaat worden. Jarenlang heeft hij veel dingen geroepen die slecht waren etc. en er zijn nauwelijks wetenschappelijke papers over te vinden waarom dat dan zo slecht zou zijn. Het ironische is dat meneer Fowler al een tijdje zich louter bezighoudt met Ruby, omdat static typed languages kennelijk zich niet lenen voor moderne software, maar de realiteit is natuurlijk dat met het OH-en over static-typed talen je geen zalen meer gevuld krijgt en de boeken niet meer verkocht krijgt.

Het geneuzel waarom een anemic domain model dan wel zo slecht is, is bv totaal niet onderbouwd met wetenschappelijk onderzoek, maar met gutt-feeling gebaseerd pseudoscience en ander koffietafelgeklets. Het is bv vrij simpel aan te tonen dat een multi-class consuming process beter in 1 class kan worden geplaatst dan gefragmenteerd over meerdere classes. Maar goed...
Hier heb ik eigenlijk weinig aan toe te voegen. Misschien nog het feit dat je services ook je transactional boundaries bewaken, je concurrency en je resources. Deze dingen bestaan niet in je eigenlijke domain, maar zijn implementatie artifacts. Hiervan wil ik mijn entities zoveel mogelijk vrij houden, daar deze dikwijls in diverse contexten gebruikt kunnen worden.

Speciaal in Java, entity beans defineren nu eenmaal geen transactional contexts, laat staan dat ze wat zeggen over transaction propagation. In veel gevallen zijn declarative transactions toch wel aan te bevelen, en niet wat jij doet met user managed transactions. Session beans daarentegen zijn standaard transactional en je kunt gebruiken maken van de @Resource annotation en de @PersistenceContext annotation om references te krijgen naar andere resources/services resp. 'de database'. In je entity beans zou je dat programmatisch moeten doen via static factories. Ook kun je een service makkelijk anynchroon laten reageren op messages (een message driven bean). Zulk soort gedrag allemaal zelf lopen te programmeren in je entities lijkt me gewoon niet de weg.

Natuurlijk zijn een aantal van deze dingen specifiek aan Java en kunnen andere platformen dit anders doen. Java biedt gewoon in het standaard platform al entities aan en natuurlijk duwt dat je gedachten in een speciale richting (b.v. als jouw platform makkelijke typed DataSets biedt, maar geen makkelijke standaard O/R mapping, dan denk je misschien al wat sneller in termen van die typed DataSets). Een alternatief/aanvulling voor de standaard library van Java is b.v. Spring, en maakt voor zover ik begrepen heb een wat minder strikte scheiding tussen entities en services. Dat wil zeggen, in dat geval kun je transacties declareren voor -elke- Spring bean, en als jij je Spring bean dan als een entity wilt gebruiken dan kan dat (geloof ik, ik gebruik zelf geen Spring).

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • Confusion
  • Registratie: April 2001
  • Laatst online: 01-03-2024

Confusion

Fallen from grace

Verwijderd schreef op zaterdag 06 december 2008 @ 11:06:
Nee, dat gaat niet werken, dat stelde ik volgens mij ook helemaal niet voor. Zo'n render methode heeft daar niets te zoeken. Die makeReservation trouwens ook niet. Dat zou eeder op een Person moeten staan. Zo'n makeReservation zou ik vervolgens aanroepen vanuit een Service die transaction boundaries regelt en toegangscontrole uitvoert.
Een Reservering heeft normaalgesproken een has-a relatie met een Person. In de database ligt er ook een FK van de reserveringentabel naar de personentabel. Je zal van een persoon soms alle reserveringen moeten ophalen, maar dat is een speciaal geval en geen reden een Reservering als onderdeel van een Person te beschouwen. Derhalve hoort een makeReservation() methode niet in een Person entity thuis. Althans, het hoort daar netzoveel thuis als in de relevante Product entity die gereserveerd wordt en de relevante Leverancier entity waarbij de reservering geplaatst wordt. Dan kies je niet willekeurig een van die entities om de methode in onder te brengen, maar breng je hem onder in een losse klasse. Als een klant verkiest een stoel te reserveren:
Java:
1
ReservationService.makeReservation(customer, product, leverancier)
of
Java:
1
reservationBean.makeReservation(product)
, als die stateful bean al aan een klant en leverencier gekoppeld is.
Wellicht was m'n methode naam een beetje verkeerd, want ik bedoelde er mee dat een Person object een kaart kan ontvangen. Soort van $person->receiveCard(new Card());
Dat lijkt me geen handige aanpak. Er zijn waarschijnlijk contexten waarin het niet betekenisvol is om op een Person instance de receiveCard() methode aan te roepen. Niet alleen kan het misgaan als de verkeerde laag de methode aanroept (waardoor je je programmeurs de mogelijkheid geeft logica in de verkeerde laag uit te laten voeren, waardoor je je wezenloos zoekt waarom al die kaarten toch dubbel bezorgd worden), maar bovendien wil je misschien helemaal niet dat die laag uberhaupt weet wat een Card is. Je wilt het Person object kunnen serialiseren en verzenden, zonder dat die onlosmakelijk met het Card object verbonden is.
Volgens mij wil je wel zo'n notify methode op je domein object hebben aangezien je het desbetreffende Person object een bericht wilt sturen. Daarom hoort de methode in mijn ogen ook thuis op het Person object
Een Person object representeert de data van een person. Als een ingelogde user over een nieuwe card geinformeerd moet worden, dan is er een Person object aan een ingelogde sessie gekoppeld, maar het is die sessie die een registerCardListener() heeft uitgevoerd en daardoor bij iedere nieuwe opgevraagde pagina automatisch ziet of er een nieuwe kaart beschikbaar is. Daar heeft de Person niets mee te maken. Als je alle methoden die op een of andere manier met een gebruiker te maken hebben in de Person klasse wil hangen, dan kan je de hele applicatie daar wel in gaan stoppen.
Verwijderd schreef op zaterdag 06 december 2008 @ 13:51:
Als je in bovenstaand voorbeeld niet de notify methode op de person zet, maar direct een MessageRepository aanspreekt, ben je eigenlijk een Transaction Script aan het schrijven, waardoor de herbruikbaarheid van het aspect "stel een persoon op de hoogte van iets" uit je Domein Model verdwijnt.
Ik vind het bijzonder dubieus om dezelfde notify() methode voor verschillende dingen te herbruiken. Als een bepaald object pakweg 'Krabbels' en 'Email' moet kunnen ontvangen, dan graag een updateKrabbels() en een updateEmails() methode, voorafgegaan door een registerEmailListener() en een registerKrabbelListener(). Een registerListener() met een update() callback verzand al snel in update(EventType, Event, someVar, someOtherVar), waarbij niet alle variabelen niet altijd gevuld zijn en de update() methode diverse if's en switch statements bevat. Als de update methoden code delen, dan kan je ze beide eenzelfde supermethode laten aanroepen.

offtopic:
Dit is, BTW, precies waarom dynamische talen vaak zulke rotzooi opleveren, tot fantatische situaties, waarin dezelfde call-parameter afhankelijk van een andere call-parameter een compleet andere betekenis aan kan nemen. En daarmee is het precies waarom Java en C# tot in lengte van dagen niet door PHP, Python, Ruby, Groovy of welke andere nieuwerwetse scriptingtaal dan ook verdrongen zullen worden. Wie niet fatsoenlijk kan programmeren, kan dat zeker niet in een dynamische taal. Wie wel fatsoenlijk kan programmeren, moet voordurend op zijn hoede zijn zich niet door de perverse mogelijkheden te laten verleiden.

[ Voor 22% gewijzigd door Confusion op 06-12-2008 16:05 ]

Wie trösten wir uns, die Mörder aller Mörder?


Acties:
  • 0 Henk 'm!

Verwijderd

Confusion schreef op zaterdag 06 december 2008 @ 15:52:
[...]

Je zal van een persoon soms alle reserveringen moeten ophalen, maar dat is een speciaal geval en geen reden een Reservering als onderdeel van een Person te beschouwen. Derhalve hoort een makeReservation() methode niet in een Person entity thuis.
Je hebt inderdaad gelijk. Ik heb nog te weinig ervaring met DDD en had er niet goed over nagedacht. Net zoals het overboeken van geld, hoort het boeken van iets idealiter op een Service.
Confusion schreef op zaterdag 06 december 2008 @ 15:52:
[...]

Dat lijkt me geen handige aanpak. Er zijn waarschijnlijk contexten waarin het niet betekenisvol is om op een Person instance de receiveCard() methode aan te roepen. Niet alleen kan het misgaan als de verkeerde laag de methode aanroept (waardoor je je programmeurs de mogelijkheid geeft logica in de verkeerde laag uit te laten voeren, waardoor je je wezenloos zoekt waarom al die kaarten toch dubbel bezorgd worden), maar bovendien wil je misschien helemaal niet dat die laag uberhaupt weet wat een Card is.
Hier mis ik even de kern van de zaak. Over wat voor soort contexten spreken we waarbij zo'n notify aanroep zinloos is? Bedoel je bijvoorbeeld een View waarin je *nooit* een notify methode wil kunnen aanroepen? Is het niet mogelijk om voor dat soort contexten een Data Transfer Object te gebruiken zodat je in je View context alleen getters kan aanroepen?
Confusion schreef op zaterdag 06 december 2008 @ 15:52:
Je wilt het Person object kunnen serialiseren en verzenden, zonder dat die onlosmakelijk met het Card object verbonden is.


[...]

Een Person object representeert de data van een person. Als een ingelogde user over een nieuwe card geinformeerd moet worden, dan is er een Person object aan een ingelogde sessie gekoppeld, maar het is die sessie die een registerCardListener() heeft uitgevoerd en daardoor bij iedere nieuwe opgevraagde pagina automatisch ziet of er een nieuwe kaart beschikbaar is. Daar heeft de Person niets mee te maken. Als je alle methoden die op een of andere manier met een gebruiker te maken hebben in de Person klasse wil hangen, dan kan je de hele applicatie daar wel in gaan stoppen.
Interessante benadering. Ik zal hier eens over na gaan denken. Klinkt als een soort van Event Driven Design? Is hier toevallig nog een speciale naam voor?
Confusion schreef op zaterdag 06 december 2008 @ 15:52:

[...]

Ik vind het bijzonder dubieus om dezelfde notify() methode voor verschillende dingen te herbruiken. Als een bepaald object pakweg 'Krabbels' en 'Email' moet kunnen ontvangen, dan graag een updateKrabbels() en een updateEmails() methode, voorafgegaan door een registerEmailListener() en een registerKrabbelListener(). Een registerListener() met een update() callback verzand al snel in update(EventType, Event, someVar, someOtherVar), waarbij niet alle variabelen niet altijd gevuld zijn en de update() methode diverse if's en switch statements bevat. Als de update methoden code delen, dan kan je ze beide eenzelfde supermethode laten aanroepen.
Mijn grote vraag is dan: wat zou je wel op je domein objecten zetten? Hou je ze persoonlijk zo kaal mogelijk, en stop je zoveel mogelijk Business Logic in je Services?

Mijn volgende vraag is: wat zet je dan op je service? Wat voor soort methoden staan daar op? Zijn die allemaal private of zitten er ook public methoden op? Mag een BookingService een EmailService aanroepen zodat de gebruiker een bevestiging per email krijgt, of mogen Services geen weet hebben van elkaar?

Een probleem waarmee ik nu zit is als volgt:

ik werk aan een voetbal spel, en daar heb je Players die in Teams zitten. Een Player kan onder behandeling zijn bij een bepaalde Employee; zoals een KeeperTrainer. Daarnaast kan een Player verkocht worden en kan een speler een Player op de Transfer lijst zetten.

Er is een klein atomair blokje: removeFromTransferList. Deze methode kan op (tenminste) twee momenten aangeroepen worden: wanneer een speler verkocht wordt in de transfer methode (hij wordt verkocht en moet daarom van de transfer lijst afgehaald worden), maar ook wanneer de gebruiker er voor kiest om de Player van de transferlijst af te halen moet removeFromTransferList aangeroepen worden.

In het eerste geval is removeFromTransferList een klein blokje in een groter geheel; een buy methode. De speler moet immers ook van team wisselen, er moet gekeken worden of het kopende team wel genoeg geld heeft etc. In het tweede geval is het het enige blokje dat uitgevoerd moet worden.

Op dit moment hebben we dit als volgt vormgegeven:

PHP:
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
class TransferServiceImpl implements TransferService {
    public function transfer (Player $player, Team $buyer, $price)
    {
        $this->database->startTransaction (); 
        $player->transfer ($buyer, $price);
        $this->database->commit ();
    }
    
    public function removeFromTransferList (Player $player)
    {
        $this->database->startTransaction ();
        $player->removeFromTransferList ();
        $this->database->commit ();
    }
}

class Player {
    public function removeFromTransferList() {
        $transferRepository = TransferRepositoryFactory::getInstance();
        $transferRepository->removeTransfer ($this); 
    }

    public function transfer(Team $buyer, $amount) {
        // doe bepaalde controles;
        $this->removeFromTransferList();
    }
}


Het probleem zit hem dus in het feit dat wanneer een speler verkocht wordt, hij van de transferlijst gehaald moet worden, maar dit moet niet in een aparte transactie gebeuren. Hoe zou je dit anders kunnen modelleren zonder dat je het gedeelte binnen de removeFromTransferList op de Player class hoeft te herhalen in de twee Service methoden. In dit geval valt er nog iets voor te zeggen dat dat prima kan, maar er zijn gevallen te bedenken waar dat 'kleine' atomaire blokje, weer veel andere 'kleine' atomaire blokjes aanroept. Dat wil je niet overal herhalen op alle plaatsen waar je die code moet uitvoeren. Kortom: hoe zou jij dit probleem oplossen?

Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
Verwijderd schreef op zaterdag 06 december 2008 @ 21:56:
[...]
Het probleem zit hem dus in het feit dat wanneer een speler verkocht wordt, hij van de transferlijst gehaald moet worden, maar dit moet niet in een aparte transactie gebeuren. Hoe zou je dit anders kunnen modelleren zonder dat je het gedeelte binnen de removeFromTransferList op de Player class hoeft te herhalen in de twee Service methoden.
Dit heet transaction propagation en is iets wat veel platforms al standaard voor je oplossen. Het komt er op neer dat een methode gedeclareerd wordt als "transactional". Andere methoden die aangeroepen worden door deze methode doen dan automatisch mee aan de lopende transaction, d.w.z. de transaction scope propageerd dus naar de methoden die jij aanroept.

Als de methode die jij aanroept zelf ook gedeclareerd is als transactional, dan zal deze wanneer ie wordt aangeroepen door een andere methode die al een transactie heeft gestart mee doen aan die transactie. Wordt ie echter aangeroepen door een methode die zelf nog geen transactie begonnen is, dan begint ie wel zelf een transactie.

Je kunt hier in de regel nog erg veel aan tunen. Het bovengenoemde gedrag is slechts 1 mogelijkheid. Java kent standaard b.v. 6 mogelijkheden: NotSupported, Supports, Required, RequiresNew, Mandatory en Never. Dit houdt resp in dat een methode niet mee doet, wel mee doet, wel mee doet of zelf een transactie begint, altijd zelf een transactie begint, meedoet aan een transaction en een exception gooit als er niet reeds een transaction is, niet meedoet aan een transaction en een exception gooit als er reeds een transaction is.

Sommige andere platformen bieden tot wel 8 of 9 verschillende transaction propagation varianten aan. In de meeste gevallen kunnen ook diverse resources (XA compatible resources) mee doen aan een transaction, dus niet alleen de database. Dat kunnen b.v. dingen zijn als een cache of een messaging system.

Het gebruik van met name de declarative transaction mogelijkheden die de diverse platformen bieden is redelijk eenvoudig. In Java zijn b.v. de methods van een session bean altijd standaard transactional volgens de bovenbeschreven propagation variant "required" (dus, doet mee als er een transaction gaande is, start zelf een transaction als er nog geen gaande is). Voor andere platformen is het dikwijls of een annotation op je method of class, of ergens een declaratie in een XML file.

Er zijn twee dingen die afgeraden resp sterk afgeraden worden: programmatisch transaction beheer (ook wel user managed transactions genoemd) wordt afgeraden. Ook al biedt b.v. Java je een vrij simpele interface tot een transaction manager (JTA), die feitelijk alleen een start, commit en rollback kent, geniet declarative toch bijna altijd de voorkeur.

Wat sterk afgeraden wordt is zelf een transaction mechanism te gaan bouwen en al helemaal om zelf een mechanisme te bouwen om transaction scopes te propageren. Gebruik gewoon de API en mogelijkheden van je platform/framework. Zelf bouw leidt in de regel tot meer problemen en de complexiteit om diverse dingen precies goed te krijgen is vrij groot.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

Verwijderd

Verwijderd schreef op zaterdag 06 december 2008 @ 21:56:
Mijn volgende vraag is: wat zet je dan op je service? Wat voor soort methoden staan daar op? Zijn die allemaal private of zitten er ook public methoden op? Mag een BookingService een EmailService aanroepen zodat de gebruiker een bevestiging per email krijgt, of mogen Services geen weet hebben van elkaar?
Ik reageer alleen even hierop....

Ik kies er zelf inderdaad voor om mijn services met elkaar te laten communiceren. Een BookingService roept dus inderdaad een EmailService aan als dat nodig is.

Mijn domeinobjecten hebben gedrag in zich dat zorgt dat het domeinobject zich in de juiste staat bevindt. Als ik dus een Voetbalspeler zou hebben, dan heeft deze een property Transfervrij. Op het moment dat de speler is verkocht zet de voetbalspeler zelf zijn property Transfervrij op false. En op het moment dat hij toch ineens bij zijn huidige club wil blijven, zet hij zijn property Transfervrij ook op false.

Stel dat iemand wil kijken welke spelers er op de Transferlist staan, dan vraag de Transferlist aan de PlayerRepository alle spelers op die transfervrij zijn.

Maar goed, dat is een mogelijke implementatie natuurlijk, en zou zijn er nog vele andere mogelijkheden.


Ohja, dan nog even in grote lijnen de situatie van de transfer:

Ik zou in ieder geval een service maken die voor de transfer zorgt:
- Stuurt een persbericht uit
- Geeft bij de kopende club aan dat ze een nieuwe speler hebben en dat ze geld moeten betalen (eventueel via een andere service)
- Geeft bij de verkopende club aan dat ze een speler minder hebben, en zorgt dat ze het geld krijgen (eventueel via een andere service)
- Roept methods van de Voetbalspeler aan.

De Voetbalspeler heeft dan een method zoals TransferTo(club nweClub)
en die method zorgt er zelf voor dat de property ClubNaam wordt ingesteld, maar ook Transfervrij op false wordt gezet. Zodat dus de staat van het domeinobject goed is geregeld.

Nouja, ik hoop dat 't een beetje duidelijk is. Dit is overigens mijn persoonlijke mening, en natuurlijk niet de beste oplossing ofzo (hoogstens een van de vele mogelijke oplossingen)

Acties:
  • 0 Henk 'm!

Verwijderd

In een woord ge-ni-aal. Dit mechanisme zal vast en zeker via AOP verlopen. Nog een reden om nog sneller over te stappen op Java. Alleen het refactoren kwam me de neus al uit in PHP, maar met dit soort voordelen ben ik eigenlijk gekke henkie dat ik nog met PHP werk. Nu valt alles ook beter op z'n plaats.
Verwijderd schreef op zondag 07 december 2008 @ 15:54:
[...]

Mijn domeinobjecten hebben gedrag in zich dat zorgt dat het domeinobject zich in de juiste staat bevindt.
Dit vind ik best een interessant aspect. Wellicht zou dit (een deel) van een goede beslisprocedure zijn of iets wel of niet op een domein object hoort. Zo zou je bijvoorbeeld een methode kunnen hebben waarmee je opvraagt of een speler geblesseerd is, of hij verkocht mag worden, wat zijn gemiddelde rapportcijfer is.

Zou je dan in feite kunnen zeggen dat je afgeleide data van een bepaald domein object wil kunnen berekenen in een methode op je domein object? Bijvoorbeeld het totaalbedrag van een order, het gemiddelde rapportcijfer van de afgelopen wedstrijden dat de speler gespeeld heeft, of een speler opgesteld mag worden, of hij geblesseerd is, etc. Het resultaat van deze afgeleide berekening ga je niet weer opslaan, en daarom vermoed ik dat het prima op je domein object thuishoort.

Om terug te komen op het eerste voorbeeld: volgens deze definitie is het dan ook nuttig om zo'n isTodayBirthday methode op een entity te zetten. Dus de getters en setters horen er dan op thuis, en alle afgeleide data.

Op die manier voorkom je in ieder geval dat bijvoorbeeld de template een actie kan aanroepen op je domein object waarvan je idealiter niet wil dat hij aangeroepen kan worden. Zo is het niet zinvol om in een template een notify methode op een User object aan te roepen om een bericht te sturen naar de User. Daarentegen lijkt het me wel handig dat je de voorbeelden van afgeleide data die ik hierboven gegeven heb, wel kan gebruiken in je template.

Aangezien we nu weer redelijk terug zijn bij het originele onderwerp van dit topic, lijkt me dit handig om verder te bediscussieren :)
Verwijderd schreef op zondag 07 december 2008 @ 15:54:

De Voetbalspeler heeft dan een method zoals TransferTo(club nweClub)
en die method zorgt er zelf voor dat de property ClubNaam wordt ingesteld, maar ook Transfervrij op false wordt gezet. Zodat dus de staat van het domeinobject goed is geregeld.
Dus in zo'n transferTo methode voer je alleen de acties uit om de desbetreffende staat van de betrokken domein objecten weer goed te krijgen. De overige romslomp om iedereen op de hoogte te stellen (Application Logic als ik het goed begrepen heb), kan je dan in je Service laag houden die bijvoorbeeld een generieke MessageService aanspreekt. Het is me alleen niet geheel duidelijk geworden waar je de code aanroept die ervoor zorgt dat een speler niet meer onder behandeling is. Een invariant van transfer is namelijk dat een speler niet meer onder behandeling is op moment van transfer. Voer je dan in je service een controle uit en roep je dan een EmployeeService aan die een Player bij een bepaalde Employee weghaalt?

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class TransferServiceImpl implements TransferService {
    public function transfer(Player $player, Team $to, $price) {
        if ($player->isUnderTreatment()) {

            $treatmentService = TreatmentServiceFactory::getInstance();
            $treatmentService->terminateAllTreatmentsFor($player);

            $messageService = MessageServiceFactory::getInstance();
            $messageService->send($player->getTeam()->getTrainer(), 'De speler die je verkocht hebt was onder behandeling bij een van je werknemers. Je kan een nieuwe speler bij de werknemer onderbrengen.');
        }

        $player->transferTo($to, $price);
    }
}

class Player {
    public function transferTo(Team $to, $price) {
        if (!$player->isAcceptablePrice($price)) {
            throw new UnrealisticPriceException();
        }
        $this->setTeam($to);
        $this->setUnderTreatment(false);
    }
}


Wanneer zo'n transferTo methode op je Player staat, is het wel weer mogelijk om in je template een transferTo methode aan te roepen. Vind je dat geen probleem, of is zo'n Player instantie niet beschikbaar in je template? Tweede vraag: wie is verantwoordelijk voor het beeindigen van alle behandelingen van een speler? Hoort dat toch in de transferTo methode op de Player, of hoort het zoals hierboven op de Service?

[ Voor 37% gewijzigd door Verwijderd op 07-12-2008 18:37 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Verwijderd schreef op zondag 07 december 2008 @ 18:10:
Dit vind ik best een interessant aspect. Wellicht zou dit (een deel) van een goede beslisprocedure zijn of iets wel of niet op een domein object hoort. Zo zou je bijvoorbeeld een methode kunnen hebben waarmee je opvraagt of een speler geblesseerd is, of hij verkocht mag worden, wat zijn gemiddelde rapportcijfer is.
Ja, daar hou ik ook erg van. Dat is dus de check vooraf: IsInjured, IsTransferable. Methods die kunnen worden gebruikt door een service.
Maar op het moment dat een actie wordt uitgevoerd, bijvoorbeeld een speler wordt verkocht aan een andere club, dan checkt de speler zelf ook of ie wel verkocht mag worden en anders geeft ie een error.
Zou je dan in feite kunnen zeggen dat je afgeleide data van een bepaald domein object wil kunnen berekenen in een methode op je domein object? Bijvoorbeeld het totaalbedrag van een order ...
Ja exact. Het totaalbedrag ga je wat mij betreft niet in een service leggen, die de repository aanspreekt en vervolgens een query uitvoert om een sum of count te doen. Al die info zit al in het order object, die de bedragen van de orderregels optelt en teruggeeft.
Idem voor de andere voorbeelden die je gaf.
Het is me alleen niet geheel duidelijk geworden waar je de code aanroept die ervoor zorgt dat een speler niet meer onder behandeling is.
Ligt er een beetje aan waar die 'behandeling' allemaal bekend is. Is het alleen een property van een speler, dan moet je speler er zelf voor zorgen. Maar als er (ook) een behandelaar is, dan moet die speler misschien daar afgemeld worden. Dat kan een service dan weer doen.
Dat is natuurlijk niet de heilige graal he, want er zijn vast ook mensen die ervoor kiezen om het Speler object het Behandelaar object een seintje te laten geven.
Wanneer zo'n transferTo methode op je Player staat, is het wel weer mogelijk om in je template een transferTo methode aan te roepen. Vind je dat geen probleem, of is zo'n Player instantie niet beschikbaar in je template?
Ik weet niet zo goed wat een 'template' is, heb 't topic doorgezocht maar jij noemt het voor de eerste keer. Misschien dat je 't over het template design pattern hebt, maar dat ken ik niet zo goed.
Wat mij betreft is Player.TransferTo in ieder geval een public method. lol weet niet of je daar wat aan hebt.
Tweede vraag: wie is verantwoordelijk voor het beeindigen van alle behandelingen van een speler? Hoort dat toch in de transferTo methode op de Player, of hoort het zoals hierboven op de Service?
Ligt een beetje aan je design denk ik. Is het voldoende om bij de player te registreren dat hij nergens meer onder behandeling is? Of moet de behandelaar dit ook weten? Of wordt er een lijst bijgehouden met alle spelers die in behandeling zijn?
Simpel gezegd, als het meer is dan alleen een simpele property van Player instellen, dan zou ik het in een Service (-method) stoppen.

Acties:
  • 0 Henk 'm!

Verwijderd

Een Trainer (gebruiker van het spel) is trainer van een Team. Hij kan nul of meer personeelsleden hebben. Aan sommige personeelsleden kunnen spelers gekoppeld zijn; bijvoorbeeld een Spitsentrainer die elke dag een bepaalde speler traint.

Door het hele spel heen zijn er niet alleen personeelsleden, maar een speler kan ook opgesteld zijn, hij kan nog als een of meer specialisten ingesteld zijn (of hij corners moet nemen, of hij penalties moet nemen, of hij aanvoerder is etc.)

Wanneer een speler verkocht wordt aan een ander team dan moet de speler niet meer gekoppeld zijn aan de werknemer, moet hij niet meer opgesteld zijn, moet hij als specialist gereset worden.

De vraag is dus: waar plaats je dat soort business logica? Het zijn dus geen velden van een player zelf, maar is er een foreign key naar player_id op andere tabellen. Zo heeft een employee tabel een foreign key naar de player tabel zodat je aan een employee een player kan koppelen.

Op dit moment hebben we een removeFromEmployees() methode op de Player klasse. Die het volgende doet:

PHP:
1
2
3
4
5
6
7
8
class Player
{
    public function removeFromEmployees ()
    {
        $employeeRepository = EmployeeRepositoryFactory::getInstance();
        $employeeRepository->removePlayer($this);
    }
}


Deze removeFromEmployees() methode wordt aangeroepen binnen de transferTo(Player $player) methode. Nu vraag ik me alleen af of je dat wel zo wilt hebben. De hamvraag is: wie is verantwoordelijk voor het feitelijk loskoppelen van de bewuste spelers van de werknemer(s) waaraan hij gekoppeld kan zijn?

Acties:
  • 0 Henk 'm!

  • MisterBlue
  • Registratie: Mei 2002
  • Laatst online: 10-09 20:13
flowerp schreef op zaterdag 06 december 2008 @ 15:02:
[...]

Een wijs man hier of got zei ooit het volgende:

[...]
Een ander wijs man is Dr Alan Kay die de term object oriented programming bedacht heeft:
http://userpage.fu-berlin...jf47ht81Ht/doc_kay_oop_en

Fowler wijst ons alleen op de oorspronkelijke filosofie achter object georienteerd denken. De industrie is massaal een andere weg ingeslagen, of het beter of slechter is laat ik in het midden, maar een beetje historie kan geen kwaad.

Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
MisterBlue schreef op dinsdag 09 december 2008 @ 23:51:
[...]

Fowler wijst ons alleen op de oorspronkelijke filosofie achter object georienteerd denken.
Dat kan op 2 manieren uitgelegd worden.

De eerste is dat een oorspronkelijke filosofie helemaal verkeerd begrepen wordt en toegepast wordt voor iets waar het nooit bedoel voor was. HTTP/HTML is daarvan een mooi voorbeeld. Dat is een protocol en een markup taaltje om hypertext mee te versturen resp. beschrijven. Je weet wel, hypertext als in witte pagina's, eenvoudige zwarte text en af en toe een blauw onderstreept linkje. Maar wij, als de s*kkels die we zijn, zijn het gaan misbruiken om complete remote applicaties mee te gaan bouwen, iets waar het nooit voor bedoeld was en ook helemaal niet echt geschikt voor is.

De 2de uitleg is dat dingen evolueren. We schrijven nu ook niet meer programma's in een taal lager als assembly omdat dat de "oorspronkelijke filosofie" achter programmeren was. Er is een reden dat er procedureel, OO, aspects, generic, etc programmeren is ontstaan en dat is omdat die oorspronkelijke programmeertalen gewoon niet voldeden.
De industrie is massaal een andere weg ingeslagen, of het beter of slechter is laat ik in het midden, maar een beetje historie kan geen kwaad.
Ik denk dat velen zich er wel van bewust zijn. Een oude gedachte was dat in OO, objecten black boxen waren die zelf hun eigen dependencies regelde. Dingen als dependency injection lijken, vanaf een zeker standpunt, bijna weer hoe we vroeger in C werkte: objecten krijgen al hun dependencies van buiten af, net zoals C functies vroeger (noodzakelijkerwijs) al hun dependencies mee kregen. Er zijn natuurlijk wel grote verschillen: op de OO manier kan 1 plek de dependencies invullen, het object bewaart deze dependencies (state) en encapsuleerd ze, en biedt hogere level operaties aan aan de client van het object. Wat dat betreft is het object voor de client nog steeds een black box, die zowel data als operaties bundelt.

Entities, als 'domme' dragers van data lijken in de eerste instantie ver verwijdert van de bedoeling van een Object. Ze bevatten immers alleen data, en de methods die ze hebben (getters/setters) zijn feitelijk alleen maar artifacts van een programmeertaal zoals Java; ze dienen exact hetzelfde doel als gewoon een directe field access op b.v. een struct in C. Toch doen zelfs de meest simpele van deze entities wel wat meer; hun doel is data te representeren (uit het domein) en dat doen ze op een OO manier. Immers, de manier waarop je die data verkrijgt is encapsulated. In Hibernate, bij een managed, lazy loaded property, kan een getter call resulteren in een DB query. Dat is toch pretty fancy voor een "struct".

Ook kun je je afvragen in hoeverre het model van slim entities, DAOs en services daadwerkelijk van het OO denken is verwijdert. Een typische DAO ziet je code via een interface, de implementatie verkrijg je via een factory, waarbij die DAO implementatie in de regel inherit van een GenericDAO en hulp objecten bevat (b.v. de Java entity manager) om z'n werk uit te voeren. Dat zijn toch aardig wat OO patronen die er even voorbij komen. Het zelfde verhaald geldt ongeveer voor een Service, die de interactie met meerdere entities 'sequenced'. Het hele concept "DAO" is b.v. juist iets wat het OO denken uitstekend uitbuit, in tegenstelling tot 1 enkel Object die zelf de functies "getFromDB()" etc aanbiedt. Een logisch gevolg van het gebruik van DAOs is dat de functies die deze aanbiedt al tenminste niet (zichbaar) meer in je Entities hoeven te zitten.

Tevens kun je je nog afvragen in hoeverre je behavior uit het domain exact kan vatten in entities. person.sendCard()... Heel leuk, maar laten we eens nadenken. In het echt, als ik Rob een kaart wil sturen, roep ik dan het gedrag "send card" aan op Rob? Dat is gewoon niet zo. -ik- koop een kaart (bij een kaarten winkel), schrijf de kaart, en stuur deze kaart op (via een postbus).

Wat modelleert die werkwijze uit het domein nu beter?

Java:
1
2
Person rob = new Person("rob");
rob.sendCard(); // let rob take care of buying, writing and sending a card to himself? WTF!?


of

Java:
1
2
3
Card card = shop.getNewCard();
card.setText("Hoi rob!");
postBox.sendMail(card, "rob@example.com");


Het 2de stukje code is een taskflow die op verzoek van mij wordt uitgevoerd, wat ongeveer doet wat ik in het domein ook zou doen.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
MisterBlue schreef op dinsdag 09 december 2008 @ 23:51:
[...]
Fowler wijst ons alleen op de oorspronkelijke filosofie achter object georienteerd denken. De industrie is massaal een andere weg ingeslagen, of het beter of slechter is laat ik in het midden, maar een beetje historie kan geen kwaad.
'Massaal' ? Welke richting dan? En wat heeft Fowler met historie te maken, anders dan dat hij termen van anderen van een andere definitie voorziet?

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


Acties:
  • 0 Henk 'm!

  • MisterBlue
  • Registratie: Mei 2002
  • Laatst online: 10-09 20:13
EfBe schreef op woensdag 10 december 2008 @ 09:10:
[...]

'Massaal' ? Welke richting dan? En wat heeft Fowler met historie te maken, anders dan dat hij termen van anderen van een andere definitie voorziet?
De richting dat entity/domein objecten geen gedrag kennen, maar dat we manager, service en controller objecten daarvoor gebruiken. Ongeveer wat flowerp hierboven beschrijft. Met goede argumenten, maar het blijft toch anders dan wat Alan Kay in gedachten had.

Historie haalde ik aan, omdat veel programmeurs de oorsprong niet kennen.

Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
MisterBlue schreef op woensdag 10 december 2008 @ 10:41:
[...]
De richting dat entity/domein objecten geen gedrag kennen, maar dat we manager, service en controller objecten daarvoor gebruiken. Ongeveer wat flowerp hierboven beschrijft. Met goede argumenten, maar het blijft toch anders dan wat Alan Kay in gedachten had.
Ik denk dat alleen binnen de groepen waar het anemic domain model als anti-pattern wordt gezien dit enige waarde wordt toegedicht. De rest is gewoon pragmatisch met de materie om aan het gaan. Immers kent men 3 soorten BL:
- single entity single attribute oriented (id > 0)
- single entity multiple attribute oriented (orderdate <=shipping date)
- multi-entity oriented (customer.goldcustomer -> wanneer customer binnen de laatste 4 maanden 5 orders van 10 euro of meer heeft afgenomen)

De 1e 2 zullen geen probleem zijn, de 3e is denk ik de reden van deze discussie. Het punt is nl. dat de plaats waar dit soort logic geplaatst moet worden niet alleen OO technisch vaak niet 1 2 3 logisch is, maar je zit ook nog eens met logica buiten de entity die met de database gaat praten en die werkt met graphs van objects.

Om een voorbeeld te noemen: een entity mag geen 'Save' method hebben, want dat wordt als 'infrastructure' gezien, maar eigenlijk is dat onzin, want er is maar 1 object die gaat over de data IN een entity object, nl. het entity object zelf. M.a.w.: de discussie is krom en leidt niet tot een bruikbaar antwoord zolang dat soort aannames blijven bestaan bij dezelfde groep mensen die menen dat anemic domain models fout zijn.
Historie haalde ik aan, omdat veel programmeurs de oorsprong niet kennen.
Lijkt me ook niet relevant, temeer zelden dingen beginnen bij meneer Fowler.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
flowerp schreef op woensdag 10 december 2008 @ 00:52:
De 2de uitleg is dat dingen evolueren. We schrijven nu ook niet meer programma's in een taal lager als assembly omdat dat de "oorspronkelijke filosofie" achter programmeren was. Er is een reden dat er procedureel, OO, aspects, generic, etc programmeren is ontstaan en dat is omdat die oorspronkelijke programmeertalen gewoon niet voldeden.
Ik denk dat ze ontstaan uit het feit dat sommingen menen dat wat voorhanden is niet expressief genoeg is voor het probleem dat moet worden opgelost.
Ik denk dat velen zich er wel van bewust zijn. Een oude gedachte was dat in OO, objecten black boxen waren die zelf hun eigen dependencies regelde. Dingen als dependency injection lijken, vanaf een zeker standpunt, bijna weer hoe we vroeger in C werkte: objecten krijgen al hun dependencies van buiten af, net zoals C functies vroeger (noodzakelijkerwijs) al hun dependencies mee kregen. Er zijn natuurlijk wel grote verschillen: op de OO manier kan 1 plek de dependencies invullen, het object bewaart deze dependencies (state) en encapsuleerd ze, en biedt hogere level operaties aan aan de client van het object. Wat dat betreft is het object voor de client nog steeds een black box, die zowel data als operaties bundelt.
IoC is een manier van ontkoppeling, hoe dat gedaan wordt is niet relevant, ookal denken sommigen van wel. Het is ook zo dat de koppeling niet zo los is als men soms denkt: ergens moet toch een koppeling gemaakt worden tussen wat object O aan dependencies heeft.
Entities, als 'domme' dragers van data lijken in de eerste instantie ver verwijdert van de bedoeling van een Object. Ze bevatten immers alleen data, en de methods die ze hebben (getters/setters) zijn feitelijk alleen maar artifacts van een programmeertaal zoals Java; ze dienen exact hetzelfde doel als gewoon een directe field access op b.v. een struct in C. Toch doen zelfs de meest simpele van deze entities wel wat meer; hun doel is data te representeren (uit het domein) en dat doen ze op een OO manier. Immers, de manier waarop je die data verkrijgt is encapsulated. In Hibernate, bij een managed, lazy loaded property, kan een getter call resulteren in een DB query. Dat is toch pretty fancy voor een "struct".
Met 'entities' bedoel je neem ik aan entity class instances?
Ook kun je je afvragen in hoeverre het model van slim entities, DAOs en services daadwerkelijk van het OO denken is verwijdert.
Maar... word je nou betaald om OO te denken of om problemen op te lossen? OO is een middel, geen doel.
Een typische DAO ziet je code via een interface, de implementatie verkrijg je via een factory, waarbij die DAO implementatie in de regel inherit van een GenericDAO en hulp objecten bevat (b.v. de Java entity manager) om z'n werk uit te voeren. Dat zijn toch aardig wat OO patronen die er even voorbij komen. Het zelfde verhaald geldt ongeveer voor een Service, die de interactie met meerdere entities 'sequenced'. Het hele concept "DAO" is b.v. juist iets wat het OO denken uitstekend uitbuit, in tegenstelling tot 1 enkel Object die zelf de functies "getFromDB()" etc aanbiedt. Een logisch gevolg van het gebruik van DAOs is dat de functies die deze aanbiedt al tenminste niet (zichbaar) meer in je Entities hoeven te zitten.
Maar, mag een DAO DTO/Entity object een Save() method hebben? Immers, een DAO DTO/Entity bezit zn eigen data, toch?
Tevens kun je je nog afvragen in hoeverre je behavior uit het domain exact kan vatten in entities. person.sendCard()... Heel leuk, maar laten we eens nadenken. In het echt, als ik Rob een kaart wil sturen, roep ik dan het gedrag "send card" aan op Rob? Dat is gewoon niet zo. -ik- koop een kaart (bij een kaarten winkel), schrijf de kaart, en stuur deze kaart op (via een postbus).
Tja, maar dat is dus procedureel denken, waar niets mis mee is, maar in een OO wereld vraag je objects dingen te doen voor je. ->
Wat modelleert die werkwijze uit het domein nu beter?
Java:
1
2
Person rob = new Person("rob");
rob.sendCard(); // let rob take care of buying, writing and sending a card to himself? WTF!?


of

Java:
1
2
3
Card card = shop.getNewCard();
card.setText("Hoi rob!");
postBox.sendMail(card, "rob@example.com");


Het 2de stukje code is een taskflow die op verzoek van mij wordt uitgevoerd, wat ongeveer doet wat ik in het domein ook zou doen.
De 1e vraagt rob een kaart te sturen, terwijl jij de kaart stuurt, dus dat is niet correct. De 2e vraagt niets aan objects, maar is procedureel de handel aan het oplossen. IMHO, zou de laatste regel in het 2e voorbeeld moeten zijn:
card.SendViaEMail("rob@example.com");
immers, je vraagt aan een object om iets voor je te doen. Hoe 'card' verstuurd wordt, dat interesseert de caller toch niet? Daar gebruik je toch objects voor: om complexiteit te encapsulaten. (jaja, je kunt ook een method card.Send(new EmailArguments("rob@example.com")) aanmaken, waarbij EmailArguments erft van SendArguments etc.)

McConnell heeft hier uiteraard iets over geschreven in code complete. Het komt erop neer dat wanneer het creeeren van een method in class C of een nieuwe class C2 voor een probleem het probleem simpeler en minder complex maakt, de creatie legitiem is. Er is geen andere reden om een method aan te maken of een class aan te maken. Ook domain classes worden gecreeerd om die reden.

[ Voor 1% gewijzigd door EfBe op 13-12-2008 11:05 . Reden: DAO => DTO/Entity ]

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


Acties:
  • 0 Henk 'm!

  • MisterBlue
  • Registratie: Mei 2002
  • Laatst online: 10-09 20:13
Lijkt me ook niet relevant, temeer zelden dingen beginnen bij meneer Fowler.
Voor alle duidelijkheid: het is niet zo dat ik Fowler link aan de historie van OO.
Mijn historie opmerking sloeg op Alan Kay. En met hem is wel degelijk wat begonnen en ik vind dat wel relevant.

[ Voor 4% gewijzigd door MisterBlue op 10-12-2008 13:02 ]


Acties:
  • 0 Henk 'm!

  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
EfBe schreef op woensdag 10 december 2008 @ 12:34:
[...]
. De 2e vraagt niets aan objects, maar is procedureel de handel aan het oplossen.
Nja, shop, card, and postBox zijn wel degelijk objects. shop kan best een ingewikkelde implementatie hebben, waarbij het ook state bevat. Denk b.v. aan het feit dat ik een rekening bij de shop heb lopen, dus dat die getNewCard call op shop er automatisch voor zorgt dat die card ge-billed wordt op mijn rekening.

postBox lijkt me ook duidelijk. sendMail maakt natuurlijk gebruik van interne state, waar tenminste een mailServer, name en password is geconfigureerd. Zeer waarschijnlijk heeft dat ding ook wel intern een queue, waarbij om de zoveel tijd mails in bulk verstuurd worden.

Die 3 statements zijn natuurlijk een fragment, waarschijnlijk zitten ze in een session bean die b.v. vanaf een backing bean wordt aangeroepen die op z'n beurt weer een actionHandler implementeerde die bind naar een knop waar -ik- op clickte ;)

Dat betekent dat die session bean zeer waarschijnlijk wordt aangesproken via een (local) interface. Dat zijn toch wederom ook best wel weer een aantal OO patterns die gebruikt worden.

Je hebt natuurlijk 100% gelijk dat OO een methode is en geen doel opzich, het doel is absoluut om een probleem op te lossen. Voor de helderheid van je software architectuur kan een team natuurlijk wel afspreken om dingen zo veel mogelijk via een bepaald paradigma te doen.

[ Voor 18% gewijzigd door flowerp op 10-12-2008 13:34 ]

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Acties:
  • 0 Henk 'm!

Verwijderd

EfBe schreef op woensdag 10 december 2008 @ 12:34:

Maar, mag een DAO object een Save() method hebben? Immers, een DAO bezit zn eigen data, toch?
* hier stond onzin *

DAO is een term die niet in mijn vocabulair voorkomtkwam en ik was even in de veronderstelling dat het om een Entity (volgens Domain Driven Design) ging waarbij zo'n save methode als een ActiveRecord kan dienen.

[ Voor 62% gewijzigd door Verwijderd op 12-12-2008 22:33 ]


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:07
Ik volg even niet meer ....
Waarom zou je een DAO willen 'opslaan' ? Een DAO is voor mij nog altijd een class die verantwoordelijk is om 'een object (van een ander type)' te persisten, op te halen ...
En die Save() method van een DAO voert dan idd bv rechtstreeks een SQL query uit.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

Verwijderd

whoami schreef op vrijdag 12 december 2008 @ 21:53:
Ik volg even niet meer ....
Waarom zou je een DAO willen 'opslaan' ? Een DAO is voor mij nog altijd een class die verantwoordelijk is om 'een object (van een ander type)' te persisten, op te halen ...
En die Save() method van een DAO voert dan idd bv rechtstreeks een SQL query uit.
Terecht dat je me niet volgde. Mijn reply bevatte een hoop onzin. Heb hem even aangepast om verwarring te voorkomen. Ik dacht even dat een DAO de term was voor Entity in Domain Driven Design. Silly me :Y)
EfBe schreef op woensdag 10 december 2008 @ 12:21:
[...]

*knip*

- single entity single attribute oriented (id > 0)
- single entity multiple attribute oriented (orderdate <=shipping date)
- multi-entity oriented (customer.goldcustomer -> wanneer customer binnen de laatste 4 maanden 5 orders van 10 euro of meer heeft afgenomen)

De 1e 2 zullen geen probleem zijn, de 3e is denk ik de reden van deze discussie. Het punt is nl. dat de plaats waar dit soort logic geplaatst moet worden niet alleen OO technisch vaak niet 1 2 3 logisch is, maar je zit ook nog eens met logica buiten de entity die met de database gaat praten en die werkt met graphs van objects.
Evans legt in zijn boek Domain Driven Design uit dat je de 3e via een Specification pattern kan oplossen. De logica op basis waarvan bepaald wordt of een gebruiker een gold status heeft kan eenvoudig zijn, maar ook enorm complex. Zo'n evaluatie naar true of false kan eenvoudig zijn, of ingewikkeld. Stel dat Customer een gold status heeft als hij minimaal 5 orders van ten minste 10 euro heeft afgenomen.

Ten eerste zou ik dit gedrag absoluut in het Domein Model houden en zeker niet in de Application Layer en het liefst ook niet ergens in de Service Layer. Het zijn namelijk business rules die echt bij je probleemdomein horen. Aan de andere kant wil je niet dat je Customer object overspoelt wordt met onnodige bindingen met andere objecten.

Je zou op Customer een hasGoldStatus methode kunnen implementeren:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Customer {
    public boolean hasGoldStatus() {
        Set results = new HashSet();
        Iterator it = this.orders.iterator();
        int orderCount = 0;
        while (it.hasNext()) {
            Order order = (Order) it.next();
            if (order.getTotal() > 10) {
                orderCount++;
            }
        }
        return orderCount >= 5;
    }
}


Persoonlijk ben ik helemaal niet tegen een implementatie als deze op je Customer object om de volgende twee redenen: 1.) hasGoldStatus() is een methode die de staat van een Customer uitdrukt 2.) de methode gebruikt alleen informatie van andere Domein Objecten die tot dezelfde Aggregate Root behoren. De Customer en Collection van Orders zijn nou eenmaal aan elkaar verbonden en je zou afbreuk doen aan je Domein Model om deze koppeling weg te abstraheren.

Het wordt echter een probleem wanneer verifieren van de gold status van een Customer ingewikkelder wordt en voor de berekening Domein Objecten moeten worden aangesproken die niet meer tot de Aggregate Root behoren. In zo'n geval zou een Specification het probleem kunnen oplossen. De Specification is een simpel Value Object welke een Customer als input krijgt, en als uitkomst true of false retouneert;

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class GoldStatusSpecification extends CustomerSpecification {
    public boolean test (Customer customer) {
        Set results = new HashSet();
        Iterator it = customer.orders.iterator();
        int orderCount = 0;
        while (it.hasNext()) {
            Order order = (Order) it.next();
            if (order.getTotal() > 10) {
                orderCount++;
            }
        }
        return orderCount >= 5;
    }
}

class Customer {
    public boolean satisfies(CustomerSpecification customerSpecification) {
        return customerSpecification.test(this);
    }
}


Overal waar je wil testen of een Customer aan een bepaalde Specification voldoet, kan je de satisfies methode aanroepen met een CustomerSpecification als argument. Op die manier beschouw je de gold status nog wel als een eigenschap van een Customer, voorkom je onnodige rotzooi in je Customer object en blijven de business rules voor het bepalen of een Customer een gold status heeft in je Domein Model.

Wat vinden jullie van dit concept? Ben voornamelijk geinteresseerd in de nadelen die zo'n opzet heeft in jullie ogen.

[ Voor 7% gewijzigd door Verwijderd op 12-12-2008 22:38 ]


Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
whoami schreef op vrijdag 12 december 2008 @ 21:53:
Ik volg even niet meer ....
Waarom zou je een DAO willen 'opslaan' ? Een DAO is voor mij nog altijd een class die verantwoordelijk is om 'een object (van een ander type)' te persisten, op te halen ...
En die Save() method van een DAO voert dan idd bv rechtstreeks een SQL query uit.
Ik was even in de war met DTO 8)7 , my bad :). Ik bedoelde dus DTO/Entity class.

@quist: specification pattern (waar mensen vaak moeite mee hebben want er is niet echt een hapklare implementatie beschikbaar) is een manier om de logica buiten de entity te definieren maar binnen de entity te gebruiken, dus een oplossing die centraal dingen regelt maar ze niet centraal uitvoert, dus imho de ideale oplossing voor wanneer je wel de logica in de entity wilt uitvoeren maar het daar niet wil definieren.

[ Voor 34% gewijzigd door EfBe op 13-12-2008 11:04 ]

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com

Pagina: 1