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.