Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tribulation d'un développeur Java dans le Cloud

Tribulation d'un développeur Java dans le Cloud

Comme beaucoup de développeurs une grande partie de mon temps libre est utilisé pour découvrir de nouvelles technologies et développer des applications avec celles-ci.

J'ai donc choisi de découvrir le développement d'application Java sur le cloud, avec Google AppEngine, pour créer le site http://www.resultri.com qui permet de gérer les resultats de triathlon (mon autre passion).
Développer cette application est une aventure interessante que je partage avec vous durant ce BOF:

découverte de GAE et des outils de developpement
les "surprises" du NoSQL, surtout pour un cerveau "cablé relationnel comme le mien"
hmmm tout n'est pas gratuit?
les quelques trucs à savoir : l'importance de memcache, utilisation de CloudSQL, les batchs....

Tugdual Grall

May 03, 2012
Tweet

More Decks by Tugdual Grall

Other Decks in Technology

Transcript

  1. Ce que vous allez apprendre • Retour d’experience d’un developpeur

    du “Dimanche” • Comprendre “mes” choix • Difficultés Surprises du Cloud • Ce qu’est le triathlon 2 Thursday, May 3, 12
  2. About me : “Tug” • CTO chez eXo depuis 2008

    • Développeur, Product Manager chez Oracle • Co fondateur du NantesJUG • @tgrall sur Twitter & RunKeeper • Triathlete • Développeur de resultri.com 3 Thursday, May 3, 12
  3. Agenda • Le Cloud, Pourquoi ? Comment ? • Les

    surprises Bonnes et Mauvaises • Et Alors ? 4 Thursday, May 3, 12
  4. Le cloud, pourquoi? • Besoins fonctionnels : • Analyser les

    résultats de courses :Triathlons ou autres • Partager ces résultats simplement : réseaux sociaux • Besoins techniques : • Apprendre à développer pour le cloud • Pas de gestion système, ressources disponibles 5 Thursday, May 3, 12
  5. Le triathlon • Natation • Vélo • Course à pied

    6 • Le plus “connu”: • Ironman Hawaii • 3.8km / 180 km / 42.2 • Mais également aux JO • 1.5km / 40km / 10 km Thursday, May 3, 12
  6. Choisir une plateforme • Aout 2011, avant mon départ en

    vacances • Support du mode déconnecté • Java & NoSQL • Documentée et “Connue” • Gratuite • Disponible sur Mac OS X sans “hack” 8 Thursday, May 3, 12
  7. Choisir une plateforme • Aout 2011, avant mon départ en

    vacances • Support du mode déconnecté • Java & NoSQL • Documentée et “Connue” • Gratuite • Disponible sur Mac OS X sans “hack” 8 Thursday, May 3, 12
  8. Google AppEngine • Google PaaS • Languages : Java, Python

    and Go • Base de données : BigTable (NoSQL) & CloudSQL (MySQL) • Accès simplifié aux API et Services Google • Google Cloud Storage, Analytics, Google Apps, Maps... 9 Thursday, May 3, 12
  9. Resultri en quelques mots • Pour les Geeks • Pure

    Servlet/JSP • REST avec JAX-RS (Jersey) • Twitter Boostrap 10 • Pour les Sportifs • 33 courses • 46 754 résultats individuels • 3500 visiteurs uniques • 11 300 pages vues Thursday, May 3, 12
  10. Accès aux données? • Ma premiere réelle experience du monde

    NoSQL • Mon passé (passif?) : 9 années d’Oracle, ... très SQL (JPA,Toplink, BC4J !) • API : JPA, JDO, Datastore API ou autre (Objectify) ? 12 Thursday, May 3, 12
  11. Accès aux données? • Mon choix : JDO & Datastore

    API • Limité aux API Google (documenté dans le SDK) • JDO : car la doc était la plus complète • Datastore : est venu ensuite pour la simplicité 13 Thursday, May 3, 12
  12. Resultri: Les Données 14 Sites “résultats” HTML, TXT, CSV, XML

    Traitements en local Utilisation du GAE Dev Server Export CSV Thursday, May 3, 12
  13. Resultri: Les Données 14 Sites “résultats” HTML, TXT, CSV, XML

    Traitements en local Utilisation du GAE Dev Server Export CSV Import GAE Utilisation des outils GAE Import CSV Thursday, May 3, 12
  14. Dev to “Cloud” 1/2 15 1: DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

    2: ... 3: Map<String, Object> propertiesMap = result.getProperties(); 4: properties = (String[]) propertiesMap.keySet()...; 5: ... 6: Query q = new Query("TriathlonResult"); 7: q.addFilter("raceURI", Query.FilterOperator.EQUAL,race); 8: PreparedQuery pq = datastore.prepare(q); 9: for (Entity result : pq.asIterable()) { 10: ! for (int i = 0; i < properties.length; i++) { 11: 12: Object o = result.getProperty(properties[i]); 13: String printValue = null; 14: if (o != null) { 15: ! if (o instanceof java.util.Date) { 16: ! ! SimpleDateFormat formatter = new SimpleDateFormat("yyy ... H:mm:ss") ; 17: ! ! Date date = (Date) o; 18: ! ! printValue = formatter.format(date); 19: ! } else if ... { ...! } 20: } 21: out.print((printValue == null)?"":printValue); 22: out.print(","); 23: ! } 24: ... • Une fois les données importées en local • Création d’un Fichier CSV • Utilisation des API Datastore • Trop “couteux” à faire sur le Cloud Thursday, May 3, 12
  15. Dev to “Cloud” 2/2 16 • Création des entités par

    CLI • Très rapide • Import des données “formatées” appcfg.py upload_data --config_file=config.yml --filename=ironman-south-africa.csv --url=http://dev-result-db.appspot.com/remote_api --application=dev-result-db --kind=TriathlonResult Uploading data records. [INFO ] Logging to bulkloader-log-20120416.155812 [INFO ] Throttling transfers: [INFO ] Bandwidth: 250000 bytes/second [INFO ] HTTP connections: 8/second [INFO ] Entities inserted/fetched/modified: 20/second [INFO ] Batch Size: 10 [INFO ] Opening database: bulkloader-progress-20120416.155812.sql3 [INFO ] Connecting to dev-result-db.appspot.com/remote_api Please enter login credentials for dev-result-db.appspot.com Email: [email protected] Password for [email protected]: [INFO ] Starting import; maximum 10 entities per post ................................................................. [INFO ] 1552 entities total, 0 previously transferred [INFO ] 1552 entities (7016782 bytes) transferred in 60.4 seconds [INFO ] All entities successfully transferre Thursday, May 3, 12
  16. Recherche Full Text • Beta privée en Octobre... • Solution

    “Resultri” • Utilisation de Cloud SQL 17 Thursday, May 3, 12
  17. Recherche Full Text • Beta privée en Octobre... • Solution

    “Resultri” • Utilisation de Cloud SQL 17 BigTable CloudSQL First & Last Names Thursday, May 3, 12
  18. Accès aux données • Utilisation de Memcache • Cache disponible

    dans GAE • Gestion du cache par le biais de l’API JCache (JSR-107) ou API Google • Dans Resultri: • Tout Entity est automatiquement cachée 19 Thursday, May 3, 12
  19. 22 1: log.info(" -- Start -- "); 2: DatastoreService datastore

    = DatastoreServiceFactory.getDatastoreService(); 3: Entity employee = new Entity("Employee"); 4: employee.setProperty("firstName", "Nicolas"); 5: employee.setProperty("lastName", "Martignole"); 6: Date hireDate = new Date(); 7: employee.setProperty("hireDate", hireDate); 8: employee.setProperty("attendedHrTraining", true); 9: datastore.put(employee); 10: employee = new Entity("Employee"); 11: employee.setProperty("firstName", "Antonio"); 12: employee.setProperty("lastName", "Goncalvez"); 13: hireDate = new Date(); 14: employee.setProperty("hireDate", hireDate); 15: employee.setProperty("attendedHrTraining", true); 16: datastore.put(employee); 17: Query q = new Query("Employee"); 18: PreparedQuery pq = datastore.prepare(q); 19: for (Entity emp : pq.asIterable()) { 20: ! log.info(emp.getProperty("firstName") + " "+ emp.getProperty("lastName" ); 21: } 22: log.info(" -- End -- "); Création de données Thursday, May 3, 12
  20. 22 1: log.info(" -- Start -- "); 2: DatastoreService datastore

    = DatastoreServiceFactory.getDatastoreService(); 3: Entity employee = new Entity("Employee"); 4: employee.setProperty("firstName", "Nicolas"); 5: employee.setProperty("lastName", "Martignole"); 6: Date hireDate = new Date(); 7: employee.setProperty("hireDate", hireDate); 8: employee.setProperty("attendedHrTraining", true); 9: datastore.put(employee); 10: employee = new Entity("Employee"); 11: employee.setProperty("firstName", "Antonio"); 12: employee.setProperty("lastName", "Goncalvez"); 13: hireDate = new Date(); 14: employee.setProperty("hireDate", hireDate); 15: employee.setProperty("attendedHrTraining", true); 16: datastore.put(employee); 17: Query q = new Query("Employee"); 18: PreparedQuery pq = datastore.prepare(q); 19: for (Entity emp : pq.asIterable()) { 20: ! log.info(emp.getProperty("firstName") + " "+ emp.getProperty("lastName" ); 21: } 22: log.info(" -- End -- "); Création de données 1: Apr 16, 2012 4:22:25 PM com....Servlet doGet 2: INFO: -- Start -- 3: Apr 16, 2012 4:22:25 PM com....Servlet doGet 4: INFO: Nicolas Martignole 5: Apr 16, 2012 4:22:25 PM com....Servlet doGet 6: INFO: -- End -- 7: Apr 16, 2012 4:22:34 PM com.......$PersistDatastore persist 8: INFO: Time to persist datastore: 4 ms Mais où est parti Antonio???? Thursday, May 3, 12
  21. 23 1: Query q = new Query("Employee"); 2: q.addSort("lastName"); 3:

    q.addSort("firstName"); 4: PreparedQuery pq = datastore.prepare(q); 5: for (Entity emp : pq.asIterable()) { 6: o.println(emp.getProperty("lastName") +" "+ emp.getProperty("firstName") ); 7: } Datastore : Queries Thursday, May 3, 12
  22. 23 1: Query q = new Query("Employee"); 2: q.addSort("lastName"); 3:

    q.addSort("firstName"); 4: PreparedQuery pq = datastore.prepare(q); 5: for (Entity emp : pq.asIterable()) { 6: o.println(emp.getProperty("lastName") +" "+ emp.getProperty("firstName") ); 7: } Datastore : Queries 1: Doe John 2: Goncalvez Antonio 3: Grall Briac 4: Grall Corentin 5: Grall Malo 6: Grall Nolwenn 7: Grall Tug 8: Grall Virginie 9: Martignole Nicolas 10: Tabarly Eric Thursday, May 3, 12
  23. 24 1: Query q = new Query("Employee"); 2: q.addFilter("lastName", FilterOperator.EQUAL

    , "Grall"); 3: q.addSort("lastName"); 4: q.addSort("firstName"); 5: PreparedQuery pq = datastore.prepare(q); 6: for (Entity emp : pq.asIterable()) { 7: o.println(emp.getProperty("lastName") + " "+ emp.getProperty("firstName") ); 8: } Datastore : Queries Thursday, May 3, 12
  24. 24 1: Query q = new Query("Employee"); 2: q.addFilter("lastName", FilterOperator.EQUAL

    , "Grall"); 3: q.addSort("lastName"); 4: q.addSort("firstName"); 5: PreparedQuery pq = datastore.prepare(q); 6: for (Entity emp : pq.asIterable()) { 7: o.println(emp.getProperty("lastName") + " "+ emp.getProperty("firstName") ); 8: } Datastore : Queries 1: Grall Briac 2: Grall Corentin 3: Grall Malo 4: Grall Nolwenn 5: Grall Tug 6: Grall Virginie Thursday, May 3, 12
  25. 25 1: Query q = new Query("Employee"); 2: q.addFilter("lastName", FilterOperator.EQUAL

    , "Grall"); 3: q.addFilter("birthYear", FilterOperator.LESS_THAN_OR_EQUAL , 2000); 4: q.addSort("lastName"); 5: q.addSort("firstName"); 6: PreparedQuery pq = datastore.prepare(q); 7: for (Entity emp : pq.asIterable()) { 8: o.println(emp.getProperty("lastName") + " "+ emp.getProperty("firstName") ); 9: } Datastore : Queries Thursday, May 3, 12
  26. 25 1: Query q = new Query("Employee"); 2: q.addFilter("lastName", FilterOperator.EQUAL

    , "Grall"); 3: q.addFilter("birthYear", FilterOperator.LESS_THAN_OR_EQUAL , 2000); 4: q.addSort("lastName"); 5: q.addSort("firstName"); 6: PreparedQuery pq = datastore.prepare(q); 7: for (Entity emp : pq.asIterable()) { 8: o.println(emp.getProperty("lastName") + " "+ emp.getProperty("firstName") ); 9: } Datastore : Queries ? Thursday, May 3, 12
  27. 25 1: Query q = new Query("Employee"); 2: q.addFilter("lastName", FilterOperator.EQUAL

    , "Grall"); 3: q.addFilter("birthYear", FilterOperator.LESS_THAN_OR_EQUAL , 2000); 4: q.addSort("lastName"); 5: q.addSort("firstName"); 6: PreparedQuery pq = datastore.prepare(q); 7: for (Entity emp : pq.asIterable()) { 8: o.println(emp.getProperty("lastName") + " "+ emp.getProperty("firstName") ); 9: } Datastore : Queries ? java.lang.IllegalArgumentException: The first sort property must be the same as the property to which the inequality filter is applied. In your query the first sort property is firstName but the inequality filter is on birthYear Thursday, May 3, 12
  28. 26 1: PersistenceManager pm = PMF.get().getPersistenceManager(); 2: Query q =

    pm.newQuery(Employee.class,"lastName=='Grall'&& firstName=='Malo'"); 3: List<Employee> results = (List<Employee>) q.execute();! 4: o.println(results); Datastore : Queries Thursday, May 3, 12
  29. 26 1: PersistenceManager pm = PMF.get().getPersistenceManager(); 2: Query q =

    pm.newQuery(Employee.class,"lastName=='Grall'&& firstName=='Malo'"); 3: List<Employee> results = (List<Employee>) q.execute();! 4: o.println(results); Datastore : Queries 1: Grall Malo Thursday, May 3, 12
  30. 27 1: PersistenceManager pm = PMF.get().getPersistenceManager(); 2: Query q =

    pm.newQuery(Employee.class,"lastName=='Grall'|| firstName=='Malo'"); 3: List<Employee> results = (List<Employee>) q.execute();! 4: o.println(results); Datastore : Queries Thursday, May 3, 12
  31. 27 1: PersistenceManager pm = PMF.get().getPersistenceManager(); 2: Query q =

    pm.newQuery(Employee.class,"lastName=='Grall'|| firstName=='Malo'"); 3: List<Employee> results = (List<Employee>) q.execute();! 4: o.println(results); Datastore : Queries ? Thursday, May 3, 12
  32. 27 1: PersistenceManager pm = PMF.get().getPersistenceManager(); 2: Query q =

    pm.newQuery(Employee.class,"lastName=='Grall'|| firstName=='Malo'"); 3: List<Employee> results = (List<Employee>) q.execute();! 4: o.println(results); Datastore : Queries ? org.datanucleus.store.appengine.query.DatastoreQuery$UnsupportedDatastoreFeatureException: Problem with query <SELECT FROM com.grallandco.model.Employee WHERE lastName=='Grall' || firstName=='Malo'>: Or filters cannot be applied to multiple properties (found both lastName and firstName). Thursday, May 3, 12
  33. Manipulation des données • Les données ne sont pas toujours

    disponibles • High Data Replication (HDR) : surprenant pendant les tests • “Limitations” des requêtes (GQL) et JDO • Attentions aux index : sans index pas de “requetes” • Verifier la disponibilité des index • Utilisation de Memcache (must have! ) 28 Thursday, May 3, 12
  34. Task Queues • Service pour l’execution de taches en arrière

    plan • Possibilité d’inclure les taches dans un ordonnanceur (cron) • Resultri: • Synchronization (BigTable/CloudSQL) , Nettoyage des données 30 1: Queue queue = QueueFactory.getQueue("SynchappAppQueue"); 2: TaskOptions taskOptions = TaskOptions.Builder.withUrl("/worker/ProcessSynchronizeDataServlet") 3: .param("race-uri", "ironman-florida-2005") 4: .param("type", "syncDB") 5: .method(TaskOptions.Method.POST); 6: queue.add(taskOptions); Thursday, May 3, 12
  35. Test en Local • Un version de Jetty avec le

    SDK Google AppEngine 31 Simulation “Authentification Google” Console Developpeur Thursday, May 3, 12
  36. Authentification Google • Utilisation de Google Account • Se base

    sur la sécurité JavaEE standard • Gestion des administrateurs par le biais de la console AppEngine 33 <security-constraint> <web-resource-collection> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> Thursday, May 3, 12
  37. URL Fetch • Accès à des serveurs autres que AppEngine

    par HTTP/HTTPS • Utilisation de java.net.URL ou com.google.appengine.api.urlfetch.URLFetchService • Dans Resultri : utilisation du service de GeoCodage de Google Maps 34 1: URL url = new URL("http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address="+ address ); Thursday, May 3, 12
  38. Accès aux API Google • Google offre de nombreux Services

    et API • Un seul compte => tous les services 35 Thursday, May 3, 12
  39. Resultri & Google • Les services que j’utilise pour Resultri

    • Google Apps : Gestion du nom de domaines, Mail, ... • CloudSQL : Recherche Full Text (MySQL) • Cloud Storage : Sauvegarde BigTable, Serveur de fichiers • Google Maps • Analytics, Adsense, Webmaster 36 Thursday, May 3, 12
  40. Gratuit ? • Oui mais.... • Avec des quotas •

    Les règles peuvent changer • Septembre : Google modifie ses tarifs 38 Thursday, May 3, 12
  41. Gratuit ? • Oui mais.... • Avec des quotas •

    Les règles peuvent changer • Septembre : Google modifie ses tarifs 38 Thursday, May 3, 12
  42. Gestion du cout • En Fevrier : Augmentation du cout

    • Pas d’activité utilisateur, mais cout en augmentation 40 Thursday, May 3, 12
  43. Gestion du cout • En Fevrier : Augmentation du cout

    • Pas d’activité utilisateur, mais cout en augmentation 40 • Une idée ? Thursday, May 3, 12
  44. Merci Google! • Google Bots, et autres moteurs de recherche

    • Utilisation de Google Webmaster Tools • Mise en place d’un fichier robots.txt 41 Thursday, May 3, 12
  45. Merci Google! • Google Bots, et autres moteurs de recherche

    • Utilisation de Google Webmaster Tools • Mise en place d’un fichier robots.txt 41 User-agent: Twiceler Disallow: / User-agent: psbot Disallow: / User-agent: * Disallow: /search Disallow: /races/*/compare Disallow: /rest Disallow: /races/*/*/position Disallow: /races/*/*/rank Disallow: /races/*/*/otherRaces Disallow: /races/*?page=-* Thursday, May 3, 12
  46. Merci Google! • Google Bots, et autres moteurs de recherche

    • Utilisation de Google Webmaster Tools • Mise en place d’un fichier robots.txt 41 User-agent: Twiceler Disallow: / User-agent: psbot Disallow: / User-agent: * Disallow: /search Disallow: /races/*/compare Disallow: /rest Disallow: /races/*/*/position Disallow: /races/*/*/rank Disallow: /races/*/*/otherRaces Disallow: /races/*?page=-* • 243 784 URL bloquées sur Resultri Thursday, May 3, 12
  47. Conclusion • AppEngine est-il le bon choix pour Resultri? •

    Techniquement passionnant • Pour l’instant trop couteux et “puissant” • A moi d’améliorer mon application (Gestion des indexes, requetes, ...) • OUI le Cloud est la bonne solution • OUI AppEngine est la bonne solution 44 Thursday, May 3, 12
  48. Conclusion • Techniquement : j’apprends beaucoup.... • NoSQL, CloudSQL, Memcache....

    • Utilisation de Git • Twitter Bootstrap, Less 45 • Publication d’articles sur mon blog • Twitter Boostrap 2 and Google Maps • Google AppEngine Full Text Search with Cloud SQL • Installing Memcached on Mac OS X and using it in Java • JAX-RS: Jersey and JSON single element arrays • Create and Deploy a JAX-RS REST service on Google App Engine • .... Thursday, May 3, 12