Mise en œuvre à l’aide d’exemples simples
L’architecture propre est une philosophie de conception logicielle qui sépare les préoccupations d’une application en couches distinctes, chacune avec une responsabilité spécifique. Il est appelé « propre » car il promeut un code facile à lire, à tester et à entretenir, et n’est pas lié à un cadre ou à une technologie spécifique.
Dans le contexte d’une application iOS écrite en Swift à l’aide des frameworks SwiftUI et Combine, une architecture propre pourrait ressembler à ceci :
- La couche de présentation, qui est la couche la plus externe, est responsable de la gestion des entrées utilisateur et de la sortie d’affichage. Cette couche contiendrait les
SwiftUI
vues qui définissent l’interface utilisateur de l’application. - La couche de domaine, qui se trouve au centre de l’architecture, est responsable de la mise en œuvre de la logique métier de base de l’application. Cette couche contiendrait des classes qui définissent les modèles de données, les règles métier et les cas d’utilisation de l’application.
- La couche d’accès aux données, qui est la couche la plus interne, est responsable de l’accès et du stockage des données. Cette couche contiendrait des classes qui récupèrent les données du réseau ou d’une base de données locale et les fournissent à la couche de domaine.
Chaque couche a une responsabilité claire et bien définie et communique avec les couches qui l’entourent via un ensemble d’interfaces bien définies. Cela permet aux différentes couches d’être développées, testées et maintenues indépendamment les unes des autres, et facilite l’ajout de nouvelles fonctionnalités ou la modification de l’application sans affecter les autres couches.
Par exemple, si l’application doit ajouter une nouvelle fonctionnalité qui implique la récupération de données à partir d’une nouvelle source, le développeur ajoutera simplement une nouvelle classe dans la couche d’accès aux données qui implémente les interfaces et méthodes nécessaires. Cela n’affecterait pas la couche de domaine ou la couche de présentation, qui continueraient à fonctionner comme avant.
Dans l’ensemble, l’utilisation d’une architecture propre dans une application iOS écrite en Swift, avec les frameworks SwiftUI et Combine, peut aider à créer une application bien structurée, maintenable et évolutive.
Adaptation des codes
Nous commencerons par cet exemple simple de la façon dont une application iOS utilisant les frameworks SwiftUI et Combine pourrait implémenter l’architecture propre :
Dans cet exemple, le MyView
struct dans la couche de présentation définit l’interface utilisateur de l’application et utilise la MyViewModel
classe dans la couche de domaine pour afficher les données.
La MyViewModel
classe récupère les données à l’aide de la couche d’accès aux données, qui se compose de la DataFetcher
protocole et le NetworkDataFetcher
et DatabaseDataFetcher
classes qui l’implémentent.
Les différentes couches de l’architecture sont clairement séparées, avec des interfaces bien définies entre elles. Cela facilite l’ajout de nouvelles fonctionnalités ou la modification de l’application sans affecter les autres couches.
Par exemple, si l’application doit récupérer des données à partir d’une nouvelle source, le développeur ajoutera simplement une nouvelle classe qui implémente le DataFetcher
protocole, et l’utiliser dans le MyViewModel
classer. Cela n’affecterait pas la MyView
struct ou les autres classes de la couche d’accès aux données, qui continueraient à fonctionner comme avant.
Théoriquement, Injection de dépendance est une technique de mise en œuvre du principe d’inversion de dépendance. Ce principe stipule que les modules de haut niveau ne doivent pas dépendre sur des modules de bas niveau, mais les deux devraient plutôt dépendre d’abstractions.
Dans le contexte d’une application iOS, l’injection de dépendances signifie qu’un objet qui dépend d’un autre objet, tel qu’un contrôleur de vue qui dépend d’un objet modèle, reçoit une référence à l’objet dont il dépend, plutôt que de créer l’objet lui-même. Cela permet à l’objet qui dépend de l’autre objet d’être plus flexible et plus facile à tester, car on peut lui donner un objet fictif à utiliser pendant le test.
Adaptation des codes
Voici un exemple simple de DE (Injection de dépendance ) en utilisant les frameworks SwiftUI et Combine dans une application iOS :
Dans cet exemple, le UserListView
Depend de UserViewModel
et le UserViewModel
Depend de NetworkManager
.
Au lieu de créer le NetworkManager
directement à l’intérieur du UserViewModel
une référence à la NetworkManager
est passé dans le UserViewModel
est l’initialiseur.
Cela permet au UserViewModel
être plus flexible, car on peut lui donner n’importe quel objet conforme à la NetworkManager
protocole, pas seulement une implémentation spécifique de NetworkManager
. Cela facilite le test de UserViewModel
car un objet factice peut être injecté pendant les tests.
Maintenant, nous allons essayer de mélanger à la fois l’architecture propre et l’injection de dépendances, en utilisant les connaissances que nous avions dans les exemples précédents pour avoir un point de vue clair et complet.
PS : L’injection de dépendance n’est pas strictement nécessaire pour une architecture propre dans SwiftUI et Combine, mais elle peut être utile pour promouvoir un couplage lâche et rendre votre code plus facile à tester.
Adaptation des codes
Sur la base du premier exemple de code et de ce que nous avons appris sur DI :
La MyViewModel
classe dans la couche de domaine est construite avec une instance de la DataFetcher
protocole, qui lui est injecté lors de sa création.
Cela permet au MyViewModel
classe pour utiliser l’injecté DataFetcher
instance pour récupérer des données à partir du réseau ou d’une base de données locale, sans savoir ni se soucier de l’implémentation spécifique de la DataFetcher
protocole est utilisé.
L’utilisation de l’injection de dépendances dans cet exemple permet de découpler davantage les différentes couches de l’architecture.
La MyViewModel
la classe n’est pas étroitement couplée à une implémentation spécifique de la DataFetcher
protocole, et peut être testé et maintenu indépendamment des autres classes de la couche d’accès aux données. Cela facilite l’ajout de nouvelles fonctionnalités ou la modification de l’application sans affecter les autres couches.
Maintenant que vous comprenez comment DI et CleanArch fonctionnent dans les coulisses, vous voudrez peut-être automatiser le processus d’injection de nombreuses instances de DataFetcher
à l’intérieur de votre ViewModel
.
Mais parfois, cela peut représenter beaucoup de travail. Voici la partie où certains packages Swift bien construits peuvent nous aider à automatiser le processus, tels que Résolveur qui est mon préféré.
Utilisation du résolveur
Sachant que la plupart d’entre nous sont paresseux pour lire la documentation, j’ai converti le même exemple que nous avons utilisé précédemment en utilisant Resolver
.
Dans cet exemple, le MyViewModel
la classe dans la couche de domaine est marquée par le @Injected
attribut pour indiquer qu’il nécessite une instance de DataFetcher
protocole à lui injecter lors de sa création.
La Resolver
package est utilisé pour enregistrer le MyViewModel
classe et spécifiez comment elle doit être construite, en utilisant la classe injectée DataFetcher
exemple.
Lorsque l’application est exécutée, le Resolver
package créera automatiquement une instance du MyViewModel
classe et injecter la mise en œuvre appropriée de la DataFetcher
protocole dans celui-ci, sur la base de l’enregistrement spécifié dans le Resolver
extension.
La MyViewModel
la classe peut alors utiliser l’injecté DataFetcher
instance pour récupérer des données à partir du réseau ou d’une base de données locale, sans savoir ni se soucier de l’implémentation spécifique de la DataFetcher
protocole est utilisé.
L’utilisation de la Resolver
package dans cet exemple aide à découpler davantage les différentes couches de l’architecture et facilite la gestion et le test des dépendances entre elles.
La MyViewModel
la classe n’est pas étroitement couplée à une implémentation spécifique de la DataFetcher
protocole, et peut être testé et maintenu indépendamment des autres classes de la couche d’accès aux données. Cela facilite l’ajout de nouvelles fonctionnalités ou la modification de l’application sans affecter les autres couches. Nous discuterons plus sur les tests avec des exemples dans mon prochain article.