In dit stuk laten we zien hoe je stap voor stap sequentiediagrammen kunt maken op basis van een domain story, klassendiagram van het domein en pre- en postcondities.
Als voorbeeld gebruiken we een variant op de Metropolis-casus uit de reader over Domain Storytelling. De kaartverkoop gaat in deze variant via een app in plaats van een persoon.
Scenario: een bezoeker koopt een kaartje voor een film.
Hieronder zie je een fine grained domain story van het happy path van de kaartverkoop.
Van bovenstaande domain story hebben we een eerste versie van een klassendiagram van het domein gemaakt.
PlantUML broncode voor "klassendiagram domein"
@startuml hide circle class Vertoning <<Entity>> { selecteer() } note left of Vertoning::selecteer Waarschijnlijk wordt dit een methode uit de Cinema App end note class Stoel <<Entity>> { isBeschikbaar reserveer() } class Kaartje <<Value Object>> { titel datum starttijd filmzaal stoelNummer genereer() } note right of Kaartje Datatypes kunnen we later bepalen. Waarschijnlijk kunnen dit Value Objects worden end note note bottom of Kaartje Constructor? end note Vertoning "1" --> "stoelen *" Stoel @enduml
Klassendiagram met 3 klasse(n) en 1 relatie(s).
Klassen:
Klasse Vertoning met stereotype Entity
met:
publieke methode 'selecteer', zonder parameters, return type void
geen attributen
Klasse Stoel met stereotype Entity
met:
publieke methode 'reserveer', zonder parameters, return type void
publieke attribuut 'isBeschikbaar'
Klasse Kaartje met stereotype Value Object
met:
publieke methode 'genereer', zonder parameters, return type void
publieke attribuut 'titel'
publieke attribuut 'datum'
publieke attribuut 'starttijd'
publieke attribuut 'filmzaal'
publieke attribuut 'stoelNummer'
Relaties:
Vertoning heeft een associatie-relatie met naam 'stoelen *' met Stoel
Je kunt één sequentiediagram maken van de domain story, maar dan wordt het sequentiediagram vrij groot. Daarom maken we voor elke actie die de bezoeker initieert een apart sequentiediagram. In dit geval maken we een sequentiediagram voor:
Beschikbare stoelen vinden
Stoel reserveren
Voordat we een sequentiediagram maken, inventariseren we eerst welke van deze pre- en postcondities relevant zijn voor de actie die je wilt uitbeelden.
Opmerking
In een groter project maak je vaak eerst user stories voordat je sequentiediagrammen maakt. In dit geval zouden "Beschikbare stoelen vinden" en "Stoel reserveren" twee user stories kunnen worden. Het vinden van user stories op basis van domain stories behandelen we in week 4.
Voor dit sequentiediagram geldt alleen de preconditie dat er beschikbare stoelen zijn. Er geldt geen postconditie, want dit diagram beschrijft alleen het ophalen van informatie zonder de applicatie te wijzigen.
In de eerste stap neem je de actoren uit de domain story op en bepaal je welke informatie ze uitwisselen. We hebben twee actoren:
Bezoeker
CinemaApp
PlantUML broncode voor "actoren"
@startuml title Bezoeker communiceert \n\ met CinemaApp autonumber actor Bezoeker participant "app:\nCinemaApp" as app @enduml
Sequentiediagram met 2 deelnemers: Bezoeker en app van het type CinemaApp.
Nu bedenken we welke informatie de Bezoeker aan de Cinema App moet geven. We bedenken (op basis van onze ervaring met het maken van web applicaties) dat het handig is als de gebruiker een id van de bedoelde vertoning kan doorgeven aan de app en we bedenken de methode-naam selecteerVertoning die de intentie van de Bezoeker goed weergeeft
PlantUML broncode voor "eerste bericht"
@startuml title Bezoeker communiceert \n\ met CinemaApp autonumber actor Bezoeker participant "app:\nCinemaApp" as app Bezoeker -> app : selecteerVertoning(id) @enduml
Sequentiediagram met 2 deelnemers: Bezoeker en app van het type CinemaApp.
Interacties:
Bezoeker roept app.selecteerVertoning(id) aan
Opmerking over UI
Er is een grote kans dat de Bezoeker in een browser, of app op een knop drukt die gekoppeld is aan een url met een id van de geselecteerde vertoning. Probeer (net als in een domain story) geen interactie met de user interface op te nemen omdat dit te gedetailleerd is.
Opmerking over url
De Bezoeker roept de methode selecteerVertoning waarschijnlijk niet direct aan. In plaats van deze methode-aanroep kun je ook een URL of een REST-route opnemen, zoals hieronder te zien is.
PlantUML broncode voor "geen rest"
@startuml title Bezoeker communiceert \n\ met CinemaApp autonumber actor Bezoeker participant "app:\nCinemaApp" as app Bezoeker -> app : GET vertoningen/id @enduml
Sequentiediagram met 2 deelnemers: Bezoeker en app van het type CinemaApp.
Interacties:
Bezoeker roept app.GET vertoningen/id() aan
Dit is echter ook een vrij gedetailleerde beslissing die we nu nog niet willen nemen. In week 3 doen we dit wel omdat we dan bestaande code willen analyseren met een sequentiediagram.
Op basis van deze eerste pijl uit het sequentiediagram kunnen we het klassendiagram van het domein als volgt aanpassen:
PlantUML broncode voor "klassendiagram domein versie 2"
@startuml hide circle class Vertoning <<entity>> { id <strike>selecteer()</strike> } note right of Vertoning::id Toegevoegd end note note left of Vertoning::selecteer Is inderdaad niet nodig end note class Stoel <<entity>> { isBeschikbaar reserveer() } note left of Stoel::isBeschikbaar Toevoeging vanwege post-conditie end note class Kaartje <<Value Object>> { titel datum starttijd filmzaal stoelNummer genereer() } Vertoning "1" --> "stoelen *" Stoel @enduml
Klassendiagram met 3 klasse(n) en 1 relatie(s).
Klassen:
Klasse Vertoning met stereotype entity
met:
publieke methode 'selecteer', zonder parameters, return type
publieke attribuut 'id'
Klasse Stoel met stereotype entity
met:
publieke methode 'reserveer', zonder parameters, return type void
publieke attribuut 'isBeschikbaar'
Klasse Kaartje met stereotype Value Object
met:
publieke methode 'genereer', zonder parameters, return type void
publieke attribuut 'titel'
publieke attribuut 'datum'
publieke attribuut 'starttijd'
publieke attribuut 'filmzaal'
publieke attribuut 'stoelNummer'
Relaties:
Vertoning heeft een associatie-relatie met naam 'stoelen *' met Stoel
De Cinema App moet een vertoning-object vinden zodat deze teruggegeven kan worden aan de Bezoeker. Het is waarschijnlijk dat deze vertoning uit een database gehaald wordt op basis van het id. We willen ons op dit moment echter nog niet verbinden aan een specifieke database en daarom gebruiken een meer algemene Store waarbij we er vanuit gaan dat er een methode bestaat findVertoning die een id meekrijgt. Deze methode retourneert een Vertoning-object die we opvangen in de variabele vertoning.
Het sequentiediagram ziet er dan als volgt uit:
PlantUML broncode voor "app zoekt vertoning"
@startuml Title CinemaApp moet een \n\ vertoning kunnen vinden autonumber actor Bezoeker participant "app:\nCinemaApp" as app database Store Bezoeker -> app : selecteerVertoning(id) app -> Store : vertoning = findVertoning(id) @enduml
Sequentiediagram met 3 deelnemers: Bezoeker, app van het type CinemaApp en Store.
Interacties:
Bezoeker roept app.selecteerVertoning(id) aan
app roept Store.vertoning = findVertoning(id) aan
Opmerking Database
Waarschijnlijk wordt de Store uiteindelijk een SQL-database. In dat geval kun je ook zoiets als het onderstaande tekenen:
PlantUML broncode voor "sql"
@startuml Title CinemaApp moet een \n\ vertoning kunnen vinden autonumber actor Bezoeker participant "app:\nCinemaApp" as app database Database Bezoeker -> app : selecteerVertoning( \n\ id) app -> Database: vertoning = \n\ SELECT * FROM vertoningen \n\ WHERE vertoningId = id @enduml
Sequentiediagram met 3 deelnemers: Bezoeker, app van het type CinemaApp en Database.
Omdat een vertoning een lijst van stoelen bijhoud, zoals te zien in het klassendiagram, verzinnen we dat het handig is om een methode te hebben die alle beschikbare stoelen teruggeeft.
PlantUML broncode voor "beschikbare stoelen vinden"
@startuml title CinemaApp vindt beschikbare stoelen autonumber actor Bezoeker participant "app:\nCinemaApp" as app participant "vertoning:\nVertoning" as vertoning database Store Bezoeker -> app : selecteerVertoning( \n\ id) app -> Store : vertoning = findVertoning(\n\ id) app -> vertoning : beschikbareStoelen = \n\ getBeschikbareStoelen() Bezoeker <-- app: beschikbareStoelen @enduml
Sequentiediagram met 4 deelnemers: Bezoeker, app van het type CinemaApp, vertoning van het type Vertoning en Store.
Interacties:
Bezoeker roept app.selecteerVertoning( \n\() aan
app roept Store.vertoning = findVertoning(\n\() aan
app roept vertoning.beschikbareStoelen = \n\() aan
Je ziet in bovenstaand diagram twee manieren om een returnwaarde aan te geven. In pijl 2 en 3 zie je de korte manier. Je kunt deze manier gebruiken als de returnwaarde gelijk beschikbaar is en je geen extra acties hoeft te ondernemen om de returnwaarde te krijgen.
Pijl 4 kan gezien worden als de return-waarde van pijl 1. Omdat tussen pijl 1 en 4 twee tussenstappen zitten, gebruiken we de langere manier.
Om te zien of een stoel beschikbaar is, voegen we een attribuut isBeschikbaar toe aan de klasse Stoel.
PlantUML broncode voor "klassendiagram domein versie 3"
@startuml hide circle class Vertoning <<Entity>> { id getBeschikbareStoelen() } note left of Vertoning::getBeschikbareStoelen() Toegevoegd end note class Stoel <<Entity>> { isBeschikbaar reserveer() } note left of Stoel::isBeschikbaar Toegevoegd end note class Kaartje <<Value Object>> { titel datum starttijd filmzaal stoelNummer genereer() } Vertoning "1" --> "stoelen *" Stoel @enduml
Klassendiagram met 3 klasse(n) en 1 relatie(s).
Klassen:
Klasse Vertoning met stereotype Entity
met:
publieke methode 'getBeschikbareStoelen', zonder parameters, return type void
publieke attribuut 'id'
Klasse Stoel met stereotype Entity
met:
publieke methode 'reserveer', zonder parameters, return type void
publieke attribuut 'isBeschikbaar'
Klasse Kaartje met stereotype Value Object
met:
publieke methode 'genereer', zonder parameters, return type void
publieke attribuut 'titel'
publieke attribuut 'datum'
publieke attribuut 'starttijd'
publieke attribuut 'filmzaal'
publieke attribuut 'stoelNummer'
Relaties:
Vertoning heeft een associatie-relatie met naam 'stoelen *' met Stoel
Notities:
Bij klasse Vertoning: ":getBeschikbareStoelen()"
Bij klasse Stoel: ":isBeschikbaar"
Opmerking
In plaats van het attribuut isBeschikbaar toe te voegen, hadden we de lijst stoelen kunnen splitsen in een lijst van beschikbare stoelen en een lijst van gereserveerde stoelen. Het diagram zou er dan zo uitzien:
PlantUML broncode voor "klassendiagram domein versie 3 alternatief"
@startuml hide circle class Vertoning <<entity>> { id getBeschikbareStoelen() } class Stoel <<entity>> { reserveer() } class Kaartje <<Value Object>> { titel datum starttijd filmzaal stoelNummer genereer() } Vertoning "1" --> "beschikbareStoelen \n*" Stoel Vertoning "1" --> "gereserveerdeStoelen \n*" Stoel @enduml
Klassendiagram met 3 klasse(n) en 2 relatie(s).
Klassen:
Klasse Vertoning met stereotype entity
met:
publieke methode 'getBeschikbareStoelen', zonder parameters, return type void
publieke attribuut 'id'
Klasse Stoel met stereotype entity
met:
publieke methode 'reserveer', zonder parameters, return type void
geen attributen
Klasse Kaartje met stereotype Value Object
met:
publieke methode 'genereer', zonder parameters, return type void
publieke attribuut 'titel'
publieke attribuut 'datum'
publieke attribuut 'starttijd'
publieke attribuut 'filmzaal'
publieke attribuut 'stoelNummer'
Relaties:
Vertoning heeft een associatie-relatie met naam 'beschikbareStoelen' met Stoel, multipliciteit 1 naar *
Vertoning heeft een associatie-relatie met naam 'gereserveerdeStoelen' met Stoel, multipliciteit 1 naar *
Er is op dit moment niet echt een goede reden om voor het een of het ander te kiezen, dus we kiezen nu voor de eerste optie omdat dit simpeler is.
In de eerste stap bedenk je welke informatie de Bezoeker aan de App geeft en welke methode-naam je daarvoor wilt gebruiken. De Cinema App moet weten welke stoel van welke vertoning de bezoeker wil reserveren. Het sequentiediagram ziet er zo uit:
PlantUML broncode voor "stoel reserveren"
@startuml title Stoel reserveren autonumber actor Bezoeker participant "app:\nCinemaApp" as app participant "vertoning:\nVertoning" as vertoning participant "stoel:\nStoel" as stoel database Store Bezoeker -> app : reserveerStoel(\n\ stoelNummer, id) app -> Store : vertoning = findVertoning(id) @enduml
Sequentiediagram met 5 deelnemers: Bezoeker, app van het type CinemaApp, vertoning van het type Vertoning, stoel van het type Stoel en Store.
Interacties:
Bezoeker roept app.reserveerStoel(\n\() aan
app roept Store.vertoning = findVertoning(id) aan
1 Omdat stoelen onderdeel zijn van een vertoning, kiezen we er nu voor om stoelen te reserveren via de vertoning. Dat betekent dat een stoelNummer niet uniek is binnen de hele applicatie en we alleen de juiste stoel kunnen vinden door een id van de vertoning en een stoelNummer mee te geven.
2 We maken de sequentiediagrammen onafhankelijk van elkaar, dus in dit diagram staat ook dat de applicatie de juiste vertoning moet vinden.
PlantUML broncode voor "klassendiagram domein versie 4"
@startuml hide circle class Vertoning <<entity>> { id getBeschikbareStoelen() } note right of Vertoning::selecteer Is inderdaad niet nodig end note class Stoel <<entity>> { stoelNummer isBeschikbaar reserveer() } note left of Stoel::stoelNummer Toegevoegd end note class Kaartje <<Value Object>> { titel datum starttijd filmzaal stoelNummer genereer() } Vertoning "1" --> "stoelen *" Stoel @enduml
Klassendiagram met 3 klasse(n) en 1 relatie(s).
Klassen:
Klasse Vertoning met stereotype entity
met:
publieke methode 'getBeschikbareStoelen', zonder parameters, return type void
publieke attribuut 'id'
Klasse Stoel met stereotype entity
met:
publieke methode 'reserveer', zonder parameters, return type void
publieke attribuut 'stoelNummer'
publieke attribuut 'isBeschikbaar'
Klasse Kaartje met stereotype Value Object
met:
publieke methode 'genereer', zonder parameters, return type void
publieke attribuut 'titel'
publieke attribuut 'datum'
publieke attribuut 'starttijd'
publieke attribuut 'filmzaal'
publieke attribuut 'stoelNummer'
Relaties:
Vertoning heeft een associatie-relatie met naam 'stoelen *' met Stoel
Eerder hebben we gezien dat we een Store kunnen gebruiken en ervan uit kunnen gaan dat deze een find methode heeft. Je kunt ook een find methode gebruiken als het ene object een verzameling, of lijst van een ander object bijhoudt. Omdat in het klassendiagram Vertoning een lijst van Stoel bijhoudt, kunnen we er in het sequentiediagram vanuit gaan dat er findStoel methode bestaat in Vertoning.
PlantUML broncode voor "systeem reserveert stoel optie 1"
@startuml title CinemaApp reserveert stoel autonumber actor Bezoeker participant "app:\nCinemaApp" as app participant "vertoning:\nVertoning" as vertoning participant "stoel:\nStoel" as stoel database Store Bezoeker -> app : reserveerStoel(\n\ stoelNummer, id) app -> Store : vertoning = findVertoning(id) app -> vertoning: stoel = findStoel(stoelNummer) app -> stoel : reserveer() stoel -> stoel: isBeschikbaar(false) @enduml
Sequentiediagram met 5 deelnemers: Bezoeker, app van het type CinemaApp, vertoning van het type Vertoning, stoel van het type Stoel en Store.
Interacties:
Bezoeker roept app.reserveerStoel(\n\() aan
app roept Store.vertoning = findVertoning(id) aan
app roept vertoning.stoel = findStoel(stoelNummer) aan
In onderstaand alternatief reserveert vertoning de juiste stoel in plaats van CinemaApp. Hierdoor hoeft de app niet eerst een Stoel-object te vinden. Tijdens de les bepalen we of één van de alternatieven de voorkeur heeft.
❓ Heb je nu al voorkeur voor het eerste of het tweede alternatief?
PlantUML broncode voor "systeem reserveert stoel optie 2"
@startuml title Vertoning reserveert stoel autonumber actor Bezoeker participant "app:\nCinemaApp" as app participant "vertoning:\nVertoning" as vertoning participant "stoel:\nStoel" as stoel database Store Bezoeker -> app : reserveerStoel(\n\ stoelNummer, id) app -> Store : vertoning = findVertoning(id) app -> vertoning: reserveerStoel(stoelNummer) vertoning -> vertoning: stoel = findStoel(stoelNummer) vertoning -> stoel : reserveer() stoel -> stoel: isBeschikbaar(false) @enduml
Sequentiediagram met 5 deelnemers: Bezoeker, app van het type CinemaApp, vertoning van het type Vertoning, stoel van het type Stoel en Store.
Interacties:
Bezoeker roept app.reserveerStoel(\n\() aan
app roept Store.vertoning = findVertoning(id) aan
app roept vertoning.reserveerStoel(stoelNummer) aan
vertoning roept vertoning.stoel = findStoel(stoelNummer) aan