dbourdette / fun-reco

0 stars 0 forks source link

Service REST d'édition des données #21

Closed dbourdette closed 12 years ago

dbourdette commented 12 years ago

A la suite de #19 (Service REST de test), construire un ensemble de services REST afin d'éditer les profils, amis et actions.

Il faut commencer par définir les chemins pour ces ressources.

Les controleurs doivent faire appel à la facade basique construite en #17 afin de sauver et lire les données en base.

Si nécessaire, on pourra modifier ou rajouter des méthodes à l'interface RecommendationFacade

aprestaux commented 12 years ago

J'ai un problème avec les constructeurs de Groovy à partir d'un JSON, dans la doc il est indiqué que le Marshalling JSON est automatique lorsqu'on indique "resource" dans l'URLMapping mais j'avais à chaque fois une erreur comme quoi le constructeur à partir d'une string était inconnu. J'ai fait des tests dans Document et j'ai fini par réussir en prenant un détour par un JSONArray mais déjà ce n'est pas joli du tout et en plus cela ne fonctionne pas pour Action

De plus, je suis allée faire un tour dans ma base de test MongoDB et j'ai l'impression que tout n'est pas bien sauvé : l'objet Object a ses properties à null, alors qu'on lui fournit une liste dans le test de persistence et tous les objets Action ne contiennent qu'une date, alors qu'ils devraient également contenir un profil et un object

dbourdette commented 12 years ago

J'ai regardé et je ne trouve pas non plus. La doc semble plus que flou et cela ne fonctionne pas en l'état. Je regarderai demain, vous pouvez essayer en attendant.

dbourdette commented 12 years ago

Bon, après encore plus de tests, ça veut pas. De plus, en lisant la doc, le format du json ne semble pas très flexible (par exemple, le nom de la classe de base doit être dans le json). On va donc se tourner vers la lib de reférence pour le mapping java <-> json : jackson

On change donc de plan :

Voici un exemple simple trier le la :

import org.codehaus.jackson.map.ObjectMapper

class User 
{
    static class Name{
        String first;
        String last;
    }

    Name name
    Boolean verified
    String gender
    byte[] userImage
}

String json = """{
  "name" : { "first" : "Joe", "last" : "Sixpack" },
  "gender" : "MALE",
  "verified" : false,
  "userImage" : "Rm9vYmFyIQ=="
}
"""

ObjectMapper mapper = new ObjectMapper(); 
User user = mapper.readValue(json, User.class);

assert user.name.first == "Joe"
assert user.name.last == "Sixpack"
assert user.gender == "MALE"
assert user.verified == false
assert user.userImage.size() == 7

Au résultat, de toutes façons, cela va nous offrir beaucoup plus de flexibilité.

dbourdette commented 12 years ago

De plus, le service REST doit utiliser les objets de l'API publique (package com.github.funreco)

aprestaux commented 12 years ago

l'API ne doit pas utiliser le modèle de données mis en place au ticket 16 ?

dbourdette commented 12 years ago

Non, au ticket 16 c'est le modèle de persistance, qui va servir au moteur, derrière la facade.

Est ce que le schéma suivant vous permet de mieux visualiser ce que nous construisons : https://github.com/dbourdette/fun-reco/wiki/schéma

aprestaux commented 12 years ago

Voila un premier test avec la lib Jackson, cela à l'air de bien fonctionner, les problèmes viennent maintenant de l'encodage des objets complexes (type Hash) en JSON

Je ne suis pas sûre de bien comprendre le modèle d'utilisation des données : il y a une différence entre le modèle de l'API publique et le modèle de données que nous avons choisi dans fun.reco : comment faire la liaison ?

dbourdette commented 12 years ago

Vous devez pouvoir supprimer la config JSON du BootStrap.groovy.

Oui il y aura une différence :

C'est la facade qui devra faire la transcription. Cela permettra de faire évoluer le moteur, changer complètement le modèle privé si besoin sans que l'API publique ne bouge. C'est ce que j'ai fait pour mon propre moteur, la partie publique est la même et pourtant les données sont stoquées dans un format très différent de celui que vous avez commencé à faire.

Cela devrait être plus clair après le ticket #17

aprestaux commented 12 years ago

La config JSON du Bootstrap.groovy permet de paramétrer le encodeAsJson de Groovy pour qu'il ne mettre pas de clés superflues, j'ai essayé sans et le mapping Jackson ne fonctionnait pas

dbourdette commented 12 years ago

En utilisant le modèle publique, il ne devrait pas (ou presque) y avoir de clés superflues. A la fin, le mapping avec Jackson doit pouvoir fonctionner mais la configuration demandera sans doute aussi quelques lignes. Pour le moment, rien de tout cela n'est très grave.

aprestaux commented 12 years ago

J'ai quelques questions concernant le schema (et donc la manière d'implémenter l'API REST) :

dbourdette commented 12 years ago
aprestaux commented 12 years ago

D'accord, donc le ActionRestController que j'ai commencé n'utilise pas les bons objets c'est bien ça ?

dbourdette commented 12 years ago

toutafé

aprestaux commented 12 years ago

Pour vérifier dans les tests que les objets POST dans les webservices sont bien sauvegardés en base, je dois passer par la Facade également (pour compter les objets en base) ? Parce que là par exemple je crois que j'ai une erreur qui vient d'une différence entre le modèle de l'API publique et le modèle de données MongoDB que nous avons choisi (au niveau de l'attribut friendsIds de Profile)

dbourdette commented 12 years ago

Vu ce qu'on veut tester, je déplacerais plutôt le test dans unit et je ferais un mock de la facade. Ainsi, je vérifie que le service REST appelle bien les bonnes méthodes de la Facade et retourne les bons codes http. Le test de la facade et de la persitance est plus lié au ticket #17

Ainsi, c'est plus simple a tester. Bon par contre, il faut faire un mock

Si vous ne souhaitez pas faire ça, il vaut mieux attendre que la Facade soit opérationnelle via #17

Sinon, quelle est votre erreur ? Je peux au moins essayer de vous indiquer le sens de celle ci.

aprestaux commented 12 years ago

L'erreur est

Unrecognized field "friendsIds" (Class com.github.funreco.Profile), not marked as ignorable at [Source: java.io.StringReader@7ea47c7a; line: 1, column: 76](through reference chain: com.github.funreco.Profile["friendsIds"])

Je pense qu'elle vient du fait que j'instancie une Action du package fun.reco alors que le controller utilise pour l'instant une implémentation de la facade avec les Action de l'API publique

dbourdette commented 12 years ago

Unrecognized field vient de jackson qui n'arrive pas a lire un champs dans le modèle.

Depuis votre test, vous utilisez des fun.reco (venant de TestData qui a servi pour la persitance) alors qu'a la désérialisation, le controller s'attend a avoir des com.github.funreco

Bref, dans ActionRESTTests, Action doit être du package com.github.funreco si vous voulez faire un encodeAsJSON(). Et encore, cette méthode doit introduire un attribut class... le mieux serait d'utiliser jackson.

Il faut reconnaitre que notre choix de nom de packages est loin d'être judicieux pour le moment. Nous essayerons de corriger cela prochainement.

dbourdette commented 12 years ago

Pour les dépendances, le mieux est de faire des mocks afin de pouvoir avancer facilement sans attendre la fin de la Facade.

aprestaux commented 12 years ago

J'ai fait un premier essai de mock qui n'est pas très concluant, j'ai l'impression que ma facade mock ne n'instancie pas, car je récupère une erreur

Cannot invoke method updateProfile() on null object

or le params.profile est bien récupéré et désérialisé, j'ai fait des tests. Pourriez-vous m'indiquer si j'ai mal configuré le mock ?

dbourdette commented 12 years ago

grails test-app unit: ProfileRestTests

java.lang.NullPointerException: Cannot invoke method updateProfile() on null object
  at com.github.dbourdette.api.ProfileRestController.save(ProfileRestController.groovy:19)
  at com.github.dbourdette.api.ProfileRestTests.testSave(ProfileRestTests.groovy:17)

Donc ProfileRestController.groovy ligne 19

if (recommendationFacade.updateProfile(profile)) {

Donc recommendationFacade est null

En fait, en lisant la doc, si on fait un mockFor (au lieu d'un @Mock), il faut tout faire à la main. Donc il faut aussi injecter la dépendance à la main

controller.recommendationFacade = facade.createMock()
dbourdette commented 12 years ago

Au passage, le code pour envoyer du json dans le body (content) de la requête devrait être le suivant.

Profile profile = new Profile(facebookId: "testFB", email: "testEmail", name: "testName")
controller.request.contentType = "application/json"
controller.request.content = new ObjectMapper().writeValueAsBytes(profile)

Coté serveur, le json est disponible via request.JSON

La doc grails sur les services REST est loin d'être claire.

aprestaux commented 12 years ago

Effectivement en injectant la dépendance à la main, la facade est bien déclarée, merci.

En revanche, j'ai essayé de passer le JSON via request.content et de le traiter avec request.JSON mais je n'ai pas réussi à le faire marcher. La méthode en passant par params était celle présentée dans la doc du ticket 19

dbourdette commented 12 years ago

Il faut pouvoir éditer les profils, amis et actions.

Voici les méthodes et les chemins possibles

ProfileRestController

GET /api/profile/{facebookId} affiche un profil POST /api/profile/{facebookId} met a jour un profil

ActionRestController

PUT /api/profile/{facebookId}/actions ajoute une action pour le profil

FriendsRestController

GET /api/profile/{facebookId}/friends affiche les amis d'un profil POST /api/profile/{facebookId}/friends met à jour les amis d'un profil

La démarche est la suivante :

Il faut donc écrire les tests unitaires et les controllers qui vont avec.

dbourdette commented 12 years ago

J'avais fait un test rapide avec request.content et cela tournait. C'est assez mal documenté mais coté client, le contenu doit être envoyé dans le body.

De ce que je comprends, resource dans les mappings (anciennement parseRequest) va lire le body et faire comme si c'était des params.

Ce n'est pas très important, nous pourrons corriger en temps utiles.

dbourdette commented 12 years ago

Cela semble pas mal, c'est beaucoup plus agréable à lire.

Voici quelques propositions que j'ai comité dans ActionRestControllerTests

Maintenant, on verra comment ces services évolueront à l'usage.