ZEP-02 : Elaboration d'une API

a marqué ce sujet comme résolu.

Bien le bonjour à tous,

Excusez moi pour ma longue absence mais comme Javier l'a fait remarqué il y a quelques jours, j'étais en vacances ce mois de septembre. J'ai pris des vacances aussi bien de mon boulot que de la programmation, voire par moment du net compris (mon dieu que ça fait du bien).

Je pense que nous arrivons doucement au bout de la rédaction de cette ZEP ! Voici un récapitulatif complet de tout ce qui a été dit précédemment sur l'élaboration d'une API des membres (devons-nous renommer le sujet de cette ZEP en ce sens d'ailleurs ?) en incluant les dernières remarques de Javier.

J'indique avec des balises d'information les parties modifiées par rapport au précédent récapitulatif.

API des membres

Informations sur la requête HTTP

Version de l'API
1
http://api.zestedesavoir.com/v0.0/...
  • La version est indiquée dans l'URL d'appel de l'API.
  • L'api est spécifié comme sous-domaine de l'URL pour ne pas la surcharger.

Pour la prod, nous pouvons imaginer une adresse plus ou moins équivalente :

1
http://api.preprod.zestedesavoir.com/v0.0/...
User-Agent

Cette section a été modifiée.

L'en-tête HTTP User-Agent doit être renseigné par le client.

Pour des informations complémentaires sur les User-Agent, rendez-vous sur son article Wikipedia.

Cross-Origin Request Sharing

Cette section a été modifiée.

Le serveur doit renvoyer les en-tête HTTP suivants afin d'éviter les soucis de CORS :

1
2
3
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link
Access-Control-Allow-Credentials: true

Pour des informations complémentaires sur CORS, rendez-vous sur son article Wikipedia version anglaise.

Considération générales

Formatage des erreurs

Lorsqu'un appel API provoque une erreur, il faut s'appuyer sur les codes HTTP standards. La réponse HTTP doit donc systématiquement utiliser un code interprétable pour le client. En plus de cela, le contenu de la réponse doit indiquer un code (par défaut le même que celui de la réponse HTTP, mais peut être différents pour donner de plus amples informations) ainsi qu'un message d'erreur compréhensible par le développeur de l'API.

Exemple, une réponse HTTP 404 devrait avoir le body suivant :

1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:404, 
            message:"Ce membre n'existe pas"
        }
    ]
}
Format d'entrée
  • Seul le JSON est accepté.
  • Lecture du header Content-Type, seule la valeur application/json est acceptée. Les autres formats donnent lieu à un code d'erreur HTTP 406 Not Acceptable. (NB : Twitter fait la différence entre le format des données envoyées et le format dans lequel les données sont envoyées. Dans le cas présent, ils renverraient une 404. A nous de décider ce qu'on fait).
1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:406, 
            message:"Le client doit envoyer des données au format application/json et positionner le header HTTP Content-Type de façon à le signaler"
        }
    ]
}
Format de sortie
  • Uniquement en JSON.
  • Lecture du header HTTP Accept, si la valeur application/json est absente, envoi d'un code HTTP 406 et de l'erreur suivante :
1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:406, 
            message:"Le client doit accepter des réponses au format JSON et le signaler en positionnant le header HTTP Accept avec la valeur application/json"
        }
    ]
}
Caching

Cette section a été modifiée.

Pour obtenir un premier système de cache, l'ETag sera utilisé. L'avantage se situe principalement sur un niveau : d'alléger le back-office dans sa charge de travail. Il ne sera pas toujours nécessaire d'aller chercher l'information s'il remarque qu'elle n'a pas été modifiée entre deux appels.

Pour y parvenir, voici le fonctionnement basique d'un ETag :

  1. Lorsqu'un client fait appel à l'API, la réponse inclue un ETag avec une valeur correspondant à un hash de la donnée retournée par l'API. La valeur de cette ETag est sauvegardée pour une prochaine utilisation si nécessaire.
  2. Au prochain même appel du client sur l'API, le client peut inclure dans le header de sa requête, l'attribut If-None-Match avec le précédent ETag renvoyé par le back-office.
  3. Si l'ETag n'a pas changé, le code de la réponse sera 304 - Not Modified et aucune donnée ne sera renvoyée.
  4. Si l'ETAg a changé, le back-office fera la requête et renverra la donnée avec un nouveau ETag. Ce nouveau ETag sera sauvegardé et utilisé pour de prochains appels.
Pagination
  • Les résultats de requête doivent être paginés. Par défaut, une page de résultat doit contenir 50 résultats

  • L'utilisateur indique la page de résultats qu'il souhaite récupérer à l'aide du paramètre &page=. Dans le cas où ce paramètre est invalide (n'est pas un nombre entier ou en dehors des limites de la pagination), l'API retourne une erreur 4001 ou 4002 :

1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:4001, 
            message:"le paramètre indiqué pour la pagination n'est pas un nombre entier valide"
        }
    ]
}
1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:4002,
            message:"la page indiquée n'existe pas"
        }
    ]
}
  • L'API fournit des indications dans les headers de la réponse HTTP afin d'aider le client à naviguer dans les ressources paginées à l'aide du header Link. de la façon suivante :
1
2
3
4
Link: <http://zestedesavoir.com/membres/?search=toto&page=4>; rel="next",
  <http://zestedesavoir.com/membres/?search=toto&page=5>; rel="last",
  <http://zestedesavoir.com/membres/?search=toto&page=1>; rel="first",
  <http://zestedesavoir.com/membres/?search=toto&page=2>; rel="prev"

NB : Ne pas oublier de remettre tous les paramètres utilisés par le client dans l'URL (ici search=)

  • Dans de futures versions, l'utilisateur pourra indiquer le nombre de résultats souhaités en positionnant le paramètre &perPage= dans sa requête
Format de dates

Le format de dates retenu pour la communication avec le serveur est le format ISO 8601 (attention Java 7 nécessaire pour les utilisateurs de Java), pas de soucis en Python a priori.

En ce qui concerne les en-têtes HTTP, voici la liste des formats à gérer côté serveur.

Les différentes versions de l'API

Objectif

Fournir aux clients un ensemble d'outils pour consulter la liste des membres et leurs profils.

Représentation d'un membre
Exemple d'objet
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "id": 123456,
    "username": "Clem",
    "show_email": true,
    "email": "clem@zestedesavoir.com",
    "is_staff": true,
    "is_active": true,
    "site": "www.zestedesavoir.com",
    "avatar_url": "http://i.imgur.com/k6R1plZ.png",
    "biography": "Quae dum ita struuntur, indicatum est apud Tyrum indumentum regale textum occulte, incertum quo locante vel cuius usibus apparatum. ideoque rector provinciae tunc pater Apollinaris eiusdem nominis ut conscius ductus est aliique congregati sunt ex diversis civitatibus multi, qui atrocium criminum ponderibus urgebantur.",
    "sign": "",
    "email_for_answer": true,
    "last_visit": "1977-04-22T06:00:00Z",
    "date_joined": "1977-04-22T06:00:00Z"
}

J'ai délibérément omis les champs liés à la modération et spécifique au site comme le survol ou le clique pour le menu.

Colonnes
Colonne Type Description
id long Identifiant de l'utilisateur
username String Nom d'utilisateur
show_email boolean Affiche ou non l'e-mail. Si faux, le champ email ne sera pas spécifié dans l'objet
email String E-mail de l'utilisateur
is_staff boolean Est un membre du staff ou non
is_active boolean Est un compte actif ou non
site String Site web de l'utilisateur
avatar_url String Url vers l'avater de l'utilisateur
biography String Biographie de l'utilisateur
sign String Signature de l'utilisateur
email_for_answer boolean Permet l'envoie d'e-mail depuis ZdS
last_visit Date Dernière visite de l'utilisateur sur ZdS
v0.0 : Afficher un profil utilisateur
Objectif

Mise en place simple du framework et routage sur une URL

Routes
Méthode URL Paramètres et signification Description Codes d'erreur
GET /v0.0/membres/{id} Aucun Renvoie la fiche profil d'un membre 400 si l'identifiant fourni ne correspond pas au format d'identifiant attendu. 404 si le membre n'existe pas
v0.1 : Lister les membres
Objectif

Renvoyer une liste au lieu d'un objet.

Routes
Méthode URL Paramètres et signification Description Codes d'erreur
GET /v0.1/membres/ Aucun pour le moment, search pour de futures versions Renvoie la liste des membres sans restriction (pagination ou différentielle) Aucun autre que les codes standard
v0.2 : Adaptation/ponts

Cette section a été échangée avec la version suivante.

Objectif

Définition de notre propre Serializer : On renvoie un modèle différent du modèle stocké en base.

v0.3 : Pagination et différentiel

Cette section a été modifiée.

Objectif

Aller toucher à la requête HTTP en lecture et écriture avec la manipulation des QuerySet et calcul du ETag.

Pour cette version, l'ETag ne sera pas sauvegardée et sera donc à chaque fois recalculée mais cela permettra d'avoir une version fonctionnelle et testable de l'ETag avant de statuer sur sa persistance.

v0.4 : Gestion de l'authentification.

Cette section a été modifiée.

Objectif

Permettre une authentification OAuth.

Routes
Méthode URL Paramètres et signification Description Codes d'erreur
POST /v0.4/login/ Aucun, le login et le passwod est contenu dans la requête Authentification de l'utilisateur dans l'API A définir
GET /v0.4/membres/monprofil Aucun Affiche toutes les données personnelles d'un utilisateur connecté A définir
v0.5 : gestion du PUT
Objectifs

Interaction en écriture avec le code côté serveur

Routes
Méthode URL Paramètres et signification Description Codes d'erreur
PUT /v0.5/membres/ Aucun, les informations se trouvent en paramètre de la requête Utilisateur authentifié peut modifier sa fiche profil via l'API A définir
v0.6 : gestion du POST
Objectif

Création de compte via l'API membres.

Routes
Méthode URL Paramètres et signification Description Codes d'erreur
POST /v0.6/membres/ Aucun, les informations se trouvent en paramètre de la requête Utilisateur non identifié peut se créer un compte A définir

Questions à régler pendant ou après la réalisation de la ZEP

Cette section a été ajoutée.

  1. Comment sauvegarder l'ETag pour chaque donnée par chaque client ?
  2. Comment retirer des données de son profil ? Champ null ou vide ?

Futur de l'API des membres (possiblement hors scope de cette ZEP)

Cette section a été modifiée.

v1.x : Gestion des paramètres de requête.
Objectif

Etablir la liaison entre la couche API et la couche d'accès aux données. Que peut-on reprendre du code utilisé actuellement pour les vues ? Peut on factoriser du code pour faire une page de recherche parmi les membres sur le site ?

Routes
Méthode URL Paramètres et signification Description Codes d'erreur
GET /v0.7/membres/ search Possibilité de rechercher, filtrer, trier la liste des membres Aucun autre que les codes standard
v1.x : Gérer les associations dans le modèle renvoyé au client.
Objectif

Aller plus loin que "un modèle en base" <-> "un modèle renvoyé au client".

Ex : extraire un bout (l'url, le nom, …) d'un cours en bêta pour le mettre dans le contenu de la réponse envoyé lors de la consultation de la fiche d'un membre.

Excellent travail de synthèse Andr0, merci.

Je pense que we're Good2Go (ahem…).

Pour les codes d'erreur manquant :

v0.4 : gestion du login :

POST :

  • 400 si les headers Authbearer sont absents

  • 403 si login failed

GET /membres/monprofil :

  • 401 : pour un utilisateur non identifié qui essaie d'accéder à sa page de profil

Il manque un peu de détails sur le login d'ailleurs, il va falloir qu'on spécifie un peu plus le processus. Comment le token est retourné ? Est-ce-qu'on fournit un refreshToken ?

v0.5 :

Attention : "les informations se trouvent en paramètre de la requête" (c'est ptetre moi qui ai écrit ça à la base) c'est pas tout à fait juste : les paramètres se trouvent en contenu de la requêtes. Paramètres = queryParameters, Contenu = POST ou PUT content.

Pour le path : plutôt un PUT sur /membres/monprofil ou sur membres/id. PUT sur /membres devrait retourner une 405 (on ne modifie pas la liste à proprement parler).

Codes manquant :

  • 400 si un champ n'est pas formaté comme attendu (400x à définir : email invalide, …) s'inspirer de la gestion d'erreur déjà présente sur la page de profil.

  • 401 si non authentifié

  • 403 si essai de modifier un profil autre que le sien

  • 404 si id du membre n'existe pas

+0 -0

Le premier post de ce topic a été modifié pour rendre la ZEP-02 générique à toute l'API et liste toutes les ZEP spécifiques pour chaque ressource (pour l'instant, seulement les membres).

La ZEP-17 est donc en route mais j'encourage n'importe quel membre qui veut contribuer à l'élaboration de l'API à créer une nouvelle ZEP sur une nouvelle ressource pour en rédiger ses spécifications et la mener au statut d'accepté auprès des développeurs et du staff de Zeste de Savoir !

Il faut juste penser à venir notifier de leurs créations sur ce topic pour pouvoir les référencer avec les autres. :)

(Sorry double post)

Ceux qui se tiennent un peu au courant de l'avancement de l'API de ZdS le savent, la ZEP 17 (consacré à l'API des membres) vient de passer au statut "Validation" et ne devrait pas tarder à passer au statut suivant (à savoir, "Accepté") pour commencer son développement.

Cela ne m'empêche pas de déjà regarder techniquement comment va se passer la mise en oeuvre de cette API. J'aimerais profiter de ce topic (donnons lui un peu d'utilité) pour discuter de l'implémentation concrète derrière toutes nos discussions et nos débats.

Par où commencer …

J'ai l'impression que le code actuel dans le back-end n'est pas du tout prévu pour accueillir une API sans avoir une répétition de code monstrueuse. Je m'explique : Nous avons commencé par un module simple (et heureusement), le module des membres. Ce module possède les fichiers suivants :

  • admin.py : Inutile dans le cadre de l'API. Ca permet d'avoir une interface admin pour les membres.
  • decorator.py : Inutile dans le cadre de l'API. Il possède une seule méthode pour gérer un utilisateur sanctionné.
  • factories.py : Inutile dans le cadre de l'API. Il est utilisé pour les tests du module.
  • forms.py : Inutile dans le cadre de l'API. Il est utilisé pour construire des formulaires et les renvoyer dans les templates.
  • models.py : Toutes les classes du modèle qui regroupent un certain nombre de requêtes vers la base pour récupérer des informations relatives aux membres. Ce fichier sera très largement utilisé dans le cadre de l'API.
  • urls.py : Regroupe toutes les routes vers les vues. Il sera réutilisé pour rajouter toutes les routes de l'API.
  • views.py : Il est utilisé pour répondre aux requêtes utilisateurs et utiliser un peu près toutes les classes des fichiers précédents pour construire les vues à l'utilisateur.

Mon objectif serait de réutiliser tout ce qui se trouve dans le fichier views.py puisque ce sont dans ses méthodes qu'il y a quasi (oui, parce que parfois elles sont dans les classes du modèles …) toutes les règles métiers. Mais il y a plusieurs autres problèmes :

  • Dans une utilisation poussée de Django Rest Framework, il n'est pas possible d'utiliser les méthodes contenues dans views.py puisque des classes mères spécifiques sont utilisées.
  • Dans une utilisation moins poussée de Django Rest Framework, il devrait être possible de réutiliser les vues de views.py mais les méthodes ne seront plus du tout maintenable !
  • L'état actuel des méthodes de views.py sont déplorables. Il y a des requêtes en base des classes des modèles du module et d'autres modules dans les méthodes et de la logique métier. Un refactoring en profondeur semble nécessaire !
  • Aucune utilisation de bonnes pratiques Django comme les managers ou les adapteurs pour répartir les différentes responsabilités et avoir un code plus maintenable et plus simple (il me semble) à utiliser pour l'API.

Je ne suis pas un expert Django. Si ça se trouve, je suis complètement à côté de mes pompes mais les résultats de mes recherches de cette soirée font peurs et remettent en cause la mise en oeuvre à plus ou moins moyen terme d'une API, même pour un petit module comme les membres.

Dans une utilisation poussée de Django Rest Framework, il n'est pas possible d'utiliser les méthodes contenues dans views.py puisque des classes mères spécifiques sont utilisées.

Andr0

Django Rest Framework propose 2 façon d'écrire son API.

  • ajouter des décorateurs du genre @api_view(['GET', 'POST']) sur les méthodes des views.py. Ce qui est très souvent fait quand on passe écrit une API après avoir fait son site.
  • restructurer complètement le fichier views.py ce qui implique, quand on part d'une base de code, de dupliquer le code et donc de maintenir pendant un moment deux morceaux de code. Pour la simple et bonne raison que cette structure ne s'accorde pas toujours avec la structure Django elle même.

C'est donc aussi simple que ça. Soit on part sur la méthode 1 et on aura probablement quelques fonctions avancées de Django Rest Framework qui nous manqueront (je doute qu'on en ait le besoin), soit on part sur la méthode deux et il faudra maintenir 2 codes parfaitement identique à chaque évolution.

Ta première solution correspondait à ma seconde solution et ta deuxième solution à mes constats du code actuel.

Donc oui, j'avais déjà vu dans le décorateur @api_view une solution intéressante pour nous mais pour illustrer le problème, prenons un exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def details(request, user_name):
    """Displays details about a profile."""

    usr = get_object_or_404(User, username=user_name)
    try:
        profile = usr.profile
        bans = Ban.objects.filter(user=usr).order_by("-pubdate")
    except SiteProfileNotAvailable:
        raise Http404

    # refresh moderation chart

    dot_chart = pygal.Dot(x_label_rotation=30)
    dot_chart.title = u"Messages postés par période"
    dot_chart.x_labels = [
        u"Dimanche",
        u"Lundi",
        u"Mardi",
        u"Mercredi",
        u"Jeudi",
        u"Vendredi",
        u"Samedi",
    ]
    dot_chart.show_legend = False
    dates = date_to_chart(profile.get_posts())
    for i in range(0, 24):
        dot_chart.add(str(i) + " h", dates[(i + 1) % 24])
    img_path = os.path.join(settings.MEDIA_ROOT, "pygal")
    if not os.path.isdir(img_path):
        os.makedirs(img_path, mode=0o777)
    fchart = os.path.join(img_path, "mod-{}.svg".format(str(usr.pk)))
    dot_chart.render_to_file(fchart)
    my_articles = Article.objects.filter(sha_public__isnull=False).order_by(
        "-pubdate").filter(authors__in=[usr]).all()[:5]
    my_tutorials = \
        Tutorial.objects.filter(sha_public__isnull=False) \
        .filter(authors__in=[usr]) \
        .order_by("-pubdate"
                  ).all()[:5]

    my_tuto_versions = []
    for my_tutorial in my_tutorials:
        mandata = my_tutorial.load_json_for_public()
        my_tutorial.load_dic(mandata)
        my_tuto_versions.append(mandata)
    my_article_versions = []
    for my_article in my_articles:
        article_version = my_article.load_json_for_public()
        my_article.load_dic(article_version)
        my_article_versions.append(article_version)

    my_topics = \
        Topic.objects\
        .filter(author=usr)\
        .exclude(Q(forum__group__isnull=False) & ~Q(forum__group__in=request.user.groups.all()))\
        .prefetch_related("author")\
        .order_by("-pubdate").all()[:5]

    form = OldTutoForm(profile)
    oldtutos = []
    if profile.sdz_tutorial:
        olds = profile.sdz_tutorial.strip().split(":")
    else:
        olds = []
    for old in olds:
        oldtutos.append(get_info_old_tuto(old))
    return render_template("member/profile.html", {
        "usr": usr,
        "profile": profile,
        "bans": bans,
        "articles": my_article_versions,
        "tutorials": my_tuto_versions,
        "topics": my_topics,
        "form": form,
        "old_tutos": oldtutos,
    })

Cette méthode dans views.py récupère un membre en fonction de son username. Ceci correspond donc (quasiment puisque nous nous basons sur l'identifiant) à la version 0.0 de notre API des membres comme décrit dans la spec que nous avons rédigé pour la ZEP 17.

Tu vois les problèmes où je les énumère ?

  • Cette méthode récupère déjà les tutoriels et les articles du membre (hors scope de l'API des membres).
  • Il y a du rendu dans la méthode, notamment avec dot_chart et surtout à la fin avec la méthode render_template.
  • Des requêtes brutales des articles et des tutoriels.
  • Si nous voulons supporter l'API dans cette méthode, elle va très certainement triplé de volume comme toutes les autres méthodes du fichier.

Si tu me dis qu'annoter @api_view cette méthode suffira, je ne te croirais pas. Ce n'est donc pas "aussi simple que ça".

Juste une question : pourquoi on ne crée pas un fichier api.py? Non parce que là on va se retaper des fichiers de 10 000 lignes.

artragis

Actuellement, quasi toutes les règles métiers sont dans les méthodes views.py. Ca va donc demander de maintenir deux fichiers (api.py et views.py) avec quasiment le même code. C'est ce que disait firm1 dans son second point.

Actuellement, quasi toutes les règles métiers sont dans les méthodes views.py. Ca va donc demander de maintenir deux fichiers (api.py et views.py) avec quasiment le même code. C'est ce que disait firm1 dans son second point.

Andr0

o_O je connais pas Django mais les deux approches que vous décrivez me paraissent complètement folles.

C'est pas possible d'avoir deux contrôleurs, l'un qui retourne un template, l'autre qui retourne du "pur" JSON, et que les deux soient branchés sur un même service ??

Quel que soit le framework que j'ai utilisé j'ai absolument toujours retrouvé cette façon de faire. Qu'on l'appelle Manager, Service ou quoi, cette partie du code est toujours "à part".

Je vous laisse trancher pour la bonne solution, mais c'est pas possible de découper comme ça ?

+0 -0

Je ne suis pas un expert Django non plus mais d'après des recherches rapides, si. C'est ce que je décris dans mon dernier point :

  • Aucune utilisation de bonnes pratiques Django comme les managers ou les adapteurs pour répartir les différentes responsabilités et avoir un code plus maintenable et plus simple (il me semble) à utiliser pour l'API.

Rien n'est fait nul part dans le projet à l'heure où j'écris ces lignes.

Question à ce sujet : j'ai aussi regardé récement le code, et seule des fonctions sont utilisées comme vues. Pourquoi ne pas avoir utilisé des ClassBasedView ? Cela permet par exemple d'utiliser des Mixin pour l'API, de factoriser à mort le code (plus de if request.type == POST) et de bien simplifier le tout.

Du coup, quitte à refactoriser le code, pourquoi ne pas utiliser ce genre de vues ?

+0 -0

Il n'y aura pas de branches ZEP-02 mais ZEP-17, je ne suis même pas sûr qu'il faut une branche à part (je ne l'ai pas fais) puisque la ZEP est bien versionnée et peut se faire par itération. Par contre, je n'ai personnellement pas le temps de faire le refactoring pour l'instant, comme indiqué dans mon précédent message.

je ne suis même pas sûr qu'il faut une branche à part (je ne l'ai pas fais) puisque la ZEP est bien versionnée et peut se faire par itération

L'idée d'avoir une branche a coté, c'est de permettre aussi à plusieurs personnes de travailler sur une itération de la ZEP, en même temps. Quelqu'un pourrait pousser la doc, quelqu'un d'autre les tests, quelqu'un d'autre encore une partie de la refacto, etc.

Si tout le monde fait sa branche dans son coin ce n'est pas viable, car on ne pourra pas pousser sur le dépôt par exemple une PR qui fait de la doc pour un module qui n'existe pas encore. D'ou l'idée de centraliser toute la ZEP-17 sur une branche du dépot officiel pour faire participer le plus de personne étant donné que la spec est déjà formalisée

Ok, je comprends mais il reste un problème. Le refactoring nécessaire pour la ZEP ne devrait pas être lié au développement de cette ZEP, et donc dans sa branche. Et très sincèrement, c'est un travail énorme de refactorer tout le projet pour pouvoir utiliser l'existant dans le développement de l'API.

Le refactoring nécessaire pour la ZEP ne devrait pas être lié au développement de cette ZEP

Andr0

D'après ce que tu dis, c'est pourtant un pré-réquis de la ZEP. Donc si le pré-requis n'est pas fait, la ZEP non plus. ça peut tourner en rond pendant longtemps, et au final on risque d'abandonner parce que c'es trop gros.

A mon avis, il faut penser communautaire. Pas forcément se dire que tout sera fait par une seule personne, mais justement que tout le monde puisse participer (aussi minime soit les apports) au développement. Ceci passe donc par une ouverture et une publication fréquente dès qu'on a quelque chose. ça permet aux autres de suivrent, et a certaines personnes de pouvoir apporter une aide ponctuelle sur certains points.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte