blog.smart-java.nl
Ordina J-Technologies – Java Blog



Lagen in de referentiearchitectuur

By: Pieter van Boxtel, 6 October 2008

In de referentiearchitectuur hebben we lagen onderkend, zoals je ze vaak tegenkomt in een J(2)EE applicatie; een presentatielaag voor presentatie en user interface logica, een servicelaag voor remoting en service gerelateerde faciliteiten, een businesslaag waar het echte werk gebeurt en een datalaag voor database mapping. Bij uitwerking van dit model kwamen echter een aantal knellende vragen naar boven rondom afhankelijkheden van het domeinmodel. Hoe kan de datalaag nu afhankelijk zijn van het domeinmodel dat zich in de businesslaag bevindt? Hoe kunnen we ongewenste koppelingen voorkomen? Tijd om een stapje terug te doen. Wat is nu eigenlijk een laag in de software architectuur?

In een lagenmodel wordt software opgedeeld in een aantal lagen die elk hun eigen nivo van abstractie hebben. In principe heeft een laag hierbij alleen kennis van de direct onderliggende laag. Omdat lagen elk een eigen abstractie, een eigen “aandachtsgebied” hebben, hebben ze ook een eigen levenscyclus en zijn ze gevoelig voor andere soorten wijzigingen. Door de applicatie in lagen onder te verdelen wordt de gevoeligheid voor wijzigingen gelokaliseerd en wordt de applicatie in zijn geheel beter onderhoudbaar. En dat is iets waar een software architect een warm gevoel van krijgt.

Het klassieke voorbeeld van een lagenmodel in software is het OSI model voor computer netwerken. Iets complex als internet is mogelijk omdat netwerk protocollen gelaagd zijn (SOAP bovenop HTTP bovenop TCP bovenop IP bovenop Ethernet). Ook voor software architectuur zijn lagen voorgesteld om de complexiteit te verminderen. Het boek “Pattern Oriented Software Architecture” [POSA] introduceert het concept van lagen in de architectuur als een pattern. POSA geeft als voorbeeld van een lagenmodel het OSI model. Het noemt ook kort de toepassing van het lagenmodel in informatiesystemen, maar gaat daar niet diep op in. De gezaghebbende (?) Java architectuur-bijbels als “Core J2EE Patterns” [Alur], “Patterns of Enterprise Application Architecture” [Fowler] en “EJB Design Patterns” [Marinescu] schetsen een opdeling van een applicatie in lagen. “Domain-Driven Design” [Evans] beschrijft hoe het lagen patroon toegepast kan worden in een domeinmodel. Maar tot zover de literatuur.

In een enterprise applicatie kun je, afhankelijk van de richting van waaruit je ernaar kijkt, verschillende soorten lagen onderscheiden:

  1. à la OSI zijn er infrastructurele lagen; de applicatie gebruikt Java/JEE libraries die gebruik maken van een container en JVM die gebruik maken van een OS.

  2. POSA, Fowler, et cetera onderkennen architecturale lagen voor informatie systemen / enterprise applicaties.

  3. Multi-tiering brengt lagen aan in de deployment; een browser praat met een applicatieserver die met een database praat.

  4. Evans beschrijft hoe je lagen kunt gebruiken om het domeinmodel te structureren.

Deze soorten lagen zijn onafhankelijk van elkaar en je zou ze weer kunnen geven in een multi-dimensioneel plaatje, ware het niet dat wij niet zo goed zijn in het tekenen en begrijpen van plaatjes met veel dimensies.

Architecten (met name architecten…) houden van simpele plaatjes. Ze beperken zich in de regel dus tot 1 dimensie; nummer 2 in bovenstaand rijtje. Dit is ook de dimensie waarin in de referentiearchitectuur wordt gekeken. Daarin onderkennen we zoals gezegd 4 lagen; Presentatielaag, Servicelaag, Bussinesslaag en Datalaag.

Echter, die laatste twee lagen, dat zit niet helemaal lekker… Hoe kan een component in de datalaag nu een domeinobject persisteren als het geen domeinobjecten kent? Volgens de lagendefinitie kent een laag immers de onderliggende laag, maar niet de bovenliggende. De oplossing die we in de referentiearchitectuur hebben gekozen blijft achteraf knagen. In de logische view hebben we het probleem genegeerd en in de implementatie view hebben we de domeinobjecten in een afzonderlijke “laag” naast de overige lagen getekend. Daarmee is het lagenplaatje meer een blokkenplaatje geworden met pijlen allerlei kanten op.

Hoe maken we van dit plaatje nu een correct lagenplaatje? Het antwoord is eigenlijk heel simpel; gum het onderscheid tussen business- en datalaag weg. Dit sluit mooi aan bij Evans; onze Data Access componenten zijn gewoon repositories die onderdeel zijn van het domein model. Weg met de datalaag dus.

Maar daarmee zijn we nog niet helemaal klaar. We hebben ook nog die service agents en wat te doen als we moeten koppelen met een database schema dat niet lekker aansluit op ons domeinmodel? Dit vraagt om infrastructurele logica waarmee we ons domeinmodel niet willen vervuilen. Hier hebben we weldegelijk een tussenliggend laagje voor nodig. Laat ik dit laagje Integratielaag noemen. In de Integratielaag vinden we good old DAO’s. Parameters van deze DAO’s zijn DTO’s die een representatie zijn van het datamodel waar de DAO aan koppelt. Ze geven dus geen domeinobjecten of afspiegelingen daarvan terug. De Integratielaag heeft, technisch maar ook conceptueel, geen enkele notie van hoe domeinobjecten eruit zien. Vertaling van extern datamodel naar domeinmodel is verantwoordelijkheid van de domeinlaag.

De Integratielaag hebben we alleen nodig als we willen koppelen met de grote boze buitenwereld. Zolang we doen wat we meestal doen, een databaseschema maken specifiek voor ons domeinmodel en dat met hibernate aan onze domeinobjecten koppelen, hebben we deze niet nodig. Hiermee kom ik uit op onderstaan plaatje. Dit lijkt sterk op het lagenmodel zoals iedereen het kent, maar wel met een kanttekening bij de toepassing van de Integratielaag. (En de businesslaag heb ik omgedoopt tot domeinlaag om te onderstrepen dat ik fan ben van DDD.)

In bovenstaand plaatje zijn ook de systeemgrenzen weergegeven. Wanneer we persisteren naar een database die binnen onze eigen systeemgrenzen valt, en die we naar believen aan kunnen passen, dan hebben we geen integratielaag nodig. Wanneer we moeten integreren met de grote boze buitenwereld dan hebben we (misschien) een integratielaag nodig om de domeinlaag “clean” te houden. In het plaatje zie je ook dat de servicelaag de plek is waar aanroepen vanuit een extern systeem opgevangen worden.

Resten nog twee vragen waar een wakkere lezer nu wellicht nog mee worstelt:

1) Waar zit de hibernatejar dan? Toch niet in de domeinlaag? Nou nee, dit is een infrastructureel ding dat je niet terugziet in dit lagenplaatje. Het zit in een andere dimensie (de eerste in bovenstaand lijstje), in een parallel universum zo je wilt. Het architecturale lagenplaatje toont niet alle spelers. Net zomin als dat de Sun SPARC Enterprise Server waar de applicatieserver op draait zichtbaar is in het plaatje, zijn alle gebruikte 3rd party libraries zichtbaar.

Een architectuurbeschrijving bevat verschillende views waarin de architectuur vanuit verschillende perspectieven beschreven wordt. Voor elk van deze views kunnen we een lagenmodel opstellen. In de deployment view tekenen we een plaatje waarin een plekje is voor onze Sun SPARC Enterprise Server. In de implementatie view kunnen we een plaatje tekenen waarin de hibernatejar zichtbaar is. Een veelgebruikte oplossing is om naast de lagen in bovenstaand plaatje een “common” laag te tekenen voor hibernate, spring en ander spul, maar zoals intussen duidelijk moge zijn ben ik daar geen voorstander (meer) van.

2) Een van de argumenten om de implementatie van persistentie in een afzonderlijke laag te stoppen is dat het dan mogelijk is om het persistentiemechanisme te vervangen door een ander mechanisme. Deze mogelijkheid wil je ons toch niet afpakken? Nee, natuurlijk niet, ik zou niet durven. Hoewel ik het eerste project nog tegen moet komen dat daadwerkelijk een persistentiemechanisme vervangt, hecht ik wel waarde aan encapsulation en loose coupling. Het lagenmodel biedt echter geen goede oplossing voor deze vraag.

We dienen dit op te lossen binnen de domeinlaag en dat kan op niet al te ingewikkelde wijze door interface en implementatie van repositories te scheiden in afzonderlijke packages en eventueel jars. Een specifieke implementatie van een repository injecteren we gewoon in het domein en als we de implementatie willen wijzigen (bijvoorbeeld stub data zodat we kunnen testen) dan injecteren we een andere implementatie. Omdat dit alles onderdeel is van één laag, hoeven we niet moeilijk te doen over het feit dat de repository-implementaties domeinobjecten geven en nemen.

19 reacties op “Lagen in de referentiearchitectuur”

  1. Jan-Kees van Andel zegt:

    Over vraag 2: De oplossing klinkt in theorie goed, maar ik heb persoonlijk weinig zin in nog een lading DTO’s in mijn applicaties. Ik vind DTO’s tussen service- en presentatielaag al vervelend, en op deze manier komt er nog een stel DTO’s bij.

    Code generatie zou dit mooi op kunnen lossen, maar van al die DTO’s en mapping code met de hand schrijven wordt ik niet zo gelukkig.

    Een andere oplossing zouden scripttalen kunnen zijn. Dan kun je gemakkelijk je DTO’s bouwen met JSON ofzo. Type safety ben je kwijt, maar het maakt je code wel wat overzichtelijker.

  2. Pieter van Boxtel zegt:

    Ik denk dat wij een hekel aan DTO’s delen. Hoe komen we nu van die DTO’s af? DTO’s ontstaan op lagenscheidingen. Je ziet het pattern toegepast worden zodra een laag data aan een bovenliggende laag wil geven. Door de datalaag te schrappen en ORM in de domeinlaag onder te brengen voorkomen we DAO’s die DTO’s communiceren.

    De integratielaag is een optioneel ding dat alleen gebruikt wordt als je service / database / whatever integratie zo gecompliceerd wordt, dat je het in een afzonderlijke laag onder wilt brengen. Dit zijn gevallen waar je met een hibernate mapping ofzo niet meer uit de voeten kunt.

    De belangrijkste maatregel om vervelende boilerplatecode te voorkomen is een eenvoudige software architectuur. Dat is wat ik probeer te bereiken. DSL’s zijn een mooie maatregel om het programmeren verder te versnellen. Scripttalen en andere trucen om DTO code weg te bezuinigen zie ik niet echt zitten. Ik heb eens een applicatie gemaakt waar ik Maps gebruikte ipv DTO’s om data aan de presentatielaag terug te geven, maar daar was ik achteraf toch niet echt tevreden over.

  3. Roy van Rijn zegt:

    Dus als we een object volgen uit de database hebben we de volgende volgorde (als ik het goed begrijp?):

    - Integratie laag doet (met een mechanisme, Hibernate) een query naar de database
    - Integratie laag zet deze data in een DTO die overeenkomt met de database
    - Domeinlaag krijgt deze DTO en maakt er domeinobjecten van
    - Servicelaag werkt met deze domeinobjecten
    - Servicelaag vertaalt deze domeinobjecten naar DTO’s en geeft deze door aan de presentatielaag
    - Presentatielaag neemt deze DTO’s en kan daarmee de view opbouwen.

    Maar dan heb je precies hetzelfde probleem? Nu is het de verantwoordelijkheid van je domeinlaag, die puur is met domeinobjecten/businesslogica om DTO’s te vertalen van/en naar de integratielaag. Dus als je twee verschillende persistentie-mechanisme zou willen gebruiken krijg je twee verschillende DTO’s in je domeinlaag… Dit is juist iets (lijkt me) waarmee je je pure domeinlaag niet wil vervuilen, dat kan dan mooi in de specifiek geschreven integratielaag. (Al kent deze dan wel de domeinobjecten).

    Maar opzich is het feit dat het domeinobjecten kent niet heel erg. Hetzelfde geldt namelijk ook voor de service-view. In je service staat de domeinobject-overstijgende businesslogica (?) maar je gaat daar ook vertaling doen naar view-specifieke DTO’s….

    Het blijft gewoon erg moeilijk om lagen goed te scheiden… het belangrijkste is volgens mij dat je domeinobjecten puur zijn en je businesslogica omvatten, dan zijn die tenminste uitbreidbaar en herbruikbaar.. de rest blijven altijd voor en tegens geven.

    Trouwens, we zijn dit jaar al van 3 lagen naar 5 lagen gegaan, en terug naar 3… gaan we nu naar 4? :-D

  4. Jan-Kees van Andel zegt:

    Trouwens, we zijn dit jaar al van 3 lagen naar 5 lagen gegaan, en terug naar 3… gaan we nu naar 4? :-D

    En dan heb je nog geluk dat we nu alleen nog in de logical view zitten. Tel daarbij een deployment view op en het wordt nog complexer. Bijv. een rich client die op een ander platform gebaseerd is (ik denk aan Flex of Ajax).

    Maar al met al vind ik de opdeling niet eens zo vreemd. Voor een CRUD app is het zware overkill, maar als je iets voor een bank ofzo maakt, waar mensen de komende 10(?) jaar mee te maken hebben, wil je wel simpele interfaces aan de voorkant en technologie-onafhankelijke interfaces aan de achterkant.

    Ik heb nu te maken met een ’service layer’ waar (legacy) technologie de interface bepaalt en ik kan je vertellen dat dat echt geen pretje is. Tipje van de sluier:
    - Eigen datatypen, in plaats van Strings, ints of Dates.
    - Remoting code in interfaces.
    - Je moet zelf dates parsen en op de terugweg weer naar een string omzetten met simpledateformat. En dan maar hopen dat je het formaat goed gokt, want de documentatie ontbreekt.
    - Wijzigingen in mainframes merk ik tot in mijn webapp.
    - Keuzes van vroeger die in java niet werken, oneindig is bijvoorbeeld 99999999.
    - De meest vreemde method names…

    Brrrrr…

  5. Roy van Rijn zegt:

    Tja, je blijft hetzelfde verhaal houden:

    - Als je het model eenvoudiger maakt loop je het risico dat je geen goede scheiding hebt en je te grote stukken code/logica krijgt.

    - Als je lagen toevoegt krijg je het probleem dat je meer koppeling hebt tussen de lagen, waardoor de lagen misschien wel beter uitbreidbaar en herbruikbaar zijn… maar meer koppeling is ook minder performance en meer typ werk en onderhoud als je iets wil aanpassen door alle lagen heen, plus meer conversies en vertalingen.

    Elk voordeel heb zn nadeel… ik denk dus ook dat er geen ideale architectuur is. Misschien dat er een referentie architectuur zou bestaan voor een aantal standaard problemen, maar één ontwerp gaat niet lukken ben ik bang.

    Maar ik laat me graag verrassen :)

  6. Pieter van Boxtel zegt:

    Roy, de volgorde die jij beschrijft (2 reacties geleden alweer) is niet correct. In jouw rechttoe rechtaan scenario laat ik de integratielaag achterwege. Het scenario begint bij stap 3:
    - Domeinlaag doet (met een mechanisme, Hibernate) een query naar de database
    - Servicelaag werkt met deze domeinobjecten
    - et cetera.
    In dit geval betekent integratie niets meer dan een paar hibernate annotaties in de domeinlaag. Dat rechtvaardigt geen aparte integratielaag.

    De integratielaag komt pas in beeld als het integratieverhaal te ingewikkeld wordt om in de domeinlaag te proppen. Het voorbeeld dat Jan-Kees beschrijft rechtvaardigt een een aparte laag. Integratie met dergelijke infrastructurele ellende zou ik niet in mijn mooie pure domeinlaag willen.

    Als je de keuze maakt om je integratielogica in een aparte laag onder te brengen, dan is de consequentie dat je de boel ontkoppelt. De domeinlaag heeft dan kennis van de integratielaag, maar niet andersom. De integratiecomponten kennen geen domeinobjecten. Het enige dat dat de integratielaag aan de domeinlaag aan kan bieden zijn DTO’s en die DTO’s zijn de prijs die je betaald voor de vierde laag.

  7. Jan-Kees van Andel zegt:

    De domeinlaag heeft dan kennis van de integratielaag, maar niet andersom. De integratiecomponten kennen geen domeinobjecten. Het enige dat dat de integratielaag aan de domeinlaag aan kan bieden zijn DTO’s en die DTO’s zijn de prijs die je betaald voor de vierde laag.

    En de DTO’s staan in de integratielaag neem ik aan? DTO’s in je domeinlaag vind ik smerig want qua lifecycle past dit niet. Domeinobjecten zouden in theorie een veel langere levensduur moeten hebben dan een integratie component.

    Het zou leuk zijn om een keer zoiets groots from scratch op te zetten, zonder 20 verschillende legacy mainframe/database/etc componenten. Dan loont het ook om tijd te steken in een nette domeinlaag, in plaats van het aan elkaar plakken van niet op elkaar passende systemen.

  8. Pieter van Boxtel zegt:

    De integratie DTO’s zitten inderdaad in de integratielaag. Maar staar je niet blind op de integratielaag. Het is echt een ding dat je niet nodig hebt in een “normale” applicatie waar je zelf je domeinobjecten mag persisteren. Het is een optioneel ding voor noodgevallen. Om dat te wat duidelijker te maken heb ik het plaatje aangepast en de integratielaag vager gemaakt.

  9. Jan-Kees van Andel zegt:

    Maar staar je niet blind op de integratielaag.

    Zal ik niet doen, maar het is wel handig om een paar vuistregels te hebben wanneer en hoe je die laag gebruikt. Vandaar het doorvragen. Het was niet zeikerig bedoeld… ;)

  10. gnoij zegt:

    Pieter, ik zou je integratielaag niet over de gehele breedte van de lagen tekenen, maar half (en ernaast evetueel de database zetten). Zo zie je meteen het onderscheid tussen externe koppelingen en de applicatie interne database.

    Verder zou ik de in code wel degelijk een scheiding aanbrengen tussen het domein (de businesslogica) en de persistence logica, bijvoorbeeld door er een apart project van te maken, die zich in dezelfde laag bevindt. Hier kan je dan ook je hibernate mappings plaatsen. Ik gebruik hier liever geen annotaties, omdat je dan de ‘echte’ domeincode vervuilt met persistencelogica, terwijl je die juist gescheiden wilt houden.

  11. Pieter van Boxtel zegt:

    Gerry, je idee om het plaatje aan te passen zie ik wel zitten. Zodra ik weer eens achter mijn laptop zit ga ik het eens uitproberen. Scheiding tussen domein en persistentielogica daar ben ik ook voor, dit is waar ik in de laatste alinea op aanstuur. En annotaties, tsja… Het grote kenmerk van annotaties is volgens mij dat ze klassen ‘vervuilen’ met logica die er niet direct thuishoort. Met de introductie van annotaties is de Java taal misschien gemakkelijker geworden, maar is het ook mooier? Als iemand een goed verhaal heeft om me daarvan te overtuigen, dan houd ik me aanbevolen.

  12. Jan-Kees van Andel zegt:

    Annotaties hebben volgens mij feitelijk maar één voordeel bovenop XML, namelijk dat ze (doordat ze op een class/property/method/etc) gezet worden, er impliciet een koppeling mee hebben. In XML heb je dit niet en moet je met de hand deze koppeling leggen, bijvoorbeeld door het opgeven van een fully qualified classname in de XML.

    Aan de andere kanten spelen er weer nadelen, zoals dat je niet zomaar een lijstje kunt zien van alle met X geannoteerde classes. Bijvoorbeeld: Geef mij alle session scoped JSF managed beans of alle Servlets. Daarnaast ben je ook overgeleverd aan zwarte magie zoals classpath scanning, want in tegenstelling tot bij XML worden de beans niet automatisch gevonden.

    Los van deze (en andere) verschillen is het volgens mij erg afhankelijk van a) context en b) smaak of het gebruik van annotaties positief/mooi is of niet.

    Sommigen zweren erbij, sommigen vinden ze ronduit lelijk. Ikzelf zit er volgens mij wat pragmatischer in, als je met annotaties gemakkelijk objecten kunt configureren, gebruik ik ze. (JPA entities) Als ik behoefte heb aan abstractie, zoals bij IoC, geef mij dan maar dat extra regeltje XML. Ik kan persoonlijk weinig met het woord “mooi”, code is een middel om een applicatie op te leveren en als bepaalde technieken daarin ondersteuning bieden, zal ik ze graag gebruiken.

    1 noot: Het valt me wel op dat het voor juniors op een project (dus zonder JSF/Spring/JPA/WS-*/appserver/etc) ervaring vaak gemakkelijker is om code met annotaties te doorgronden, dan code die verspreid is over verschillende classes en XML config files.

    My 2 cents…

  13. Vincent zegt:

    Reagerend op verschillende reacties:

    (Van Jan-Kees):

    Het zou leuk zijn om een keer zoiets groots from scratch op te zetten, zonder 20 verschillende legacy mainframe/database/etc componenten. Dan loont het ook om tijd te steken in een nette domeinlaag, in plaats van het aan elkaar plakken van niet op elkaar passende systemen.

    Misschien vind je dat leuk, maar het slaat helemaal nergens op ;-) . De praktijk is dat we namelijk altijd met legacy componenten te maken hebben die niet op elkaar passen maar die we wel aan elkaar moeten plakken. Een nieuwe architectuur verzinnen op basis van helemaal niets (‘from scratch’) is een zinloze activiteit.

    Beter is het om vast te stellen dat we nu eenmaal altijd te maken hebben met componenten van verschillend allure, en de architectuur daarop in te richten. Dat is mogelijk door een doelarchitectuur op te stellen: hoe willen we dat ons hele landschap er over 5 jaar uitziet? En vervolgens stukje bij beetje alle componenten richting die architectuur migreren.

    Dat kun je blijven doen, want iedere paar jaar moet je je architectuur doelbijstellen. Wat 2 jaar geleden de heilige graal was is dat inmiddels niet meer. (Zijn SOA’s al weer passe? Nog niet? Jammer…) Dus het doel verschuift ook nog eens. Met andere woorden: ‘design for change’.

    (Van Gerry):

    Ik gebruik hier liever geen annotaties, omdat je dan de ‘echte’ domeincode vervuilt met persistencelogica, terwijl je die juist gescheiden wilt houden.

    Dat wil ik helemaal niet. Te vaak doen we nog alsof de database waarin we onze domeinobjecten in persisteren een speciale vorm van integratie is. Dat is het niet. Het is alleen een ingewikkelde manier om ‘implements Serializable’ op te schrijven. (Dit is in lijn met de wijze waarop Pieter dit tegenwoordig benadert.)

    Over het algemeen zijn er twee manieren om domeinobjecten in de database te krijgen (en er weer uit te halen, natuurlijk):
    1. Volledige loskoppeling van de domeinobjecten van de persistentie. Zie het Data Mapper pattern (Fowler).
    2. Volledige integratie van domeinobjecten en het persistentiemechanisme. Zie elke vorm van ORM.

    Elke manier ergens tussen 1 en 2 in gaat ergens problemen geven. Meestal performance-gerelateerd.

    Hibernate mapping files ergens buiten de domeinobjecten (bijvoorbeeld in een ander project) beheren lijkt een vorm van optie 1, maar is gewoon optie 2. Met als bijkomend nadeel dat onderhoud lastiger is. Een wijziging in de tabelstructuur heeft bijna altijd effect op de domeinobjecten en andersom ook. Dat betekent dus aanpassingen doorvoeren op 2 plekken.

    Met andere woorden: wil je per se geen annotaties in je domeinobjecten (of losse mapping files, da’s hetzelfde verschil) gebruik dan geen Hibernate (of andere vorm van ORM) op je domeinmodel, maar op een ander, speciaal voor persistentie gebouwd model. Met Data Mappers tussen het persistentiemodel en het domeinmodel.

    En inderdaad: dat wil je niet :-)

    (Van Jan-Kees):

    Aan de andere kanten spelen er weer nadelen, zoals dat je niet zomaar een lijstje kunt zien van alle met X geannoteerde classes. Bijvoorbeeld: Geef mij alle session scoped JSF managed beans of alle Servlets. Daarnaast ben je ook overgeleverd aan zwarte magie zoals classpath scanning, want in tegenstelling tot bij XML worden de beans niet automatisch gevonden

    Opmerking 1: voor het vinden van lijstjes van alle met X geannoteerde classes heb je als het goed is een fatsoenlijke IDE. En anders schrijf je een simpele annotation processor die het voor je doet.

    Opmerking 2: die ‘zwarte magie’ waaraan je refereert is een implementatiekeuze van bepaalde libraries en frameworks. Zoals Spring. Dat hoeft helemaal niet. Zie bijvoorbeeld OpenJPA. Je beschrijft een nadeel van een framework, niet van annotaties an sich.

    Ik pleit niet vóór annotaties, maar wil wel aangeven dat de nadelen die tot nu toe zijn genoemd wat mij betreft geen hout snijden. En het allerbelangrijkste nadeel van XML – zeker in combinatie met een taal als Java – is nog niet genoemd: XML compileert niet.

  14. lennart zegt:

    Het doel is dat bepaalde domeinlogica in het domeinmodel blijft en dat domeinmodeloverstijgende logica in de businesslaag wordt geimplementeerd. De structuur dient het doel en is daarmee hier ook aan ondergeschikt. Het is daarmee prima om de domeinlaag als kolom te zien welke door de business en de persistence laag heen loopt. Dit staat een nette en uitwisselbare ORM implementatie toe.

    Paar losse opmerkingen:
    Het systeem hoort niet (te) afhankelijk zijn van gegenereerde classes. Hibernate en Datamapper kunnen hiervoor zorgen. De fout die voorkomen moet worden is om met hibernate de domainclasses te genereren. De minimale implementatie is met extends. Een betere is via encapsulation. Met beide zijn de domeinclasses meteen gescheiden van de persistentie classes en zijn alleen de attributen ORM-tool managed objects c.q. attributes. Idem wordt altijd al gedaan bij andere integratie implementaties: webservice gegenereerde classes gebruiken we ook nooit in de interfaces van de applicatieservicelaag, maar we converteren deze eerst naar dto’s. Zo ook tussen EJB’s en de presentatielaag. De generated hibernate attribuut-classes zijn de integratie-dto’s tussen de database en het systeem.

    Daarin ga ik dus mee met 2.

    Op welk niveau doen we de integratie met andere systemen? Eerst wat duidelijk is: externe systemen welke als client met het systeem integreren doen dit altijd via de servicelaag. Waar het echt om gaat is de integratie waarin het systeem zelf de client is en een extern beheerd domeinmodel wordt binnengehaald. Dit kan:
    a) businesslaag, via een business integratieservice via messaging, rmi of webservice
    b) persistentielaag, bij directe koppeling meerdere databases/bronnen.
    Daarover verschillen meningen. Ik prefereer zelf a) omdat daar meer controle mogelijkheden zijn en de verantwoordelijk van persistentie van het ‘geleende’ domeinmodel op 1 plek blijft (en de businessintegratieservice kan prima binnen de transactie blijven).

    groeten,
    Lennart

  15. lennart zegt:

    p.s. met betrekking tot annotations versus mapping files.

    Wat mij betreft is de discussie over annotations voor domeinobjecten een uitbreiding op de discussie waarin de database wordt gegenereerd vanuit hbm’s, te weten een non-discussie. Als de mogelijkheid er al is, want DBA-ers die de database moeten gaan beheren willen SQL zien. Ze willen ook altijd externe hibernate mapping files zien t.b.v. onderhoudbaarheid en analyseerbaarheid = geen annotations voor persistentie. Derhalve is het vaak een design constraint om ORM in een mappingfile (hbm) te doen. Ook wil je de verantwoordelijkheid voor de ORM in een project graag bij een beperkt aantal mensen beleggen omdat er performance aspecten aan de orde zijn. Drie redenen dus om bij een referentiearchitectuur in ieder geval de mappingfile optie te ondersteunen. Mijn inziens ook voldoende redenen om hier altijd voor te kiezen, of beter: om over te stappen op de OO Database Cache ;-)

  16. gnoij zegt:

    Dat wil ik helemaal niet. Te vaak doen we nog alsof de database waarin we onze domeinobjecten in persisteren een speciale vorm van integratie is. Dat is het niet. Het is alleen een ingewikkelde manier om ‘implements Serializable’ op te schrijven. (Dit is in lijn met de wijze waarop Pieter dit tegenwoordig benadert.)

    De benadering van Pieter is een benadering die ik zelf al jaren hanteer. Echter wil ik wel een fysieke scheiding (projecten of packages) aanbrengen tussen de core functionaliteit (het domeinmodel) en de techniek (de database + mappingframeworks). Logisch gezien zitten ze echter wel in dezelfde laag. Dat ik daarbij soms code op twee plekken (domeinklasse en mapping) moet aanpassen neem ik hierbij op de koop toe. Een database (framework) is voor mij namelijk niets anders dan een browser (userinterfaceframework) namelijk een device dat gebruikt wordt. Het core domeinmodel zou niet van het bestaan hiervan op de hoogte moeten zijn, omdat het onafhankelijk hiervan zou moeten kunnen werken. Het zou voor het model niet uit moeten maken of je persisteert naar een DB2 of Oracle database, via Hibernate of TopLink of een ander persistence framework of via Mock implementaties voor je unittesten.

    De integratielaag wordt (in mijn visie) alleen gebruikt om te koppelen met externe componenten.

    Mijn (conceptuele) benadering is er een waarbij ik het domeinmodel ‘puur’ wil houden, zonder inmenging van de techniek zoals userinterface of persistency framework. Eigenlijk op dezelfde manier als dat gebeurt voor externe componenten in de integratielaag.

  17. Pieter van Boxtel zegt:

    Voila, Gerry op zijn wenken bedient met een aangepast plaatje. Hierin is onder andere te zien dat persistentie een eilandje is in de domeinlaag. Dit alles om de laatste alinea van mijn verhaal nog eens extra te onderstrepen.

    Vincent slaat met zijn “implements serializable” de spijker op de kop. Voor serialisatie moeten we als het in een database gebeurt ietsje meer doen dan een marker interface implementeren. Dit ietsje meer proberen we binnen de domeinlaag te scheiden van de “echte” businesslogica door gebruik te maken van standaard OO technieken. Geen aparte persistentielaag ofzo dus.

    In lennarts opmerkingen staan wel een paar dingen die me prikkelen:

    Uit de eerste alinea meen ik op te maken dat businesslaag en persistentielaag verschillende lagen zouden zijn waarover logica verdeeld wordt, en dat de domeinlaag er als met een kolomboor dwars doorheen gejaagd wordt. Dit is nu net waartegen ik betoog en daarom zeg ik het nog maar een keer; Weg met al die lagen! Domeinlaag, daar gebeurt het. En zeker niet boren, want dan gaat de boel alleen maar lekken.

    Een domeinlaag bouwen door gegenereerde klassen aan te passen is een nachtmerriescenario, daarover is iedereen het wel eens denk ik. Ook het extenden van gegenereerd spul moet verboden worden. 2 kanttekeningen echter; Natuurlijk is het uit oogpunt van luiheid toegestaan om Hibernate te misbruiken om een eerste versie van het domeinmodel te genereren. Zolang dat maar een eenmalige actie is en je niet wijzigingen in het domeinmodel door wilt voeren door de database aan te passen en opnieuw te gaan genereren. Verder is extenden van gegenereerde klassen een standaardpraktijk in DSLs (Mod4J), maar dat is een ander verhaal. Bij een DSL genereer je top-down. De domain DSL heeft doel het modelleren van een domeinmodel. Met Hibernate, webservice generators, et cetera genereer je bottom-up. En op basis van uit infrastructuur gegenereerd spul moet je geen businesslogica bouwen.

  18. lennart zegt:

    Het punt is niet goed geinterpreteerd. Het punt is dat domeinobjecten bestaan uit attributen en methoden. De attributen worden gepersisteert en horen daarom in de persistentielaag. De methoden bevatten de domeinlogica en horen in de domeinlaag hoort. Dat dit op 1 hoop gegooid wordt is de oorzaak van de moeilijkheid in welke laag een domeinobject nou hoort.

    Bv generated classes bevatten attributen en horen daarmee in de persistentielaag. Andere mogelijkheden zijn er ook. In de domeinlaag kan je deze dan uitbreiden met methoden. Bv. classes die extenden van de generated class. Andere mogelijkheden zijn er ook.

    Uiteraard worden gegenereerde classes niet aangepast. Dat wordt ook niet gesuggereert. Sterker nog, domeinclasses moet je niet genereren. De analogie met de boor gaat mijn inziens ook niet op, daar attributen geen logica bevatten en vice verca dat methoden niet gepersisteerd worden. Dus tenzij een class niet in twee lagen mag bestaan zie ik redenen om bepaalde zaken binnen 1 class te verdelen over twee lagen. Dat kan prima binnen een OO-taal.

    De oplossing om lagen weg te doen en alles in de domeinlaag te implementeren omdat het niet strict in 1 laag past vind ik daarom een onterechte modelleringskeuze. Wanneer een stricte structuur wordt verheven boven het eigenlijke principe dan ontstaan inderdaad constructies waar het wringt. Ik zou dit dan ook niet doen. Het lagenmodel is een middel tot een doel en domeinobjecten spelen daar een rol in, twee rollen wel te verstaan. Zowel doel als rol zijn te verwezenlijken, zoals aangegeven.

  19. Ordina J-Technologies » Blog archief » Domain Driven Design in de Referentie Architectuur. zegt:

    [...] een afzonderlijke laag, de datalaag, en heten Data Access Logic component. Echter, zoals ik in een vorige blog betoogde is dat niet terecht en horen ze in de domeinlaag [...]

Laat een reactie achter