% -*- mode: latex; -*- mustache tags:  
\ifx\wholebook\relax\else

	\documentclass[10pt,twoside,english]{root/support/latex/sbabook/sbabook}
  \usepackage{import}
  \subimport{root/support/latex/}{common.tex}

  \hypersetup{pdfinfo = {
    Title = {Persistance des données de TinyBlog avec Voyage et Mongo},
    Author = {},
    Keywords = {pharo, smalltalk}}}
  \begin{document}
\fi

\chapter{Persistance des donn\'{e}es de TinyBlog avec Voyage et Mongo}\section{Correction semaine pr\'{e}c\'{e}dente}
Vous pouvez charger la correction de la semaine pr\'{e}c\'{e}dente en ex\'{e}cutant le code suivant:

\begin{displaycode}{smalltalk}
Gofer new 
   smalltalkhubUser: 'PharoMooc' project: 'TinyBlog';
   package: 'ConfigurationOfTinyBlog';
   load. 
#ConfigurationOfTinyBlog asClass loadWeek3Correction
\end{displaycode}

Puis, testez le code en ex\'{e}cutant:

\begin{displaycode}{smalltalk}
	TBTeapotWebApp start
\end{displaycode}

Attention, v\'{e}rifiez d'abord que votre propre serveur HTTP ne soit pas d\'{e}j\`{a} lanc\'{e} sur le m\^{e}me port . Si c'est le cas, arr\^{e}tez votre application ou changez son port. 

Vous devez ajouter des posts dans le blog pour voir quelquechose. 

\begin{displaycode}{smalltalk}
	TBBlog reset ; createDemoPosts
\end{displaycode}

Avant de passer \`{a} la suite, stoppez votre serveur Teapot:

\begin{displaycode}{smalltalk}
	TBTeapotWebApp stop
\end{displaycode}
\section{Pr\'{e}paration de sauvegarde ext\'{e}rieure avec Voyage}
Avoir un mod\`{e}le d'objets en m\'{e}moire fonctionne bien, et sauvegarder l'image Pharo sauve aussi ces objets. Toutefois, il est pr\'{e}f\'{e}rable de pouvoir sauver les objets dans une base de donn\'{e}es ext\'{e}rieure. Voyage permet de sauver les objets dans une base de donn\'{e}e Mongo. Voyage est un frameworks qui propose la m\^{e}me API a plusieurs bases de donn\'{e}es documents comme Mongo ou UnQLite.

C'est ce que nous allons voir. Dans un premier temps, nous allons utiliser la capacit\'{e} de Voyage \`{a} simuler une base ext\'{e}rieure. Ceci est tr\`{e}s pratique en phase de d\'{e}veloppement. Dans un second temps, nous installerons une base de donn\'{e}es Mongo et nous y acc\'{e}derons \`{a} travers Voyage. 
\section{Configurer Voyage pour sauvegarder des objets TBBlog}
Gr\^{a}ce \`{a} la m\'{e}thode de classe \textcode{isVoyageRoot}, nous d\'{e}clarons que les objets de la classe \textcode{TBBlog} doivent \^{e}tre sauv\'{e}s dans la base en tant qu'objets racines.

\begin{displaycode}{smalltalk}
TBBlog class >> isVoyageRoot
   "Indicates that instances of this class are top level document in noSQL databases"
   ^ true
\end{displaycode}

Nous devons ensuite soit cr\'{e}er une connexion sur une base de donn\'{e}es r\'{e}elle soit travailler en m\'{e}moire. C'est cette derni\`{e}re option que nous choisissons pour l'instant en utilisant cette expression. 

\begin{displaycode}{smalltalk}
VOMemoryRepository new enableSingleton. 
\end{displaycode}

Le message \textcode{enableSingleton} indique \`{a} Voyage que nous n'utilisons qu'une seule base de donn\'{e}e ce qui nous permet de ne pas avoir \`{a} pr\'{e}ciser avec laquelle nous travaillons. 

Nous d\'{e}finissons une m\'{e}thode \textcode{initializeVoyageOnMemoryDB} dont le r\^{o}le est d'initialiser correctement la base. 

\begin{displaycode}{smalltalk}
TBBlog class >> initializeVoyageOnMemoryDB
   VOMemoryRepository new enableSingleton
\end{displaycode}

Ici nous n'avons pas besoin de stocker la base dans une variable d'instance car nous n'avons qu'une seule base de donn\'{e}e en mode singleton. 

Nous red\'{e}finissons les m\'{e}thodes de classe \textcode{reset} et \textcode{initialize} pour nous assurer que l'endroit o\`{u} la base est sauv\'{e}e est r\'{e}initialis\'{e} lorsque l'on charge le code. 

La m\'{e}thode \textcode{reset} r\'{e}initialize la base. 

\begin{displaycode}{smalltalk}
TBBlog class >> reset
      self initializeVoyageOnMemoryDB
      
TBBlog class >> initialize
      self reset
\end{displaycode}

Le cas de de la m\'{e}thode \textcode{current} est plus d\'{e}licat. Avant l'utilisation de Mongo, nous avions un singleton tout simple. Cependant utiliser un Singleton 
ne fonctionne plus car imaginons que nous ayons sauv\'{e} notre blog et que le serveur s'\'{e}teigne par accident ou que nous rechargions une nouvelle version du code. Ceci conduirait \`{a} une r\'{e}initialisation et cr\'{e}ation d'une nouvelle instance. Nous pouvons donc nous retrouver avec une instance diff\'{e}rente de celle sauv\'{e}e. 

Nous red\'{e}finissons \textcode{current} de mani\`{e}re \`{a} faire une requ\^{e}te dans la base. Comme pour le moment nous ne g\'{e}rons qu'un blog il nous suffit de faire \textcode{self selectOne: {[} :each \textbar{} true {]}} ou \textcode{self selectAll anyOne}. Nous nous assurons de cr\'{e}er une nouvelle instance et la sauvegarder si aucune instance n'existe dans la base. 

\begin{displaycode}{smalltalk}
TBBlog class >> current
   ^ self selectAll 
			ifNotEmpty: [ :x | x anyOne ]
			ifEmpty: [ self new save ]
\end{displaycode}

La variable \textcode{uniqueInstance} qui servait auparavant \`{a} stocker le singleton \textcode{TBBlog} peut \^{e}tre enlev\'{e}e.
\section{Sauvegarde d'un blog}
Nous devons maintenant modifier la m\'{e}thode \textcode{writeBlogPost:} pour sauver le blog lors de l'ajout d'un post.

\begin{displaycode}{smalltalk}
TBBlog >> writeBlogPost: aPost
   "Write the blog post in database"
	self allBlogPosts add: aPost.
   self save
\end{displaycode}

Nous pouvons aussi modifier la m\'{e}thode \textcode{remove} afin de sauver le nouvel \'{e}tat d'un blog. 

\begin{displaycode}{smalltalk}
TBBlog >> removeAllPosts
   posts := OrderedCollection new.
   self save.
\end{displaycode}
\section{Utilisation de la base}
Alors m\^{e}me que la base est en m\'{e}moire et bien que nous pouvons acc\'{e}der au blog en utilisant le singleton de la classe \textcode{TBBlog},
nous allons montrer l'API offerte par Voyage. C'est la m\^{e}me API que nous pourrons utiliser pour acc\'{e}der \`{a} une base mongo.

Nous cr\'{e}ons des posts ainsi :

\begin{displaycode}{smalltalk}
TBBlog createDemoPosts.
\end{displaycode}

Nous pouvons compter le nombre de blog sauv\'{e}s. \textcode{count} fait partie de l'API directe de Voyage. Ici nous obtenons 1 ce qui est normal puisque le blog est impl\'{e}ment\'{e} comme un singleton. 

\begin{displaycode}{smalltalk}
TBBlog count
>1
\end{displaycode}

De la m\^{e}me mani\`{e}re, nous pouvons s\'{e}lectionner tous les objets sauv\'{e}s. 

\begin{displaycode}{smalltalk}
TBBlog selectAll
\end{displaycode}

On peut supprimer un objet racine.

\begin{displaycode}{smalltalk}
TBBlog current remove
\end{displaycode}

Vous pouvez voir l'API de Voyage en parcourant

\begin{itemize}
\item la classe \textcode{Class}, et 
\item la classe \textcode{VORepository} qui est la racine d'h\'{e}ritage des bases de donn\'{e}es en m\'{e}moire ou ext\'{e}rieure.
\end{itemize}

Ces queries sont plus pertinentes quand on a plus d'objets mais nous ferions exactement les m\^{e}mes. 
\section{Si nous devions sauvegarder les posts}
Cette section n'est pas \`{a} impl\'{e}menter et elle est juste donn\'{e}e \`{a} titre d'exemple plus d'explications sont donn\'{e}es dans le chapitre sur Voyage dans Enterprise Pharo disponible a \url{http://www.pharo.org}. Nous voulons illustrer que d\'{e}clarer une classe comme une racine Voyage a une influence sur comment une instance de cette classe est sauv\'{e}e et recharg\'{e}e. En particulier car elle ne fait plus partie de l'objet qui lui faisait r\'{e}f\'{e}rence : dans la base elle n'est plus une souspartie du document repr\'{e}sentant l'objet qui la r\'{e}f\'{e}rence mais un document autonome.  

Nous pourrions d\'{e}finir qu'un post soit un \'{e}l\'{e}ment qui peut \^{e}tre sauvegard\'{e} de mani\`{e}re autonome. Cela  permettrait de sauver des posts de mani\`{e}re ind\'{e}pendante d'un blog. Par contre, si nous repr\'{e}sentions les commentaires d'un post nous ne les d\'{e}clarerions pas non plus comme racine car sauver ou manipuler un commentaire en dehors du contexte de son post ne fait pas beaucoup de sens. 

Lorsqu'un post n'est pas une racine, vous n'avez pas la certitude d'unicit\'{e} de celui-ci lors du chargement depuis la base. En effet, lors du chargement (et ce qui peut \^{e}tre contraire \`{a} la situation du graphe d'objet avant la sauvegarde) un post n'est alors pas partag\'{e} entre deux instances de blogs. Si avant la sauvegarde en base un post \'{e}tait partag\'{e} entre deux blogs, apr\`{e}s le chargement depuis la base, ce post sera dupliqu\'{e} car recr\'{e}\'{e} \`{a} partir de la d\'{e}finition du blog (et le blog contient alors compl\`{e}tement le post).
\subsection{Post comme racine = Unicit\'{e}}
Si vous d\'{e}sirez qu'un post soit partag\'{e} et unique entre plusieurs instances de blog, alors les objets \textcode{TBPost} doivent \^{e}tre d\'{e}clar\'{e}s comme une racine dans la base. Lorsque c'est le cas, les posts sont sauv\'{e}s comme des entit\'{e}s autonomes et les instances de \textcode{TBBlog} feront r\'{e}f\'{e}rence \`{a} ces entit\'{e}s au lieu que leurs d\'{e}finitions soient incluses dans celle des blogs. Cela a pour effet qu'un post donn\'{e} devient unique et partageable via une r\'{e}f\'{e}rence depuis le blog. 

Pour cela on nous d\'{e}finirions les m\'{e}thodes suivantes:

\begin{displaycode}{smalltalk}
TBPost class >> isVoyageRoot
   "Indicates that instances of this class are top level document in noSQL databases"
   ^ true
\end{displaycode}

Lors de l'ajout d'un post dans un blog, il est maintenant important de sauver le blog et le nouveau post.

\begin{displaycode}{smalltalk}
TBBlog >> writeBlogPost: aPost
   "Write the blog post in database"
   posts add: aPost.
   aPost save. 
   self save 
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> removeAllPosts
   posts do: [ :each | each remove ].
   posts := OrderedCollection new.
   self save.
\end{displaycode}

Ici dans la m\'{e}thode \textcode{removeAllPosts} nous enlevons chaque posts puis nous remettons \`{a} jour la collection.
\section{Tester avec une base Mongo}
Nous allons maintenant utiliser une base Mongo externe \`{a} Pharo. 
En utilisant Voyage nous pouvons rapidement sauver nos posts dans une base de donn\'{e}es Mongo. Cette section explique rapidement la mise en oeuvre et les quelques modifications que nous devons apporter \`{a} notre projet Pharo pour y parvenir. 
\section{Installation de Mongo}
Quel que soit votre syst\`{e}me d'exploitation (Linux, Mac OSX ou Windows), vous pouvez installer un serveur Mongo localement sur votre machine. Cela est pratique pour tester votre application sans avoir besoin d'une connexion Internet. Nous ne d\'{e}taillons pas ici comment installer Mongo sur votre syst\`{e}me, r\'{e}f\'{e}rez-vous \`{a} la documentation Mongo. 

\begin{note}
Le serveur Mongo ne doit pas utiliser d'authentification car la nouvelle m\'{e}thode de chiffrement SCRAM utilis\'{e}e par MongoDB 3.0 n'est actuellement pas support\'{e}e par Voyage.
\end{note}

Cependant vous devez cr\'{e}er une base de donn\'{e}es nomm\'{e}e 'tinyblog'. 
Lorsque, vous avez install\'{e} et lanc\'{e} un serveur Mongo localement, vous pouvez y acc\'{e}der depuis Pharo.
\section{Connexion \`{a} un serveur local}
Nous d\'{e}finissions les m\'{e}thodes \textcode{initializeLocalhostMongoDB} pour \'{e}tablir la connexion vers la base de donn\'{e}es.

\begin{displaycode}{smalltalk}
TBBlog class >> initializeLocalhostMongoDB
   | repository |
   repository := VOMongoRepository database: 'tinyblog'.
   repository enableSingleton.
\end{displaycode}

Il faut aussi s'assurer de la r\'{e}-initialisation de la connexion \`{a} la base lors du reset de la classe.

\begin{displaycode}{smalltalk}
TBBlog class >> reset
   self initializeLocalhostMongoDB
\end{displaycode}
\subsection{En cas de probl\`{e}me}
Notez que si vous avez besoin de r\'{e}initialiser la base ext\'{e}rieure compl\`{e}tement, vous pouvez utiliser la m\'{e}thode \textcode{dropDatabase}.

\begin{displaycode}{smalltalk}
(VOMongoRepository
   host: 'localhost'
   database: 'tinyblog') dropDatabase
\end{displaycode}
\subsection{Attention : Changements de TBBlog}
Si vous utilisez une base locale plut\^{o}t qu'une base en m\'{e}moire, \`{a} chaque fois que vous ajoutez une nouvelle racine d'objets ou modifiez la d\'{e}finition d'une classe racine (ajout, retrait, modification d'attribut) il est capital de r\'{e}initializer le cache maintenu par Voyage.  La r\'{e}initialisation se fait comme suit:

\begin{displaycode}{smalltalk}
   VORepository current reset
\end{displaycode}
\section{Conclusion}
Voyage propose une API sympathique pour g\'{e}rer de mani\`{e}re transparente la sauvegarde d'objets soit en m\'{e}moire soit dans une base de donn\'{e}es document. Votre application peut maintenant \^{e}tre sauv\'{e}e dans la base et vous \^{e}tes donc pr\^{e}t pour construire son interface web. 



\ifx\wholebook\relax\else
\end{document}
\fi