Exemple d'architecture à trois couches en Visual Basic

Inconnu

Exemple d'architecture à trois couches en Visual Basic ♪

- Titre : Exemple d'architecture à trois couches en Visual Basic

- Auteur : Inconnu

- Parution : 17 mars 2003

- Mise à jour : 3 janvier 2013

- Licence : Copyright ® 2003 Inconnu. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Exemple d'application classique

- II. Rôle de chacune des couches

- III. Avantages de cette architecture

- IV. Exemple d'architecture à trois couches

- IV-A. Couche données

- IV-B. Couche métier

- IV-C. Couche présentation

I - Exemple d'application classique

Prenons en exemple une application très simple de gestion de comptes bancaires. Les données sont stockées dans une base composée de ces trois tables :

Table tPersonne :

Titulaires des comptes

Table tCompte :

Les comptes bancaires

Table tOperation :

Opérations sur les comptes

Pour voir la structure des tables en détail, Cliquez ici.

Dans la feuille ci-contre, l'utilisateur demande l'affichage des comptes d'un client en entrant l'identifiant du client, puis en cliquant sur le bouton "Afficher".

Voici une façon simple de gérer l'affichage des comptes dans la datagrid, sur l'évènement Click() du bouton :

1. Private Sub btnSearch_Click()

3. Dim cnn As ADODB.Connection, rst As ADODB.Recordset, sql As String

5. If IsNumeric(Text1.Text) Then

7. sql = "SELECT tCompte.Co_Numero, tCompte.Co_DateCreation, tCompte.Co_Solde FROM tCompte" & _

8. " WHERE Co_IdProp=" & Text1.Text

10. Set rst = New ADODB.Recordset

11. Set cnn = New ADODB.Connection

13. cnn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & App.Path & "\db.mdb" & _

14. ";Persist Security Info=False"

15. rst.CursorLocation = adUseClient

16. rst.Open sql, cnn, adOpenForwardOnly, adLockReadOnly

17. Set rst.ActiveConnection = Nothing

18. Set DataGrid1.DataSource = rst

19. cnn.Close

20. Else

21. MsgBox "L'identifiant d'un client est un numérique.", vbCritical, "Erreur de saisie"

22. End If

24. End Sub

Si le contenu du champ texte est numérique, une connexion est créée, les comptes sont récupérés dans un recordset qui devient la source de la datagrid.

L'affichage des comptes d'une personne peut être scindé en trois parties distinctes :

- l'interface utilisateur ;

- la logique de l'application : vérification de l'identifiant saisi et lecture du compte dans la base ;

- connexion à la base de données et ouverture d'un recordset.

Le code source ci-dessus contient donc ces trois parties, alors qu'elles devraient être clairement distinctes. Cela permettrait de réutiliser le code qui crée une connexion à la base de données, et celui qui retrouve les comptes bancaires d'une personne. De façon générale, il vaut mieux programmer une application en séparant ces 3 fonctions : présentation, logique de l'application, et stockage des données.

II - Rôle de chacune des couches

La couche présentation :

- affiche les données ;

- envoie les demandes de l'utilisateur à la couche métier pour qu'elle les effectue ;

- reçoit les résultats renvoyés par la couche métier et les affiche.

La couche métier (logique de l'application) :

- reçoit et analyse les demandes de l'utilisateur ;

- retrouve et modifie les données via la couche données ;

- renvoie les résultats à la couche présentation.

La couche données :

- modifie les données ;

- récupère les données ;

- assure la sécurité et l'intégrité des données.

III - Avantages de cette architecture

Séparer l'application en trois couches a de nombreux avantages, en voici une liste non exhaustive :

- la gestion des données et la logique métier peuvent être indépendantes du type d'interface :
La logique de l'application et ses données pourront être utilisées par une interface Windows ou par un site ASP par exemple ;

- les couches métier et données sont encapsulées :
ces couches assurent l'intégrité des données en analysant les demandes du client avant de les effectuer, et on gagne donc en robustesse. Pendant la réalisation de la couche métier, on peut se concentrer sur la logique de l'application et l'intégrité des données, sans se soucier de la présentation, et la maintenance en devient plus facile ;

- les couches métier et données peuvent être placées sur un serveur ou chacune sur un serveur distinct.

- La couche présentation n'accédant pas directement aux données, la configuration de l'accès aux données ne se fait alors que sur le serveur où réside la couche données.

- Si la couche métier ou celle des données est modifiée, il n'y a pas de réinstallation à faire sur les postes clients.

IV - Exemple d'architecture à trois couches

Nous allons reprendre l'application de gestion de comptes bancaires, qui nous avait servi d'exemple.
La couche présentation sera un EXE standard, et les couches métier et données seront chacune une Dll ActiveX. Commençons par la couche des données.

IV-A - Couche données

Cette couche aura donc la forme d'une dll ActiveX. Pour créer ce type de projet, cliquez sur le menu Fichier -> Nouveau projet, puis sur DLL ActiveX. Allez dans les propriétés du projet et saisissez "Data" pour le nom du projet. La classe Class1 a été créée par défaut, renommez-la en clsData. Ce sera la seule classe contenue dans cette couche.

La classe clsData doit pouvoir : ouvrir une connexion à la base de données, et renvoyer un recordset déconnecté à partir d'une requête passée en paramètre.

1. Option Explicit

3. Public Enum DataErrorEnum

4. dtErrorConnexion = 4000

5. dtErrorOpenRecordset = 4001

6. dtErrorGeneral = 4002

7. End Enum

9. Private m_baseDeDonnees As String

10. Private m_chaineDeConnexion As String

12. Private Sub Class_Initialize()

14. m_baseDeDonnees = App.Path & "\db.mdb"

15. m_chaineDeConnexion = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & m_baseDeDonnees & _

16. ";Persist Security Info=False"

17. End Sub

Nous avons tout d'abord une énumération qui représente les numéros d'erreurs qui peuvent être créées par cette classe. Ces erreurs peuvent être signalées à la couche métier. La procédure Class_Initialize() est exécutée à l'initialisation de la classe. C'est à ce niveau que nous initialisons les variables qui contiennent les paramètres de notre connexion.

1. Private Function CreeConnexion() As ADODB.Connection

3. Dim cnn As ADODB.Connection

5. On Error GoTo fail

6. Set cnn = New ADODB.Connection

7. cnn.Open m_chaineDeConnexion

8. Set CreeConnexion = cnn

9. Exit Function

10. fail:

11. Err.Raise dtErrorConnexion, "Data.clsData", _

12. "La connexion à la base de données a échoué." & vbCrLf & Err.Number & ": " & Err.Description

13. End Function

15. Private Function CreeRecordset(sql As String) As ADODB.Recordset

17. Dim cnn As ADODB.Connection, rst As ADODB.Recordset

19. On Error GoTo fail

21. Set cnn = CreeConnexion()

23. Set rst = New ADODB.Recordset

24. rst.CursorLocation = adUseClient

25. rst.Open sql, cnn, adOpenForwardOnly, adLockReadOnly

26. Set rst.ActiveConnection = Nothing

28. cnn.Close

29. Set cnn = Nothing

31. Set CreeRecordset = rst

32. Exit Function

34. fail:

35. Select Case Err.Number

36. Case dtErrorConnexion:

37. Err.Raise dtErrorConnexion, "Data.clsData", Err.Description

38. Case Else

39. Err.Raise dtErrorOpenRecordset, "Data.clsData", _

40. "L'ouverture d'un recordset a échoué." & vbCrLf & Err.Number & ": " & Err.Description

41. End Select

43. End Function

La fonction CreeConnexion() instancie un objet ADODB.Connection, se connecte à la base de données et renvoie l'objet instancié.

La fonction CreeRecordset() crée puis renvoie un recordset déconnecté, le code sql de la requête devant être passé en paramètre.

Nous renvoyons un recordset déconnecté, car un recordset connecté qui est transféré sur le réseau, conserve avec lui toutes les informations nécessaires pour rester lié à sa source de données.

Pour que le recordset soit déconnecté, vous devez :

- donner à la propriété CursorLocation la valeur adUseClient, et ceci avant d'ouvrir le recordset. Ainsi le curseur sera géré du côté client ;

- donner à la propriété ActiveConnection la valeur Nothing, après l'ouverture du recordset. Cela a pour effet de déconnecter le recordset de sa source de données.

En utilisant cette méthode, le recordset est transféré du serveur vers le client, morceau par morceau. Il faut donc plusieurs allers-retours entre le serveur et le client pour que le recordset soit totalement transféré, ce qui n'est pas l'idéal. Ceci peut-être encore amélioré en utilisant les composants RDS pour accéder aux données. Pour plus d'informations sur RDS, voir la documentation sur MSDN.

Ici la valeur choisie pour le "locktype" du recordset est adLockReadOnly, parce que les enregistrements n'ont pas besoin d'être modifiables par le client.

IV-B - Couche métier

Nous allons créer cette couche sous la forme d'une autre dll ActiveX. Tout d'abord le projet Data doit avoir été compilé. Puis sans fermer le projet, cliquez sur le menu Fichier -> Ajouter un projet, et sélectionnez DLL ActiveX. Renommez le nouveau projet en "Rules". La couche métier va utiliser la couche données, donc l'activeX "Data" doit être ajouté dans les références du projet.

La couche données contiendra deux classes. La classe clsServices qui nous fournira les comptes de quelqu'un, ainsi que les opérations d'un compte. Et clsCompte qui nous permettra de lire ou de modifier un compte. Voici le code source de la classe clsServices :

1. Option Explicit

3. Function ListeComptes(idProprietaire As Long) As ADODB.Recordset

5. Dim objData As clsData, sql As String

7. On Error GoTo fail

9. sql = "SELECT * FROM tCompte WHERE co_IdProp=" & idProprietaire

11. Set objData = New clsData

12. Set ListeComptes = objData.CreeRecordset(sql)

13. Set objData = Nothing

14. Exit Function

16. fail:

17. Err.Raise Err.Number, Err.Source, Err.Description

19. End Function

22. Function ListeOperations(idCompte As Long, dateOperations As Date) As ADODB.Recordset

24. Dim objData As clsData, sql As String

26. On Error GoTo fail

28. sql = "SELECT * FROM tOperation WHERE (op_compteS=" & idCompte & ")"

29. If IsDate(dateOperations) Then sql = sql & "AND (co_DateCreation=#" & dateOperations & "#)"

31. Set objData = New clsData

32. Set ListeOperations = objData.CreeRecordset(sql)

33. Set objData = Nothing

34. Exit Function

36. fail:

37. Err.Raise Err.Number, Err.Source, Err.Description

39. End Function

Ces deux méthodes renvoient un recordset déconnecté. À ce niveau on se connecte à la base de données, mais l'on ne sait pas comment. Le type de base de données, son emplacement, la manière de se connecter, le mot de passe s'il y en a un, sont gérés dans la couche des données.

Voici la classe clsCompte :

1. Option Explicit

3. Public Enum RulesErrorEnum

4. rlErrorReadOnly = 5000

5. rlErrorIncorrectValue = 5001

6. End Enum

8. Private m_id As Long

9. Private m_numero As String

10. Private m_dateCreation As Date

11. Private m_solde As Single

12. Private m_idProprietaire As Long

13. Private m_loaded As Boolean

15. Property Get Id() As Long

16. Id = m_id

17. End Property

19. Property Get DateCreation() As Date

20. DateCreation = m_dateCreation

21. End Property

23. Property Get Numero() As String

24. Numero = m_numero

25. End Property

27. Property Let Numero(LeNumero As String)

29. If m_loaded Then

30. Err.Raise rlErrorReadOnly, "Rules.clsCompte", "Le numéro d'un compte ne peut pas être modifié."

31. Else

32. If IsNull(LeNumero) Or (Not IsNumeric(LeNumero)) Then

33. Err.Raise rlErrorIncorrectValue, "Rules.clsCompte", "La valeur spécifiée pour un numéro de compte est incorrecte."

34. Else

35. m_numero = LeNumero

36. End If

37. End If

39. End Property

41. Property Get Solde() As Single

42. Solde = m_solde

43. End Property

45. Property Let Solde(nouveauSolde As Single)

46. m_solde = nouveauSolde

47. End Property

49. Property Get Proprietaire() As Long

50. Proprietaire = m_idProprietaire

51. End Property

53. Property Let Proprietaire(idProprietaire As Long)

55. If m_loaded Then

56. Err.Raise rlErrorReadOnly, "Rules.clsCompte", "Le propriétaire d'un compte ne peut pas être modifié."

57. Else

58. m_idProprietaire = idProprietaire

59. End If

61. End Property

63. Public Function Load(idCompte As Long) As Boolean

65. Dim objData As clsData, sql As String, rst As ADODB.Recordset

67. On Error GoTo fail

69. sql = "SELECT * FROM tCompte WHERE co_id=" & Id

71. Set objData = New clsData

72. Set rst = objData.CreeRecordset(sql)

74. If Not rst.EOF Then

75. m_id = rst("co_Id").Value

76. m_numero = rst("co_numero").Value

77. m_dateCreation = rst("co_DateCreation").Value

78. m_solde = rst("co_Idprop")

79. m_loaded = True

80. Load = True

81. End If

82. rst.Close

83. Set rst = Nothing

84. Exit Function

86. fail:

87. Err.Raise Err.Number, "Rules.clsCompte", Err.Description

89. End Function

91. Public Function Save() As Boolean

93. Dim objData As clsData, sql As String, cnn As ADODB.Connection

95. On Error GoTo fail

97. If Id() > 0 Then

98. sql = "UPDATE tCompte SET solde=" & Solde() & " WHERE co_Id=" & Id()

99. Else

100. sql = "INSERT INTO tCompte (co_solde,co_IdProp,co_DateCreation,co_numero) VALUES (" & _

101. Solde & "," & Proprietaire() & ",#" & Format(Date, "mm/dd/yyyy") & "#,'" & Numero() & "')"

102. End If

104. Set objData = New clsData

105. Set cnn = objData.CreeConnexion()

106. cnn.Execute sql

107. Save = True

109. cnn.Close

110. Set cnn = Nothing

111. Set objData = Nothing

112. Exit Function

114. fail:

115. Err.Raise Err.Number, "Rules.clsCompte", Err.Description

117. End Function

119. Private Sub Class_Initialize()

120. m_dateCreation = Date

121. End Sub

Ceci n'est qu'un aperçu de ce que pourrait être la classe clsCompte dans une application complète. Elle doit contenir toutes les règles de fonctionnement relatives à un compte. Les valeurs des champs ne sont modifiables que par l'intermédiaire des propriétés de la classe, qui contrôlent leur modification. Par exemple, il est interdit de modifier le numéro du compte si celui-ci a déjà été créé.
Un compte est lu avec la méthode Load(). La méthode Save() permet d'ajouter un nouveau compte ou d'enregistrer les modifications effectuées sur un compte existant.

IV-C - Couche présentation

À ce stade nous n'avons plus qu'à programmer l'interface, qui affichera les données et effectuera les commandes demandées par l'utilisateur, et ceci simplement en manipulant les objets de la couche métier.

Pour créer cette couche, ajoutez un projet de type Exe Standard. Ajoutez l'activeX Rules dans ses références.

Voici ce que devient notre code source du premier chapitre, qui affichait les comptes d'une personne dans une datagrid :

1. Private Sub btnSearch_Click()

3. Dim services As clsServices, rst As ADODB.Recordset

5. On Error GoTo fail

6. Set services = New clsServices

7. Set rst = services.ListeComptes(CLng(Text1.Text))

8. Set DataGrid1.DataSource = rst

9. Exit Sub

11. fail:

12. Select Case Err.Number

13. Case 13:

14. MsgBox "Vous devez saisir un numérique."

15. Case Else:

16. MsgBox Err.Source & ": " & "Erreur n° " & Err.Number & ": " & Err.Description

17. End Select

19. End Sub

Vous pouvez télécharger les sources.