Depuis l’apparition de .Net Core la galaxie, ou devrais-je dire la jungle, des frameworks Microsoft a beaucoup évolué. Le constat est assez clair : sans une vraie curiosité pour l’actualité Microsoft et une volonté de creuser l’information, il n’est pas facile de s’y retrouver dans les différentes versions : Framework, Standard, Core.
Cet article aura pour principal objectif de revenir en détail sur l’architecture du monde .Net puis de dresser les conséquences et Best Practices pour un développeur.
Nous verrons même qu’il est possible de travailler en faisant cohabiter tout ce petit monde.
From Yesterday to Now
Le monde d’avant
J’ai toujours considéré que le C# d’abord, et le framework .NET plus globalement, étaient d’excellents outils. Ils sont faciles d’accès (par là j’entends qu’ils ont un coût d’entrée relativement faible) et peuvent en même temps se révéler particulièrement riches et complexes. Ils permettent d’élaborer des composants logiciels extrêmement performants et pratiques.
La richesse du framework .NET passe par le nombre important de terminaux pour lesquels nous pouvions produire une solution, les ordinateurs et serveurs utilisant un OS Microsoft avec le framework .NET, les appareils mobiles utilisant le framework .NET compact, les serveurs web en utilisant l’ASP.NET, la programmation pour mobile et les applications Windows Store, si on s’en tient à l’écosystème Microsoft. A cela il faut ajouter Mono et Xamarin qui nous ont permis de développer pour Android et les appareils de l’écosystème Apple (macOS et iOS). En faisant attention, il était même possible de passer une partie de son code d’un environnement vers un autre : après compilation, tout fonctionnait correctement.
L’inconvénient majeur de ce modèle est que chacun de ces environnements dispose de son propre runtime et de sa propre implémentation du framework. C’est là que les choses se corsent. En effet, il faut pouvoir garantir une homogénéité à tout le monde, même si des équipes différentes sont en charge des évolutions. De la même façon il faut pouvoir satisfaire la demande de chacun de ces modèles applicatifs si on veut pouvoir continuer à tous les faire vivre.
Dès lors que l’on prend conscience de cette hétérogénéité, on comprend rapidement à quel point cela peut être difficile d’apporter des évolutions dans le framework. D’autant plus que l’évolution de l’informatique tend à faire émerger de nouveaux modèles applicatifs (le Cloud par exemple) alors que certains n’étaient pas couverts (Linux).
Welcome to the new world
Ce monde en silo a finalement commencé à bouger (à mon sens) après l’arrivée du framework 4.5.1 avec les annonces du nouveau Jit (RyuJit) et de Roslyn, la nouvelle plateforme de compilation. Le mouvement vers une meilleure unification a pu ainsi commencer. C’est d’ailleurs peu après que l’on a commencé à voir apparaître le terme de .NET Core.
L’idée est d’abord de pouvoir fournir un écosystème identique quelle que soit la machine qui fait tourner une application. Évidemment cela passe par l’utilisation d’un seul et même compilateur que ce soit au build (Roslyn) ou à la volée (RuyJit). Cela passe également par une factorisation du code existant pour faire émerger les contrats et API nécessaires au bon fonctionnement des outils et ce quelles que soient les machines utilisées.
Tout en haut de cette pile, se tiennent les API relatives aux modèles applicatifs. Elles sont bâties au-dessus des composants nommés Base Class Library (BCL) qui sont le résultat du travail de factorisation nécessaire à ce travail d’homogénéisation. Ensuite on trouve les couches permettant le fonctionnement du runtime.
En plus de cette construction, il a été choisi de rendre disponible l’intégralité des composants à travers NuGet. C’est un choix assez naturel dans l’écosystème Microsoft qui pousse au bout le raisonnement de déploiement. En effet, auparavant les frameworks étaient disponibles par la mise à jour de Windows ou la distribution d’exécutables spécifiques ce qui pouvait être problématique en entreprise si on prend en compte la politique relative à la sécurité des SI.
Désormais le scénario de déploiement est différent, puisque les packages sont inclus lors de la phase de développement. Plus besoin de se poser la question de la version du framework disponible sur les machines clientes. Cela facilite d’autant cette phase qui peut se révéler parfois délicate. Il est même possible d’avoir un exécutable spécifiant le runtime cible avec l’option de build –runtime et en précisant le runtime cible. On peut se référer au catalogue de runtime pour plus de détails.
De plus, la BCL est en fait constituée de nombreuses petites unités qui peuvent toutes être trouvées et déployées de façon unitaire grâce à NuGet. Ceci facilite les mises à jour des composants et plus généralement la maintenance des projets. Il est donc possible de n’embarquer que les éléments réellement utiles à l’application afin de réduire le volume à déployer.
Finalement, nous arrivons à la situation suivante :
La situation est désormais plus homogène au niveau de l’infrastructure (compilateur, runtime, etc.). Cependant on note qu’il reste encore une importante partie en silo.
. Net Standard
Le .NET Standard a poussé encore plus loin le raisonnement précédent. Dans les trois silos présentés au-dessus, on note que chacun dispose de sa propre librairie de base (BCL pour le framework, Core library pour le .NET Core et Mono Class library pour Mono). Seulement, de nombreux composants sont communs à toutes ces librairies.
On peut donc définir différentes API et implémentations qui pourront être utilisées par ces trois silos. Cela présente l’avantage de réduire la surface d’adhérence au modèle applicatif (et à la plateforme ciblée). Cela augmente d’autant la réutilisabilité du code tout en facilitant (encore une fois) les scénarios de déploiement.
C’est un travail de standardisation important afin de fournir les garanties nécessaires de disponibilité et de consistance des fonctionnalités du .NET. En d’autres termes, le .NET standard est le contrat minimal garanti sur n’importe quelle plateforme pouvant faire tourner une application .NET.
Au niveau applicatif, ça ne change pas grand chose finalement, puisqu’on travaillera le plus vraisemblablement avec les API des modèles applicatifs ciblés. Le développement des librairies portables (PCL) sera lui impacté dans le sens où l’on travaillera le plus souvent directement avec les class du .NET Standard. Dans le cas contraire, pour être vraiment portable il faudra traiter le cas de tous les modèles applicatifs que l’on peut cibler en .NET (#IF).
Comme on peut le voir sur le schéma ci-dessus, le .NET standard est lui livré dans une seule librairie, un meta package Nuget (plus d’informations sur les meta packages). Cette philosophie du “une seule pour les gouverner toutes” peut paraître étonnante avec la politique engagée dans le .NET Core (qui lui est packagé en plusieurs petits paquets). En fait, il s’agit encore une fois de faciliter les scénarios de déploiement et plus particulièrement d’update des librairies.
En effet, une application utilisant du .NET Core va embarquer un certain nombre de librairies pouvant être mises à jour indépendamment les unes des autres. L’inconvénient est que la dépendance entre les librairies se fait aussi par numéro de version ce qui aurait rapidement pu se révéler être un casse-tête si le .NET Standard était lui aussi divisé en plusieurs petites librairies. Plus le numéro de version est grand et plus l’API du .NET Standard propose d’éléments.
Un update de librairies requérant une version plus évoluée de la librairie .NET Standard ne devrait donc pas poser de problème aux autres librairies (qui seront rétro-compatibles avec la nouvelle version).
A savoir aussi : pour nos librairies, on pourra cibler au choix la librairie .NET Standard ou des librairies portables (PCL) ciblant une version de la librairie .NET Standard compatible avec notre librairie (la cible de notre librairie est d’une version antérieure ou égale à celle de notre PCL).
Versionning et compatibilité
Le versionning et la compatibilité entre les modèles applicatifs et le .NET Standard peuvent être trouvés sur le github du projet. Au moment où j’écris ces lignes voici ce que l’on y trouve :
New .NetPlatform().InPractice()
Comme nous l’avons vu sur le papier tout ceci est bien préparé et très alléchant. Personnellement, je développe sous Windows (dans le milieu pro et perso) et sous Mac (au niveau perso) et tout fonctionne très bien.
C’est sous Mac que mon expérience est la plus intéressante je trouve. J’utilise aussi bien Visual Code que Visual Studio en faisant appel à du .NET Core. J’ai pu migrer les projets que je voulais dans ces versions et continuer tranquillement les développements. Voir un précédent article pour plus d’informations sur les points et les liens que j’ai utilisés pour faire ces migrations. Et honnêtement jusqu’à présent je n’ai rencontré aucune difficulté. J’ai pu basculer mon code sous Windows sans aucun problème.
Nuançons un peu ce propos en soulignant que mes projets perso n’ont absolument pas la taille que l’on peut rencontrer dans le milieu pro. Cela dit, je pense que le travail de migration ne sera pas plus difficile mais plus fastidieux (dû à la quantité).
Quand le Core ? quand le Framework ?
La question de l’outillage se pose tout naturellement. Maintenant que j’ai le choix entre le .NET Framework et le .NET Core, que choisir ? En fait je préfère formuler cette question ainsi : par quel angle aborder le processus de choix ? Pour moi cela passe par les éléments critiques qui peuvent influer directement sur la prise de décision.
Utiliser le .Net Core
Vous devrez considérer le .NET Core si vous ciblez un environnement multiplateforme, puisque c’est fait pour ça !!! Mais aussi si vous visez une architecture en microservice et/ou des conteneurs de type Docker.
Le microservice peut d’ailleurs être utilisé pour migrer plus progressivement vers du .NET Core. Si on part d’un système monolithique, on commencera par diviser ce système en sous-systèmes beaucoup plus légers et on commencera à les développer un par un afin de remplacer petit à petit l’ensemble des fonctionnalités. La façon de migrer un système monolithique est en soit le sujet d’un article à part entière, voire de bien plus, mais l’idée est là.
Le .NET Core bénéficie aussi de beaucoup d’améliorations par rapport au framework classique. Il est également particulièrement performant comparativement au marché comme on peut le voir sur le banc d’essai techempower ou dans ce bench Kestrel vs Iris go. Nous ferons d’ailleurs un prochain article sur ce sujet avec pour base un benchmark entre Kestrel et NodeJs. Il est donc particulièrement intéressant de l’utiliser dans les scénarios où la performance est importante.
Utiliser .NET Framework
Pour le .NET Framework, il faut être pragmatique : dans le cas d’une application existante, il faut avoir une très sérieuse raison pour migrer. Il reste bien entendu possible d’étendre les fonctionnalités en utilisant le .NET Core.
Une migration peut être également compromise par le recours à des technologies non disponibles en .NET Core (WPF, WF par exemple mais également le WinForms, les WebForms etc…). Il faut aussi prendre en compte que tout n’a pas encore été porté en .NET Core (on peut avoir une idée du reste à faire ici). De plus, certaines plateformes Azure exigent .NET Framework et donc ne supportent pas (encore ?) .NET Core. Il faut donc être prudent vis à vis des technologies déjà utilisées.
D’ailleurs, si dans l’écosystème Microsoft tout ne supporte pas encore le .NET Core, c’est également le cas des librairies tierces disponibles sur NuGet. En effet, même si les librairies adoptent rapidement le .NET Standard afin de pouvoir être partagées sur toutes les plateformes le supportant, toutes ne l’ont pas fait. Cette migration sera encore plus encouragée avec le .NET Standard 2.0 qui offre un mode de compatibilité entre .NET Core et .NET Framework.
Cohabitation Core/Framework
Le .NET Standard 2.0 introduit de nombreuses choses nouvelles, de nouvelles API, de nouvelles fonctionnalités et parmi elles un mode de compatibilité avec les librairies .NET Framework. Pour expliquer son fonctionnement, revenons sur un point évoqué précédemment : j’ai dit que les librairies .NET Standard que nous créerons dans nos projets peuvent référencer des librairies .NET Standard ou des librairies portables (PCL). En fait depuis le .NET Standard 2.0, elles peuvent également référencer des librairies .NET Framework.
On peut trouver le détail du fonctionnement de ce mécanisme dans les spécifications du .NET Standard 2.0. Le cas d’utilisation est simple, permettre de faire coexister des dll du .Net Framework et du .Net Standard (au sein du même projet).
Conclusion
Le .NET Standard constitue donc la nouvelle base sur laquelle nous pouvons bâtir les différents éléments constitutifs de nos applications. A partir de lui, nous pouvons soit bâtir des librairies standard, soit utiliser une technologie nécessitant l’utilisation de ce dernier. Ce qui est intéressant c’est que désormais même des technologies plus anciennes que le .Net Standard se sourcent sur ce dernier (.NET Framework, Mono etc…).
Le .NET Core constitue lui le pendant du .NET Framework en version multiplateforme. Il garantit la portabilité de la façon la plus simple possible (déployer et lancer) tout en fournissant des fonctionnalités plus avancées que le .NET Standard mais aussi plus spécifiques. Il fournit en particulier la possibilité de développer en utilisant le ASP .NET Core et l‘UWP (Universal Windows Platform). Personnellement, je ne connais pas l’UWP, et je n’ai donc aucune idée de la qualité des clients que l’on peut créer avec lui. Cependant grâce à lui (entre autres), il est tout à fait envisageable de développer une application complète : serveur, microservice, client léger ou lourd.
Microsoft fournit un bel effort avec le .NET Standard 2.0 et le .NET Core. On sent que beaucoup de choses ont évolué rapidement et dans le bon sens (c’est mon avis). Il est désormais possible d’entreprendre de plus en plus de choses en .NET sans contrainte de plateforme comme cela a pu être le cas. Bien entendu, ces évolutions entrent dans la stratégie de Microsoft de s’inscrire plus durablement dans le futur technologique. Mais je dois bien avouer que je suis plutôt content de pouvoir en bénéficier, d’autant plus que je suis un utilisateur de Mac pour mon usage personnel et que je fais désormais tous mes projets personnels en .NET Standard et .NET Core.
Espérons que cela continue dans ce sens !!
Vos commentaires
Bonjour,
Suite à des impératifs historique, nous souhaiterions pouvoir exploiter la couche métier de notre application (en .Net 4.0) et être consommé par une Dll intermédaire en Standard 2.0, qui elle même sera consommé par un projet final en Core 2.0.
Toute fois, je n’arrive pas à faire en sorte que le projet en Standard 2.0 arrive à consommer notre couche métier(en .Net 4.0). J’ai essayé d’ajouter le TargetFramework :
netstandard2.0;net40
Mais j’ai toujours une erreur :
Error NU1201 Project [Intermédiaire] is not compatible with net40 (.NETFramework,Version=v4.0). Project [Intermédiaire] supports: netstandard2.0 (.NETStandard,Version=v2.0)
Je ne sais pas si je suis claire…
Merci d’avance
Benjamin
L’erreur NU1201 dit simplement que le projet en .NET Framework v4.0 n’est pas compatible avec du .Net Standard 2.0. Tu peux avoir le tableaux compatibilité minimal entre .NET Standard et .Net Framework ici : https://docs.microsoft.com/fr-fr/dotnet/standard/net-standard.
A ma connaissance, je dirais qu’il y a deux solutions :
– vous migrer la lib en framework 4.0 vers du 4.6.1
– si ce n’est pas possible, vous encapsuler la lib métier dans un service métier et vous communiquez par message.
J’espère que ça pourra t’être utile.