-
Notifications
You must be signed in to change notification settings - Fork 3
/
DSS.qmd
412 lines (329 loc) · 22.1 KB
/
DSS.qmd
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# Diagrammes de séquence système (DSS) {#sec-dss}
Un diagramme de séquence système (DSS) est un diagramme UML (diagramme de séquence) limité à un acteur (provenant du scénario d'un cas d'utilisation) et au Système.
Le DSS décrit l'interaction entre l'acteur et le système, sans rentrer dans les détails du système (une modélisation appelée "boîte noire" parce que les détails sont obscures).
La modélisation à ce niveau permet de définir plus tard les détails à l'intérieur du système.
## L'interface de programmation (*Application Programming Interface*)
Selon l'[Office québécois de la langue française](https://vitrinelinguistique.oqlf.gouv.qc.ca/fiche-gdt/fiche/8394269/interface-de-programmation), l'interface de programmation est *l'ensemble de routines standards, accessibles et documentées, qui sont destinées à faciliter au programmeur \[et à la programmeuse\] le développement d'applications*.
Le terme en anglais est *Application Programming Interface* (API).
En d'autres mots, c'est la façon (normalisée et documentée) dont un programme d'application communique avec un ensemble d'éléments logiciels qu'il utilise pour faire une application utile.
Par exemple, une classe dans un logiciel orienté objet peut être utilisée pour créer une application.
Les méthodes publiques de la classe composent son interface API, comme le montre la @fig-apiclasse.
Un logiciel en ligne de commande peut aussi servir pour des applications.
Les commandes et les paramètres que le logiciel accepte forment son interface API.
Une commande sous Linux peut avoir une interface API, comme il est illustré dans l'exemple de la @fig-command_line_api.
Un [service REST](https://fr.wikipedia.org/wiki/Representational_state_transfer)\ {{< fa brands wikipedia-w >}} est une façon de créer une application Web.
On communique avec le service REST à l'aide de requêtes HTTP.
La requête HTTP contient l'URI vers la routine à accéder, la [méthode de requête (verbe HTTP)](https://developer.mozilla.org/fr/docs/Web/HTTP/Methods) et les paramètres de la routine, tel qu'illustré sur la @fig-rest_api.
Les interfaces humain-machine (IHM), dont les interfaces graphiques, ne sont pas une interface API puisqu'elles ne peuvent être utilisées de manière logicielle.
Elles permettent de faire le pont entre les utilisateurs et utilisatrices (qui communiquent par les gestes, par exemple un clic avec une souris) et l'interface API du logiciel afin d'en faciliter l'utilisation.
```{.plantuml genurl="true" #fig-apiclasse caption="Les méthodes publiques d'une classe sont un exemple d'interface API." }
@startuml
!include normal.pumlinclude
class Point {
+ getX() : number
+ setX(x : number)
+ getY() : number
+ setY(y : number)
+ distance(point : Point) : number
{static} + distance(point1 : Point, point2 : Point) : number
}
@enduml
```
::: {#fig-command_line_api fig-align="left"}
```{.bash}
$ pwd --help
pwd: pwd [-LPW]
Print the name of the current working directory.
Options:
-L print the value of $PWD if it names the current working directory
-P print the physical directory, without any symbolic links
-W print the Win32 value of the physical directory
```
L'interface API de la commande `pwd` sous Linux.
:::
::: {#fig-rest_api fig-align="left"}
```{.bash }
GET https://service.com/api/v1/employee
GET https://service.com/api/v1/employee/{id}
POST https://service.com/api/v1/employee
{
"name": "John Doe",
"rate": 20
}
```
Exemple d'interface API d'un service REST.
:::
\filbreak
## La séparation des couches présentation et domaine
Une des forces du DSS est qu'il fait abstraction de beaucoup de détails.
Cependant, il sera nécessaire de les comprendre, car certaines règles s'imposent dans la méthodologie et on veut préparer le terrain pour expliquer pourquoi ces règles existent.
En plus, dans la partie de la méthodologie où on va rentrer dans les détails ([les réalisations de cas d'utilisation](#sec-rdcu)), il faudra maîtriser le principe de *la séparation des couches présentation et domaine*.
Commençons par une explication de base de ce principe.
Puisqu'il y a des acteurs humains interagissant avec le système, on doit avoir une IHM dans le système.
Avec l'évolution rapide des technologies, il y a toujours eu un potentiel de concevoir des IHM de plus en plus conviviales et efficientes.
Par exemple, les premières versions de Microsoft Word tournaient sur MS-DOS (PC d'IBM) au milieu des années 1980 (la souris n'était même pas nécessaire pour l'utiliser).
Pourtant, le concept de style nommé (gabarit) pour les paragraphes existait, comme on le trouve dans la version de Word d'aujourd'hui.
Les développeurs et développeuses ont appris qu'au fil du temps, les parties du code liées à l'IHM (les menus, les boutons, l'animation, etc.) sont relativement instables par rapport aux parties liées au domaine du problème (le concept de style de paragraphe).
Aujourd'hui, des applications populaires sont déployées sur les plateformes mobiles, PC et Web (infonuagiques).
La [reconnaissance automatique de la parole](https://fr.wikipedia.org/wiki/Reconnaissance_automatique_de_la_parole)\ {{< fa brands wikipedia-w >}} et [les dispositifs haptiques](https://fr.wikipedia.org/wiki/Dispositif_haptique)\ {{< fa brands wikipedia-w >}} sont des exemples plus modernes de technologies qui permettent en principe une meilleure IHM.
Donc, pour minimiser l'impact des changements de l'IHM sur tout un système, on peut comprendre l'intérêt d'isoler la partie "domaine" (qui est relativement stable) de la partie IHM (qui est différente selon la plateforme et qui a une tendance à évoluer à cause des nouvelles technologies).
La conception de ces systèmes comprend un aspect important qui est la *séparation des couches*, notamment celle de l'IHM qu'on nomme *la couche présentation* et celle qui contient la logique d'affaires qu'on nomme *la couche domaine*.
Il y a d'autres bénéfices de cette séparation, mais elle impose certaines règles qui vont à l'encontre des habitudes des développeurs et développeuses qui aiment coder rapidement.
::: {#fig-separation_couches layout-ncol=2}
```{.plantuml genurl="true" caption="Sans séparation, les éléments de l'IHM sont fortement couplés aux éléments de la logique de l'application. Ce genre de solution peut être créé rapidement. Cependant, si le code de l'IHM doit changer pour accommoder une nouvelle technologie, ces changements peuvent affecter le reste du système." #fig-sans_separation}
@startuml
!include normal.pumlinclude
hide empty members
rectangle "Sans séparation des éléments" #ffcccc {
note as n1
Beaucoup de couplage
entre éléments de l'IHM
et du domaine.
end note
class " IHM " as P1 #ffffcc
class " IHM " as P2 #ffffcc
class " IHM " as P3 #ffffcc
class " D1 " as D1 #ccccff
class " D2 " as D2 #ccccff
class " D3 " as D3 #ccccff
class " D4 " as D4 #ccccff
class " D5 " as D5 #ccccff
class " D6 " as D6 #ccccff
P1 -d- D1
P1 -d- D2
P1 -d- D3
P1 -d- D4
P1 -d- D5
P1 -d- D6
P2 -d- D1
P2 -d- D2
P2 -d- D3
P2 -d- D4
P3 -d- D3
P3 -d- D4
P3 -d- D5
P3 -d- D6
}
@enduml
```
```{.plantuml genurl="true" caption="La définition et la séparation des couches présentation et domaine permettent de minimiser l'impact des changements dus aux évolutions dans les technologies liées à l'IHM et d'avoir une conception plus cohésive. Cependant, cette séparation requiert un respect de l'architecture par les développeurs et développeuses." #fig-avec_separation}
@startuml
!include normal.pumlinclude
hide empty members
skinparam class {
borderthickness<< controleur >> 5
}
hide stereotype
rectangle "Bonne séparation entre couches" #ccffcc {
package "Couche présentation" #white {
class " IHM " as P1 #ffffcc
class " IHM " as P2 #ffffcc
class " IHM " as P3 #ffffcc
}
package "Reste du système (couche domaine)" #white {
class " D1 " as D1 #ccccff
class " D2 " as D2 #ccccff
class " D3 " as D3 << controleur >> #ccccff
class " D4 " as D4 << controleur >> #ccccff
class " D5 " as D5 #ccccff
class " D6 " as D6 #ccccff
note as NG1
Contrôleur
GRASP
end note
note as NG2
Contrôleur
GRASP
end note
}
P1 -d-> D3 : opérations système\n(définies au **DSS**)
D3 -- D2
D3 -- D6
P2 -d-> D4
D4 -- D1
D4 -- D2
P3 -d-> D3
D3 -- D5
NG1 .l. D3
NG2 .r. D4
note as NF
Pattern Façade (GoF)
réduit le couplage
et favorise la séparation
des couches.
end note
NF .. D3
NF .. D4
}
@enduml
```
Raison d'être de la séparation des couches présentation et domaine.
:::
Le DSS vise à modéliser l'interface API du système développé, et ce, peu importe la façon dont les utilisateurs et les utilisatrices vont interagir avec celui-ci. Lorsqu'on crée un DSS dans la méthodologie de ce manuel, on propose une conception de haut niveau qui fait un lien direct entre les besoins (les étapes d'un cas d'utilisation) et une solution qui répond à ces besoins, sans aller trop en détail (on fera cela plus tard, justement dans les [RDCU](#sec-rdcu)), tout en respectant la séparation des couches présentation et domaine.
Voici d'autres avantages de la séparation des couches présentation et domaine:
- Une forte cohésion des éléments de chaque couche, grâce à la [séparation des préoccupations](https://fr.wikipedia.org/wiki/S%C3%A9paration_des_pr%C3%A9occupations)\ {{< fa brands wikipedia-w >}}.
- Un faible couplage entre la couche présentation et la couche domaine.
- La couche domaine est "protégée" des évolutions du code dans la couche présentation.
- Il peut y avoir plusieurs couches de présentation (site web, app mobile) pour la même couche domaine.
- L'interface API de la couche domaine peut être utilisé par des systèmes externes.
- Les classes cohésives peuvent être plus facilement réutilisées dans un autre projet.
- Les classes cohésives sont plus faciles à tester.
- Une séparation peut rendre l'application plus simple à comprendre (pour les nouvelles personnes dans l'équipe de développement).
Finalement, l'architecture [modèle-vue-contrôleur ou MVC](https://fr.wikipedia.org/wiki/Mod%C3%A8le-vue-contr%C3%B4leur)\ {{< fa brands wikipedia-w >}} existe depuis 1978, et la séparation des couches en est un principe fondamental.
## Les DSS en tant qu'artefact
Les DSS sont expliqués en détail dans le chapitre 10\ {{< fa solid book >}}, mais voici des points importants pour la méthodologie de ce manuel:
- Le DSS a toujours un titre.
- L'acteur est indiqué dans la notation par un bonhomme et est représenté comme une *instance* de la classe du bonhomme, comme `:Joueur` sur la @fig-dssattaquerunpays (le ":" signifie une instance).
- Le Système est un objet (une instance `:Système`) et n'est jamais détaillé plus.
- Le but du DSS est de définir des opérations système, qui deviendront l'interface API du système; il s'agit d'une conception de haut niveau.
- Le côté acteur du DSS n'est pas un acteur tout seul, mais une couche logicielle de présentation, comme une interface graphique ou un logiciel qui peut reconnaître la parole. Cette couche reconnaît des gestes de l'acteur et envoie une opération système. Un geste peut être un clic sur un bouton dans l'interface, une demande "Dis Siri", etc.
- Puisque la couche présentation reçoit des informations des êtres humains, **les opérations système ont des arguments de type primitif**. Il est difficile pour un utilisateur de spécifier une référence (pointeur en mémoire) à un objet. Alors, on peut donner le nom (de type `String`) d'un morceau de musique à jouer, ou spécifier une quantité (de type `Integer`).
- Puisque les types des arguments sont importants, on les spécifie dans les opérations système du DSS.
- Un message de retour (ligne pointillée avec flèche ouverte) vers l'acteur représente la communication des informations précises, par exemple les valeurs des dés dans l'attaque du jeu Risk. Puisque la couche présentation a beaucoup de moyens pour afficher ces informations, **on ne va pas spécifier les messages de retour comme des méthodes**.
## Exemple: DSS pour Attaquer un pays
La @fig-dssattaquerunpays est un exemple de DSS pour le cas d'utilisation *Attaquer un pays*.
Dans cet exemple, il y a quatre opérations système (avec les arguments de type primitif, sauf la dernière qui n'a aucun argument) et un message de retour.
Vous pouvez noter tous les détails (titre, arguments, types).
```{.plantuml genurl="true" #fig-dssattaquerunpays caption="Diagramme de séquence système pour le scénario *Attaquer un pays*." }
@startuml
!include ecriture.pumlinclude
scale 1
skinparam sequenceMessageAlign center
'title DSS pour //Attaquer un pays//
actor ":Joueur" as c
participant ":Système" as s
c->s : démarrerAttaque(paysAttaquant : String,\npaysDéfenseur : String)
loop pas terminé
c->s : annoncerAttaque(nbRégimentsAttaquant : int)
c->s : annoncerDéfense(nbRégimentsDéfenseur : int)
c<<--s : résultats des deux lancers, régiments perdus \nde l'attaquant et du défenseur le cas échéant
end loop
c->s : terminerAttaque()
@enduml
```
## Les DSS font abstraction de la couche présentation
Le but du DSS est de se concentrer sur l'interface API (les opérations système) de la solution.
Dans ce sens, c'est une conception de haut niveau.
Le "Système" est modélisé comme une boîte noire.
Par exemple, sur la @fig-dssabstraction, il y a l'acteur, le Système et une opération système.
On ne rentre pas dans les détails, bien qu'ils existent et soient importants.
```{.plantuml genurl="true" #fig-dssabstraction caption="Une opération système dans un DSS. C'est une abstraction." }
@startuml
!include ecriture.pumlinclude
scale 1.3
skinparam sequenceMessageAlign center
actor ":Joueur" as c
participant ":Système" as s
c->s : démarrerAttaque(...)
@enduml
```
Plus tard, lorsque c'est le moment d'implémenter le code, les détails importants devront être respectés.
Il faut faire attention aux principes de la séparation des couches présentation et domaine.
Par exemple, la @fig-dssdetails rentre dans les détails de ce qui se passe réellement dans une opération système quand la solution fonctionne avec un service Web:
- D'abord, l'acteur clique sur un bouton;
- Ce clic se transforme en appel REST;
- Un routeur transforme l'appel REST en une opération système envoyée à un contrôleur GRASP. Notez que c'est un **objet du domaine qui reçoit l'opération système** -- c'est l'essence du principe GRASP Contrôleur;
- Le contrôleur GRASP dirige la suite, selon la solution proposée dans la réalisation de cas d'utilisation (RDCU).
```{.plantuml genurl="true" #fig-dssdetails caption="Une opération système est envoyée par la couche présentation et elle est reçue dans la couche domaine par son contrôleur GRASP. Ceci est un exemple avec un navigateur Web, mais d'autres possibilités existent pour la couche présentation."}
@startuml
!include normal.pumlinclude
scale 1
skinparam sequenceMessageAlign center
skinparam participantPadding 10
skinparam sequenceBoxBorderColor transparent
actor ":Joueur" as j
box "Couche présentation" #ddffdd
participant ":Button" as b << NavigateurWeb >>
participant ":Routeur" as r << NodeExpress >>
end box
box "Couche domaine" #ddddff
participant ":JeuRisk" as c << ContrôleurGRASP >>
participant "..." as s
end box
j -> b : cliquer
b ->> r : HTTP GET\n/api/v1/démarrerAttaque/...
note over r : Router handler (Express)
r ->> r : démarrerAttaque
note over r, c: Opération système définie dans DSS
r -> c : **<size:18>démarrerAttaque(...)**
c -> s : ...
note over c, s : selon la RDCU
@enduml
```
::: {.callout-warning appearance="simple"}
La @fig-dssdetails est à titre d'information seulement. Un DSS ne rentre pas dans tous ces détails.
:::
## FAQ DSS
### Faut-il une opération système après une boucle?
::: {.callout}
Dans l'exemple pour *Attaquer un pays* (@fig-dssattaquerunpays), à l'extérieur de la boucle, il y a une opération système `terminerAttaque`.
Est-ce obligatoire d'avoir une opération système après une boucle?
:::
L'opération système `terminerAttaque` sert à signaler la fin de la boucle.
Le système saura que l'acteur ne veut plus répéter les actions dans la boucle.
Mais elle permet aussi de faire des calculs concernant ce qui s'est passé dans la boucle, par exemple pour déterminer qui contrôle quel pays après les attaques.
Cependant, si vous avez une boucle pour indiquer la possibilité de répéter une action (par exemple ajouter des produits dans un système d'inventaire) et que vous n'avez pas besoin de faire un calcul à la fin, alors une opération système pour terminer une telle boucle n'est pas nécessaire (surtout avec une application Web).
### Comment faire si un cas d'utilisation a des *scénarios alternatifs*?
::: {.callout}
Fait-on plusieurs DSS (un pour chaque [scénario alternatif](#sec-scenarios_alternatifs)) ou utilise-t-on la notation UML (des blocs `opt` et `alt`) pour montrer des flots différents dans le même DSS?
:::
Un objectif derrière le DSS est de **définir les opérations système**.
Donc, on peut se poser la question suivante: les scénarios alternatifs impliquent-ils une ou plusieurs opérations système n'ayant pas encore été définies?
Si la réponse est non, on peut ignorer les scénarios alternatifs dans le DSS.
Par contre, si la réponse est oui, il est essentiel de définir ces opérations système dans un DSS.
Quant au choix de faire des DSS séparés ou d'utiliser la notation UML pour montrer les flots différents dans le même DSS, ça dépend de la complexité de la logique des flots.
Un DSS devrait être *facile à comprendre*.
C'est à vous de juger si votre DSS avec des `opt` ou des `alt` est assez simple ou est compliqué à lire.
Utilisez un autre DSS (ou plusieurs) ayant le nom des scénarios alternatifs si cela vous semble plus clair.
### Est-ce un bon design d'avoir une opération système avec beaucoup d'arguments (de type primitif)?
::: {.callout}
Selon la méthodologie, une opération système doit avoir seulement des arguments de type primitif.
À cause de ça, j'ai plusieurs opérations système avec de nombreux (plus de cinq) arguments.
Par exemple, pour créer un Devoir (dans le projet du laboratoire), il y a beaucoup d'informations provenant du formulaire Web et on doit les passer toutes à l'opération système.
Pourquoi n'est-il pas permis d'instancier un objet de Devoir d'abord et le passer comme un seul argument?
:::
Il y a deux volets à cette question.
1. Il y a le problème de beaucoup d'arguments (de type primitif) pour une opération système.
Cela peut arriver, surtout pour les opérations sur les objets du domaine (comme le Devoir) qui ont beaucoup d'attributs. En effet, il n'est pas convivial d'avoir beaucoup d'arguments dans un appel (une opération).
2. Ensuite, il n'est pas conseillé de passer des *objets du domaine* (par exemple, le Devoir) comme arguments, puisque c'est la couche *présentation* qui invoque l'opération système.
La couche présentation n'est pas censée manipuler directement les objets dans la couche domaine, sinon elle empiète sur les responsabilités de la couche domaine.
Une solution pour réduire le nombre d'arguments sans utiliser un objet du domaine est d'appliquer un [réusinage](#Refactoring) pour le *smell* nommé *Long Parameter List*, par exemple [Introduce Parameter Object](https://refactoring.com/catalog/introduceParameterObject.html).
Notez que l'objet de paramètre que vous introduisez n'est pas un objet (classe) du domaine!
La distinction est importante, car la logique d'affaires demeure dans la couche domaine.
En TypeScript, une fonction peut être définie avec un objet de paramètre.
Cet exemple montre même comment on peut ["déstructurer" l'objet](https://www.typescriptlang.org/docs/handbook/variable-declarations.html#object-destructuring) pour déclarer les variables utilisées dans la fonction:
```typescript
// La fonction n'a qu'un seul argument (un objet de paramètre).
// Exemple inspiré de https://leanpub.com/essentialtypescript/read#destructuring
function compteARebours({ initial: number, final: final = 0,
increment: increment = 1, initial: actuel }) {
while (actuel >= final) {
console.log(actuel);
actuel -= increment
}
}
compteARebours({ initial: 20 }); // on passe un objet de paramètre
compteARebours({ initial: 20, increment: 2, final: 4 });
```
Passer un objet de paramètre à une opération système respecte la séparation des couches (la modularité) et augmente la lisibilité du code.
### Ne serait-il pas plus simple de passer l'objet `body` de la page Web au contrôleur GRASP?
::: {.callout}
Décortiquer toutes les informations dans un formulaire Web est compliqué, puis on doit passer tout ça à un contrôleur GRASP comme des arguments de type primitif.
Ne serait-il pas plus simple de passer l'objet `body` de la page Web au contrôleur GRASP et de le laisser faire le décorticage?
:::
Dans un sens, ça serait plus simple (pour le code de la couche présentation).
Cependant, on veut séparer les couches pour favoriser le remplacement de la couche présentation, par exemple à travers une application iOS ou Android.
Si vous mettez la logique de la couche présentation (décortiquer un formulaire Web) dans la couche domaine (le contrôleur GRASP), ça ne respecte pas les responsabilités des couches.
Imaginez un tel contrôleur GRASP si vous aviez trois types d'applications frontales (navigateur Web, application iOS et application Android).
Le contrôleur GRASP recevra des représentations de "formulaire" de chaque couche présentation différente.
En passant, l'objet `body` n'a rien à voir avec une interface Android!
Ce pauvre contrôleur serait alors obligé de connaître toutes les trois formes (Web, iOS, Android) et, ainsi, sa cohésion serait beaucoup plus faible.
Pour respecter les responsabilités, on laisse la couche présentation faire le décorticage et construire une opération système selon l'interface API définie dans le DSS.
Cela simplifie aussi le contrôleur GRASP.
## Exercices
{{< include _callout_conseils_diagrammes.qmd >}}
::: {#exr-DSS}
### Dessiner un DSS à partir d'un cas d'utilisation
Esquissez le DSS pour le cas d'utilisation *[Réserver un livre de la bibliothèque](#sec-CU_Réserver_livre)*.
Toutes les opérations système doivent définir le type de chaque argument, si nécessaire.
:::