L’Architecture Hexagonale sur un projet Web + Mobile (Partie 1 sur 5)

Explorons comment la logique métier peut être partagée et gérée efficacement à travers différentes plateformes.

Hola, je vais vous présenter le début d'une nouvelle série d'articles dédiées à la construction d'un projet Web et Mobile en mettant à profit l'Architecture Hexagonale. Durant toute cette série, nous allons explorer comment la logique métier peut être partagée et gérée efficacement à travers différentes plateformes.

Nos objectifs

Nous allons avoir plusieurs objectifs à atteindre au fil de ce projet :

  • Apprendre comment développer une application Web et Mobile à la fois en mettant à profit les technologies modernes (Nx, Expo, Remix, Vitest, etc.)
  • Comprendre les principes de l'Architecture Hexagonale et comment l'appliquer pour optimiser le partage de la logique métier
  • Gagner en compétence et en confiance pour lancer votre propre projet multi-plateforme, tout en développant une base de code propre et maintenable

La structure de la série

Comme je l'ai dis au début de ce premier article, ce projet donnera lieu à une série d'articles qui sera structurée de cette manière :

  • Partie 1 (cet article) : présentation du projet et mise en place d'un monorepo avec Nx
  • Partie 2 : développer sans UI avec l'Architecture Hexagonale
  • Partie 3 : partager de la logique métier et des composants entre le Web et le Mobile
  • Partie 4 : refactor serein avec les tests et l'Architecture Hexagonale
  • Partie 5 : déploiement Web et Mobile avec Netlify et EAS

Le projet

Le projet qui va nous aider à mettre en avant l'Architecture Hexagonale est un outil de gestion de budget que l'on appellera broney (le bro qui t'aides à gérer ta money 😎). Cet outil sera composé de deux applications, une première, web, développée avec Remix et une deuxième, mobile, développée avec Expo. Nous aurons donc 2 applications React et React Native avec un package TypeScript qui contiendra la logique métier partagée entre ces 2 applications.

Stack

La Stack que j'ai choisi est très subjective, on y trouve quelques frameworks qui mérite selon moi plus de lumière (Remix notamment et Nx face à NextJS et Turborepo). Néanmoins il est important de comprendre que peu importe les frameworks et libraires utilisées, le coeur de l'application sera complètement agnostique et réutilisable dans n'importe quel contexte.

Fonctionnalités

Pour mettre en avant l'Architecture Hexagonale nous allons avoir besoin de développer quelques fonctionnalités pour avoir de la logique métier. Nous allons nous focus sur les fonctionnalités suivantes :

  • Mettre en place le storage : react native mmkv pour le mobile et localStorage pour le web
  • Gérer les catégories : lister, ajouter, modifier et supprimer
  • Gérer les portefeuilles : lister, ajouter, modifier et supprimer
  • Gérer les transactions d'un compte : lister, ajouter, modifier et supprimer
  • Authentification avec Supabase
  • Dynamiser toute l'app avec Supabase

Modèle de données

Pour mettre en place les fonctionnalités nous allons avoir besoin des entités suivantes :

  • Wallet, un portefeuille qui a un solde négatif ou positif (par exemple on peut avoir le portefeuille "Compte Principal Julien" qui a un solde positif de 1000€)
  • Category, des catégories servant à préciser le contexte des transactions faites (par exemple on a les catégories "Maison", "Restaurants" et "Divertissements")
  • Transaction, les transactions sont liées à un portefeuille et à une catégorie pour savoir où l'argent est transférée (par exemple on a une transaction du portefeuille "Compte Principal Julien" de 50€ sur la catégorie "Restaurants")

Mise en place du monorepo avec NX

Initialisation du projet

Nous allons utiliser les commandes de Nx pour initialiser notre projet.

➜ npx create-nx-workspace@latest

✔ Where would you like to create your workspace? · broney
✔ Which stack do you want to use? · none
✔ Package-based monorepo, integrated monorepo, or standalone project? · package-based
✔ Enable distributed caching to make your CI faster · Yes

Avec cette commande nous avons le projet Nx configuré de base et sans libs pour le moment. Nous allons travailler avec le style Package-Based Repos qui nous offre plus de liberté en limitant le couplage avec Nx si jamais on souhaite changer facilement d'outil de monorepo. Cela permet également d'avoir des node_modules différents pour chaque app ou lib du projet. En savoir plus sur les différents style d'implémentation de Nx.

Création de la lib core

Nous allons maintenant ajouter notre première lib, la plus importante : core. En effet, c'est dans cette lib que nous allons mettre notre Architecture Hexagonale et la logique métier qui sera utilisée par nos applications Web et Mobile.

➜ nx g @nx/js:lib libs/core

✔ Which unit test runner would you like to use? · vitest
✔ Which bundler would you like to use to build the library? Choose 'none' to skip build setup. · rollup

Cette commande nous a généré une lib avec le framework de test Vitest, une config eslint et prettier que l'on peut adapter à nos preferences que je ne détaillerai pas ici.

Il est possible de compiler notre lib avec la commande nx core build et d'executer les tests avec nx core test.

Mise en place de la CI

Maintenant que nous avons les tests setup ainsi que prettier et eslint, il est pertinent de mettre en place une CI pour avoir du feedback régulier sur la bonne tenue du code. Pour la CI nous allons simplement suivre la documentation de Nx et utiliser les GitHub Actions.

Nous allons donc simplement ajouter un fichier .github/workflows/ci.yml assez simple qui peut être étoffé.

name: CI
on:
	push:
	branches:
	- main
	pull_request:

jobs:
	main:
		runs-on: ubuntu-latest
		steps:
			- uses: actions/checkout@v4
				with:
					fetch-depth: 0
			# Cache node_modules
			- uses: actions/setup-node@v3
				with:
					node-version: 20
					cache: 'yarn'
			- run: yarn --no-progress --frozen-lockfile
			- uses: nrwl/nx-set-shas@v4.0.4

			- run: npx nx format:check
			- run: npx nx affected -t lint,test,build --parallel=3

Cette simple CI permet vérifier le bon formatage prettier, d'effectuer les validations eslint et de build et de s'assurer que les tests sont sans erreurs.

Structure du projet

Rentrons plus en détails dans ce que l'on vise comme structure de projet une fois les apps mise en place et notre lib core développée avec l'architecture hexagonale.

- apps

    - mobile : notre application React Native développée avec Expo
    - web
: notre application React développée avec React

- libs
    - ui
: nos composants React et React Native utilisés par les apps web et mobile
    - tailwind
: notre configuration tailwind utilisée par les apps web et mobile ainsi que la lib ui
    - core
: notre architecture hexagonale qui contient le coeur de notre application et toute la logique métier réutilisable par les apps web et mobile

Pour aller plus loin, on peut très bien envisager d'avoir une app en plus pour un Storybook.

La lib qui va nous intéresser et la lib core évidemment. Elle sera structurée de cette manière :

- libs
    - core
         - src
              - wallet
                   - tests
                        - wallet.service.test.ts
: la logique métier testées
                        - wallet.test.ts
: les règles métiers testées
                   - domain
                        - wallet.ts
: l'entité qui représente les portefeuilles et qui contient des règles métiers
                        - wallet.repository.ts
: le contrat qui détermine comment manipuler l'entité pour lister, ajouter, etc.
                        - wallet.service.ts : le service qui consume une implémentation de contrat

                   - infrastructure
                        - in-memory-wallet.repository.ts
: une implémentation du contrat
                        - local-storage-wallet.repository.ts
: idem
                        - supabase-wallet.repository.ts : idem
                   - user-interface
                        - wallet.store.ts :
un store zustand vanilla, utilisable dans n'importe quel environnement javascript et qui sera utilisé dans nos apps
              - category
                   - ...
           - ...

Nous verrons le contenu de chaque fichiers ainsi que les détails du fonctionnement de ces derniers dans le prochain article !

Conclusion

Nous avons terminé le premier article de cette série sur le développement d'une application web et mobile avec l'Architecture Hexagonale et le partage de la logique métier et des composants UI.

Dans cette première partie nous avons vu comment mettre un place un monorepo et nous avons pourquoi et comment ce monorepo va nous aider à partager la logique métier entre nos différentes applications. Nous avons également bien délimité le périmètre et les fonctionnalités attendues pour notre première version, le MVP, de broney.

Enfin, à la fin de cet article nous avons commencé à entrevoir la structure du projet en mettant en évidence l'Architecture Hexagonale, ce sera le thème de la deuxième partie : Développer sans UI avec l'Architecture Hexagonale.

Sommaire
Nos autres catégories
Partager sur :
Nos autres catégories
Partager sur :

Ne manquez rien
Abonnez-vous à notre newsletter

Notre newsletter tous les mois :
Je m'abonne
Merci ! C'est dans la boîte :)
Oops! Something went wrong while submitting the form.

Nos experts vous parlent
Le décodeur

Design Pattern : Compound Components
25/6/2024

Introduction

En tant que développeurs, nous savons que les projets évoluent constamment : les besoins changent, les designs se métamorphosent et les spécifications initiales peuvent rapidement devenir obsolètes.

Face à cet environnement mouvant, nos composants traditionnels montrent parfois leurs limites. Ils ne sont pas tous adaptés pour faire face de manière flexible et robuste à cette évolution constante.

Qui n'a jamais été frustré par un composant trop rigide pour s'accommoder d'un changement de maquette ou d'une mise à jour des exigences du projet ?

Examinons ensemble, deux exemples, pour illustrer le Design Pattern : Compound Components.

Exemple d’un composant d’UI simple ✏️

Supposons que nous devons créer un composant Card tout ce qu’il y a de plus classique. On a besoin d’affiche un title, une description et un thumbnail.

Voilà une implementation simple de ce que pourrait être ce composant :

// card.tsx

type CardProps = {
	title: string;
	description: string;
	thumbnail: string;
}

function Card({ title, description, thumbnail }: CardProps) {
	return (
		<View>
			<Image source={{ uri: thumbnail }} />
			<Text>{title}</Text>
			<Text>{description}</Text>
		</View>
	)
}

Ainsi que son usage :

// home.tsx

function HomeScreen() {
	return (
		<View>
			<Card
				title="Lorem ipsum"
				description="Quis enim aliqua ad et consectetur laboris reprehenderit ea anim occaecat adipisicing duis exercitation magna cupidatat."
				thumbnail="https://picsum.photos/200/300"
			/>
		</View>
	)
}

Jusque là, tout va bien, notre composant est simple à développer, simple à utiliser et facile à relire.

Maintenant, comme dans tous les projets, le besoin évolue et le design de nos composants avec. Admettons, que notre besoin a évolué de façon à ce qu’on ai besoin d’ajouter un bouton sur notre composant Card. Mais, ce bouton ne doit pas apparaître à tous les endroits de mon application.

Ce que l’on va retrouver dans la plupart des projets professionnels aujourd’hui, c’est une surcharge de propriétés sur le composant. Le plus souvent, notre composant serait comme suit :

// card.tsx

type CardProps = {
	title: string;
	description: string;
	thumbnail: string;
	buttonLabel?: string;
	showButton?: boolean;
	onPressButton?: () => void;
}

function Card({ 
	title, 
	description, 
	thumbnail,
	buttonLabel,
	showButton = false,
	onPressButton
}: CardProps) {
	return (
		<View>
			<Image source={{ uri: thumbnail }} />
			<Text>{title}</Text>
			<Text>{description}</Text>
			{showButton && <Button label={buttonLabel} onPress={onPressButton} />}
		</View>
	)
}


NB : c’est volontairement exagéré pour mettre en avant le problème. Même sans Compound Components que l’on verra après, on pourra avoir un composant bien plus propre !

Et l’usage du composant serait comme suit :

// home.tsx

function HomeScreen() {
	return (
		<View>
			<Card
				title="Lorem ipsum"
				description="Quis enim aliqua ad et consectetur laboris reprehenderit ea anim occaecat adipisicing duis exercitation magna cupidatat."
				thumbnail="https://picsum.photos/200/300"
			/>
			<Card
				title="Lorem ipsum"
				description="Quis enim aliqua ad et consectetur laboris reprehenderit ea anim occaecat adipisicing duis exercitation magna cupidatat."
				thumbnail="https://picsum.photos/200/300"
				buttonLabel="Lorem"
				onPressButton={() => { /* ... */}}
				showButton
			/>
		</View>
	)
}


Observations

Que peux-tu observer sur cet exemple de composant “traditionnel” qui ne représente que de l’UI ?

  1. Structure rigide
    Le composant Card a une structure définie, il contient toujours une image, un titre, une description et un bouton. Il n’y a flexibilité pour changer la structure d’un composant en fonction des besoins.
  2. Passage de props
    Toutes les données dont le composant Card a besoin sont passées via les props. Cela peut devenir encombrant et difficile à maintenir à mesure que nous ajoutons plus de props au composant.
  3. Moins de réutilisabilité
    Les sous-composants ne peuvent pas être réutilisés indépendamment. Par exemple, si nous voulons utiliser seulement le bouton ou l'image de la carte dans un autre composant, cela ne serait pas possible.
  4. Peu extensible
    Ajouter de nouvelles fonctionnalités à la carte nécessite une modification de l'implémentation de la carte elle-même, augmentant potentiellement le risque de créer des bugs non liés.
  5. Simplicité
    Cependant, dans certains cas, cette approche peut être préférable pour sa simplicité. Si votre composant est très simple et n'a pas besoin des avantages offerts par le pattern de Compound Components, le surcoût en complexité peut ne pas en valoir la peine.

Exemple d’un composant plus complexe ✏️

Supposons maintenant que nous devons créer un composant plus complexe, des composants mêmes, qui ont besoin de travailler ensuite pour mettre en oeuvre une fonctionnalité de Todo-list.

Pour cela, nous allons avoir les composants TodoList (pour afficher une liste d’item de todo), TodoItem (qui représente un item de todo), TodoForm (qui représente le formulaire d’un item de todo) et TodoStats (qui affiche des statistiques pour une liste de todo donnée).

Voilà une implementation de ce que pourrait être ces composants :

// todo-list.tsx

type TodoListProps = {
	todos: Array<{ id: string; content: string }>;
	onPressDelete: (id: string) => void;
}

function TodoList({ todos, onPressDelete }: TodoListProps) {
	return (
		<View>
			{todos.map((todo) => (
				<TodoItem 
					key={todo.id} 
					id={todo.id} 
					content={todo.content}
					onPressDelete={onPressDelete}
				/>
			)}
		</View>
	)
}

// todo-item.tsx

type TodoItemProps = {
	id: string;
	content: string;
	onPressDelete: (id: string) => void;
}

function TodoItem({ id, content onPressDelete }: TodoListItemProps) {
	return (
		<View>
			<Text>{content}</Text>
			<Button label="Delete" onPress={() => onPressDelete(id)} />
		</View>
	)
}

// todo-form.tsx

type TodoFormProps = {
	onPressSubmit: (content: string) => void;
}

function TodoForm({ onPressSubmit }: TodoFormProps) {
	const [value, setValue] = useState<string>('')
	
	return (
		<View>
			<TextInput value={value} onChangeText={setValue} />
			<Button label="Add" onPress={() => onPressSubmit(value)} />
		</View>
	)
}

// todo-stats.tsx

type TodoStatsProps = {
	todos: Array<{ id: string; content: string }>;
}

function TodoStats({ todos }: TodoStatsProps) {
	return (
		<View>
			<Text>Sum of todos: {todos.length}</Text>
			{/* average number of characters */}
			{/* ... */}
		</View>
	)
}


Ainsi que l’usage de ces composants :

// home.tsx

function HomeScreen() {
	const [todos, setTodos] = useState([])
	
	return (
		<View>
			<TodoList 
				todos={todos} 
				onPressDelete={(id) => 
					setTodos((state) => state.filter(todo) => todo.id !== id
				}
			/>
			<TodoStats todo={todos} />
			<TodoForm 
				onPressSubmit={(content) =>
					setTodos((state) => [...state.todos, { id: uuid(), content }])
				} 
			/>
		</View>
	)
}


Observations

Que peux-tu observer sur cet exemple de composant “traditionnel” qui ne représente cette fois une fonctionnalité plus complexe ?

  1. Rigidité
    Dans l'état actuel, la structure est assez rigide. Par exemple, si vous voulez une autre variante de TodoItem qui a un bouton pour marquer une tâche comme terminée, ou peut-être une variante de TodoForm qui a des champs supplémentaires, l'adaptation de ces composants à ces scénarios serait plus complexe.
  2. Passage de props
    Les fonctions de suppression et d'ajout sont transmises en tant que props aux composants enfants TodoItem et TodoForm depuis le composant parent HomeScreen. Cela peut devenir compliqué à gérer à mesure que l'application s'agrandit, car chaque fois que vous voulez utiliser ces fonctions, vous devez les transmettre à travers tous les composants intermédiaires.
  3. Manque d’encapsulation
    Les composants TodoItem et TodoForm exposent trop de détails d'implémentation. Par exemple, TodoItem a besoin de connaître non seulement le contenu de la tâche, mais aussi son id et comment traiter une action de suppression. De même, TodoForm doit gérer son propre état et savoir comment gérer une action de soumission. Cela pourrait être évité avec une version composée qui masquerait ces détails.
  4. Peu extensible et peu lisible
    Ce point est suffisamment explicite je pense !

Le Design Pattern : Compound Components 👀

Le Design Pattern : Compound Components s’applique à n’importe quel langage fonctionnant avec des composants et de la gestion d’états. Il s’agit d’une approche qui offre :

  1. Structure
    Le terme "Compound Components" décrit une relation "a un" entre les composants. Un composant comporte plusieurs sous-composants qui travaillent ensemble pour former une unité cohérente. Le composant parent sert de composant de mise en page tandis que les sous-composants déterminent le contenu.
  2. Flexibilité
    Les Compound Components offrent une grande flexibilité dans l'arrangement des sous-composants. Les utilisateurs de cette API de composant peuvent contrôler l'organisation, la structure et la présentation d'un composant.
  3. Abstraction
    Ils permettent une bonne séparation des préoccupations car chaque sous-composant traite une fonctionnalité particulière. Cela permet une meilleure réutilisation des composants et simplifie le test et la maintenance.
  4. Pas de passage de props
    Un avantage majeur du modèle de Compound Components est l'évitement du prop-drilling, qui est un problème où des props doivent être passés à travers de nombreux niveaux de composants. Les Compound Components résolvent ce problème en utilisant le contexte React pour partager la valeur entre les composants.
  5. Encapsulation
    Avec les Compound Components, nous pouvons exposer ce qui est nécessaire et masquer les détails d'implémentation spécifiques. Cela aide à produire un code plus clair et plus facile à maintenir.

Mise en pratique

Maintenant, voyons ensemble un refactor de nos composants précédents en version Compound Components.

Le composant d’UI simple en Compound Components

Reprenons notre composant d’UI simple et convertissons les props title, description, etc. en sous-composants pour en faire une composition comme suit :

// card.tsx

type CardProps = PropsWithChildren

function Card({ children }: CardProps) {
	return <View>{children}</View>
}

Card.Title = CardTitle
Card.Description = CardDescription
Card.Thumbnail = CardThumbnail
Card.Button = CardButton

// card-title.tsx

type CardTitleProps = {	title: string }

function CardTitle({ title }: CardTitleProps) {
	return <Text>{title}</Text>
}

// card-description.tsx

type CardDescriptionProps = {	description: string }

function CardDescription({ description }: CardDescriptionProps) {
	return <Text>{description}</Text>
}

// card-thumbnail.tsx

type CardThumbnailProps = {	source: string }

function Card({ source }: CardThumbnailProps) {
	return <Image source={{ uri: thumbnail }} />
}

// card-button.tsx

type CardButtonProps = {
	label: string;
	onPress: () => void;
}

function CardButton({ label, onPress }: CardButtonProps) {
	return <Button label={label} onPress={onPress} />
}


Et maintenant l’usage :

// home.tsx

function HomeScreen() {
	return (
		<View>
			<Card>
				<Card.Thumbnail source="https://picsum.photos/200/300" />
				<Card.Title title="Lorem ipsum" />
				<Card.Description 
					description="Quis enim aliqua ad et consectetur laboris reprehenderit ea anim occaecat adipisicing duis exercitation magna cupidatat."
				/>
			</Card>
			<Card>
				<Card.Thumbnail source="https://picsum.photos/200/300" />
				<Card.Title title="Lorem ipsum" />
				<Card.Description 
					description="Quis enim aliqua ad et consectetur laboris reprehenderit ea anim occaecat adipisicing duis exercitation magna cupidatat."
				/>
				<Card.Button label="Lorem" onPress={() => { /* ... */}} />
			</Card>
		</View>
	)
}


Observations

Quelles observations peux-tu faire cette fois ci ?

  1. Flexibilité
    Le Compound Components donne un plus grand contrôle sur l'organisation des éléments dans le rendu. Dans le deuxième exemple d'utilisation, nous avons de l'information supplémentaire et une absence de bouton, ce qui ne serait pas possible avec une version non composée du composant qui limiterait strictement la structure.
  2. Réutilisabilité
    Les sous-composants, comme CardTitle, CardImage, et CardContent peuvent être réutilisés et réarrangés librement. Cette approche réduit la duplication du code et accroît la maintenabilité.
  3. Lisibilité
    Le code est plus facile à comprendre. Alors qu'un composant non composé pourrait avoir un grand nombre de props, ce qui pourrait rendre le code plus difficile à suivre, chaque sous-composant sait clairement quel est son rôle dans le composant de carte.
  4. Isolation
    Les sous-composants (comme CardButton ou CardImage) peuvent être mis à jour indépendamment des autres sous-composants, évitant ainsi les effets de bord inattendus.
  5. Scalabilité
    Les nouveaux sous-composants peuvent être ajoutés facilement en suivant cette approche, permettant au composant de s'adapter et de se développer avec le temps. Par exemple, un sous-composant CardFooter pourrait être ajouté si besoin.

Le composant complexe en Compound Components

Enfin, passons au plus intéressant, le groupe de composant qui représente la fonctionnalité de Todo-list, voilà la version Compound Components :

// home.tsx

function HomeScreen() {
	return (
		<View>
			<Card>
				<Card.Thumbnail source="https://picsum.photos/200/300" />
				<Card.Title title="Lorem ipsum" />
				<Card.Description 
					description="Quis enim aliqua ad et consectetur laboris reprehenderit ea anim occaecat adipisicing duis exercitation magna cupidatat."
				/>
			</Card>
			<Card>
				<Card.Thumbnail source="https://picsum.photos/200/300" />
				<Card.Title title="Lorem ipsum" />
				<Card.Description 
					description="Quis enim aliqua ad et consectetur laboris reprehenderit ea anim occaecat adipisicing duis exercitation magna cupidatat."
				/>
				<Card.Button label="Lorem" onPress={() => { /* ... */}} />
			</Card>
		</View>
	)
}

Ainsi que son usage, drastiquement simplifiée :

// home.tsx

function HomeScreen() {
	return (
		<View>
			<Todos todos={[]}>
				<Todos.List />
				<Todos.Stats />
				<Todos.Form />
			</Todos>
		</View>
	)
}


Observations

Que peux t-on observer sur cette dernière partie ?

  1. Flexibilité d’affichage
    Avec l'approche de Compound Components, la disposition des composants est beaucoup plus flexible. Vous pouvez choisir de rendre Todos.List, Todos.Form, et Todos.Stats dans n'importe quel ordre ou même de ne pas les afficher en fonction des spécificités des spécifications ou des besoins de votre application.
  2. Utilisation du Contexte
    Grâce à l'utilisation de React Context (TodosContext), vous pouvez facilement partager des données (todos) et des fonctions (add, remove) entre tous les composants enfants. Cela permet d'éviter le problème de prop-drilling propre à l'approche non compound components.
  3. Hook personnalisé
    Ils utilisent un hook personnalisé useTodosContext pour obtenir les valeurs du contexte. Ce hook rend le code plus lisible et plus facile à utiliser.
  4. Réutilisabilité accrue
    Les composants sont désormais plus indépendants et peuvent être facilement réutilisés ailleurs dans l'application. Par exemple, Todos.List pourra être utilisé dans un autre écran ou dans une sidebar sans avoir besoin de passer d'informations supplémentaires via les props.
  5. Extensibilité
    Avec cette approche, vous pouvez également étendre facilement le composant Todos en ajoutant des sous-composants supplémentaires sans bouleverser l'architecture existante. Par exemple, si vous voulez ajouter une fonctionnalité pour marquer les tâches comme faites, vous pourriez créer un nouveau sous-composant Todos.Checkbox.

Le mot de la fin 👋

De manière générale, le composant “traditionnel” est plus simple, mais il offre moins de flexibilité et de potentiel de réutilisation que le Compound Components. Le choix entre les deux approches dépend des besoins spécifiques du projet. Mais de mon experience, partir direct sur du Compound Components est rarement une mauvaise idée !

Les inconvénients potentiels de cette approche sont qu'elle est plus complexe et qu'elle nécessite une compréhension plus approfondie des concepts de React (pour le cas de React), tels que le Contexte et les Compound Components eux-mêmes. De plus, il est important de noter que bien que le Context puisse sembler être une solution à tous les problèmes, il doit être utilisé avec parcimonie pour éviter un couplage excessif entre les composants de votre application.

L'adoption du pattern Compound Components dans la conception d'interfaces utilisateur peut sembler déroutante au début, mais les avantages qu'elle offre en termes de modularité, de flexibilité et de réutilisabilité sont indéniables. Ainsi, en décomposant intelligemment les composants en des sous-éléments logiques, nous pouvons produire des systèmes d'UI flexibles, réutilisables et gérables.

Vous pouvez retrouvez cet article au format vidéo sur YouTube en suivant ce lien.

Changer de version Node.js avec NVM : le guide
24/1/2024

Dans l'univers du développement d'applications mobiles, la gestion des versions de Node.js est cruciale. Découvrez ici comment utiliser Node Version Manager (NVM) pour changer, installer des versions LTS, et éviter les conflits. Que vous soyez débutant ou expert, ce guide vous aidera à maîtriser cet outil essentiel.

Comprendre l'importance de Node Version Manager (NVM)

L'utilisation de différentes versions de Node.js peut être délicate. NVM simplifie ce processus en vous permettant de basculer facilement entre les versions. Vous évitez ainsi les conflits et assurez la compatibilité de vos projets.

NVM offre une flexibilité totale pour installer et gérer des versions spécifiques de Node.js en fonction de vos besoins. Son utilisation est simple, même pour les débutants, avec des commandes intuitives. NVM vous protège contre les conflits de versions en isolant les environnements.

NVM garantit que vos projets restent compatibles avec la version de Node.js sur laquelle ils ont été développés. Vous pouvez également mettre à jour vos projets en douceur sans crainte de problèmes de compatibilité.

En automatisant la gestion des versions de Node.js, NVM vous permet de vous concentrer sur le développement plutôt que sur la résolution de problèmes de version. C'est un gain de temps précieux.

En comprenant ces points, vous serez mieux préparé à tirer parti de Node Version Manager (NVM) dans votre travail de développement Node.js.

Installation de Node Version Manager

L'installation de Node Version Manager (NVM) est la première étape essentielle pour gérer vos versions Node.js. Cette section vous guidera à travers le processus d'installation sur différentes plateformes : 

  • Linux : Utilisez la commande wget pour récupérer NVM depuis GitHub. Suivez notre guide étape par étape pour une installation sans faille.

  • macOS : Apprenez comment installer NVM sur macOS en utilisant la commande curl. Suivez nos instructions pour garantir une installation réussie.

  • Windows : Si vous êtes sur Windows, découvrez comment installer NVM en utilisant des outils tels que Git Bash ou Windows Subsystem for Linux (WSL).

Après l'installation, nous vous montrerons comment configurer NVM pour une utilisation optimale. Vous serez prêt à commencer à gérer vos versions Node.js avec aisance.

Familiarisez-vous avec les commandes de base de NVM, telles que nvm --version pour vérifier la version installée, nvm ls pour afficher les versions disponibles et nvm install pour installer une version spécifique.

Suivez les instructions détaillées dans cette section pour installer NVM sur votre plateforme de choix. Vous serez rapidement opérationnel pour gérer vos versions Node.js de manière fluide.

Utilisation de NVM pour installer la dernière version LTS de Node.js

Maintenant que vous avez installé Node Version Manager (NVM), apprenons comment utiliser cet outil pour installer la dernière version LTS (Long Term Support) de Node.js.

Avant de procéder à l'installation, il est essentiel de savoir quelles versions LTS de Node.js sont disponibles. Vous pouvez le faire en utilisant la commande nvm ls-remote --lts.

Suivez ces étapes simples pour installer la dernière version LTS :

  1. Exécutez nvm install --lts pour installer la dernière version LTS disponible.
  2. Pour vérifier que l'installation a réussi, utilisez node --version pour afficher la version de Node.js installée.

Si vous souhaitez que la dernière version LTS soit la version par défaut utilisée par NVM, exécutez nvm alias default <version>.

Grâce à ces étapes simples, vous pouvez désormais installer et utiliser la dernière version LTS de Node.js avec facilité en utilisant Node Version Manager (NVM). Cela vous permettra de bénéficier des avantages de stabilité et de support à long terme pour vos projets Node.js.

Gestion de multiples versions de Node.js avec NVM

La gestion de plusieurs versions de Node.js est une nécessité pour de nombreux développeurs. Voici comment utiliser Node Version Manager (NVM) pour gérer efficacement ces versions.

Pour voir toutes les versions de Node.js installées sur votre système, utilisez simplement la commande nvm ls. Vous obtiendrez une liste claire de toutes les versions disponibles.

Pour basculer entre les versions, utilisez la commande nvm use <version>. Cela changera votre environnement de développement pour utiliser la version spécifiée.

Vous pouvez également créer des alias pour des versions spécifiques avec nvm alias <alias> <version>. Cela simplifie encore la gestion des versions.

Avec ces commandes simples, vous pouvez gérer facilement plusieurs versions de Node.js sur votre système, en utilisant Node Version Manager (NVM). Cela vous permet de maintenir la compatibilité de vos projets et de travailler sur des versions spécifiques selon vos besoins.

Mettre à jour Node.js avec NVM

Maintenir votre installation Node.js à jour est crucial pour bénéficier des dernières fonctionnalités et correctifs de sécurité. Voici comment effectuer des mises à jour en utilisant Node Version Manager (NVM).

Pour vérifier si des mises à jour sont disponibles, exécutez nvm ls-remote <version>. Cela affichera les versions de Node.js disponibles à la mise à jour.

Pour mettre à jour Node.js vers la dernière version LTS disponible, utilisez nvm install --lts. Cela installera la dernière version LTS sans affecter vos versions précédentes.

Si vous avez une version spécifique que vous souhaitez mettre à jour, utilisez nvm install <version> pour obtenir la dernière version de cette branche.

En utilisant ces commandes simples, vous pouvez maintenir votre installation Node.js à jour avec facilité, en garantissant que vos projets sont toujours optimisés en termes de performances et de sécurité.

Exemples pratiques et code source

Dans cette section, nous explorerons quelques exemples pratiques d'utilisation de Node Version Manager (NVM) avec des extraits de code source pour une meilleure compréhension.

Exemple 1 : Installation de Node.js LTS

Une image contenant capture d’écran, texte, Police, GraphiqueDescription générée automatiquement

Cette commande installe la dernière version LTS de Node.js.

Exemple 2 : Basculer vers une version spécifique

Une image contenant texte, capture d’écran, Police, GraphiqueDescription générée automatiquement

Utilisez cette commande pour basculer vers une version spécifique de Node.js (dans cet exemple, la version 14.17.6).

Exemple 3 : Créer un alias pour une version

Une image contenant texte, capture d’écran, Police, GraphiqueDescription générée automatiquement

Créez un alias pour définir une version spécifique de Node.js comme version par défaut.

Exemple 4 : Vérifier les versions installées

Une image contenant capture d’écran, texte, GraphiqueDescription générée automatiquement

Cette commande affiche la liste des versions de Node.js installées sur votre système.

Exemple 5 : Mise à jour de Node.js

Une image contenant texte, capture d’écran, PoliceDescription générée automatiquement

Mettez à jour Node.js vers la dernière version (ici, la version actuelle) tout en conservant les packages de la version précédente.

Utilisez ces exemples pratiques et extraits de code source pour mieux comprendre comment utiliser NVM dans vos projets Node.js. Cela vous aidera à gérer efficacement les versions et à optimiser votre environnement de développement web.

Les meilleures pratiques de gestion des versions Node.js

Pour tirer le meilleur parti de Node Version Manager (NVM) et maintenir un environnement de développement Node.js efficace, suivez ces meilleures pratiques :

  • Gardez NVM à jour : Pensez à mettre à jour régulièrement NVM pour bénéficier des dernières améliorations et corrections de bogues.
  • Utilisez les versions LTS : Privilégiez les versions LTS (Long Term Support) pour une stabilité à long terme. Cela garantit que vos projets restent stables et sécurisés.
  • Créez des alias significatifs : Lors de la création d'alias pour des versions spécifiques, choisissez des noms significatifs pour vous faciliter la gestion.
  • Documentez vos projets : Tenez un journal des versions utilisées pour chaque projet afin de garantir une compatibilité continue.
  • Gérez les dépendances avec npm : N'utilisez pas NVM pour gérer les dépendances de vos projets. Utilisez npm pour gérer les packages Node.js spécifiques à chaque projet.
  • Restez informé : Suivez les annonces de nouvelles versions Node.js et les mises à jour de sécurité pour rester au courant des dernières avancées.

En suivant ces meilleures pratiques, vous optimiserez votre gestion des versions Node.js avec NVM et garantirez la stabilité, la sécurité et la facilité de gestion de vos projets.

Dans cet article, nous avons exploré en détail l'utilisation de Node Version Manager (NVM) pour la gestion des versions Node.js. Que vous soyez un développeur expérimenté ou que vous découvriez Node.js, NVM est un outil essentiel pour maintenir un environnement de développement propre et efficace.

Nous avons abordé les étapes clés, de l'installation de NVM sur différentes plateformes à la gestion de multiples versions de Node.js, en passant par les mises à jour et les meilleures pratiques. En utilisant NVM, vous pouvez facilement basculer entre les versions, maintenir la compatibilité de vos projets et garantir la sécurité de votre environnement de développement.

Les exemples pratiques et les extraits de code source ont été fournis pour vous aider à mieux comprendre comment utiliser NVM dans vos projets. En suivant les meilleures pratiques recommandées, vous pouvez maintenir un environnement de développement Node.js optimal.

En fin de compte, Node Version Manager (NVM) est un outil puissant qui facilite grandement la gestion des versions Node.js. Il vous permet de rester à jour avec les dernières versions, d'adapter vos projets aux besoins spécifiques et de maintenir un flux de travail de développement efficace. Intégrez NVM dans votre boîte à outils de développement Node.js dès aujourd'hui pour une expérience de développement plus fluide et plus productive.

Maintenant que vous savez correctement utiliser NVM, nous vous invitons à consulter notre guide complet sur l’outil de gestion de versions Git.

Le multi-tenant, un indispensable pour une solution SaaS
23/12/2023

Lorsque l’on développe une solution SaaS, il est nécessaire de bien penser son architecture, surtout si à l’avenir vous réfléchissez déjà à faire découpler plusieurs instances de celle-ci.

Pour imager, prenons pour exemple un site e-commerce.

Vous pouvez faire le choix de partir sur une architecture simple pour votre MVP, avec tout simplement votre boutique à Paris, mais dès lors où le besoin d’avoir plusieurs boutiques se présente, plusieurs questions vont venir à vous.

Ces questions pourraient concerner :

La gestion du stock : est-elle centralisée ? Y-a-t’il un stock par boutique ?

La gestion des produits : est-ce que chaque boutique est indépendante, est-ce qu’elle a ses propres produits ?

La gestion des utilisateurs : est-ce que je stocke les données utilisateurs par boutique ? Est-ce que j’ai une base commune d’utilisateurs ?

Toutes vos réponses vont impacter la façon dont vous allez mettre en place le multi-tenant.

Le multi-tenant

Vous l’avez compris, on parle de multi-tenant lorsque l’on doit gérer plusieurs contextes dans une application, si l’on devait reprendre notre exemple précédent on considèrerait chaque boutique comme un contexte.

Architecture single-tenant vs multi-tenant

Gestion en single-database

La gestion du multi-tenant au moyen d'une seule base de données présente plusieurs avantages significatifs.

Architecture multi-tenant single-database

Tout d'abord, elle simplifie considérablement la maintenance car il n'y a qu'une seule base a gérer en cas de bugs ou de restauration des backups.

De plus, la base de données demeure relativement simple à gérer avec l'utilisation d'un champ tenant_id (store_id) pour distinguer les différents tenants.

Cela offre un avantage financier car il n'y a pas de surcoût au niveau de l'infrastructure.

L'approche du multi-tenant avec une seule base de données comporte également certains inconvénients notables.

Dans le cas de l'utilisation d'un Framework PHP tel que Laravel ou Symfony par exemple, l'adaptation des packages de la communauté ainsi que des requêtes SQL est nécessaire, ce qui peut entraîner des coûts de développements supplémentaires.

En effet, il faudra ajouter un critère à chaque requête pour spécifier le bon tenant à utiliser, un oubli entraînerait des conséquences assez importantes.

De plus, la centralisation des données peut rendre la restauration de données complexe si on a besoin de restaurer les données pour un tenant précis.

Gestion en single-database multi-schema

Une alternative possible dans l'implémentation du multi-tenant consiste à attribuer à chaque tenant ses propres tables au sein de la base de données.

Architecture multi-tenant multi-schema

Cette approche offre une isolation accrue et la gestion des données s'en retrouve simplifiée. Tout comme pour l'implémentation précédente, la restauration des données reste simple. En adoptant cette approche, on ajoute donc une séparation des données de tenants.

Cependant, cette approche présente également quelques inconvénients.

La nécessité de restaurer un tenant spécifique peut être plus compliquée, car il faut sélectionner individuellement chacune des tables lors du backup ou lors de la restauration.

De plus, à mesure que le nombre de tenants augmente, le nombre de tables associées peut devenir considérable, ce qui risque de compliquer la gestion à long terme.

Si des modifications sont apportées à la structure d'une table, chaque table dupliquée pour chaque tenant doit être mise à jour individuellement.

Cela rend également la gestion des migrations compliquées avec des frameworks comme Laravel ou Symfony puisqu'ils n'ont pas été prévu à cet effet.

Gestion en multi-database

L'utilisation du multi-tenant avec une base de données spécifique par tenant offre plusieurs avantages.

Architecture multi-tenant multi-database

Une simplicité côté développement, où il suffit de spécifier quel tenant est utilisé sans adaptations complexes de packages ou de requêtes SQL. L'implémentation est donc plus rapide et le code plus facile à maintenir.

Pour le backup et la restauration, il suffit de le faire sur la base de données du tenant.

On peut optimiser les performances en ajustant les ressources allouées à chaque tenant en fonction de ses besoins.

C’est également le schéma idéal si dans un projet chaque tenant correspond à un site client et que ces clients souhaitent une confidentialité et isolation de leur données.

Et pour les désavantages de cette implémentation, on peut avoir plus de serveurs ou plus de base de données à maintenir, il faut avoir quelques bases côté infrastructure pour mettre en place et configurer les environnements et le coût d'infrastructure sera plus conséquent.

Conclusion

Chaque architecture a ses avantages et inconvénients, la décision devra se prendre en fonction de vos besoins, de vos coûts, de l’effectif de votre équipe et de nombreux facteurs qui composeront la pérennité de votre projet.

Sur le Framework Laravel, plusieurs packages existent pour gérer le multi-tenant. Si on devait en opposer deux, le package Laravel Multitenancy de Spatie propose une implémentation simple et légère qu’il faudra agrémenter de “Tasks” selon le mode de gestion que vous allez choisir, tandis que le package Tenancy d’ArchtechX propose plutôt une architecture plus complexe qui répond à un maximum de besoins avec plus d'opinion.

Il est primordial de s’intéresser à chacune des solutions existantes et de créer des POCs avant de se lancer tête baissée dans l’implémentation du multi-tenant.

Et vous ? Lequel de ces packages choisiriez-vous ?

Si vous hésitez encore pas de panique ! Nous étudierons sans doute plus en détails les différences dans un prochain article.

Échangeons sur votre projet !

Développement web
Application mobile
Design & Product
Nous contacter

Simulateur

Bienvenue dans le
simulateur d’estimation

Sélectionnez
vos besoins

Sélectionnez un ou plusieurs choix

Définissez les
fonctionnalités

Sélectionnez un ou plusieurs choix

Dernière
étape !

Renseignez votre adresse mail pour recevoir l’estimation !
Obtenez l’estimation
Précédent
Suivant

Bravo ! Vous avez terminé
l’estimation de votre future app !

Vous recevrez dans votre boite mail l’estimation personnalisé. Une estimation vous offre la possibilité de vous projeter dans un budget, vous permettant ainsi de planifier en toute confiance. Néanmoins, chez Yield, nous adoptons une approche agile, prêts à remettre en question et ajuster nos évaluations en fonction de l'évolution de vos besoins et des spécificités de votre projet.
Retour au site
Oops! Something went wrong while submitting the form.