\chapter{Application Programming Interface}


\url{https://news.ycombinator.com/item?id=30221016\&utm_term=comment} vs
Django Rest Framework vs Marshmallow

Expliquer pourquoi une API est intéressante/primordiale/la première chose à réaliser/le cadet de nos soucis:

\begin{itemize}
    \item
        Intéressante: ouverture
    \item
        Primordiale: services
    \item
        La première chose à réaliser: mobile-first
    \item
        Le cadet de nos soucis: monolithique (cf. Rework)
\end{itemize}

Voir peut-être aussi
\url{https://christophergs.com/python/2021/12/04/fastapi-ultimate-tutorial/}

Au niveau du modèle, nous allons partir de quelque chose de très simple: des personnes, des contrats, des types de contrats, et un service d'affectation.
Quelque chose comme ceci:

\begin{minted}{python}
# models.py

from django.db import models

class People(models.Model):
    CIVILITY_CHOICES = (
        ("M", "Monsieur"),
        ("Mme", "Madame"),
        ("Dr", "Docteur"),
        ("Pr", "Professeur"),
        ("", "")
    )
    last_name = models.CharField(max_length=255)
    first_name = models.CharField(max_length=255)
    civility = models.CharField(
        max_length=3,
        choices=CIVILITY_CHOICES,
        default=""
    )

    def __str__(self):
        return "{}, {}".format(self.last_name, self.first_name)


class Service(models.Model):
    label = models.CharField(max_length=255)

    def __str__(self):
        return self.label


class ContractType(models.Model):
    label = models.CharField(max_length=255)
    short_label = models.CharField(max_length=50)

    def __str__(self):
        return self.short_label


class Contract(models.Model):
    people = models.ForeignKey(People, on_delete=models.CASCADE)
    date_begin = models.DateField()
    date_end = models.DateField(blank=True, null=True)
    contract_type = models.ForeignKey(ContractType, on_delete=models.CASCADE)
    service = models.ForeignKey(Service, on_delete=models.CASCADE)

    def __str__(self):
        if self.date_end is not None:
            return "A partir du {}, jusqu'au {}, dans le service {} ({})".format(
                self.date_begin,
                self.date_end,
                self.service,
                self.contract_type
            )

        return "A partir du {}, à durée indéterminée, dans le service {}({})".format(
            self.date_begin,
            self.service,
            self.contract_type
        )
\end{minted}

\includegraphics{images/rest/models.png}

\section{Mise en place}

La configuration des points de terminaison de notre API peut être relativement touffue.
Pour cette raison, il convient de s'infliger à suivre une structure qui soit similaire pour chaque point de terminaison \cite[Predictability, Rule \#1]{django_for_startup_founders}.
Il convient de:

\begin{enumerate}
    \item
        \textbf{Spécifier les permissions}
    \item
        \textbf{Copier et assainir les éléments communiqués en entrée vers des variables locales}
    \item
        \textbf{Valider les données d'entrée}
    \item
        \textbf{Enforce business requirements}
    \item
        \textbf{Perform busines logic}
    \item
        \textbf{Retourner une réponse HTTP}
\end{enumerate}

-> Répartir les responsabilités selon les composants ci-dessous

\begin{enumerate}
    \item
        Configurer les sérialiseurs, càd. les champs que nous souhaitons exposer au travers de l'API,
    \item
        Configurer les vues, càd le comportement de chacun des points de terminaison,
    \item
        Configurer les points de terminaison eux-mêmes, càd les URLs permettant d'accéder aux ressources.
    \item
        Et finalement ajouter quelques paramètres au niveau de notre application.
\end{enumerate}

\section{Django Rest Framework}

\subsection{Serialiseurs}

Les sérialiseurs agissent litérallement comme des \texttt{forms}, mais au niveau de l'API.
Ils se basent sur un modèle, définit au niveau de la \texttt{class Meta}, permettent de choisir les champs qui seront sérialisés, définissent différentes méthodes d'accès à des propriétés spécifiques et des méthodes de validation.
Tout comme les forms.
Par exemple, avec Django Rest Framework:

\begin{minted}{python}
    # serializers.py

    from django.contrib.auth.models import User, Group
    from rest_framework import serializers

    from .models import People, Contract, Service

    class PeopleSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = People
            fields = ("last_name", "first_name", "contract_set")

    class ContractSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = Contract
            fields = ("date_begin", "date_end", "service")

    class ServiceSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = Service
            fields = ("name",)
\end{minted}

\subsection{Vues}

\begin{minted}{python}
    # views.py

    from django.contrib.auth.models import User, Group
    from rest_framework import viewsets
    from rest_framework import permissions

    from .models import People, Contract, Service
    from .serializers import PeopleSerializer, ContractSerializer, ServiceSerializer


    class PeopleViewSet(viewsets.ModelViewSet):
        queryset = People.objects.all()
        serializer_class = PeopleSerializer
        permission_class = [permissions.IsAuthenticated]


    class ContractViewSet(viewsets.ModelViewSet):
        queryset = Contract.objects.all()
        serializer_class = ContractSerializer
        permission_class = [permissions.IsAuthenticated]


    class ServiceViewSet(viewsets.ModelViewSet):
        queryset = Service.objects.all()
        serializer_class = ServiceSerializer
        permission_class = [permissions.IsAuthenticated]
\end{minted}

\subsection{URLs}

\begin{minted}{python}
    # urls.py

    from django.contrib import admin
    from django.urls import path, include
    from rest_framework import routers

    from core import views

    router = routers.DefaultRouter()
    router.register(r"people", views.PeopleViewSet)
    router.register(r"contracts", views.ContractViewSet)
    router.register(r"services", views.ServiceViewSet)

    urlpatterns = [
        path("api/v1/", include(router.urls)),
        path('admin/', admin.site.urls),
    ]
\end{minted}

\begin{minted}{python}
# settings.py

INSTALLED_APPS = [
    ...
    "rest_framework",
    ...
]

...

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':
    'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}
\end{minted}

\subsection{Résultat}

En nous rendant sur l'URL \texttt{http://localhost:8000/api/v1}, nous obtiendrons ceci:

\includegraphics{images/rest/api-first-example.png}

\subsection{Modéles et relations}

Plus haut, nous avons utilisé une relation de type \texttt{HyperlinkedModelSerializer}. C'est une bonne manière pour autoriser des relations entre vos instances à partir de l'API, mais il faut reconnaître que cela reste assez limité. Pour palier à ceci, il existe {[}plusieurs manières de représenter ces
\url{https://www.django-rest-framework.org/api-guide/relations/}:

\begin{enumerate}
    \item Soit \textbf{via} un hyperlien, comme ci-dessus,
    \item Soit en utilisant les clés primaires, soit en utilisant l'URL canonique permettant d'accéder à la ressource.
\end{enumerate}

La solution la plus complète consiste à intégrer la relation directement au niveau des données sérialisées, ce qui nous permet de passer de ceci (au niveau des contrats):

\begin{minted}{js}
{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "last_name": "Bond",
            "first_name": "James",
            "contract_set": [
                "http://localhost:8000/api/v1/contracts/1/",
                "http://localhost:8000/api/v1/contracts/2/"
            ]
        }
    ]
}
\end{minted}

à ceci:

\begin{minted}{js}
{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "last_name": "Bond",
            "first_name": "James",
            "contract_set": [
                {
                    "date_begin": "2019-01-01",
                    "date_end": null,
                    "service": "http://localhost:8000/api/v1/services/1/"
                },
                {
                    "date_begin": "2009-01-01",
                    "date_end": "2021-01-01",
                    "service": "http://localhost:8000/api/v1/services/1/"
                }
            ]
        }
    ]
}
\end{minted}

La modification se limite à \textbf{surcharger} la propriété, pour
indiquer qu'elle consiste en une instance d'un des sérialiseurs
existants. Nous passons ainsi de ceci

\begin{minted}{python}
class ContractSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Contract
        fields = ("date_begin", "date_end", "service")

class PeopleSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = People
        fields = ("last_name", "first_name", "contract_set")
\end{minted}

à ceci:

\begin{minted}{python}
class ContractSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Contract
        fields = ("date_begin", "date_end", "service")

class PeopleSerializer(serializers.HyperlinkedModelSerializer):
    contract_set = ContractSerializer(many=True, read_only=True)
    class Meta:
        model = People
        fields = ("last_name", "first_name", "contract_set")
\end{minted}


Nous ne faisons donc bien que redéfinir la propriété \texttt{contract\_set} et indiquons qu'il s'agit à présent d'une instance de \texttt{ContractSerializer}, et qu'il est possible d'en avoir plusieurs. C'est tout.

\section{Alternatives}

Django-Rest-Framework est une librarie complète qui ajoute énormément de possibilités.
Cependant \cite{django_for_startup_founders}:

\begin{enumerate}
    \item
        La documentation est \textbf{réellement} compliquée.
        Tout nouveau développeur doit appréhender, comprendre et assimiler cette documentation et tous les concepts sous-jacents.
        Ceci inclut notamment le fait que tous les verbes HTTP ont été "traduits" (GET -> retrieve, POST -> create, ...).
        Ceci a du sens par rapport à la définition d'une interface REST-compliant, mais ajoute une complexité mentale relativement lourde.
    \item
        Certains concepts de réutilisation sont tellement compliqués qu'ils prennent plus de temps à mettre en place qu'à écrire une ligne de code Python classique
    \item
        Les sérialiseurs peuvent rapidement devenir difficiles à lire ou relire, spécifiquement lorsque nous utilisons des \textit{nested serializers} ou lorsque les concepts de désérialisation sont abordés.
\end{enumerate}

\subsection{Marshmallow}

\textit{Marshmallow} est une alternative plus légère à Django-Rest-Framework, et qui présente une interface plus claire, ainsi qu'une documentation plus concise et facile à comprendre.

Une solution plus facile que les sérializeurs de DRF consistera à

\begin{enumerate}
    \item
        Gérer la validation de données en utilisant Marshmallow
    \item
        Sérialiser les données en utilisant du code Python \cite{django_for_startup_founders}.
\end{enumerate}

\section{OData}

...
