Publié par

Il y a 7 mois -

Temps de lecture 37 minutes

Maven vs SBT : quel outil de build choisir pour vos projets Scala en entreprise ?

    Maven ou SBT ? Au-delà du déclenchement d’une nouvelle Holy War pour distraire les développeurs, cette question est l’une des premières qui se pose lorsqu’un nouveau projet Scala est amorcé. Les débutants en Scala choisiront probablement naïvement SBT, poussé par Lightbend (ex-Typesafe) comme outil de build par défaut de Scala, tandis que les développeurs venant de Java n’auront d’yeux que pour Maven, pour des raisons évidentes d’habitude et de maîtrise.

De plus en plus utilisé en entreprise, Scala est un langage tout-terrain : surtout poussé par la multiplication de projets data avec Spark, il peut aussi bien être utilisé pour réaliser des back-ends utilisant des frameworks comme Play, Scalatra ou Akka. Son approche hybride (orientée objet et fonctionnelle) fait de Scala un langage de plus en plus apprécié par les développeurs. Mais il reste autour de ce langage des difficultés d’outillage, parfaitement représentées par le choix de l’outil de build.

Différentes études montrent que les 3 outils de build les plus utilisés sont – dans cet ordre – SBT, Maven et Gradle (ce dernier ne sera pas couvert dans cet article). Le terme « outil de build » est d’ailleurs un peu réducteur par rapport à leur rôle, comme nous le verrons dans la suite.

Sur des petits projets personnels, le choix de cet outil n’est pas réellement le sujet principal ; en revanche, en entreprise, ce choix est primordial, dû aux différentes contraintes que l’on peut rencontrer (environnements de développement, maintenabilité, qualité, processus de déploiement, intégration…).

Cet article propose de revenir sur les différents avantages et inconvénients de chacun de ces outils, en y apportant mon avis grâce à des situations rencontrées. La comparaison se fera entre des versions récentes au moment de l’écriture de cet article : Maven 3.5.2 (2017-10-24) et SBT 1.1.1 (2018-02-09).

Qu’est-ce-qu’un bon outil de build ?

Comme indiqué en introduction, le terme « outil de build » est un peu simpliste, puisque ces outils ne se contentent pas de simplement compiler le projet : gestion des dépendances, packaging, déploiement, … sont aussi leurs caractéristiques principales. Pour choisir le meilleur outil correspondant à notre besoin, il faut déjà connaître les caractéristiques qui le définissent. Les articles suivants en donnent un très bon aperçu (pas forcément limité à l’écosystème Java/Scala) :

Tous les points évoqués dans ces articles en particulier peuvent être regroupés sous différents aspects :

  • Développement : expérience utilisateur, intégration (IDE, environnement), apprentissage, automatisation des tests
  • Maintenabilité : conventions, maturité, reproductibilité/portabilité
  • Documentation : exemples, communauté, utilisation
  • Extensibilité : écosystème, plugins
  • Déploiement

Outre ces aspects, tous les développeurs s’accordent à dire qu’un bon outil de build fait ce qu’on lui demande, sans trop s’occuper de lui; la majorité du temps devant être consacrée à développer du code « métier », et non pas à configurer l’outil. Le respect de la règle « convention over configuration » est pour moi une part très importante dans le choix d’un outil de build. C’est d’ailleurs l’un des principaux arguments de Maven et de SBT.

Avant de comparer Maven et SBT sur les points listés ci-dessus, commençons déjà par présenter ces 2 outils.

Présentation de Maven et SBT

Je vais présenter ici un bref historique de chacun des outils, en présentant rapidement les avantages et inconvénients, sans entrer dans le détail ni les comparaisons avancées (pas d’inquiétude, la section « Points de comparaison » arrive !).

Maven

« Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central place. »

https://maven.apache.org/what-is-maven.html

Maven est devenu le standard de facto pour les projets Java, remplaçant progressivement Ant en y apportant de nouvelles fonctionnalités (conventions, système plus déclaratif, gestion des dépendance…). Maven se base sur un fonctionnement simple : des cycles de vie (par défaut 3 : clean, default et site), chacun composé de phases (pour default, on retrouve compile, test…), et à chaque phase est associé un ou plusieurs goals de plugins. C’est le POM (Project Object Model) qui définit ce mapping (pom.xml). Avec Maven, tout est configuré à l’aide de plugins.

Historique

Versions notables :

Version Date Description
1.0 07/2004 Version initiale de Maven, créée pour simplifier le build du projet Jakarta Turbine (framework web) basé alors sur Ant
2.0 10/2005 Réécriture complète de Maven 1
3.0 10/2010 Garantit la rétro-compatibilité avec Maven 2

La liste complète des releases Maven est disponible sur la page history.

Les +

  • mature : battle-testé, largement utilisé, bien connu
  • s’intègre bien avec les outils de CI en entreprise
  • bien documenté : tous les plugins ont la même forme pour la documentation, générée avec mvn:site, ce qui facilite la lecture et la recherche d’informations
  • style déclaratif (XML)
  • pour Scala, le plugin scala-maven-plugin écrit par David Bernard est très efficace

Les –

  • verbeux : la syntaxe XML peut vite devenir illisible au fur et à mesure que le pom.xml grossit
  • certains plugins pour Scala (portés depuis SBT) ont des comportements différents de leurs équivalents SBT
  • la configuration des plugins propres à Scala peut être difficile (par exemple scalatest-maven-plugin pour les tests d’intégration, la désactivation de Surefire pour ne pas lancer les tests en double…)
  • pas très flexible : les plugins sont configurables, mais les comportements sont parfois limités

SBT

« sbt is a build tool for Scala, Java, and more. sbt uses a small number of concepts to support flexible and powerful build definitions. »

https://www.scala-sbt.org/1.x/docs/

Créé à la base pour fournir à Scala un outil de build, SBT a ensuite évolué pour prendre en compte d’autres langages comme Java. L’objectif initial était de proposer un outil configurable et simple d’utilisation pour Scala, à une époque où Scala n’était pas ou très peu outillé.

SBT fonctionne sur un système de tâches définies (tasks et settings) entièrement configurables, contrairement aux phases prédéfinies de Maven. Il est aussi possible de définir des tâches personnalisées. La configuration se fait à l’aide d’un DSL Scala : chaque tâche est décrite sous forme de code.

Historique

Versions notables :

Version Date Description
0.3.2 12/2008 Première version de SBT (alors simple-build-tool) annoncée par Mark Harrah sur ce thread, exclusivement pour Scala
0.13.0 08/2013 Passage à Scala 2.10 (entre autres)
1.0.0 08/2017 Beaucoup de modifications, notamment le passage à Scala 2.12 (entre autres)

Au cours de son existence, le nom « SBT » a eu plusieurs significations; d’abord, simple-build-tool; ensuite, scala-build-tool, et désormais SBT, sans signification particulière (orphan initialism).

SBT est un projet maintenu par Lightbend (Lightbend Tooling Team), sous le lead d’eed3si9n depuis 2014 (départ de Mark Harrah), également auteur de nombreux plugins.

Les +

  • s’intègre bien avec Scala
  • pour des petits projets, pas besoin de build.sbt, SBT est capable de reconnaître le code
  • « much faster iterative compilation » (utilise Zinc pour la compilation)
  • gère la cross-compilation nativement : capable de compiler facilement vers différentes versions de Scala (2.10, 2.11, 2.12, …)
  • exécution des tâches en parallèle : par exemple, si 3 tâches A, B et C sont définies telles que B et C dépendent de A, alors B et C sont exécutées en parallèle

Les –

  • syntaxe : difficile à appréhender et cryptique
  • maturité (voir la liste des releases)
  • système de plugins confus
  • difficile à maintenir : plusieurs façons de faire la même chose
  • configuration non-triviale au fur et à mesure que le projet grossit

État des lieux : utilisation de Maven et SBT en Scala

Je vous propose les résultats d’une étude réalisée le 14/04/2018 à propos de l’utilisation de Maven, SBT et Gradle :

  • à Xebia France
  • sur Github

Ces résultats seront complétés par une étude Jetbrains de 2017 et une recherche de questions concernant ces outils sur StackOverflow. Ces résultats sont bien sûr valables seulement à la date de réalisation de l’étude.

Xebia France

À Xebia France, un sondage auprès des développeurs Scala a montré que 13 d’entre eux utilisaient ou préféraient SBT, tandis que 7 utilisaient Maven et 1 seul Gradle :

Github

Dans le monde open-source, une étude réalisée sur les projets Github en Scala et populaires (>= 200 stars, >= 500 stars, >= 1000 stars) a montré les résultats suivants :

Ces résultats montrent une utilisation accrue de SBT (environ 87%).

Notons aussi qu’il existe des projets qui utilisent plusieurs outils de build (ex. : apache/spark avec Maven et SBT). Il est intéressant de remarquer que les projets liés à la fondation Apache utilisent majoritairement Maven ou Gradle.

Il faut bien sûr nuancer ces résultats car les projets open-source populaires ne sont pas forcément représentatifs de l’utilisation des outils de build en entreprise.

Étude JetBrains

La « developer survey ecosystem » réalisée en 2017 montre les résultats suivants :

Source : JetBrains Developer Ecosystem Survey 2017

StackOverflow

Une utilisation de la recherche avancée sur StackOverflow montre les résultats suivants (recherche des questions taggées [sbt]+, et [maven]+, avec le nombre de questions non répondues) :

De cette dernière étude il n’y a pas forcément de conclusions à tirer : étant donné qu’une majorité de personnes utilisent SBT avec Scala, il est donc logique que les questions étiquetées SBT+ Scala soient plus nombreuses. Si on corrèle ces résultats avec l’utilisation de Maven et SBT suivant l’étude de Jetbrains, on peut malgré tout en conclure que par rapport au nombre d’utilisateurs, il y a plus de questions posées sur SBT que sur Maven.

Conclusion

De cette étude on voit clairement que SBT est le premier choix pour les projets Scala. Mais pourquoi ? D’après moi, ces résultats peuvent être expliqués par une seule raison : SBT est dans la tête des développeurs l’outil de build par défaut de Scala. Prenons n’importe quel guide/tutoriel pour apprendre le Scala ou pour creuser certaines fonctionnalités, il y a une très forte probabilité que SBT soit l’outil mis en avant.

De plus, SBT est souvent utilisé pour son aspect « nouveauté » : en effet, bien que SBT soit né en 2008, sa vraie expansion a commencé depuis 2013 avec la version 0.13. Ainsi, tous les développeurs apprenant Scala depuis cette époque (notamment les ex-développeurs Java qui étaient habitués à Maven) ont été attirés naturellement par SBT par l’aspect « nouvel outil de build » (notamment chez les développeurs déçus par Maven).

Dans le reste de l’article, Gradle ne sera plus abordé pour son usage encore trop confidentiel à ce jour dans la communauté Scala.

Disclaimer

La comparaison de Maven et SBT sur les points décrits ci-dessous se base en grande partie sur les différentes lectures que j’ai pu faire, sur des discussions avec d’autres développeurs, mais aussi sur les différentes expériences que j’ai pu avoir (j’ai manipulé SBT et Maven depuis respectivement leurs versions 0.13.6 et 3.0.5). Je pense donc avoir assez éprouvé ces 2 outils pour me permettre d’émettre un jugement.

J’insiste aussi sur le fait que la comparaison se base sur les projets Scala en entreprise, donc dans des contextes particuliers que j’ai pu rencontrer, toujours d’après mon expérience (de start-ups à grosses entreprises).

L’objectif de cet article est d’aider les personnes qui hésitent entre ces outils et qui sont à la recherche de points concrets pour aiguiller leurs choix. Le jugement émis n’est bien entendu valable qu’au moment de la publication de l’article, et ne saurait refléter l’évolution de chacun des outils.

Points de comparaison

Il a été très difficile de séparer les points de comparaison entre Maven et SBT puisque certains sont liés : par exemple, la rapidité d’exécution est plus ou moins liée à la résolution de dépendances, elle-même liée à la configuration, elle-même liée à la syntaxe, etc. J’ai donc essayé de séparer au mieux en différentes catégories pour que la lecture et la recherche d’informations soit les plus claires possibles (en ajoutant parfois des références entre les sections lorsque cela est nécessaire).

Prise en main

La prise en main initiale de SBT est plutôt simple. En effet, au début d’un projet, un build.sbt de quelques lignes est souvent suffisant (voire aucun build.sbt). Cependant, au fur et à mesure que le projet grossit, il est courant de devoir se plonger dans la documentation pour atteindre des comportements personnalisés, et c’est là que la pente devient plus raide. La moindre modification devient alors difficile à mettre en œuvre. L’abondance de concepts à apprendre peut devenir un frein au bon déroulement du projet, puisqu’il est parfois nécessaire de se dégager du temps à prendre en main l’outil de build plutôt que se concentrer sur l’application.

Côté Maven, le débutant peut-être dérouté dans un premier temps par la syntaxe verbeuse en XML et les différents plugins à attacher à chaque phase, mais le modèle reste assez simple à appréhender : plusieurs phases exécutées de manière séquentielle, des plugins associés à chaque phase, et une/plusieurs configurations par plugin.

L’aspect « quelques lignes de configuration nécessaires » de SBT est très séduisant, à comparer avec un bootstrap de projet avec Maven. Cependant, avec l’évolution du code, le build doit être modifié, et la prise en main des concepts de SBT devient alors plus compliquée. Beaucoup de concepts ne sont pas forcément utiles, et il sera très difficile pour le débutant de comprendre le design global de SBT.

Documentation

La documentation côté Maven est éprouvée : cela va de pair avec la maturité. Une grande majorité des documentations des plugins sont sous le même format (format Maven site), et il est très simple de trouver en un coup d’œil les différents goals d’un plugin, sa configuration, etc.

Pour SBT, bien que la documentation ait beaucoup évoluée depuis la version 1, elle propose encore une quantité de concepts théoriques, parfois ésotériques, et qui, je dois l’avouer, n’ont pas grand intérêt lorsque l’on a besoin d’aide sur un cas précis. L’absence d’exemples concrets dans la documentation est en effet très frustrante, et on se retrouve rapidement à devoir se plonger dans des explications plus ou moins vagues sur des concepts n’ayant pas forcément d’intérêt évident (par exemple les scopes) pour pouvoir configurer la moindre chose. Au sein même de la documentation officielle il est difficile de naviguer facilement et de trouver rapidement son besoin.

La multiplication des versions de SBT complique aussi la tâche. Souvent une solution valide en 0.13 n’est plus valide en 1.0, ou vice-versa. Les ressources concernant SBT sur les blogs ou forums deviennent vite obsolètes.

Syntaxe

Défenseurs ou pas de Maven, tous les développeurs s’accordent à dire que ce dernier est verbeux. La syntaxe XML n’est pas des plus simples à manipuler, et on se retrouve rapidement avec des pom.xml faisant plusieurs centaines de lignes. Cependant, le XML tant décrié garantit à Maven son côté déclaratif, ce qui fait la différence avec SBT.

En effet, le DSL (Domain Specific Language) de SBT est impératif, et sa syntaxe peut parfois paraître occulte. Malgré tout, cette syntaxe s’est améliorée depuis les versions 0.13, où :

  • en 0.13.6, les sauts de lignes étaient nécessaires
  • les opérateurs <<=, <+=, <++= ont été dépréciés en 0.13– mais remplacés par :=, +=, ++= qui, à mon sens ne sont pas forcément plus clairs

Cependant, la principale reproche faite à la syntaxe de SBT reste son côté « magique ». Sa concision peut rendre illisible la configuration (voir Maintenabilité), et tous les opérateurs ne sont pas forcément utiles et peuvent entraîner de la confusion. L’exemple de %% pour tirer une dépendance liée à la bonne version de Scala est symbolique de ceci.

Avec SBT, on ne sait pas toujours quelles variables sont accessibles dans notre scope (par exemple, libraryDependencies, disponible sans rien importer), ou bien pourquoi certaines variables sont déclarées lazy, d’autres mutables (alors que le DSL de SBT est basée sur Scala). SBT use et abuse des macros et de la redéfinition d’opérateurs, ce qui gêne fortement la lisibilité au prix d’une concision injustifiée. La magie n’a pas forcément sa place dans la configuration d’un outil de build.

Finalement, le DSL de SBT propose une approche fonctionnelle, mais y a-t-il vraiment un intérêt à utiliser la programmation fonctionnelle pour configurer un outil de build ?

Confort développeur

Interactivité

SBT propose un mode interactif qui peut être utile lors du développement. Ce mode permet aussi de réduire l’overhead de démarrage de la JVM liée à SBT, mais perd son intérêt lorsque l’on modifie une dépendance du projet puisqu’il est nécessaire de faire un reload. De plus, il est courant qu’en mode interactif la JVM tombe sans raison particulière au bout de quelques heures de développement sur de gros projets, souvent provoqué par une OutOfMemoryError lorsque le forking n’est pas configuré.

SBT offre la possibilité de recompiler automatiquement (ou de lancer n’importe quelle tâche automatiquement grâce au mécanisme de triggered execution avec ~), mais une nouvelle fois encore son intérêt est limité puisque la plupart du temps, le build automatique se configure dans l’IDE. Finalement, SBT permet de bénéficier d’un REPL Scala (sbt console) contenant dans son classpath toutes les classes du projet courant.

Ces comportements sont aussi reproductibles avec Maven, grâce au plugin scala-maven-plugin :

  • mvn scala:cc pour la recompilation automatique
  • mvn scala:cctest pour le lancement des tests automatique
  • mvn scala:console pour le REPL Scala, avec le MOJO console

Maven ne propose pas de mode interactif, mais cela n’est en réalité pas très utile : en général, la majorité des builds/tests pendant le développement sont lancés depuis l’IDE, et dans la phase d’intégration continue, le mode interactif n’est pas utilisé. Chaque commande Maven (une commande peut être composée de plusieurs phases, exécutions de plugins, …) est lancée dans sa propre JVM, ce qui garantit aussi une meilleure reproductibilité.

Bootstrap de projet

Pour bootstraper un projet Scala rapidement, Maven propose les archetypes. De son côté, depuis la version 0.13.13, SBT permet de reproduire ce comportement grâce aux templates et à Giter8. À titre d’information, sbt-extras permettait auparavant de faire la même chose, mais n’était pas intégré nativement dans SBT.

Intégration avec l’IDE

L’intégration entre Maven et les principaux IDE (Eclipse, IntelliJ, NetBeans) est éprouvée et fonctionne très bien. En revanche, avec SBT, les plugins ne sont pas toujours au point (sous IntelliJ du moins), et souvent le compilateur de l’IDE se mélange avec le/les SBT installé(s) sur le système. C’est un cas que l’on peut retrouver en compilant avec SBT, puis avec IntelliJ. De plus, si votre IDE est un peu trop vieux, n’espérez pas faire fonctionner SBT correctement.

Rapidité

La rapidité aurait pu être un sous-chapitre lié au confort développeur. 3 aspects notables jouent sur la rapidité :

  • le lancement de l’outil
  • la résolution des dépendances
  • la compilation

Lancement de l’outil

Le start-up de SBT est très lent, et c’est pour cette raison que l’usage interactif est recommandé. Cependant, comme expliqué plus tôt, lorsque le build est lancé depuis un outil de CI, cela n’a pas d’intérêt. Maven lance une JVM par commande, mais l’overhead de lancement reste moins élevé.

Résolution des dépendances

Pour ce qui est de la résolution de dépendances (voir Gestion des dépendances), SBT souffre de lenteurs (comme le démontre cette issue qui, même si elle est marquée comme résolue, est toujours d’actualité au vu des commentaires). Si la cached resolution (mise en place depuis 0.13.7) améliore un peu les choses, cela ne corrige pas tout : à chaque modification des dépendances, toutes les dépendances doivent être résolues (« There have been prior efforts to cache the result of library dependencies, but it still resulted in full resolution when libraryDependencies has changed. »). Le plugin coursier améliore un peu la rapidité de résolution des dépendances, mais n’est pas intégré nativement dans SBT.

Compilation

SBT propose un parallélisme natif. En effet, les différentes tâches définies sont ordonnancées de telle manière que plusieurs tâches indépendantes puissent être exécutées de manière parallèle.

Maven propose une exécution séquentielle des différentes phases (validate, initialize, generate-sources… pour le cycle de vie par défaut par exemple). Depuis Maven 3, il est possible de préciser une option -T et de spécifier le nombre de threads pour paralléliser le build. Les différentes phases sont toujours exécutées de manière séquentielle, mais ce paramètre permet de diminuer significativement le temps de compilation des projets multi-modules (dans le cas où tous les modules ne sont pas tous dépendants les uns des autres, on peut espérer un gain de 40 %).

La compilation incrémentale des classes Scala est un des avantages revendiqués par Lightbend pour SBT. Ce mécanisme de compilation incrémentale est rendu possible par le module Zinc, qui a été extrait du corps de SBT dans un projet indépendant depuis la version 1.0. Cependant, depuis la version 3.1.0, le plugin Maven scala-maven-plugin embarque Zinc et propose l’option incremental pour bénéficier de cette compilation incrémentale.

Configuration

Maven et SBT proposent 2 paradigmes différents pour configurer le build : déclaratif pour Maven, impératif pour SBT. Comme pour les langages, ces 2 approches possèdent des avantages et inconvénients, mais le côté impératif de SBT le rend plus propice aux bugs : à partir du moment où on introduit du code, on peut introduire des bugs. L’expressivité supplémentaire que SBT acquiert en ayant la possibilité d’écrire facilement du code se paye souvent au détriment de la maintenabilité. En général, l’extension avec du code est souvent le signe d’un smell : si ce que vous souhaitez faire n’est pas réalisable facilement dans l’outil de build, il faut parfois prendre du recul et se demander si ce besoin est primordial.

La configuration de Maven pour certains plugins liés à Scala peut être difficile ou du moins délicate (pour scalatest-maven-plugin, où il faut désactiver surefire, ou bien pour scoverage). Cette « difficulté » de configuration vient avant tout du fait que ces plugins ont été conçus à la base pour SBT, et sont d’ailleurs plus activement maintenus pour leur pendant SBT (voir par exemple le plugin sbt-scoverage contre scoverage-maven-plugin).

Maintenabilité

La maintenabilité est une caractéristique que l’on peut décrire par plusieurs des points déjà évoqués plus tôt :

La libre organisation des fichiers liés à SBT permet de découper les différentes fonctions : par exemple, un fichier Dependencies.scala regroupant les dépendances, un fichiers plugins.sbt pour lister les plugins utilisés…, tous regroupés dans le dossier project/. Cette approche de découpage reste plus propre que de travailler dans un unique fichier build.sbt, mais cependant il n’existe pas de « bonne » organisation ou de conventions : chaque développeur/équipe a ses propres préférences, et cela peut devenir assez difficile à s’y retrouver lorsque l’on travaille sur plusieurs projets SBT ne partageant pas ces mêmes conventions. Attention aussi à ce que la configuration du build ne devienne pas un projet dans le projet, qu’il faut maintenir à part entière. Pour Maven, cet aspect « liberté » est contraint par la syntaxe XML et le fait que d’un projet à l’autre, les POM sont néanmoins assez similaires.

En entreprise, les POM peuvent aussi être réutilisés d’un projet à l’autre. Avec SBT, il n’y a pas réellement de notion d’héritage, mais plutôt de la composition de plugins. Certains projets permettent de mutualiser la configuration. Le projet sbt-parent est l’un des principaux, mais cela est malheureusement relativement peu courant en entreprise.

D’expérience, il est très difficile de réaliser un fichier de build SBT maintenable par tous les développeurs de l’équipe sur de gros projets (exemple de Spark et de son SparkBuild.scala).

La facilité de gestion des projets multi-modules entre Maven et SBT est soumise à appréciation. Certains développeurs trouvent qu’il est plus facile de gérer tous les modules dans un seul fichier (1 seul build.sbt), alors que d’autres préfèrent séparer (1 POM par module). Pour ma part, il me semble toutefois plus naturel de regarder le fichier correspondant au bon module pour trouver des informations sur ce module, plutôt que de naviguer dans un uber-build.sbt.

La maintenance de SBT, du sentiment de plusieurs développeurs avec qui j’ai pu discuter, est une « perte de temps » . Au fur et à mesure que le projet grossit, l’intégration de nouveaux plugins nécessite d’en reconfigurer d’autres (conflits, versions obsolètes à remplacer), entraînant souvent en cascade de nombreux problèmes complexes à corriger et éloignant de plus en plus les développeurs de leur objectif initial.

Gestion des dépendances

Résolution des dépendances

Pour la résolution des dépendances, SBT utilise sous le capot Ivy (d’ailleurs plutôt une version patchée d’Ivy maintenue par les contributeurs de SBT). À la base, Ivy était utilisé en complément d’Ant, utilisé avant que Maven ne soit adopté. Ivy n’était pas réputé pour sa vitesse de résolution des dépendances (voir Rapidité). Maven quant à lui se base sur maven-resolver-plugin, basé sur Aether (projet d’Eclipse à la base).

Par défaut, SBT stocke les artifacts dans le répertoire local .ivy2, alors que Maven stocke ses artifacts dans le répertoire local .m2.

Pour les problèmes natifs de rapidité de résolution de dépendances de SBT, il existe coursier, un plugin SBT non installé par défaut, qui permet entre autres de télécharger en parallèle les dépendances, mais qui peut poser des problèmes de compatibilité avec d’autres plugins (de ce genre).

SBT permet aussi de dépendre facilement de sous-projets Git externes. Sous Maven, il existe Jitpack qui permet de faire la même chose. Cependant, en entreprise, il est très rare – voir dangereux – d’utiliser de telles dépendances « non-officielles ».

Conflits de dépendances

La résolution des conflits de dépendances est différente entre les 2 outils :

  • Maven utilise une heuristique nearest wins, c’est-à-dire que la version de dépendance qui est préférée en cas de conflit est celle qui est la plus proche de la racine dans l’arbre de dépendances. Cela permet par exemple d’éviter de tirer des versions de dépendances transitives lorsque la librairie en question est tirée nativement par votre projet. Ce mécanisme ne peut pas être modifié sous Maven
  • SBT utilise par défaut une heuristique latest wins pour résoudre les conflits de dépendances, c’est-à-dire que la version de dépendance la plus récente est utilisée en cas de conflit. C’est très pratique lorsque les versions de librairies externes sont rétrocompatibles, mais peut causer des soucis sinon (Guava par exemple). Cette heuristique est dangereuse dans le sens où cette version la plus récente de la dépendance peut être tirée transitivement par une dépendance. Il est possible de configurer le conflict manager (héritage d’Ivy), mais cela implique de la configuration supplémentaire de SBT

Le schéma suivant résume la différence entre les 2 heuristiques grâce à un exemple simple :

Dans le cas général, la résolution de conflits de dépendances est une tâche très complexe quel que soit l’outil de build. Personnellement, je trouve que Maven permet d’avoir un meilleur contrôle des dépendances, au prix d’une configuration lourde des dépendances dans le XML basée sur des exclusions. Le meilleur moyen de résoudre les conflits reste d’organiser son code en modules ou projets différents, chacun tirant le strict nécessaire des dépendances.

Tests

Il n’y a pas de différence significative entre Maven et SBT sur la partie tests unitaires. En revanche, SBT souffre d’un souci de design sur la partie tests d’intégration.

Deux points principaux définissent un test d’intégration :

  • il utilise des composants externes ou teste plusieurs composants de notre application ensemble
  • il s’exécute sur l’application packagée

De cette définition et surtout du second point, on peut en conclure qu’il n’existe pas réellement de notions de tests d’intégration avec SBT. La tâche it:test, en plus d’être assez fastidieuse à configurer, ne dépend en effet pas d’une tâche de packaging. Les tests d’intégration exécutés durant cette phase ne sont pas exécutés sur l’application packagée (JAR le plus souvent) mais sur les classes (et il est très rare d’utiliser les classes directement pour exécuter une application en production). La partie it sous SBT n’apporte donc aucun intérêt outre le fait de séparer logiquement les tests. En effet, les tests d’intégration réalisés durant cette phase ne pourront pas permettre de détecter par exemple des problèmes de shading, contrairement à Maven qui, se basant sur Failsafe, exécute de vrais tests d’intégration.

Extension

De par son côté impératif, il est plus facile d’étendre le comportement de SBT en écrivant du code. Cependant, cela rajoute de la complexité pour maintenir le build, et bien souvent (façon polie de dire tout le temps en réalité), le code n’est pas testé : le test consiste simplement à dire si le build fait ce que l’on souhaite ou pas. C’est pourquoi il est préférable d’écrire des plugins externes, qui ont aussi l’avantage d’être réutilisable.

Sur cette partie développement de plugins externes, Maven propose une API (MOJO) simple à étendre (seulement des fonctions à implémenter), alors qu’avec SBT la définition d’un plugin n’est pas forcément aisée, mais peut sembler laisser une plus grande liberté. Les tests sur les plugins sont assez étranges sur SBT, puisqu’ils consistent à créer un ou plusieurs projets utilisant le plugin (src/sbt-test, par exemple). Cela ressemble plus à des tests end-to-end (fonctionne/ne fonctionne pas) qu’à de réels tests unitaires comme avec Maven : chaque plugin Maven étant composé de classes Java parfaitement testables, et qui permettent en plus d’avoir des métriques plus claires sur la qualité du plugin (par exemple ce rapport de tests pour le plugin maven-assembly-plugin).

À noter aussi que les versions de plugins sont incompatibles entre versions de SBT. Il y a en effet une forte corrélation entre les versions de SBT et de Scala utilisée pour développer les plugins (Scala 2.9 pour SBT 0.12, Scala 2.10 pour SBT 0.13, et Scala 2.12 pour SBT 1.0). Pour les utilisateurs de SBT 0.13, ce lien entre versions a longtemps été très frustrant, puisqu’il empêchait de développer des plugins avec Scala 2.11.

Maven propose une grande liste de plugins, tout comme SBT. Maven a cependant l’avantage de la maturité, qui fait que ses plugins les plus populaires ont été très éprouvés. Pour les 2 outils, il faut bien sûr faire le tri entre les plugins utiles et inutiles, et ceux de mauvaise qualité. SBT propose quelques exemples de plugins officiels (tout du moins sous l’utilisateur Github sbt) inutiles, comme par exemple sbt-nocomma.

Intégration en entreprise

En entreprise, il est courant de vouloir unifier au maximum les outils pour simplifier la maintenance. Alors que l’on se pose moins la question sur le choix d’outils de CI/CD, l’outil de build est souvent choisi à la préférence de l’équipe en charge d’un projet. Il est donc facile de se retrouver avec plusieurs outils de build pour un même langage ou une même famille de langages. Si l’on considère les langages populaires basés sur la JVM et qu’il est possible de retrouver en entreprise (Java, Scala, Kotlin, Groovy, Clojure), l’avantage d’avoir un outil de build unifié peut faciliter la reprise d’un projet et le partage de ressources entre ces projets.

Si on parle de Maven, on peut très bien imaginer une équipe transverse fournissant des POM uniformisés, des archétypes, des artefacts ou bien du tuning de plugins, pour que les développeurs puissent se concentrer sur leur application et non plus sur la configuration de l’outil de build. Avec SBT, bien qu’il existe des tentatives d’unifier des plugins, cela reste assez rarement utilisé, et d’expérience chaque équipe définit son propre build, ce qui peut être problématique d’un point de vue maintenance (multiples versions de SBT qui peuvent co-exister, manière d’organiser son build différente : 1 uber-build.sbt vs 1 dossier project/ découpé en multiple fichiers Scala).

Maven a aussi l’avantage d’être facilement intégrable avec tous les langages cités plus tôt : Java et Scala bien sûr, mais aussi Kotlin, Groovy, Clojure (intégrations officielles). Pour les 3 derniers langages, il n’existe pas d’intégration officielle avec SBT (il existe des plugins SBT pour Kotlin mais non-officiels et pas toujours activement maintenus). Dans le cas où Java et Scala sont les 2 seuls langages utilisés, SBT peut paraître être une alternative à Maven, mais l’intérêt d’utiliser SBT pour Java est assez limité, puisqu’il nécessite de faire du pseudo-scala pour la configuration de l’outil de build. Dans le cas de Java, c’est pour cette raison que Maven sera toujours préféré (dans cette étude de 2016 autour de l’écosystème Java, dans la partie « Which build tool do you use most often? », SBT n’est même pas cité).

Packaging et déploiement

Un des problèmes qui revient de manière récurrente dans le packaging d’une application est la séparation de la configuration selon l’environnement (dev, pré-prod, prod). Alors que cela peut être fait simplement avec Maven grâce à la définition de profils, la configuration de sbt-native-packager pour SBT est assez fastidieuse (pour une fois, Maven est vainqueur sur la taille de la configuration nécessaire). La notion de profils de Maven n’existe pas en effet dans SBT, mais peut être reproduite programmatiquement par la définition de configurations.

SBT se démarque de Maven en proposant une option permettant de cross-builder son application vers différentes versions de Scala. Bien que cela ne soit utile que dans le cas où l’on souhaite écrire des bibliothèques externes compatibles, le cross-build SBT est plus simple et plus rapide que l’exécution de plusieurs profils Maven (un par version de Scala cible).

En ce qui concerne la publication des artifacts (Nexus, artifactory), les 2 outils proposent des fonctionnalités similaires. La configuration de SBT pour écrire dans un repository Maven nécessite tout de même de la configuration, et notamment d’écrire du XML.

Maturité

SBT évolue beaucoup : à titre d’illustration, durant l’écriture de cet article (de la phase d’initialisation à sa publication), 9 releases sont sorties (1.0.2 à 1.1.4, et 0.13.17). Le développement des versions 0.13 semble continuer, probablement pour des raisons de compatibilité, malgré un passage à la version 1.0 en août 2017. Beaucoup de gens utilisent encore des version 0.13.x, et peinent à upgrader SBT : pourquoi changer une version qu’on a eu tant de mal à faire fonctionner ? Chaque changement de version nécessite en effet une migration – malgré tout documentée (migration de 0.12 à 0.13, migration de 0.13 à 1.0) – mais entraîne des dépréciations pas toujours claires. Personnellement, je pense que le changement de version d’un outil de build ne devrait pas être un point bloquant, ni impacter outre mesure la configuration existante.

Pour Maven, depuis la version 3, il est très simple de changer de version. Seulement les comportements internes de Maven sont modifiés, n’entraînant pas de modification de l’existant.

Architecture

SBT repose principalement sur un système de tasks et settings qui vont décrire le build. En se penchant plus sur le sujet, on se rend compte que l’architecture interne de SBT est loin d’être triviale. Pour mieux la comprendre, je vous invite à lire ce très bon article de Li Haoyi : « So, what’s wrong with SBT ? » qui décrit les principaux défauts de SBT (« that makes SBT confusing, complicated and slow », d’après l’auteur). Sans entrer dans le détail ni paraphraser l’article, on retrouve parmi ces défauts : le modèle 4D de SBT, le modèle d’exécution en 3 couches, ou bien le manque de mise en cache par défaut. Martin Odersky (un des créateurs de Scala) est d’ailleurs d’accord avec la majorité de ces points, et a proposé en 2011 une liste de modifications à apporter à SBT qui n’a pas été suivie d’effets.

Maven se base sur des cycles de vie (lifecycles), le plus commun étant le cycle default, contenant des phases comme compile, test, package… À chaque phase est associé un ou plusieurs plugins, exécutés dans un ordre séquentiel.

Quelques avis de développeurs

Dans cette section vont être recensés des témoignages et avis de développeurs concernant Maven et SBT. Bien sûr, comme pour tout outil, on trouve facilement plus de critiques sur Internet que d’avis favorables, mais cet exercice reste intéressant. À noter que certains témoignages/articles ne sont pas récents mais restent tout de même d’actualité.

Maven

Pour Maven, il existe plus d’avis provenant de développeurs en Java, mais de manière globale les critiques et satisfactions restent similaires lorsque Maven est utilisé avec Scala.

Pour

Contre

SBT

Pour

Contre

  • Défauts d’architecture
  • DSL
    • « Personally I don’t want any declarative nonsense where you end up writing custom build logic anyway, only indirectly, by jumping hoops in a limited and stringly-typed DSL. I don’t like having plugins for every trivial thing. I think that’s completely backwards and in my experience SBT comes closest to a usable build tool, despite its internal complexity and idiosyncrasies. » (source)
  • Pas mature
  • Documentation/ressources pas claires
    • « True, right now my biggest problem with sbt is having a problem, google the problem, find a pre 13.x answer, scratch my head, try to make it work, try some more give up, try some more, try something stupid simple that isn’t supposed to work, wonder why it works, feel stupid for searching for a solution. » (source)
  • Autres :

Conclusion

Dans un contexte « entreprise » où la maturité, la maintenabilité et la facilité d’intégration avec les autres outils sont des enjeux primordiaux, je pense que Maven est clairement l’outil à privilégier d’après mon expérience. Dans un environnement industrialisé, avec beaucoup de projets, de dépendances et de plugins qui interviennent, mieux vaut privilégier la bonne structure que fournit Maven.

Personnellement, je trouve en effet SBT trop permissif, et les 2 principaux avantages mis en avant par Lightbend pour justifier son utilisation (compilation incrémentale et shell interactif) ne sont pas suffisants pour l’adopter, d’autant que le très bon plugin scala-maven-plugin implémente ces deux caractéristiques. La seule chose implémentée nativement dans SBT et que l’on ne retrouve pas dans Maven est la cross-compilation, mais je ne pense pas que cela suffise à justifier l’utilisation de SBT (ce comportement est tout de même reproductible avec Maven à l’aide de profils).

SBT ne doit pas être considéré comme le successeur de Maven pour des projets Scala, en comparaison au passage d’Ant vers Maven où il y a eu de vrais apports (dependency management, abandon du style procédural, conventions…). SBT semble même parfois être un retour en arrière : si on reprend les comparaisons entre Ant et Maven, on retrouve des similitudes entre Ant et SBT : style procédural, pas de cycle de vie, manque de conventions, etc.

SBT souffre de quelques problèmes de design, et même Martin Odersky (le concepteur de Scala) considère qu’il y a des améliorations à apporter. SBT et ses plugins ont un côté magique qui peut être préjudiciable dans le cas d’un outil de build : en ce qui me concerne, je suis convaincu qu’une configuration explicite sera toujours mieux qu’une configuration implicite.

La contribution au projet SBT peut poser aussi question : le projet est aux mains de 2 contributeurs permanents principaux, comparément à la fondation Apache derrière Maven (où l’on retrouve toujours plus ou moins 6 contributeurs permanents). La comparaison des graphes des contributeurs est flagrante (voir graphe des contributeurs sur SBT vs graphe des contributeurs sur Maven, sur la période du 01/01/2016 au 01/04/2018). De ce fait, et en considérant le nombre de fonctionnalités et plugins SBT officiels pas toujours utiles qui émergent (feature bloat), il est difficile de savoir si l’évolution de SBT suit une logique personnelle ou bien est le fruit de réflexions collectives et d’un système de validation stricte.

Une des questions principales que pose aussi l’utilisation de SBT est : « est-ce-que chaque langage a besoin de réimplémenter son propre outil de build ? ». Dans le domaine informatique où la stabilité est primordiale pour une bonne qualité de développement logiciel, mais avec l’émergence de beaucoup de nouveaux outils et langages, je pense qu’il est parfois nécessaire de se reposer sur les fondamentaux, surtout quand la nouveauté n’apporte pas de fonctionnalités intéressantes, et ne pas céder à des modes. On peut aussi se poser la question de l’utilité de la programmation fonctionnelle pour la configuration d’un outil de build.

Pour les réfractaires à Maven, Gradle semble être une alternative à étudier. Pour les utilisateurs qui choisiront tout de même SBT, veillez à bien aligner les versions entre les différents projets, à mettre à jour le plus tôt possible les plugins, et à adopter des conventions uniques dans toute l’entreprise, tout en restreignant la liste des plugins utilisables. Ces conseils sont aussi valables pour Maven, mais dans une moindre mesure, car Maven possède une maturité que SBT n’a pas encore aujourd’hui.

 

Publié par

Publié par Jonathan Norblin

Data Engineer & Software Craftsman

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.