Un tournant dans la mutualisation du code sur mobile ?

le 07/12/2018 par Louis Davin
Tags: Software Engineering

Gagner du temps, économiser du budget, diviser par deux les efforts en développant avec du cross-platform… Voilà un sujet sans cesse remis sur la table auquel les développeurs mobiles ont dû s'habituer. Nous ne reviendrons pas spécialement dans cet article sur la fracture qui existe entre les adeptes des technologies mobiles hybrides (hier Xamarin et Phonegap, aujourd'hui React-Native et Ionic, demain Flutter et Weex ?) et les développeurs mobiles "natifs" : les deux populations n'ont pas les mêmes problématiques, et leurs besoins sont assez largement irréconciliables.

Alors que le débat "natif vs cross-platform" a une raison d'être pour des applications mobiles simples et à créer de zéro, l'expérience prouve que pour les applications natives bien établies la solution magique n'existe toujours pas en cette fin d'année 2018.

En tous cas, le "récent" retour d'expérience d'Airbnb (qui a tenté l'ajout de react-native dans son application native existante, pour finalement décider de faire marche arrière) confirme que l'univers du natif n'est pas vraiment compatible avec l'univers du front web : en pensant économiser, on finit par dédoubler beaucoup de choses, notamment les besoins en compétences.

Pour autant, du côté "natif", faut-il jeter l'éponge ou refuser tout effort permettant une mutualisation ?

Que mutualiser ?

Ces dernières années, Les développeurs mobiles natifs ont découvert les bienfaits des architectures applicatives très découplées (type Clean Architecture ou Hexagonal Architecture).

Or, alors que l'on découpe finement les couches de responsabilité des applications mobiles, des redondances dans le code iOS et Android deviennent criantes.

Schéma d'architecture applicative découplée

Dans l'équipe mobile d'OCTO, spécialiste du développement natif, nous pratiquons depuis plusieurs années ce type d'architecture sur nos projets.

Nous considérons que la "couche UI" (partie gauche du schéma) est propre à chaque plateforme. Elle porte une grande partie de l'expertise du développement mobile qui consiste à s'intégrer parfaitement aux différentes API offertes par Apple et Google, elle est garante de la qualité du rendu et de l'évolutivité de l'application face aux mises à jour incessantes des OS mobiles. Il n'est donc pas question de la mutualiser.

De l'autre côté du schéma, dans la "couche dépôt", l'implémentation des interface permettant d'abstraire l'accès ou le stockage de la donnée sont là aussi très souvent spécifiques à la plateforme (accès file-system, database, preferences…) et doivent donc le rester. La question de la mutualisation reste ouverte sur les appels webservice et la dé-sérialisation des données.

Au milieu de notre schéma, la "couche coeur" définissant le modèle objet et implémentant les règles de gestion en totale abstraction avec la plateforme (c'est le but des architectures découplées) est évidemment le candidat parfait de la mutualisation.

Quelle techno pour mutualiser ?

Nous nous sommes donc déjà posé la question de la mise en pratique de la mutualisation sur les briques de l'architectures qui nous semblent appropriées. Les solutions sont multiples mais deux familles émergent :

  • Utiliser Javascript ou tout autre langage interprété
  • Utiliser un langage compilant vers du binaire vraiment "natif", tel que du C ou du Go

Les désavantages que nous avons détecté ne nous semblent pas justifier l'effort à date. En effet, ces solutions nécessitent dans tous les cas de :

  • Maîtriser un nouveau langage "non-mobile", ainsi que son écosystème, pour écrire sereinement un code commun testé et à l'état de l'art.
  • Passer par une étape de sérialisation/dé-sérialisation systématique pour chaque échange de donnée entre l'application et le code commun.

De plus, dans l'approche Javascript ou langage interprété "haut niveau", on sera aussi productifs qu'en Swift ou en Kotlin, mais l'application devra embarquer un interpréteur qui alourdira considérablement sa taille, pesant souvent de plusieurs Mo à plusieurs dizaines de Mo (seul iOS dispose d'un interpréteur Javascript dans la plateforme).

Dans l'approche langage compilant vers du binaire vraiment "natif" comme le C ou le GO, pas de problème d'interpréteur à embarquer, mais on accusera une sérieuse perte de productivité en utilisant ces langages très "bas niveau".

Kotlin, le grand sauveur ?

Les ingénieurs travaillant sur Kotlin n'ont jamais caché vouloir rendre leur langage multi-plateforme. La variante "originelle" de Kotlin, permettant de cibler le JDK et de tourner sur une JVM n'est plus la seule. Le support du Javascript par transpilation a été rajouté à Kotlin il y a quelques années.

La dernière addition est le support du "natif" avec Kotlin-Native : il est désormais possible de compiler du code kotlin vers de purs binaires ne nécessitant pas de machine virtuelle, en ciblant donc les plateformes et les architectures désirées.

Ce qui nous intéresse est bien évidemment la capacité à compiler du code Kotlin vers de l'iOS, sur les architectures arm32 et arm64 pour les appareils, et x64 pour le simulateur tournant sur Mac.

Quels sont les impacts de Kotlin-Native, ou plutôt Kotlin-Multiplatform (Kotlin-Multiplatform est la partie du code réellement commune, qui ne dépend pas de son implémentation JVM, JS ou Native), sur les conclusions du paragraphe précédent ?

  • Un langage de premier plan de l'écosystème mobile est désormais une piste pour mutualiser du code.
  • Il n'y a pas de problème de sérialisation/dé-sérialisation en utilisant Kotlin, car on obtient un .jar classique pour le côté Android (comme s'il n'avait jamais été question de multi-plateforme). Côté iOS, on génère un .framework contenant un .h déclarant toutes les classes, protocols et méthodes définies (comme si l'on tirait une dépendance vers un pod propriétaire dont on ne nous aurait pas partagé le code).
  • Il n'y a pas d'alourdissement démesuré des applications lié à l'ajout d'un interpréteur.
  • On profite de toutes les fonctionnalités haut-niveau du langage Kotlin.

Aujourd'hui, fin 2018, alors que Kotlin/Native est sorti de sa phase expérimentale pour être estampillé "beta", à peu près tous les voyants sont au vert, le sujet est vraiment prometteur.

Quelques points d'attention

Tout n'est pas rose pour autant, tout d'abord certaines spécificités du côté de Kotlin-Native restent à creuser en profondeur avant de se lancer :

  • Dans l'univers multiplatform, la "std-lib" de Kotlin est globalement réduite aux types classiques et aux collections : pas d'API de dates par exemple, car elle devrait être implémentée pour la JVM, JS, MacOS, Windows, Linux… De même, toutes les librairies kotlin existantes auxquelles on peut penser sont en fait des librairies java, on ne donc pourra pas s’en servir. Ceci dit la communauté est très active, il existe déjà un client HTTP multiplateforme et un embryon de librairie de gestion de dates.
  • La "remontée" d'exception de la partie iOS vers Kotlin est encore assez limitée.
  • Il faut creuser et comprendre certains choix qui ont été faits par les développeurs de Kotlin-Native concernant la gestion de la mémoire : par défaut, un objet Kotlin instancié depuis un thread iOS ne peut pas être utilisé dans un autre thread (à moins de le freezer ou d'utiliser d'autres approches documentées).
  • Bien qu'Objective-C supporte les génériques depuis 2015, Kotlin-Native ne sait pas encore les retranscrire dans le .h généré dans le framework (c'est le bon vieux id qui est utilisé).

Par ailleurs, il ne faut pas négliger l'impact énorme que la mutualisation aura sur les développements et leur organisation. Qui développe la couche commune ? Est ce qu'on la développe en avance de phase ? En même temps qu'une première plateforme ? En même temps que les deux ? Comment gérer le versionning de cette couche commune ? Doit-on avoir trois dépôts git séparés (commun, iOS et Android) ou bien un unique repo ?

À toutes ces questions pour lesquelles il n'y a pas de bonne réponse, tout d'abord car les REX manquent, et car chaque organisation a ses spécificités, il ne faut pas oublier les pré-requis habituels (mais généralement ignorés) à tout sujet promettant des économies par mutualisation :

  • Les équipes doivent déjà maîtriser les architectures applicatives découplées. Il n'est pas raisonnable d'imaginer pouvoir transformer la base de code iOS et Android tout en mutualisant dans le même temps ce qui peut l'être.
  • Il est primordial d'avoir des équipes matures, rigoureuses et très compétentes
  • Les experts de chaque plateforme doivent connaître l'autre pour bien "sentir" comment négocier la mutualisation : connaître les contraintes des API et les librairies les plus utilisées de chaque plateforme permettra d'organiser le code commun pour que la consommation soit aisée sur iOS et Android.

Conclusion

Si vous avez déjà atteint le niveau de maturité vous permettant de profiter des avantages des architectures applicatives découplées dans vos applications iOS et Android, alors la question de la mutualisation d'une partie du code peut se poser.

Les possibilités offertes par Kotlin-Multiplatform nous plaisent, car elles nous permettent d'envisager pour la première fois sur mobile une mutualisation propre et choisie du code :

  • Basée sur un des deux grands langages du mobile,
  • Chaque plateforme "garde la main" en restant intégrée à son toolchain officiel, son IDE, son outil de build et son langage,
  • Une partie du code est volontairement choisie pour être encapsulée dans une boîte noire que l'on utilisera techniquement comme une librairie classique, sans trop d'inconvénients.

Kotlin-Multiplatform est enfin un argument puissant à intégrer dans le large débat de la mutualisation voir du "natif vs cross-platform". En tous cas, il a le mérite de rappeler ce qui conditionne le succès de toute approche cherchant à diminuer l'investissement par la mutualisation :

  • Des compétences sur chaque plateforme ciblée ainsi que le langage commun
  • Des compétences et une maturité sur les architectures applicatives et les pratiques de développements
  • Une vision des impacts de la mutualisation sur l'organisation des projets, les développements et les livraisons