[Symfony2] Inheritance gebruiken of niet?

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • maarud
  • Registratie: Mei 2005
  • Laatst online: 14:52
Ik heb verschillende webapplicaties in Symfony 2 gemaakt en het gezegde 'al doende leert men' is waar in mijn geval. Telkens als ik een nieuwe applicatie maak (of als ik een huidige upgrade) wil ik mezelf weer overtreffen in niveau en zo ben ik nu op het punt gekomen om iets met Table Inheritance (of niet, afhankelijk van het antwoord) te doen.
Case:
CRM-app (kan eender welke app zijn natuurlijk).

Versimpelde weergave van tabellen:

Tabel: Company
Velden: ID (PK) company_name

Tabel: Contact
Velden: ID (PK) company_id (FK) first_name last_name

Nu wil ik een notitie-entity toevoegen zodat ik bij de huidige entities (company en contact) notities kan toevoegen. Wie weet komt er in de toekomst een entity bij, dan daarvoor ook een notitie.

De vraag is, hoe te doen.
Mogelijkheid 1 - Extra notitie-tabel aanmaken
Tabel: Notities
Velden: ID (PK) company_id (FK) contact_id (FK) contents

Daarbij is één van de twee FK's null uiteraard. Nadeel: bij een nieuw toe te voegen entity moet er ook een nieuwe kolom bij (maar ja, hoe vaak gebeurt dat nu kan je zeggen).
Mogelijkheid 2 - Inheritance
Voor zover ik het begrepen heb maak je een superclass en daar hang je andere classes onder. In mijn geval zou dat dan worden:

Entity: NotableEntity
Velden: ID

Entity: Company extends NotableEntity
Velden: company_name

Entity: Contact extends NotableEntity
Velden: company_id, first_name, last_name

Op deze manier heb ik unieke ID's voor Company én Contact, zou je zeggen. Alleen is company_id dan een ID in dezelfde tabel. dus ik weet niet of dat wel goed gaat.

De notitie tabel is dan heel simpel:
Tabel: Notities
Velden: ID (PK) notable_entity_id (FK) contents

Vraag:
Wat is slim om te doen? De tabel bevat 3000 companies dus performance-wise hoeft het niet op de milliseconde geoptimaliseerd te worden. Ik vind het wel leuk om iets op de professionele manier aan te pakken alsof het een tabel met een biljoen rijen is, alleen ben ik nu misschien bezig om teveel te optimaliseren. Zou mogelijkheid 1 (de simpele mogelijkheid) volstaan of moet ik mij bezig houden met inheritance en zo ja, wat is in dat geval de beste aanpak?

Beste antwoord (via maarud op 29-01-2016 19:44)


  • HyperioN
  • Registratie: April 2003
  • Laatst online: 07-10 22:02
maarud schreef op donderdag 28 januari 2016 @ 14:04:
[...]
Maar dan heb ik er wel weer een tabel met bijbehorende joins bij, die ik wellicht voorkwam als ik FK kolommen zou gebruiken?
Er is niks mis met een extra tabel en een extra JOIN als je daarmee je databasemodel semantisch gezien correct houdt en overzichtelijker.
[...]

Bijkomend probleem is dat een Company of Contact meerdere Notes kan hebben dus vandaar dat ik een FK in Note wilde hebben en niet een FK in Company of Contact.

Blijven er dus twee opties over:

1. Note entity met FK columns naar Company of Contact (en/of andere in de toekomst)
2. Note entity met tussentabel (referentietabellen) per entity die gekoppeld moet worden
Dat klopt. .
Zoals Wmm al zegt: Optie 2 (mijn voorstel) is absoluut the way to go hier.

Optie 1 werkt wel, maar klopt wederom niet vanuit een semantisch oogpunt.

Je db-kolommen representeren eigenschappen van een row (record). Als een kolom NULL is bij een bepaalde row, zeg je eigenlijk "deze record heeft deze eigenschap niet". Wat natuurlijk vreemd is, want dan klopt er iets niet in je design.
Ik weet dat het goed mogelijk is om NULLable columns, en zelfs NULLable foreign keys te hebben in de meeste SQL-varianten. Maar het blijft een beetje een vreemde gedachtengang.

Ook is het vreemd om een foreign key restraint te gooien op een kolom, om er vervolgens wel NULL values toe te staan. De restraint wordt dan niet echt geenforced.

Deze quote vat mooi samen wat ik bedoel:
In fact, nullable columns violate one of the most fundamental database design rules : don't combine distinct information elements in one column. Nulls do exactly that because they combine the boolean value "this field is/is not really present" with the actual value.
Je ziet overigens op die pagina erg uiteenlopende meningen; dus niet iedereen is het met bovenstaande (en dus met mij) eens.

Er zijn natuurlijk altijd meerdere manieren om je doel te bereiken. Maar aangezien je om de "beste aanpak" vroeg: het gebruiken van een referentietabel is hiervoor het meest common practice; daar ben ik van overtuigd.
Laat je niet afschrikken door het gebruik van een extra tabel.

[ Voor 92% gewijzigd door HyperioN op 28-01-2016 20:44 ]

Alle reacties


Acties:
  • 0 Henk 'm!

  • HyperioN
  • Registratie: April 2003
  • Laatst online: 07-10 22:02
Ik heb niet veel ervaring met Symfony. Maar als ik puur naar je beoogde design model kijk, zegt mij dat je tweede mogelijkheid in elk geval niet klopt.

Even simpel gezegd: inheritance gebruik je om eigenschappen en methodes van een parent-class (superclass) over te erven, om op die manier een gemeenschappelijke code te kunnen hergebruiken (en niet opnieuw te definieren).
Bijvoorbeeld:
code:
1
2
3
4
5
6
7
class Animal
class Mamal extends Animal
class Reptile extends Animal

class Vehicle
class Car extends Vehicle
class Bicycle extends Vehicle


Zie je de overeenkomsten? Je hebt dus een superclass, waarop andere classes uitbreiden (extenden).
Het is dus wel belangrijk dat deze "subklasses" en overeenkomde gemeenschap hebben. Dat ze als het ware "kinderen" zijn van dezelfde ouder.

En dat gaat volgens mij mis in jouw pattern.
Want Company en Contact hebben in principe niks te maken met een Notitie.
Wat hebben Company en Contact over te erven van een Notitie? Niks. Het feit dat je Notitie ineens hernoemd naar "NotableEntity" zegt eigenlijk al genoeg.
(Overigens gaat hier taalkundig ook wat mis. "Notable" heeft namelijk niks te maken met notities, maar betekent zoiets als "vooraanstaand" zoals in het Nederlandse woord "notabelen". Een betere keuze is het woord "Note".)

Een Notitie is dus een losstaande entiteit. Een Company kan Notities hebben en een Contact kan Notities hebben.

Ik zou het dus zo opzetten:
code:
1
2
3
4
5
6
class Company
class Contact

class Note
class ContactNote extends Note
class CompanyNote extends Note


Of, misschien nog beter:
code:
1
2
3
interface Note
class ContactNote implements Note
class CompanyNote implements Note

Hier maak je gebruik van implements ipv. extends. (Zoek maar op Google de verschillen daartussen).
Hoe je dit precies in Symfony inpast weet ik niet, ik wilde je alleen even de weg wijzen qua structuur in je OO-model.

Qua databasestructuur zou ik kiezen voor één tabel voor de Notities, en dan gebruik maken van referefentietabellen om ze te koppelen aan je Contact en Company:

code:
1
2
3
Table Note (note_id PK, content, ...)
Table CompanyNoteRef (company_id FK, note_id FK)
Table ContactNoteRef (contact_id FK, note_id FK).


Op die manier kun je namelijk ook nog gemakkelijk uitbreiden, als je een derde entiteit hebt waar Notes aan toegevoegd kunnen worden:
Table FooNoteRef (foo_id FK, note_id FK).

Dus dat lijkt me een mooiere oplossing dan het gebruik van meerdere FK-columns waarbij je er dan maar eentje vult.

Acties:
  • 0 Henk 'm!

  • Wmm
  • Registratie: Maart 2002
  • Laatst online: 09-10 16:24

Wmm

Eens met HyperioN. Een Company is geen Note, dus Company laten erven van Note is sowieso een no go.

Je zou dit prima op kunnen lossen met slechts drie classes: Company, Contact en Note. In je database mappings voor je classes laat je Company en Note de foreign keys krijgen naar Note, deze foreign key is dus nullable als Note optioneel is. Wat HyperioN zegt met referentietabellen kan ook, en dat is mogelijk ook hoe Doctrine het onder water gaat doet (tis even geleden dat ik hier helemaal in zat).

Acties:
  • 0 Henk 'm!

  • maarud
  • Registratie: Mei 2005
  • Laatst online: 14:52
HyperioN schreef op donderdag 28 januari 2016 @ 12:13:

En dat gaat volgens mij mis in jouw pattern.
Want Company en Contact hebben in principe niks te maken met een Notitie.
Wat hebben Company en Contact over te erven van een Notitie? Niks. Het feit dat je Notitie ineens hernoemd naar "NotableEntity" zegt eigenlijk al genoeg.
(Overigens gaat hier taalkundig ook wat mis. "Notable" heeft namelijk niks te maken met notities, maar betekent zoiets als "vooraanstaand" zoals in het Nederlandse woord "notabelen". Een betere keuze is het woord "Note".)
Ah, ik weet hoe het beërven werkt maar ik had even een denkfout in de richting waarop. Ik had het idee afgekeken van deze website maar zoals je zegt heeft een Notitie niets te maken met een bedrijf. Een beter voorbeeld is Address met daaronder VisitAddress en PostAddress of zoiets.
Een Notitie is dus een losstaande entiteit. Een Company kan Notities hebben en een Contact kan Notities hebben.

Ik zou het dus zo opzetten:
code:
1
2
3
4
5
6
class Company
class Contact

class Note
class ContactNote extends Note
class CompanyNote extends Note


Of, misschien nog beter:
code:
1
2
3
interface Note
class ContactNote implements Note
class CompanyNote implements Note

Hier maak je gebruik van implements ipv. extends. (Zoek maar op Google de verschillen daartussen).
Hoe je dit precies in Symfony inpast weet ik niet, ik wilde je alleen even de weg wijzen qua structuur in je OO-model.

Qua databasestructuur zou ik kiezen voor één tabel voor de Notities, en dan gebruik maken van referefentietabellen om ze te koppelen aan je Contact en Company:

code:
1
2
3
Table Note (note_id PK, content, ...)
Table CompanyNoteRef (company_id FK, note_id FK)
Table ContactNoteRef (contact_id FK, note_id FK).


Op die manier kun je namelijk ook nog gemakkelijk uitbreiden, als je een derde entiteit hebt waar Notes aan toegevoegd kunnen worden:
Table FooNoteRef (foo_id FK, note_id FK).

Dus dat lijkt me een mooiere oplossing dan het gebruik van meerdere FK-columns waarbij je er dan maar eentje vult.
Maar dan heb ik er wel weer een tabel met bijbehorende joins bij, die ik wellicht voorkwam als ik FK kolommen zou gebruiken?
Wmm schreef op donderdag 28 januari 2016 @ 13:01:
Eens met HyperioN. Een Company is geen Note, dus Company laten erven van Note is sowieso een no go.

Je zou dit prima op kunnen lossen met slechts drie classes: Company, Contact en Note. In je database mappings voor je classes laat je Company en Note de foreign keys krijgen naar Note, deze foreign key is dus nullable als Note optioneel is. Wat HyperioN zegt met referentietabellen kan ook, en dat is mogelijk ook hoe Doctrine het onder water gaat doet (tis even geleden dat ik hier helemaal in zat).
Bijkomend probleem is dat een Company of Contact meerdere Notes kan hebben dus vandaar dat ik een FK in Note wilde hebben en niet een FK in Company of Contact.

Blijven er dus twee opties over:

1. Note entity met FK columns naar Company of Contact (en/of andere in de toekomst)
2. Note entity met tussentabel (referentietabellen) per entity die gekoppeld moet worden

Acties:
  • 0 Henk 'm!

  • Wmm
  • Registratie: Maart 2002
  • Laatst online: 09-10 16:24

Wmm

Dan wordt het inderdaad een referentietabel. Optie 1 zou ik niet doen, je koppelt een Note dan heel erg aan een aantal andere entities en je breidt dat steeds uit. Dan eindig je met een Note met 10 referenties waarvan er altijd maar 1 tegelijk gebruikt wordt.

Acties:
  • Beste antwoord
  • 0 Henk 'm!

  • HyperioN
  • Registratie: April 2003
  • Laatst online: 07-10 22:02
maarud schreef op donderdag 28 januari 2016 @ 14:04:
[...]
Maar dan heb ik er wel weer een tabel met bijbehorende joins bij, die ik wellicht voorkwam als ik FK kolommen zou gebruiken?
Er is niks mis met een extra tabel en een extra JOIN als je daarmee je databasemodel semantisch gezien correct houdt en overzichtelijker.
[...]

Bijkomend probleem is dat een Company of Contact meerdere Notes kan hebben dus vandaar dat ik een FK in Note wilde hebben en niet een FK in Company of Contact.

Blijven er dus twee opties over:

1. Note entity met FK columns naar Company of Contact (en/of andere in de toekomst)
2. Note entity met tussentabel (referentietabellen) per entity die gekoppeld moet worden
Dat klopt. .
Zoals Wmm al zegt: Optie 2 (mijn voorstel) is absoluut the way to go hier.

Optie 1 werkt wel, maar klopt wederom niet vanuit een semantisch oogpunt.

Je db-kolommen representeren eigenschappen van een row (record). Als een kolom NULL is bij een bepaalde row, zeg je eigenlijk "deze record heeft deze eigenschap niet". Wat natuurlijk vreemd is, want dan klopt er iets niet in je design.
Ik weet dat het goed mogelijk is om NULLable columns, en zelfs NULLable foreign keys te hebben in de meeste SQL-varianten. Maar het blijft een beetje een vreemde gedachtengang.

Ook is het vreemd om een foreign key restraint te gooien op een kolom, om er vervolgens wel NULL values toe te staan. De restraint wordt dan niet echt geenforced.

Deze quote vat mooi samen wat ik bedoel:
In fact, nullable columns violate one of the most fundamental database design rules : don't combine distinct information elements in one column. Nulls do exactly that because they combine the boolean value "this field is/is not really present" with the actual value.
Je ziet overigens op die pagina erg uiteenlopende meningen; dus niet iedereen is het met bovenstaande (en dus met mij) eens.

Er zijn natuurlijk altijd meerdere manieren om je doel te bereiken. Maar aangezien je om de "beste aanpak" vroeg: het gebruiken van een referentietabel is hiervoor het meest common practice; daar ben ik van overtuigd.
Laat je niet afschrikken door het gebruik van een extra tabel.

[ Voor 92% gewijzigd door HyperioN op 28-01-2016 20:44 ]


Acties:
  • +1 Henk 'm!

  • maarud
  • Registratie: Mei 2005
  • Laatst online: 14:52
Ja, dat laatste kan ik me wel in vinden. Geen klommen aanmaken die je de helft van de tijd niet gebruikt. Ik maak een referentietabel aan :)
Pagina: 1