[DDD/O-R] Weg werken bidirectional association

Pagina: 1
Acties:

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 22-03 18:12
Ik heb een vraag over een questie bij het mappen van een object naar een relationele database. Nu heb ik de werking daarvan verborgen achter repositories (zoals in het boek Domain-Driven Design wordt voogesteld), zodat de werkelijke opslag methode wordt verborgen voor de domein objecten. Nu heb ik alleen een probleem bij het mappen van een geaggregeerd object. Op zich is het geen probleem, alleen vind ik het niet netjes. Het probleem is dat een bezittend object vaak een refefentie heeft naar het bezeten object, maar andersom niet. In het geval van een tabel in een relationele database is dat juist precies andersom. (Vooral in het geval van meerdere bezeten objecten.) Nu heb ik een probleem dat het bezeten object vaak toch een referentie naar zijn bezitter nodig heeft, omdat bij het mappen een waarde voor de foreign key nodig is. En de referentie wordt dan vaak verder helemaal nergens anders voor gebruikt. Nu is mijn vraag hoe jullie dat oplossen.

Een stukje voorbeeld code ter illustratie.

code:
1
2
3
4
5
tabel: customers
velden: id, name

tabel: orders
velden: id, customer_id, name


code:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class Customer
{
    public Customer(int ID, String name, OrderCollection orders)
    {
        this.ID = ID;
        this.name = name;
        this.orders = orders;
    }

    private int ID;
    public int getID()
    {
        return ID;
    }

    private String name;
    public String getName()
    {
        return name;
    }

    private OrderCollection orders;
    public OrderCollection getOrders()
    {
        return orders;
    }

    public Order addNewOrder(int ID, String name)
    {
        Order order = new Order(ID, name, this);
        orders.add(order);
        OrderRepository.store(order);
    }
}

class Order
{
    public Order(int ID, String name, Customer customer)
    {
        this.ID = ID;
        this.name = name;
        this.customer = customer;
    }

    private int ID;
    public int getID()
    {
        return ID;
    }

    private String name;
    public String getName()
    {
        return name;
    }

    private Customer customer;
    public Customer getCustomer()
    {
        return customer;
    }
}

class OrderRepository
{
    public static void store(Order order)
    {
        int customer_id = order.getCustomer().getID();
        // in de database voegen met deze waarde
    }
}


Nu zijn er bepaalde problemen met deze code.

Sowieso kan dit in princiepe helemaal niet, omdat een Order een Customer object vraagt in de constructor en Customer op zijn beurt weer een collectie Orders. Dit is uiteraard op te lossen door een setter setCustomer toe te voegen aan Order of een setter setOrders aan Customer. Beide wil ik liever niet. Om dat op te lossen moet de Customer referentie weg uit Order. (OK, mischien heeft een Order dat in een werkelijk applicatie wel nodig, maar ga er maar even vanuit dat het niet zo is, het is voor het voorbeeld niet van belang.) Maar dat zorgt er dus voor dat de store functie niet goed werkt in OrderRepository. Nu kan ik wel een functie toevoegen als addOrderToCustomer(), maar is dat wel zo netjes?

Je zou kunnen beargumenteren dat je een CustomerRepository puur de verantwoordelijkheid moet geven over het opslaan van Orders, maar als Orders los onafhankelijk van de Customer aan de hand van hun ID opgehaald moeten kunnen worden (wat waarschijnlijk zo is), dan gaat dat al niet.

Een andere mogelijke oplossing is om een Proxy of Decorator te maken voor OrderCollection welke de taak heeft om het customer_id veld in de orders tabel op de juiste waarde te zetten. OrderRepository zelf heeft dan alleen de taak om alle velden die direct met Order te maken hebben een waarde te geven. (Voor het voorbeeld alleen id en name dan.) Op zich heb ik al proxies voor veel collectie classes (die lazy loading functionaliteit bevatten), dus dat zou op zich een redelijk oplossing kunnen zijn. Alleen vereist dit wel dat per toegevoegde order 2 in plaats van 1 query uitgevoerd moet worden.

Hoe denken jullie hier over? Door al die bidirectional associations krijg je een heel net met koppelingen. Maak ik me te veel zorgen om niets of is dit wel iets om aandacht aan te besteden?

Noushka's Magnificent Dream | Unity


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:18
Waarom wil je een Customer object meegeven in de constructor van Order ?
En waarom zou je die OrderCollection meegeven aan de constructor van Customer ?
Die redenering volg ik niet helemaal.

Wat je kan doen, is een method 'AddOrder' maken bij de Customer class, die de referentie naar het customer-object in je Order verzorgt:
code:
1
2
3
4
5
void AddOrder( Order o )
{
   o.Customer = this;
   _orders.Add(o);
}


En als je zegt dat je die referentie enkel nodig hebt om de relatie te kunnen verzorgen bij het persisten, dan kan je er ook altijd voor kiezen om gewoon de FK in je object mee te nemen.
Mocht het nodig zijn dat je nog extra info nodig hebt van je customer vanuit je order object, kan je altijd de DB query-en.

https://fgheysels.github.io/


  • EfBe
  • Registratie: Januari 2000
  • Niet online
Michali schreef op zondag 25 december 2005 @ 14:22:
Ik heb een vraag over een questie bij het mappen van een object naar een relationele database. Nu heb ik de werking daarvan verborgen achter repositories (zoals in het boek Domain-Driven Design wordt voogesteld), zodat de werkelijke opslag methode wordt verborgen voor de domein objecten. Nu heb ik alleen een probleem bij het mappen van een geaggregeerd object. Op zich is het geen probleem, alleen vind ik het niet netjes. Het probleem is dat een bezittend object vaak een refefentie heeft naar het bezeten object, maar andersom niet. In het geval van een tabel in een relationele database is dat juist precies andersom. (Vooral in het geval van meerdere bezeten objecten.)
Nee dat is precies gelijk: de FK is gedefinieerd op de FK table, niet op de PK table. Dit betekent dat Order een referentie naar Customer heeft maar Customer niet naar Order.
Nu heb ik een probleem dat het bezeten object vaak toch een referentie naar zijn bezitter nodig heeft, omdat bij het mappen een waarde voor de foreign key nodig is. En de referentie wordt dan vaak verder helemaal nergens anders voor gebruikt. Nu is mijn vraag hoe jullie dat oplossen.

[snip]

Nu zijn er bepaalde problemen met deze code.

Sowieso kan dit in princiepe helemaal niet, omdat een Order een Customer object vraagt in de constructor en Customer op zijn beurt weer een collectie Orders. Dit is uiteraard op te lossen door een setter setCustomer toe te voegen aan Order of een setter setOrders aan Customer. Beide wil ik liever niet. Om dat op te lossen moet de Customer referentie weg uit Order. (OK, mischien heeft een Order dat in een werkelijk applicatie wel nodig, maar ga er maar even vanuit dat het niet zo is, het is voor het voorbeeld niet van belang.) Maar dat zorgt er dus voor dat de store functie niet goed werkt in OrderRepository. Nu kan ik wel een functie toevoegen als addOrderToCustomer(), maar is dat wel zo netjes?
Entities staan op zichzelf. Het is dus niet nuttig om in de CTor van customer en van order de reference naar andere entities toe te voegen. Je kunt nl. de FK in order ook via een value zetten en dan heb je geen customer object nodig. (om maar wat te noemen).
Je zou kunnen beargumenteren dat je een CustomerRepository puur de verantwoordelijkheid moet geven over het opslaan van Orders, maar als Orders los onafhankelijk van de Customer aan de hand van hun ID opgehaald moeten kunnen worden (wat waarschijnlijk zo is), dan gaat dat al niet.
Je kunt vanalles beargumenteren. Laat ik je 1 ding vertellen: degenen die het meest alles wegdesignen in allerlei layers krijgen het minste af. Dit neemt niet weg dat je moeite moet doen om het netjes op te zetten, maar je moet IMHO te allen tijde pragmatisch te werk gaan. Het probleem wat jij hierboven schetst is overigens een groot probleem waar bv ook SOA onder lijdt.
Een andere mogelijke oplossing is om een Proxy of Decorator te maken voor OrderCollection welke de taak heeft om het customer_id veld in de orders tabel op de juiste waarde te zetten. OrderRepository zelf heeft dan alleen de taak om alle velden die direct met Order te maken hebben een waarde te geven. (Voor het voorbeeld alleen id en name dan.) Op zich heb ik al proxies voor veel collectie classes (die lazy loading functionaliteit bevatten), dus dat zou op zich een redelijk oplossing kunnen zijn. Alleen vereist dit wel dat per toegevoegde order 2 in plaats van 1 query uitgevoerd moet worden.
Waarom laat je je O/R core niet de syncing verzorgen? Daar is deze voor tenslotte.
Hoe denken jullie hier over? Door al die bidirectional associations krijg je een heel net met koppelingen. Maak ik me te veel zorgen om niets of is dit wel iets om aandacht aan te besteden?
het is erg complex ja, daarom is een O/R mapper schrijven geen kattepis, tenminste als je verder gaat dan single object code, en bv graphs wilt saven en FKs wilt syncen gedurende de save, etc. etc.

Ik heb in Customer een entity collection 'Orders'. Deze collection weet dat hij in Customer zit. Als ik een order wil toevoegen aan Customer, kan ik doen:

myOrder.Customer = myCustomer; // A
OF
myCustomer.Orders.Add(myOrder); // B

Bij A gaat de Customer property de sync opzetten en aan de customer vertellen dat hij gereferenced is door myOrder, waardoor de customer dan myOrder aan Orders toevoegt.
Bij B weet Orders dat hij in Customer zit en geeft aan zn 'embedding entity' door dat een Add heeft plaatsgevonden en wel van entity X. de embedding entity, myCustomer in dit geval krijgt dat seintje, en notified myOrder dat deze nu is gereferenced door myCustomer via Orders, en voert dus A nog eens uit, wat de sync opzet.

Het leuke is, dit kan zonder 'session' of 'context' en dus ergens gebeuren zonder dat een o/r mapper core erbij betrokken wordt. De graph bouwt zichzelf op dus, welke actie je ook kiest.

[ Voor 11% gewijzigd door EfBe op 25-12-2005 21:43 ]

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


  • Michali
  • Registratie: Juli 2002
  • Laatst online: 22-03 18:12
whoami schreef op zondag 25 december 2005 @ 20:47:
Waarom wil je een Customer object meegeven in de constructor van Order ?
En waarom zou je die OrderCollection meegeven aan de constructor van Customer ?
Die redenering volg ik niet helemaal.
Dat heb ik gedaan,omdat ik er even vanuit wilde gaan dat de implementatie van de OrderCollection en de Customer bij instantieren bekend moet zijn en verder dan ook niet veranderd kan worden. Als je dat gebruik van setters maakt, dan breek je dat. Mischien is dat niet zo erg als je het goed documenteert.
Wat je kan doen, is een method 'AddOrder' maken bij de Customer class, die de referentie naar het customer-object in je Order verzorgt:
code:
1
2
3
4
5
void AddOrder( Order o )
{
   o.Customer = this;
   _orders.Add(o);
}
Dan zit je wel nog steeds met zo'n bidirectionele associatie. Klopt het dat zoiets dan eigenlijk gewoon redelijk normaal is?
En als je zegt dat je die referentie enkel nodig hebt om de relatie te kunnen verzorgen bij het persisten, dan kan je er ook altijd voor kiezen om gewoon de FK in je object mee te nemen.
Mocht het nodig zijn dat je nog extra info nodig hebt van je customer vanuit je order object, kan je altijd de DB query-en.
Dat is idd ook een optie.
EfBe schreef op zondag 25 december 2005 @ 21:39:
[...]

Nee dat is precies gelijk: de FK is gedefinieerd op de FK table, niet op de PK table. Dit betekent dat Order een referentie naar Customer heeft maar Customer niet naar Order.
Dat klopt, maar in geval van bijvoorbeeld een besteld product en een product specificatie, dan zit de FK naar de bijbehorende specificatie wel in de besteld product tabel. Of noem die tabel in zo'n geval dan ook een FK table?
Entities staan op zichzelf. Het is dus niet nuttig om in de CTor van customer en van order de reference naar andere entities toe te voegen. Je kunt nl. de FK in order ook via een value zetten en dan heb je geen customer object nodig. (om maar wat te noemen).
Maar impliceert een FK in een object juist geen referentie? Vooral als er zo'n object aanwezig is?
Je kunt vanalles beargumenteren. Laat ik je 1 ding vertellen: degenen die het meest alles wegdesignen in allerlei layers krijgen het minste af. Dit neemt niet weg dat je moeite moet doen om het netjes op te zetten, maar je moet IMHO te allen tijde pragmatisch te werk gaan. Het probleem wat jij hierboven schetst is overigens een groot probleem waar bv ook SOA onder lijdt.
Dat is ook zeker zo. Maar ik merk wel (en heb er al voordeel van ondervonden) dat het loskopppelen van het domein van de database veel flexibiliteit geeft. Ik heb er inmiddels al wel veel tijd ingestoken om bepaalde zaken te leren, dat geef ik wel toe, maar ik ben dan ook nog druk bezig met leren om bepaalde technieken effectief en pragmatisch toe te passen.
Waarom laat je je O/R core niet de syncing verzorgen? Daar is deze voor tenslotte.
Maar dan heb je wel de waarde van de Customer ID nodig in je Order object. In geval van een OrderCollection die daar controle over neemt (omdat deze dus afweet van het bestaan van Customer) zou je dat geheel los kunnen koppelen. Maar dat brengt wel een hoop extra complexiteit met zich mee, die mischien totaal verkeerd besteed is en waar ik helemaal geen plezier van heb.
het is erg complex ja, daarom is een O/R mapper schrijven geen kattepis, tenminste als je verder gaat dan single object code, en bv graphs wilt saven en FKs wilt syncen gedurende de save, etc. etc.

Ik heb in Customer een entity collection 'Orders'. Deze collection weet dat hij in Customer zit. Als ik een order wil toevoegen aan Customer, kan ik doen:

myOrder.Customer = myCustomer; // A
OF
myCustomer.Orders.Add(myOrder); // B

Bij A gaat de Customer property de sync opzetten en aan de customer vertellen dat hij gereferenced is door myOrder, waardoor de customer dan myOrder aan Orders toevoegt.
Bij B weet Orders dat hij in Customer zit en geeft aan zn 'embedding entity' door dat een Add heeft plaatsgevonden en wel van entity X. de embedding entity, myCustomer in dit geval krijgt dat seintje, en notified myOrder dat deze nu is gereferenced door myCustomer via Orders, en voert dus A nog eens uit, wat de sync opzet.
En hoe zet je zo'n sync dan precies op? Bedoel je dat je in geval van A dan myOrder zichzelf laat toevoegen aan de Orders collection van myCustomer en dat in geval van B myCustomer de Customer property van myOrder op zichzelf zet? Of doel je dan meer in de richting van een call naar een repository/datasource (of wat je dan ook gebruikt)?
Het leuke is, dit kan zonder 'session' of 'context' en dus ergens gebeuren zonder dat een o/r mapper core erbij betrokken wordt. De graph bouwt zichzelf op dus, welke actie je ook kiest.
Dat begrijp ik niet helemaal denk ik. Zou je dat iets kunnen toelichten?

Noushka's Magnificent Dream | Unity


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:18
Michali:
Dan zit je wel nog steeds met zo'n bidirectionele associatie. Klopt het dat zoiets dan eigenlijk gewoon redelijk normaal is?
Waarom zou een bidirectionele associatie tussen entities niet mogen ?
Je wilt toch weten welke orders een klant allemaal heeft, maar, je wil ook weten van welke klant een bepaald order is.
EfBe:
Je kunt vanalles beargumenteren. Laat ik je 1 ding vertellen: degenen die het meest alles wegdesignen in allerlei layers krijgen het minste af.
Hier ben ik het mee eens.
Op zich is het wel mooi natuurlijk als je een mooi, uitbreidbaar en flexibel design hebt, maar het is ook gewoon frustrerend soms.
'Vroeger' had ik ook wel eens zoiets van: ik doe dit en dat, en dan is m'n class zo flexibel dat ik moeiteloos aanpassingen kan doen, etc.... Echter, als je er eens bij stilstaat is het gewoon zinloos om nu al rekening te houden met eisen die 'mogelijkerwijs wel eens in de onbepaalde toekomst zouden kunnen gespecifieerd worden'.
Ik zeg natuurlijk niet dat dit het geval is in dit specifieke probleem van michali

https://fgheysels.github.io/


Verwijderd

EfBe schreef op zondag 25 december 2005 @ 21:39:
[...]
Het leuke is, dit kan zonder 'session' of 'context' en dus ergens gebeuren zonder dat een o/r mapper core erbij betrokken wordt. De graph bouwt zichzelf op dus, welke actie je ook kiest.
Ligt het voordeel hiervan bij het unit testen ofzo?

[ Voor 14% gewijzigd door Verwijderd op 26-12-2005 17:16 ]


  • EfBe
  • Registratie: Januari 2000
  • Niet online
Verwijderd schreef op maandag 26 december 2005 @ 12:59:
[...]
Ligt het voordeel hiervan bij het unit testen ofzo?
Nee, bij het feit dat je code kunt laten werken die niet een reference naar een o/r mapper core nodig heeft, dus bv in ee GUI tier, die een graph stuurt / betrekt van een remoted service. Je kunt dan wanneer de persistence plaats moet vonden gewoon de graph aanbieden aan de persistence core en die heeft alle info al in de graph, je hoeft dan niet eerst te 'attachen' of wanneer je disconnected wilt werken, te detachen.

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


  • EfBe
  • Registratie: Januari 2000
  • Niet online
Michali schreef op maandag 26 december 2005 @ 11:52:
[...]
Dat klopt, maar in geval van bijvoorbeeld een besteld product en een product specificatie, dan zit de FK naar de bijbehorende specificatie wel in de besteld product tabel. Of noem die tabel in zo'n geval dan ook een FK table?
Dat is de FK side, de table met de FK field(s), dus daarop definieer je de FK constraint, want die loopt altijd van fk side naar pk side. Product heeft geen weet van welke tables allemaal een reference naar hem hebben staan, dat is ook niet relevant voor product.
[...]
Maar impliceert een FK in een object juist geen referentie? Vooral als er zo'n object aanwezig is?
Een FK geeft een referentiele afhankelijkheid aan. Een 'Order' die een 'CustomerID' nodig heeft en waarbij je zeker wilt zijn dat die customerid ook echt bestaat heeft dus een afhankelijkheid naar customer toe, want hij is afhankelijk van de data in customer of order.customerid geldig is. Dit noemen we een sterke relatie. Customer -> Order is impliciet, en is 'weak' want je kunt een customer toevoegen zonder een order.

De impliciete relatie customer -> order is wel erg handig en daardoor wordt deze ook bijna altijd gebruikt in een o/r framework, zodat je customer.Orders beschikbaar hebt.
[...]
Dat is ook zeker zo. Maar ik merk wel (en heb er al voordeel van ondervonden) dat het loskopppelen van het domein van de database veel flexibiliteit geeft. Ik heb er inmiddels al wel veel tijd ingestoken om bepaalde zaken te leren, dat geef ik wel toe, maar ik ben dan ook nog druk bezig met leren om bepaalde technieken effectief en pragmatisch toe te passen.
Loskoppelen is wel nuttig maar verlies jezelf niet in overhead die je eigenlijk nooit gebruikt. Het komt zelden voor dat mensen 2 databases hebben die totaal verschillend zijn en waarbij dezelfde entities worden gebruikt. Verschillen komen hooguit voor in details, zoals een bit field in sqlserver en een number(1,0) in oracle voor hetzelfde entity attribute. Een o/r mapper strijkt dat dan glad voor je en biedt het entity field bv aan als een bool en achter de schermen vindt conversie plaats waar nodig.
[...]
Maar dan heb je wel de waarde van de Customer ID nodig in je Order object. In geval van een OrderCollection die daar controle over neemt (omdat deze dus afweet van het bestaan van Customer) zou je dat geheel los kunnen koppelen. Maar dat brengt wel een hoop extra complexiteit met zich mee, die mischien totaal verkeerd besteed is en waar ik helemaal geen plezier van heb.
Je kunt OrderCollection daar geen controle over geven, want je kunt niet uitgaan van het feit dat alle orders geladen zijn. Alleen de database weet dat.
[...]
En hoe zet je zo'n sync dan precies op? Bedoel je dat je in geval van A dan myOrder zichzelf laat toevoegen aan de Orders collection van myCustomer en dat in geval van B myCustomer de Customer property van myOrder op zichzelf zet? Of doel je dan meer in de richting van een call naar een repository/datasource (of wat je dan ook gebruikt)?
Syncing wordt gedaan door events: als de PK side gesaved is, raised hij een event en de FK sides subscriben op dat event. De handler krijgt dan binnen welke entity het is en gaat de sync info voor die entity ophalen in zn base class of waar je het ook maar opslaat at runtime. De sync info bevat pk-fk relations en synct de pk field(s) met de fk field(s) volgens de relatie in de sync info. Het kan bv voorkomen dat een customer 2 references heeft naar hetzelde Address object en dus 2 fk fields moeten worden gesynced met dezelfde PK over 2 relaties.

De FK en de PK side geven aan elkaar door dat ze aan elkaar gekoppeld zijn. De FK side van die logica zet de sync op door de subscriben op het event van de PK side en voor die betreffende relatie de sync info op te slaan. Dit geeft wel de verplichting dat wanneer je myOrder.CustomerID = otherValue; doet, je alles weer opruimt en dus myOrder.Customer op null zet en je sync info opruimt.

Dit levert een framework op dat zonder o/r core wel werkt in bv een gui en een master/detail opzet en je dus live de syncing meemaakt, want je kunt bv fk fields al syncen wanneer de PK side tijdens de associatie niet nieuw blijkt te zijn en dus valide is.

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

Pagina: 1