La configuration d'un environnement de développement efficace constitue l'une des étapes les plus cruciales lors de la création d'applications web robustes. Un bon setup ne se limite pas à une simple installation de bibliothèques et de frameworks, mais nécessite également une prise en compte des pratiques de développement, des outils d'automatisation et de gestion des versions. Dans cette optique, l'usage des technologies modernes telles qu'Angular, Firebase, Docker et les principes de développement agile sont devenus des incontournables pour les développeurs souhaitant obtenir une productivité maximale tout en maintenant une qualité élevée du code.
Le premier élément à prendre en compte est la sélection du framework et des outils appropriés. Angular, par exemple, reste un choix populaire pour les applications web complexes grâce à ses directives puissantes et à son architecture modulaire. Il offre une approche cohérente pour organiser le code avec des composants réutilisables et un système de gestion des états intégré comme NgRx. L'intégration avec des outils tels qu'Apollo Client pour la gestion des données via GraphQL, ou Firebase pour l'authentification et la gestion des utilisateurs, permet de simplifier l'interaction avec les backends tout en offrant des solutions scalables.
Dans le cadre de la configuration du serveur, l'utilisation d'un service d'authentification, qu'il soit basé sur des JWT (JSON Web Tokens) ou Firebase, est essentielle. L'authentification doit être sécurisée et scalable, ce qui implique souvent l'ajout de services de proxy d'authentification abstraits. De plus, la gestion du cache et l'implémentation de mécanismes comme les gardes d'authentification permettent de garantir que seules les personnes autorisées accèdent à certaines ressources ou pages.
Les pratiques d'automatisation sont également au cœur de cette configuration. L'utilisation d'outils comme Docker et des pipelines CI/CD (Intégration Continue / Déploiement Continu) permet de réduire les erreurs humaines et de garantir une livraison continue du produit avec un minimum de friction. La containerisation des applications via Docker offre une méthode fiable et reproductible pour déployer des applications sur divers environnements. La configuration de DockerHub pour publier et partager des images Docker, ainsi que la gestion des versions de ces images, assure une maintenance simplifiée.
Dans le cadre de l’optimisation des performances, il est essentiel de prendre en compte la compilation en Ahead-of-Time (AOT) et le rendu du côté serveur (SSR) pour des applications Angular. Ces techniques permettent de minimiser le temps de chargement initial des pages et d’améliorer l’expérience utilisateur. Le recours à des mécanismes de pré-chargement des données (comme les "resolvers" dans Angular) et l’optimisation des appels API garantissent également une meilleure performance tout au long de l'utilisation de l'application.
Il est également nécessaire d'aborder les tests automatisés et leur intégration dans le cycle de développement. Les tests unitaires, réalisés à l'aide de frameworks comme Jasmine ou Jest, sont cruciaux pour valider le bon fonctionnement des composants et des services. Les tests de bout en bout (e2e) avec des outils comme Cypress ou Selenium permettent de vérifier que les flux utilisateurs et les interactions fonctionnent comme prévu dans un environnement proche de la production. Ces tests doivent être intégrés aux pipelines CI/CD pour une vérification constante à chaque modification du code.
Les développeurs doivent également maîtriser l’utilisation des outils de gestion des versions comme Git et de plateformes comme GitHub pour faciliter le travail en équipe. L’utilisation de GitHub Flow, qui permet d'intégrer facilement les branches de développement dans la branche principale via des Pull Requests, assure une gestion fluide du code tout en respectant les bonnes pratiques de collaboration. L’automatisation du processus de déploiement via des outils comme CircleCI ou GitLab CI peut également être mise en place pour garantir une livraison constante et fiable du code en production.
Il convient aussi de mentionner l'importance de l’UX (expérience utilisateur) dans le cadre de la configuration d'un projet. Des outils comme les directives de formulaire dynamiques, l’utilisation de contrôles personnalisés, et la gestion des erreurs au sein des formulaires permettent d’offrir une interface utilisateur fluide et réactive. Les aspects de l'interface, comme la gestion des listes de données ou la pagination, peuvent être optimisés pour s’adapter aux besoins spécifiques des utilisateurs tout en maintenant une performance élevée.
La gestion des erreurs et des performances reste également primordiale. L’optimisation du code pour réduire la consommation de ressources, l'amélioration des temps de réponse via des pratiques comme l’utilisation de l'AOT et la gestion dynamique du cache permettent de fournir des applications plus réactives et de répondre aux attentes des utilisateurs modernes. L’utilisation de patterns comme le flux de données (Flux) ou le modèle d’état unidirectionnel assure une gestion des états claire et prévisible dans les applications complexes.
En résumé, un setup réussi passe par une approche intégrée de la gestion des environnements de développement, du contrôle de version, de l’authentification sécurisée et des bonnes pratiques de déploiement continu. L’objectif est de créer un environnement dans lequel les développeurs peuvent travailler efficacement, en collaborant aisément et en produisant du code de qualité. La maîtrise des outils et des principes modernes permet non seulement de simplifier les processus de développement, mais aussi de garantir une meilleure maintenabilité et une évolution pérenne des applications.
Comment éviter le piège de la surcharge d'ingénierie et d'une réingénierie coûteuse dans le développement d'applications
Dans le développement logiciel, la trajectoire d'une application dépend d'un équilibre entre sous-ingénierie et sur-ingénierie, où chaque décision architecturale peut avoir un impact significatif sur la viabilité du projet. Les applications de petite taille, souvent développées pour répondre à des besoins spécifiques et limités, commencent par une architecture relativement simple. Cependant, au fur et à mesure de l'évolution de l'application, le manque de flexibilité dans cette structure peut entraîner une accumulation de risques. Le coût pour ajuster cette base initiale devient alors élevé, nécessitant un effort de réingénierie considérable pour remettre l'application sur la bonne voie.
D'autre part, les grandes applications d'entreprise débutent avec une sur-ingénierie marquée par une architecture complexe dès le départ, anticipant une évolution rapide et une demande croissante de fonctionnalités. À mesure que la complexité du système augmente avec le temps, il arrive que même une architecture solide se retrouve inadaptée à de nouveaux besoins ou à un nombre plus grand d'utilisateurs. Cette situation est comparable à celle des applications de grande envergure destinées à des milliards d'utilisateurs, où la complexité architecturale doit être soigneusement gérée pour éviter une surcharge de travail inutile et coûteuse.
Dans cet environnement dynamique, l'objectif n'est pas de prévoir tous les besoins futurs mais d'adopter une approche flexible et modulaire. L'évolution des applications petites vers des applications dites "Line of Business" (LOB) est un phénomène courant. Une petite application peut se transformer, parfois de manière imprévisible, en une application plus vaste et plus complexe, souvent utilisée à des fins spécifiques dans un environnement professionnel. Ce processus peut entraîner une inefficacité dans la solution livrée, malgré les efforts fournis. Inversement, une application d'entreprise peut devenir sous-utilisée lorsque ses fonctionnalités initiales ne sont plus nécessaires ou ignorées par les utilisateurs.
Dans ce contexte, il devient crucial de concevoir une architecture qui soit suffisamment robuste pour répondre aux besoins immédiats tout en étant flexible pour évoluer. La règle du 80-20 est un principe utile : il s'agit de trouver un juste milieu entre flexibilité et fonctionnalités, tout en évitant une surcharge d'ingénierie. Une architecture "Router-first", concept que j'ai introduit en 2018, permet de maintenir un coût d'ingénierie optimal, tout en évitant une réingénierie coûteuse ou des phases de rush en fin de projet pour intégrer toutes les fonctionnalités demandées.
Le développement d'une application implique souvent des compromis entre les besoins à court terme et les attentes à long terme. Si les applications développées pour des projets personnels ou passionnés sont parfois sous-ingénierées, les applications professionnelles, en revanche, tendent à être sur-ingénierées. Lorsqu'un projet devient un succès, l'entretien et l'ajout de nouvelles fonctionnalités peuvent rapidement devenir coûteux, si l'architecture initiale n'a pas été correctement pensée.
En revanche, le développement d'applications au sein d'une équipe exige une approche disciplinée. L'innovation et l'expérimentation, bien qu'importantes, doivent se faire avec précaution. Tester de nouvelles approches dans un code de production peut entraîner des risques, notamment si les membres de l'équipe n'ont pas tous une expertise homogène. Les pratiques doivent être adaptées à l'équipe et à son environnement, afin de garantir une collaboration fluide et d'éviter de créer des frictions inutiles. Cette discipline permet de réduire le nombre de bogues et d'assurer que les équipes travaillent dans une direction commune.
L'architecture d'une application doit donc être conçue pour soutenir non seulement les besoins à court terme, mais aussi pour permettre une évolution souple et économique. La stratégie idéale consiste à maintenir une surcharge d'ingénierie minimale tout en assurant que l'architecture est extensible. Cela permet de pivoter en fonction des besoins futurs sans avoir à réécrire une grande partie du code existant. L'architecture "Router-first" incarne cette approche, permettant d'introduire des modifications de manière incrémentielle et de gérer la complexité sans sacrifier la flexibilité.
Dans un tel cadre, il est essentiel de tenir compte de plusieurs facteurs clés lors de la conception de l'architecture : la taille de l'application, la raison de son développement, les compétences des développeurs, la livraison incrémentielle des fonctionnalités, ainsi que les coûts opérationnels et la cybersécurité. La capacité d'anticiper ces éléments dans le processus de conception permettra de minimiser les risques et d'assurer une évolution fluide du projet.
Finalement, la mise en œuvre d'une architecture "Router-first" suit un processus structuré en sept étapes essentielles, allant de la définition de la feuille de route à l'optimisation de la réutilisation du code. Chaque étape repose sur une vision claire et une compréhension approfondie des besoins de l'application. Cela nécessite non seulement une planification minutieuse mais aussi une volonté de maintenir une discipline dans l'exécution, afin de garantir une architecture à la fois flexible et performante.
Comment organiser les modules et optimiser la performance d'une application Angular avec le chargement paresseux
Dans la conception d'une application Angular, la notion de modules est primordiale pour garantir une architecture propre et bien structurée. Un module racine, interchangeable avec AppConfig, représente le point d'entrée de l'application, tandis que des modules supplémentaires, appelés modules de fonctionnalité, permettent de diviser l'application en sous-ensembles indépendants. Cette organisation facilite non seulement la gestion du code, mais aussi l'optimisation des performances, notamment grâce à des techniques comme le chargement paresseux.
Angular autorise une seule instance du module racine pour toute l'application. Tous les autres modules, souvent appelés "modules de fonctionnalité", sont des modules enfants ou secondaires qui viennent enrichir le noyau sans l'alourdir directement. L'idée de modules parent-enfant se reflète aussi dans d'autres éléments d'Angular, comme les routes : une route principale (ou racine) peut avoir des routes enfants qui chargeront des modules de fonctionnalité au besoin. Ces modules peuvent être chargés dynamiquement, ce qui permet de ne charger que les ressources nécessaires pour afficher un écran spécifique.
Le concept de "lazy loading" (chargement paresseux) joue ici un rôle crucial. Cette approche consiste à diviser l'application en modules qui ne sont chargés qu'en fonction des actions de l'utilisateur. Cela permet de réduire la taille initiale de l'application et de ne télécharger que ce qui est nécessaire au moment où c'est requis, améliorant ainsi les temps de réponse et l'expérience utilisateur.
Le chargement paresseux permet aussi une séparation des préoccupations entre les différentes fonctionnalités de l'application. Par exemple, si l'application dispose de plusieurs types d'utilisateurs ayant des rôles différents (comme un gestionnaire ou un spécialiste de la saisie de données), les modules peuvent être configurés de manière à ne charger que les éléments pertinents pour chaque utilisateur. Cela minimise les ressources inutilisées, tout en accélérant le processus de chargement.
En termes d'implémentation, les modules de fonctionnalité permettent de regrouper les composants et services associés. Chaque module possède ses propres dépendances et ses propres services, qui sont définis explicitement dans ses importations, déclarations ou fournisseurs. Cela garantit qu'un module peut être développé de manière autonome, sans interférer avec les autres, ce qui favorise la collaboration entre équipes de développement. Cependant, il est important de bien comprendre que lorsque des services sont fournis au niveau des modules ou des composants, cela peut entraîner la création de plusieurs instances du même service si ces derniers sont injectés dans différents contextes, ce qui peut perturber le système d'injection de dépendances.
Une stratégie complémentaire consiste à utiliser des composants autonomes (standalone components) qui ne nécessitent pas l'intégralité de l'initialisation d'une application Angular pour être utilisés. Ces composants peuvent être également chargés de manière paresseuse, ce qui offre une flexibilité accrue dans la gestion des ressources.
Cependant, la mise en place de telles architectures nécessite une planification minutieuse. Développer une feuille de route et définir le périmètre du projet dès les premières étapes est essentiel pour assurer une structuration logique et fonctionnelle de l'application. Il est recommandé de commencer par des maquettes, des wireframes ou des prototypes interactifs afin de mieux définir les fonctionnalités de l'application et d'identifier les priorités. Lors de la conception de l'architecture, il ne faut pas tomber dans le piège de vouloir perfectionner les détails dès le départ, mais plutôt se concentrer sur les aspects fondamentaux et établir un consensus autour de ces derniers.
En parallèle de la gestion de la structure des modules, il est primordial de penser à l'optimisation des performances dès la phase de conception. Des outils comme les bibliothèques d'interface utilisateur (UI libraries) ou les assets statiques peuvent alourdir considérablement une application, en particulier sur les appareils mobiles, qui représentent une grande part de l'utilisation d'Internet aujourd'hui. Il convient donc de différer le chargement des ressources non critiques à l'aide du mécanisme de chargement paresseux, ce qui permet d’améliorer la réactivité de l’application, surtout sur mobile.
Il est aussi essentiel d’adopter une approche "stateless" dans la conception de l'application, notamment pour la gestion des données. En adoptant une architecture sans état, avec des outils comme NgRx, il est possible de gérer l'état de l'application de manière prévisible et immuable. Cela permet d'éviter les effets de bord et de simplifier la gestion de l'état au fur et à mesure que l’application se développe.
Pour que cette architecture fonctionne de manière optimale, il est important de concevoir une "squelette de marche" (walking skeleton) dès les premières étapes du projet. Ce prototype simplifié permet de tester rapidement l'interface de navigation et de vérifier si les flux de travail sont cohérents. Une fois ce prototype en place, les équipes peuvent travailler sur les modules de manière indépendante tout en ayant une vision commune du produit final. Cela facilite l'intégration ultérieure des différents composants, sans trop de risques d'erreurs ou d'incompatibilités.
Finalement, bien que l’organisation modulaire et le chargement paresseux soient des outils puissants pour l'optimisation, il est crucial de ne pas sous-estimer l’importance d’une bonne communication entre les équipes de développement, ainsi qu'une documentation claire et à jour pour toutes les décisions prises tout au long du processus de développement. Ces étapes permettent de garantir la cohérence de l'architecture et d'assurer la maintenabilité du code à long terme.
Comment structurer une API Express avec des versions et des résolveurs GraphQL ?
Dans le développement moderne d'applications web, la gestion d'APIs efficaces et évolutives est cruciale. Que ce soit pour des systèmes REST ou GraphQL, il est essentiel de comprendre comment structurer et versionner ces interfaces pour garantir une évolutivité continue et la compatibilité avec les anciennes versions. Cette gestion rigoureuse est particulièrement importante lorsque les utilisateurs ou clients interagissent avec des APIs qui évoluent au fil du temps. Cette section explore les bases de la configuration des APIs, leur versionnement, et l'usage des résolveurs GraphQL pour optimiser les appels réseau et garantir une architecture stable.
L'intégration des middleware dans Express permet une gestion facile et flexible des requêtes HTTP, et ce à différentes étapes du cycle de demande-réponse. Ces middleware, comme CORS, les parseurs JSON, les journaux et la compression, sont insérés dans la pile d'exécution à l'aide de la méthode use(). Une fois configurés, ces middleware exécutent des actions spécifiques à chaque étape : depuis la gestion des en-têtes CORS pour les demandes cross-origin, jusqu'à la compression des réponses pour améliorer la performance.
L'architecture de l'application est mise en place à l'aide de routes Express. Dans l'exemple suivant, on constate l'utilisation d'un fichier app.ts qui configure des routes statiques ainsi que des middlewares nécessaires pour gérer des requêtes HTTP simples :
Dans ce code, l'utilisation des middlewares est claire et efficace : on commence par activer CORS, puis les parseurs d'Express pour traiter le JSON et les formulaires URL-encodés. Ensuite, on configure un système de journalisation avec logger('dev') pour surveiller les requêtes pendant le développement. Enfin, la fonction express.static permet de servir un répertoire statique pour afficher les informations liées au serveur à l'utilisateur.
Versionner les APIs : un impératif pour une compatibilité future
Le versionnement d'une API est un élément central pour garantir la stabilité de l'application à mesure que celle-ci évolue. Le passage d'une version d'API à une autre ne doit pas impacter les clients qui utilisent la version précédente. Le code ci-dessous montre un exemple de configuration de routes versionnées dans api.ts :
Dans cet exemple, deux versions d'API sont configurées : v1 et v2. L'importance de versionner les APIs dès leur lancement est évidente. En effet, une fois qu'une API devient publique, il est difficile, voire impossible, de la modifier ou de la retirer sans perturber les clients qui l'utilisent. Le versionnement permet de continuer à faire évoluer l'API sans affecter les utilisateurs existants, tout en permettant aux nouveaux utilisateurs d'adopter les dernières évolutions.
Implémentation des Routes REST
L'implémentation des routes REST dans les APIs versionnées se fait facilement à l'aide de Router dans Express. Chaque version peut ainsi définir des routes et leurs comportements spécifiques, comme le montre l'exemple suivant pour la version v2 :
La syntaxe /users? permet à la fois de gérer les requêtes destinées à /user ou /users, offrant ainsi plus de flexibilité sans introduire de complexité inutile. L'usage de cette syntaxe évite les erreurs de typographie tout en respectant la logique de l'application.
Les routes de type GET, POST, PUT et DELETE sont ensuite définies dans userRouter.ts, où des paramètres de route tels que userId peuvent être facilement extraits à l'aide de req.params. En outre, l'utilisation des fonctions async et await garantit que les appels aux bases de données se déroulent correctement dans un environnement asynchrone.
GraphQL et les Résolveurs : une gestion fine des données
GraphQL, un autre paradigme pour les APIs, permet de structurer les requêtes de manière plus flexible et granulaire. Contrairement à REST, où chaque appel de route peut entraîner plusieurs allers-retours au serveur, GraphQL permet de demander uniquement les données nécessaires, avec un seul appel réseau. Les résolveurs GraphQL jouent un rôle essentiel dans ce processus en récupérant les données pour chaque champ demandé dans la requête.
Voici un exemple de définition de résolveurs dans un fichier resolvers.ts :
Chaque résolveur reçoit quatre arguments : parent, args, contextValue, et info. Ces arguments sont utilisés pour extraire les informations pertinentes, gérer l'authentification ou manipuler les données avant de les retourner. Les résolveurs pour les types non-scalaires ou les énumérations sont également nécessaires pour formater les données récupérées avant de les envoyer au client.
Dans l'exemple de type User, le résolveur id transforme l'identifiant de l'utilisateur en une chaîne de caractères :
Séparer la logique métier dans des services
La logique métier complexe ne doit pas être intégrée directement dans les routes. Au contraire, elle devrait être encapsulée dans des services distincts pour assurer une meilleure modularité, réutilisabilité et testabilité du code. Les services permettent d'isoler les appels aux bases de données et de simplifier la gestion des opérations métier.
Voici un exemple simple d'un service qui crée un nouvel utilisateur dans userService.ts :
Cette fonction est ensuite utilisée dans le userRouter pour répondre à la requête de création d'utilisateur :
Cela permet de garder la logique de la gestion des utilisateurs propre et isolée, tout en maintenant les routes légères et concentrées uniquement sur la gestion des requêtes HTTP.
Comment Angular révolutionne le développement d'applications d'entreprise évolutives ?
Angular est un projet open-source soutenu par Google et une communauté de développeurs, offrant une plateforme complète et flexible pour la création d'applications web à grande échelle. Contrairement à son prédécesseur AngularJS, Angular 2+ a radicalement changé la donne en matière d'architecture et de principes de développement. En partenariat avec Microsoft, Google a intégré TypeScript comme langage par défaut pour Angular. TypeScript est un sur-ensemble de JavaScript qui permet aux développeurs de cibler des navigateurs plus anciens comme Internet Explorer 11 tout en tirant parti des fonctionnalités modernes disponibles dans les navigateurs actuels tels que Chrome, Firefox et Edge.
AngularJS, la version précédente, était un cadre monolithique pour les applications à une seule page (SPA), tandis qu'Angular 2+ a évolué en une plateforme polyvalente qui peut viser aussi bien les navigateurs, les applications hybrides mobiles, les applications de bureau que les vues rendues côté serveur. Cette transition marque une nette amélioration de l'écosystème Angular, offrant plus de flexibilité et une plus grande portée.
L'une des principales améliorations d'Angular par rapport à AngularJS réside dans la gestion des mises à jour. Autrefois, migrer d'une version à l'autre de AngularJS était une tâche coûteuse et risquée. Chaque mise à jour, même mineure, introduisait des changements dans les modèles de codage et des fonctionnalités expérimentales, ce qui obligeait les équipes de développement à réécrire de grandes portions de code. De plus, la fréquence des mises à jour rendait les ressources nécessaires à une mise à niveau imprévisibles. Avec Angular, Google a adopté une approche plus prévisible, mettant en place des versions majeures tous les six mois. Chaque nouvelle version reçoit une prise en charge prolongée de 18 mois, avec des mises à jour de sécurité et des corrections de bugs. Cela offre aux développeurs la tranquillité d'esprit, sachant que leur code restera compatible et sera soutenu pendant une période déterminée.
La gestion des versions d'Angular repose sur le principe de "semver" (versionnement sémantique), qui garantit que les changements mineurs n'introduisent pas de ruptures incompatibles. Cette méthode prévient les désagréments liés aux changements soudains dans l'API et permet aux équipes de planifier efficacement les mises à jour. Par exemple, si vous avez développé une application sous Angular 17, vous pouvez être assuré qu'elle restera compatible avec la version 18 pendant une période de 24 mois. L'adoption de ce cycle de mise à jour garantit que les équipes ne se retrouvent pas coincées dans une version héritée, ce qui était souvent le cas avec AngularJS.
Une autre caractéristique marquante d'Angular est son processus de mise à jour simplifié. L'introduction de l'outil ng update dans Angular 6 a grandement facilité le processus de mise à jour des versions, réduisant le temps nécessaire pour la migration entre les versions majeures. En fait, des entreprises telles qu'Air France et KLM ont réussi à réduire leur temps de mise à jour de 30 jours dans Angular 2 à seulement un jour dans Angular 7, un exploit rendu possible grâce à des outils automatisés et des tests rigoureux. Cette amélioration continue permet aux développeurs de maintenir leurs applications à jour sans se heurter à des obstacles majeurs.
L'une des raisons pour lesquelles Google et Microsoft offrent ces technologies gratuitement est leur désir de stimuler l'innovation tout en attirant et en retenant des talents dans l'industrie du développement. Une plateforme sophistiquée comme Angular permet non seulement de renforcer l'image technique de ces entreprises, mais aussi de bénéficier des contributions d'une vaste communauté mondiale de développeurs. En retour, cela génère de la valeur commerciale pour Google et Microsoft, notamment en créant des expériences web exceptionnelles qui attirent davantage d'utilisateurs et de clients.
Cependant, il est essentiel de noter que la communauté Angular continue d’évoluer. La distinction entre AngularJS et Angular peut parfois prêter à confusion, surtout pour les nouveaux venus. Bien que la version moderne d’Angular se distingue nettement de son prédécesseur, il reste crucial pour les développeurs d'être vigilants sur les spécificités des versions. Par exemple, le changement d'architecture avec le moteur de rendu Ivy a eu un impact majeur sur les versions d'Angular supérieures à la 14. Ainsi, il est primordial de toujours se référer à la documentation officielle pour s'assurer que les informations et conseils suivis sont en adéquation avec la version en cours de développement.
Dans cette optique, Angular se distingue également par sa capacité à maintenir une large compatibilité tout en évoluant rapidement. Pour ceux qui envisagent d'apprendre Angular, il est important de comprendre que bien qu'il existe d'autres frameworks populaires comme React ou Vue, Angular présente des avantages uniques en matière de prise en charge à long terme et de ressources disponibles. Ces avantages ne se limitent pas seulement à sa robustesse, mais également à la capacité de la communauté à répondre à des défis complexes grâce à une documentation complète et des outils adaptés.
Dans un environnement de développement où les choix technologiques sont cruciaux, Angular se positionne comme un investissement de long terme pour les équipes de développement. Les mises à jour régulières, la stabilité et la compatibilité en font un choix judicieux, non seulement pour les grandes entreprises, mais aussi pour les petites équipes qui souhaitent investir leur temps dans une technologie prometteuse et bien supportée.
Quelles sont les technologies à l'horizon de la transition énergétique, et comment contribuent-elles à la décarbonation ?
Le terrorisme d'extrême droite : pourquoi reste-t-il une menace sous-estimée ?

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский