Logo Spiria

Les aventures d’un développeur Flask au pays de Django

30 septembre 2020.

Quand un pythoniste dévoué à Flask se voit un jour obligé d’utiliser Django, un framework qu’il traitait avec un peu de dédain jusqu’alors, il se rend compte que oui, l’herbe peut parfois être plus verte ailleurs. Rizik nous raconte son histoire :

Je suis développeur back-end Python depuis plus de 7 ans. La majorité de mon expérience professionnelle s’est faite avec le cadre de développement web Flask qui m’a toujours satisfait. Autant que je sache, les deux options concurrentes en matière de frameworks web en Python sont Django et Flask (à dire ça, je vais sans doute recevoir des messages hargneux de la part des communautés Pyramid, Bottle et autres !). Dans ma tête, j’ai toujours pensé que Flask était objectivement supérieur, car il permet une plus grande liberté de choix, est plus récent et est moins surchargé. J’avais l’idée erronée que les gens choisissaient Django simplement à cause de sa popularité. Puis, après avoir débuté dans un nouvel emploi chez Spiria, on m’a confié quelques projets qui avaient été développés avec Django. L’expérience m’a ouvert les yeux, et je me suis mis de façon inattendue à apprécier ce framework. Ci-après, je vais présenter mon évaluation de Django, du point de vue d’un développeur Flask aguerri. Vous lisez peut-être cet article en vous posant la question “Quel framework dois-je utiliser ?”. Comme souvent, la réponse à cette question est… cela dépend !

Structure de projet

Le framework Django impose une structure de projet spécifique. Vos modèles, vues et routages sont tous placés dans des endroits prévus. Les projets Django dont j’ai hérité à Spiria ont tous été initialement créés par des développeurs Rails et, bien que la base de code ait été parsemée de particularismes, je me suis quand même senti chez moi en tant que développeur Python.

Je pense que si les développeurs avaient commencé un projet avec Flask, j’aurais eu beaucoup de difficultés. Je me souviens d’une conversation que j’ai eue avec un développeur Django il y a quelques années, au sujet de l’expérience de son équipe avec un projet Flask. Ils n’arrivaient pas comprendre comment quelqu’un peut arriver à réaliser un projet d’envergure avec Flask. Après avoir approfondi la question, il m’est apparu clairement que leur application Flask contenait absolument tout le code dans un seul fichier. Dix mille lignes dans toute leur gloire. Pas étonnant qu’ils aient trouvé ça impossible à maintenir ! Flask n’impose aucune structure inhérente, ce qui peut être une arme à double tranchant. Au mieux, vous adoptez et appliquez une structure stricte dès le début de votre projet. Au pire, aucune structure n’est imposée, et vous héritez d’une très grosse application à fichier unique, ce qui donne un tout nouveau sens au concept d’application web monopage ! Cette liberté veut dire que chaque application Flask que vous rencontrerez sera probablement structurée de manière différente.

L’ORM de Django face à SQLAlchemy

Ceux d’entre vous qui sont des habitués de Flask auront très probablement utilisé SQLAlchemy pour leurs besoins en matière d’ORM (mapping objet-relationnel). SQLAlchemy est un cadre extrêmement puissant qui vous donne tout le contrôle dont vous avez besoin sur votre base de données. L’un des avantages de SQLAlchemy est que vous pouvez vous approcher du bas niveau dans les domaines où vous avez vraiment besoin d’un contrôle très fin (par exemple, dans les domaines à faible performance) en allant directement dans le noyau SQLAlchemy et en écrivant des requêtes dans le langage d’expressions SQL. Toutefois, cette flexibilité a un coût ; comme on dit, un grand pouvoir s’accompagne de grandes responsabilités. Un peu plus bas, je vous parlerai de certaines fonctionnalités vraiment cool de l’ORM de Django qui sont probablement beaucoup plus difficiles à atteindre avec SQLAlchemy en raison de cette flexibilité.

En regard, l’ORM de Django est beaucoup plus proche de Rail, probablement parce que l’ORM de Django utilise une implémentation d’enregistrement actif (“active record”). Cela signifie essentiellement que les objets de votre modèle Django ressembleront beaucoup aux lignes SQL dont ils sont dérivés. Cela présente des avantages aussi bien que des inconvénients : du côté positif, la simplicité et l’élégance du système de migration de Django (voir plus bas), sans avoir à gérer la session de base de données ; du côté négatif, le fait de ne pas pouvoir déclarer automatiquement les jointures sur les relations directement sur le modèle lui-même. Dans SQLAlchemy, cela se fait facilement grâce aux techniques de chargement des relations (“Relationship Loading Techniques”) de SQLAlchemy (lazy loading, eager loading, etc.). Pour éviter le problème du N+1, vous devrez appeler select_related() ou prefetch_related() partout où vous faites une requête pour ce modèle. Cela peut devenir fastidieux, surtout lorsque vous avez deux modèles qui sont presque toujours utilisés ensemble.

‘‘‘Example of eagerloading taken from the SQLAlchemy docs’’’
class Parent(Base):
    __tablename__ = ‘parent’

    id = Column(Integer, primary_key=True)
    children = relationship("Child", lazy=‘joined’)

Comme vous pouvez voir dans l'exemple ci-dessus, vous pouvez faire vos jointures directement sur les classes modèles de votre base de données, au lieu d'ajouter la jointure à la requête chaque fois que vous voulez optimiser votre requête.

parents_and_children = Parent.objects.select_related(‘children’).all()

Django Migrations face à Alembic

La migration des bases de données est un domaine où Django surpasse vraiment Flask — le système de migration de base de données intégré à Django est un pur plaisir à utiliser. J’ai été particulièrement impressionné lorsqu’un coéquipier et moi-même avons simultanément engagé des migrations de bases de données dans le projet : Django a immédiatement identifié le problème, et il a été simple de lancer une fusion par le biais de la commande Python manage.py makemigrations --merge. Techniquement, Alembic dispose de la fonction branches pour traiter ce problème, mais elle est en “bêta” depuis des années, ce qui n’inspire pas confiance pour une utilisation en production.

Django Admin face à Flask-Admin

Django Admin est un avantage majeur de Django. Dès la première utilisation, vous obtenez une interface web CRUD instantanée pour tous vos modèles de base de données, protégée derrière un écran de connexion utilisateur. Le panneau d’administration de Django est très performant, et un certain nombre de bibliothèques tierces facilitent la vie avec des fonctionnalités supplémentaires.

Une bibliothèque tierce pour Flask, la bien nommée Flask-Admin, occupe la même place que Django Admin. En phase avec le reste, Flask-Admin est livré avec beaucoup moins de choses préconfigurées pour vous. Vous devrez inclure vous-même un “boilerplate” pour chaque modèle de base de données que vous souhaitez ajouter au panneau Flask-Admin.

Un bémol autant pour Django Admin que Flask-Admin : bien qu’ils peuvent vous faire gagner beaucoup de temps au départ, à mesure que votre projet mûrit et que vos utilisateurs commencent à demander (ou exiger) des fonctionnalités supplémentaires dans vos panneaux d’administration, vous vous retrouverez à vous battre avec ces bibliothèques pour obtenir ce que vous voulez. J’ai trouvé que ces bibliothèques d’admin étaient mieux adaptées à l’usage des développeurs uniquement. Si vous avez l'intention d'ouvrir des fonctionnalités d'administration à votre base d'utilisateurs, il est préférable d'en écrire une admin à partir de zéro, en utilisant un template côté serveur ou une API avec un framework front-end, comme React.

Framework Django REST

En parlant d’API, j’ai trouvé que travailler avec le framework Django REST framework était un rêve absolu. Les vues génériques basées sur les classes rendent la vie pas mal plus simple et la base de code bien plus légère. En gros, vous avez vos vues, vos sérialiseurs et vos permissions. “Subclassez” la vue générique appropriée (par exemple, une ListCreateAPIView), attachez une requête par défaut, un sérialiseur (quelque chose qui définit votre requête/réponse aux endpoints et gère votre conversion entre JSON et Python) et toutes les autorisations qui sont pertinentes pour cette vue. Et c’est tout.

‘‘‘Example ListCreateAPIView class’’’
class BlogPostListCreate(ListCreateAPIView):
    permission_classes = [IsAuthenticated, BlogPostPermission]
    serializer_class = BlogPostSerializer
    queryset = BlogPost.objects.all()

En revanche, contruire ça sans Django REST ressemblerait davantage à ceci :

from django.http import JsonResponse
from django.views import View

class BlogPostListCreateView(View):

    def get(self, request):
        blog_posts = BlogPost.objects.all()
        blog_post_dicts = []
        for blog_post in blog_posts:
          blog_post_dicts.append({
              ‘id’: blog_post.id,
              ‘title’: blog_post.title,
              ‘body’: blog_post.body
          })
        return JsonResponse({‘blog_post_dicts’: blog_post_dicts})

    def post(self, request):
        new_blog_post = BlogPost.objects.create(title=request.POST[‘title’],
                                                body=request.POST[‘body’])
        return JsonResponse({‘message’: ‘Blog Post Created’})

L’exemple ci-dessus n’inclut pas la vérification des autorisations, la validation ou la pagination, ce que vous aurez en bonus avec le framework Django REST.

Un atout supplémentaire est toutes les bibliothèques additionnelles qui ont été développées autour de Django REST et qui rendent votre vie de développeur tellement plus facile. Deux bibliothèques remarquables que j’ai utilisées sont Django-Rest-Framework-Camel-Case et drf-yasg. La première garantit que vos API endpoints peuvent accepter à la fois les clés en CamelCase et celles en snake_case pour la demande et utilisent le CamelCase pour la réponse, tout en conservant les noms de variables en snake_case dans tout votre code Python. Cela permet à vos développeurs front-end d'utiliser les conventions de nommage auxquelles ils sont habitués, et à vos développeurs back-end Python d'utiliser le snake_case et de se conformer à PEP8. La seconde bibliothèque, drf-yasg, fonctionne bien avec la première et aide à générer automatiquement la documentation swagger en analysant vos vues génériques et leurs sérialiseurs. Rien ne vaut une documentation générée automatiquement !

Pour être juste à l’égard de Flask, un certain nombre de bibliothèques tierces font probablement beaucoup de ce que Django REST apporte à Django. Cependant, pour une raison ou une autre, je n’ai jamais eu recours à l’un de ces outils pendant mes développements dans Flask, ce qui mériterait en soi une enquête approfondie.

Conclusion

Je suis bien heureux d’avoir eu l’occasion d’essayer Django dans un cadre professionnel. Avant de rejoindre Spiria, j’étais un développeur Flask acharné et je ne voyais pas l’autre côté de la médaille de ce framework. Flask est un outil étonnant, et la liberté qu’il offre permet de faire beaucoup de choses. Cependant, cette liberté exige beaucoup de discipline, et une mauvaise décision au stade de l’architecture peut vous coûter cher à mesure que votre projet mûrit. Passer à Django m’a ouvert les yeux, me révélant de nombreuses façons de faciliter ma vie dont je ne soupçonnais même pas la possibilité en travaillant avec Flask. Bien sûr, il y a quelques éléments de Flask qui m’ont manqué (les charges jointes automatiques, par exemple), mais, pour les projets sur lesquels j’ai travaillé jusqu’à présent, j’ai vraiment eu le sentiment que Django était le bon choix.

Ma recommandation serait de faire appel à Flask pour la réalisation d’un prototype rapide, ou pour la production de quelque chose de tellement spécifique que les rails imposés par Django seraient trop contraignants. D’un autre côté, il faut faire appel à Django pour les projets qui peuvent bénéficier d’une telle structure, surtout lorsqu’on travaille avec une grosse équipe. Il y a certainement une place pour Flask et Django dans le monde, et la communauté Python dans son ensemble est bien nantie d’avoir de tels choix. Merci à tous ceux qui ont contribué aux deux projets et à tous les projets satellites open-source qui contribuent à faire de ces deux frameworks un rêve avec lequel travailler.