Toon posts:

[OOD] Global identity map - reference issues

Pagina: 1
Acties:

Verwijderd

Topicstarter
In Fowler's PoEAA wordt voorgesteld om tussen de domain layer en de data-access layer (in mijn geval DataMappers die met behulp van een Mapper Registry worden ingezet door een transactionele Unit of Work) een soort van globale Dictionary te plaatsen, waar domein-objecten -met als key hun GUID- kunnen worden gecached. Zo'n globale cache heeft uiteraard als voordeel dat veel datastore roundtrips overbodig worden enkelvoudige entity fetches vlotter worden. Niet geheel triviaal overigens: het betreft hier een web-omgeving (ASP.NET).

Nu overerven mijn domein objecten van een layer supertype ("DomainObject"), welke onder andere een simpel State-mechanisme voor domeinobjecten implementeert. Bijv: bij aanpassing van een property wordt een 'clean' object als 'dirty' gemarkeerd en registreert het zich bij de toegewezen Unit of Work.

Gegeven de volgende situatie:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Object is al aanwezig in de globale cache en een state-check 
// in de cache wijst uit dat die op dat moment 'clean' is; dit leidt 
// ertoe dat het teruggegeven object een referentie bevat naar het 
// object in de cache. Het is natuurlijk mogelijk dat andere clients 
// op dat moment ook al een referentie hebben naar dit object.

IUser user = UserRepository.FindById(user_Id);
IUnitOfWork localUnitOfWork = UnitOfWorkFactory.Create();

user.FirstName = "Foo";
user.LastName = "Bar";

// om de een of andere reden ontstaat hier een exception (of 
// bijvoorbeeld tijdens onderstaande  commit)

localUnitOfWork.Commit();


Andere clients die voordat bovenstaande plaatsvond ook al een referentie hadden naar het desbetreffende User object hebben nu een 'dirty' object met allemaal mogelijke vervelende gevolgen vandien.

Hoe kan je deze situatie het beste tackelen?

Enkele (mogelijke?) oplossingen waar ik al aan dacht:
  • Een soort van Undo-functionaliteit op het domein-object aanroepen, zodat deze wordt teruggezet in de 'clean' state door de unit of work als er een fout optreedt?
  • Uniquing laten schieten en in plaats daarvan klonen van de domein objecten in de cache teruggeven? (bijv. mbv. ICloneable op domein objecten)
  • Domein objecten locken terwijl die worden bijgewerkt?
  • DTO's cachen ipv de domein objecten zelf?

[ Voor 47% gewijzigd door Verwijderd op 11-05-2006 15:53 ]


  • JKVA
  • Registratie: Januari 2004
  • Niet online

JKVA

Design-by-buzzword fanatic

Locken zou ik niet aan beginnen, tenminste niet zelf een lockingsysteem maken.

Al eens naar ORM gekeken? Die vangen veel van die dingen af (locking, dirty checking). Hibernate regelt bijvoorbeled al van die dingen voor je. Plus nog vanalles.

En het mooie van in dit geval Hibernate, is dat het transparant is voor je domeinklassen, zodat je je domeinobjecten "gewoon" gemakkelijk kunt opslaan. Alleen ff in een config vastleggen.

edit:

Ps. Dit gaat ook over dirty checking. :) [rml][ OO/O-R] Dirty state van een object bepalen[/rml]

[ Voor 14% gewijzigd door JKVA op 09-05-2006 21:31 ]

Fat Pizza's pizza, they are big and they are cheezy


Verwijderd

Topicstarter
JKVA schreef op dinsdag 09 mei 2006 @ 21:29:
Locken zou ik niet aan beginnen, tenminste niet zelf een lockingsysteem maken.

Al eens naar ORM gekeken? Die vangen veel van die dingen af (locking, dirty checking). Hibernate regelt bijvoorbeled al van die dingen voor je. Plus nog vanalles.
Het idee is uiteindelijk inderdaad dit soort subsystemen te vervangen door third-party componenten, maar het opzetten van een eigen framework is een mooie leerervaring, dus vandaar.
En het mooie van in dit geval Hibernate, is dat het transparant is voor je domeinklassen, zodat je je domeinobjecten "gewoon" gemakkelijk kunt opslaan. Alleen ff in een config vastleggen.

edit:

Ps. Dit gaat ook over dirty checking. :) [rml][ OO/O-R] Dirty state van een object bepalen[/rml]
Die had ik al gelezen. :)

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Verwijderd schreef op dinsdag 09 mei 2006 @ 18:25:
In Fowler's PoEAA wordt voorgesteld om tussen de domain layer en de data-access layer (in mijn geval DataMappers die met behulp van een Mapper Registry worden ingezet door een transactionele Unit of Work) een soort van globale Dictionary te plaatsen, waar domein-objecten -met als key hun GUID- kunnen worden gecached. Zo'n globale cache heeft uiteraard als voordeel dat veel datastore roundtrips overbodig worden. Niet geheel triviaal overigens: het betreft hier een web-omgeving (ASP.NET).
Nee, dat is echt theoretische onzin. Een cache levert helemaal geen minder roundtrips op, tenzij je alleen enkelvoudige entity fetches doet. De reden is simpel: je weet nooit, wanneer je een set opvraagt, of alle entities die de filter matchen al in je cache zitten. Je moet dus t.a.t. naar de database om te kijken of je alle entities al hebt. Dit levert dus t.a.t. een fetch op van je complete set. Daarna moet je je cache syncen met de set die je opgehaald hebt omdat data door andere threads gewijzigd kan zijn en heb je dus eigenlijk MEER werk / overhead dan zonder een cache.

Met enkelvoudige entity fetches kun je een roundtrip voorkomen door eerst in de cache te kijken en indien je niets vindt een db call te doen. Ook niet echt aan te raden overigens, want je kunt te maken krijgen met data die erg 'stale' is, je kunt nl. nauwelijks ondervangen of een andere thread dezelfde data al heeft geupdate in de db.

De enige reden voor een cache is uniquing, dwz: een single entity object instance voor een entity in memory. Dus 2 keer dezelfde customer fetchen levert hetzelfde object op.

Je moet verder kijken naar context-specifieke uniquing. In veel gevallen is het nl. totaal niet interessant dat je unique entities gebruikt, maar in andere gevallen kan het handig zijn. Als je er goed naar kijkt is het eigenlijk zo dat je per editing-context, dus bv per routine of per stukje code in je routine, unique instances wilt. Het is dan beter om een klein soort cache daarvoor te gebruiken, bij Apple's O/R mapper heten die EditingContext, ik noem ze Context in LLBLGen Pro. Je kunt bv in 1 routine 2 of meerdere Context objects gebruiken, en semantisch gezien binnen die Context objects zijn instances uniek. Dit heeft voordelen, want je omzeilt alle problemen die jij hebt en dat is niet erg want uniquing is iets wat erg context specifiek is: in erg veel gevallen is het nauwelijks nuttig om dezelfde instance te gebruiken in meerdere threads en bij meerdere processes heb je sowieso al meerdere instances (remoting levert een copy op)
Verwijderd schreef op woensdag 10 mei 2006 @ 09:12:
[...]
Het idee is uiteindelijk inderdaad dit soort subsystemen te vervangen door third-party componenten, maar het opzetten van een eigen framework is een mooie leerervaring, dus vandaar.
[...]
Houd er wel rekening mee dat het erg veel tijd kost. Dus doe je dit in project-tijd, dan is het IMHO echt verspilde tijd en geld weggooien.

[ Voor 9% gewijzigd door EfBe op 10-05-2006 10:07 ]

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


Verwijderd

Topicstarter
EfBe schreef op woensdag 10 mei 2006 @ 10:06:
[...]

Nee, dat is echt theoretische onzin. Een cache levert helemaal geen minder roundtrips op, tenzij je alleen enkelvoudige entity fetches doet. De reden is simpel: je weet nooit, wanneer je een set opvraagt, of alle entities die de filter matchen al in je cache zitten. Je moet dus t.a.t. naar de database om te kijken of je alle entities al hebt. Dit levert dus t.a.t. een fetch op van je complete set. Daarna moet je je cache syncen met de set die je opgehaald hebt omdat data door andere threads gewijzigd kan zijn en heb je dus eigenlijk MEER werk / overhead dan zonder een cache.

Met enkelvoudige entity fetches kun je een roundtrip voorkomen door eerst in de cache te kijken en indien je niets vindt een db call te doen. Ook niet echt aan te raden overigens, want je kunt te maken krijgen met data die erg 'stale' is, je kunt nl. nauwelijks ondervangen of een andere thread dezelfde data al heeft geupdate in de db.
Bij een ASP.NET omgeving ligt dat al wat eenvoudiger omdat alle threads binnen hetzelfde appdomain draaien. Dan is een daadwerkelijk globale cache wat makkelijker om te implementeren.
De enige reden voor een cache is uniquing, dwz: een single entity object instance voor een entity in memory. Dus 2 keer dezelfde customer fetchen levert hetzelfde object op.
Dat is inderdaad wat ik bedoelde met m'n "(perceived) globale object-graph". Hmm, dan is uniquing wel een wat praktischere term. :)
Je moet verder kijken naar context-specifieke uniquing. In veel gevallen is het nl. totaal niet interessant dat je unique entities gebruikt, maar in andere gevallen kan het handig zijn. Als je er goed naar kijkt is het eigenlijk zo dat je per editing-context, dus bv per routine of per stukje code in je routine, unique instances wilt. Het is dan beter om een klein soort cache daarvoor te gebruiken, bij Apple's O/R mapper heten die EditingContext, ik noem ze Context in LLBLGen Pro.
Oh, en voor elke Context heb je dan een soort van lokale identitymap, zodat je niet dubbele references naar hetzelfde object kunt krijgen binnen die Context? Geef je zo'n Context dan ook transaction management verantwoordelijkheden (waar bij mij de Unit of Work voor wordt gebruikt)?
Houd er wel rekening mee dat het erg veel tijd kost. Dus doe je dit in project-tijd, dan is het IMHO echt verspilde tijd en geld weggooien.
Het betreft een eigen projectje (vooral bedoeld als leerervaring); in iemand anders tijd zou ik niet aan zoiets beginnen. :)

Als ik er nog eens overna denk is het volgens mij sowieso handiger om wat betreft caching de nadruk wat meer op een hoger niveau in de applicatie te leggen.

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Verwijderd schreef op woensdag 10 mei 2006 @ 13:35:
[...]
Bij een ASP.NET omgeving ligt dat al wat eenvoudiger omdat alle threads binnen hetzelfde appdomain draaien. Dan is een daadwerkelijk globale cache wat makkelijker om te implementeren.
Nee, want als het appdomain recycled heb je 2 appdomains. Verder heb je nog clusters van webservers etc. dus echt simpel is het niet.
[...]
Oh, en voor elke Context heb je dan een soort van lokale identitymap, zodat je niet dubbele references naar hetzelfde object kunt krijgen binnen die Context? Geef je zo'n Context dan ook transaction management verantwoordelijkheden (waar bij mij de Unit of Work voor wordt gebruikt)?
Nee de context is alleen voor uniquing, dus de core haalt entities op: gaat dan per entity (de data) kijken in de te gebruiken context of daar al zo'n object in zit. Zo ja, dan gebruik je die instance en vervang je de values in die instance met de values die je van de db leest, zo nee, dan maak je een nieuwe instance aan.

Transactions vallen daar buiten, het gaat er hier puur om: stop ik de data in een nieuwe instance of gebruik ik een bestaande, en wel degene die ik in deze context vindt. That's it.

Voor single entity fetches kun je dan de context weer vragen of een entity met een gegeven PK er in zit, zodat je een db fetch uitspaart.

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

Pagina: 1