Ga naar hoofdinhoud

Regels Aggregates

Een aggregate is een groep objecten die allen als geheel aangepast mag worden. Hiermee garandeer je dat de data in deze groep altijd aan bepaalde regels voldoet. Deze regels drukken we uit in één of meerdere invarianten. De invarianten volgen uit kennis over het domein.

hoofdselectiecriterium invariant

Maak een aggregate van objecten die kunnen veranderen, maar samen wel altijd één of meerdere invarianten moeten bewaken.

Aggregate Root

Een aggregate heeft altijd één object waarmee objecten buiten de aggregate data binnen de aggregate kunnen aanpassen. Dit object noemen we de aggregate root.

Als een aggregate uit één klasse bestaat, is deze klasse automatisch de aggregate root.

Associaties in klassendiagram

Objecten buiten een aggregate mogen alleen een verwijzing hebben naar de aggregate root en niet naar andere objecten binnen de aggregate.

Alle objecten binnen een aggregate mogen wel een verwijzing hebben naar aggregate roots van andere aggregates

Ophalen uit en opslaan in de Store

Een app-object haalt altijd in één keer de hele aggregate (dus aggregate root en alle andere objecten waar deze gebruikt van maakt) op uit een Store en slaat deze ook in één keer op.

Een app-object kan alleen aggregate roots ophalen en persisteren (=opslaan). Andere objecten binnen een aggregate kunnen niet los opgehaald of opgeslagen worden.

Een aggregate root moet altijd een id hebben dat gebruik kan worden om de betreffende aggregate op te halen uit een Store. Deze id moet uniek zijn binnen de hele aggregate.

Andere objecten binnen een aggregate hoeven geen id te hebben, maar dat mag wel. Dit id hoeft dan alleen uniek te zijn binnen de aggregate.

Over het algemeen geldt dat hoe meer objecten er in een aggregate zitten, hoe trager een applicatie kan worden omdat er meer objecten uit de Store in het geheugen van de applicatie geladen moeten worden.

Het kiezen van geschikte aggregate heeft ook veel invloed op een applicatie waar meerdere gebruikers tegelijkertijd gebruik van maken (zoals bijna elke web applicatie). Komende week gaan we hier dieper op in.

Geen nesting

Er zijn geen geneste aggregates. Dat betekent dat er geen aggregates binnen aggregates gemaakt kunnen worden.

Voorbeeld

Een voorbeeld voor een casus van een Open dag op het HBO waar de bezoekers (aspirant HBO-studenten) zich kunnen inschrijven* voor een aantal workshops, gegeven door HBO docenten. Als er voldoende aanmeldingen zijn start wat later de workshop, maar als aanmeldingen uitblijven wordt de workshop geannuleerd. De entiteit 'Docent' is bewust buiten de applicatie en opdracht gehouden, omdat dit via een 'papieren' systeem gaat (in ieder geval voorlopig ;) ).

We bespreken 3 mogelijke 'aggregate groeperingen' met in het hoofd dat er drie mogelijke aggregates: Bezoeker, Workshop en Aanmelding.

*Merk op dat de uitwerkingen hieronder 'aanmelden' gebruikt i.p.v. 'inschrijven'. Dit is een potentiele bron van verwarring en schending van de 'ubiquitous language'. In sommige domeinen is 'inschrijven' wat je online doet, en 'aanmelden' wat je op de dag zelf doet (o.b.v. een inschrijfbevestiging in je mail bijvoorbeeld). In deze casus zijn het twee synoniemen en hoeven studenten zich terplekke niet meer te identificeren, dus maakt het nu niet uit, maar dit ter illustratie van belang van precieze woordgebruik en hoe snel dit misgaat.

Optie A: Aanmelding bij Bezoeker

0bb5f0514b916e7c8b31c6b745590bcb

PlantUML broncode voor "Voorbeeld Criteria"
@startuml
hide circle

skinparam linetype ortho

rectangle "Bezoeker Aggregate" {
class Bezoeker <<Aggregate Root>> {
naam
leeftijd
meldAan(workshopId)
}

class Aanmelding <<Value Object>> {
printKaartje()
}

}

rectangle "Workshop\n Aggregate" {
class Workshop <<Aggregate Root>>{
id
titel
duur
minAantalInschrijvingen
maxAantalInschrijvingen
annuleer()
start()
}
}

Bezoeker "1" -- "*" Aanmelding: meldt aan voor workshop via
Aanmelding "*" -r-> "1" Workshop

note as N1

Voor een bezoeker weet je - na opvragen uit store
- altijd hoeveel aanmeldingen en voor welke workshops.

Maar voor Workshop weet je niet aantal Bezoekers
dat komt. En een Bezoeker kan zich nog aanmelden
voor een Workshop die al gestart is.

end note

@enduml

Klassendiagram met 3 klasse(n) en 2 relatie(s).

Klassen:

  • Klasse Bezoeker met stereotype Aggregate Root met:
    • publieke methode 'meldAan', met parameter(s) 'workshopId', return type void
    • publieke attribuut 'naam'
    • publieke attribuut 'leeftijd'
  • Klasse Aanmelding met stereotype Value Object met:
    • publieke methode 'printKaartje', zonder parameters, return type void
    • geen attributen
  • Klasse Workshop met stereotype Aggregate Root met:
    • publieke methode 'annuleer', zonder parameters, return type void
    • publieke methode 'start', zonder parameters, return type void
    • publieke attribuut 'id'
    • publieke attribuut 'titel'
    • publieke attribuut 'duur'
    • publieke attribuut 'minAantalInschrijvingen'
    • publieke attribuut 'maxAantalInschrijvingen'

Relaties:

  • Bezoeker heeft een associatie-relatie met naam 'meldt aan voor workshop via' met Aanmelding, multipliciteit 1 naar *
  • Aanmelding heeft een associatie-relatie met Workshop, multipliciteit * naar 1

Optie B: Aanmelding bij Workshop

Een andere optie is deze groepering, met eigen voor- en nadelen afhankelijk van de wensen voor de beoogde applicatie (de invarianten die bewaakt moeten wo).

bce7822834f673dcbb8bc1ab5a797197

PlantUML broncode voor "Voorbeeld Criteria"
@startuml
hide circle

skinparam linetype ortho

rectangle "Workshop Aggregate" {
class Workshop <<Aggregate Root>> {
titel
duur
minAantalInschrijvingen
maxAantalInschrijvingen
annuleer()
start()
stop()
}

class Aanmelding <<Value Object>> {
printKaartje()
}
}

rectangle "Bezoeker \nAggregate" {
class Bezoeker <<Entity>>{
naam
leeftijd
meldAan(workshopId)
}
}

Bezoeker "1" <-r- "1" Aanmelding
Aanmelding "*" -r- "1" Workshop

note as N1

Voor een workshop weet je aantal aanmeldingen.
Maar bij een Bezoeker kun je niet zien voor welke workshops deze is aangemeld.
Aanmelden nadat show al is afgelopen is helemaal niet wenselijk,
maar dat kun je in deze opzet niet controleren.

end note

@enduml

Klassendiagram met 3 klasse(n) en 2 relatie(s).

Klassen:

  • Klasse Workshop met stereotype Aggregate Root met:
    • publieke methode 'annuleer', zonder parameters, return type void
    • publieke methode 'start', zonder parameters, return type void
    • publieke methode 'stop', zonder parameters, return type void
    • publieke attribuut 'titel'
    • publieke attribuut 'duur'
    • publieke attribuut 'minAantalInschrijvingen'
    • publieke attribuut 'maxAantalInschrijvingen'
  • Klasse Aanmelding met stereotype Value Object met:
    • publieke methode 'printKaartje', zonder parameters, return type void
    • geen attributen
  • Klasse Bezoeker met stereotype Entity met:
    • publieke methode 'meldAan', met parameter(s) 'workshopId', return type void
    • publieke attribuut 'naam'
    • publieke attribuut 'leeftijd'

Relaties:

  • Bezoeker heeft een associatie-relatie met Aanmelding, multipliciteit 1 naar 1
  • Aanmelding heeft een associatie-relatie met Workshop, multipliciteit * naar 1

Optie C: Apart Object AanmeldingsManager

Als je meerdere invarianten wilt bewaken voor verschillende entiteiten kun je soms een centraal nieuw object instellen.

Dit heeft echter ook nadelen qua performance en complexiteit.

fe19b5c667c15b0038bd1a531c5b92a1

PlantUML broncode voor "Voorbeeld Criteria"
@startuml
hide circle

skinparam linetype ortho

rectangle "Aanmelding \n Aggregate" {

class AanmeldingsManager <<Aggregate Root>> {
aanmelden(workshopId, bezoekerId)
afmelden(workshopId, bezoekerId)
printKaartje(workshopId, bezoekerId)
}
class Aanmelding <<Value Object>> {
printKaartje()
}
}

note top of AanmeldingsManager

AanmeldingsManager kun
je ook OpenDag noemen

end note

rectangle "Workshop\n Aggregate" {
class Workshop <<AR>>{
titel
duur
start()
stop()
}
}

rectangle "Bezoeker \nAggregate" {
class Bezoeker <<Entity>>{
naam
leeftijd
}
}

AanmeldingsManager "1 " -- "\t*" Aanmelding
Aanmelding "1" -l-> "1" Bezoeker
Aanmelding "*" <-l-> "1 " Workshop

note as N1

Best of Both worlds? Je kunt checks doen bij aanmelden
en afmelden, maar je kunt ook zien voor welke workshops.

Maar je mag via Bezoeker of Workshop niet bij Aanmelding,
alles moet via AanmeldingsManager.

Vind de overtreding van de regels...

end note

@enduml

Klassendiagram met 6 klasse(n) en 4 relatie(s).

Klassen:

  • Klasse AanmeldingsManager met stereotype Aggregate Root met:
    • publieke methode 'aanmelden', met parameter(s) 'workshopId', 'bezoekerId', return type void
    • publieke methode 'afmelden', met parameter(s) 'workshopId', 'bezoekerId', return type void
    • publieke methode 'printKaartje', met parameter(s) 'workshopId', 'bezoekerId', return type void
    • geen attributen
  • Klasse Aanmelding met stereotype Value Object met:
    • publieke methode 'printKaartje', zonder parameters, return type void
    • geen attributen
  • Klasse Workshop met stereotype AR met:
    • publieke methode 'start', zonder parameters, return type void
    • publieke methode 'stop', zonder parameters, return type void
    • publieke attribuut 'titel'
    • publieke attribuut 'duur'
  • Klasse Bezoeker met stereotype Entity met:
    • publieke attribuut 'naam'
    • publieke attribuut 'leeftijd'
    • geen methoden
  • Klasse Vind de overtreding van de regels zonder methoden en attributen
  • Klasse . zonder methoden en attributen

Relaties:

  • AanmeldingsManager heeft een associatie-relatie met naam '\t*' met Aanmelding
  • Aanmelding heeft een associatie-relatie met Bezoeker, multipliciteit 1 naar 1
  • Aanmelding heeft een associatie-relatie met Workshop, multipliciteit * naar 1
  • Vind de overtreding van de regels heeft een afhankelijkheid naar .

Maak de quiz om te kijken of je de regels van een Aggregate begrijpt en kan herkennen in bovenstaand voorbeeld.