blog.pagesd.info

Aller au contenu | Aller au menu | Aller à la recherche

mardi 1 juin 2010

MVC Music Store / Raven DB : StoreManagerController (2° partie)

Ceci est la traduction du billet "Porting MVC Music Store to Raven: StoreManagerController, part 2", le dernier de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Le contrôleur StoreManagerController contient encore deux méthodes que nous n'avons pas étudiées. Nous allons devoir les aborder de façon un peu différente.

Est-ce que vous devinez pourquoi ?

C'est parce qu'au départ nous étions d'accord sur le fait qu'il n'y avait aucune raison valable de gérer les artistes en tant que document spécifique. Après tout, il ne s'agit que de données de référence. Sauf que maintenant nous devons y faire référence.

C'est sûr qu'on pourrait créer une série de documents artistes, ce qui faciliterait énormément la migration du code :

Mais je continue à penser que les artistes n'existent pas réellement en tant qu'entité indépendante dans notre modèle. Par conséquent, au lieu de suivre cette voie nous allons plutôt faire une projection.

Nous commençons par définir un index "Artists" à l'aide des requêtes linq map / reduce suivantes :

// map 
from album in docs.Albums
select new { album.Artist.Id, album.Artist.Name }

// reduce 
from artist in results
group artist by new { artist.Id, artist.Name } into g
select new { g.Key.Id, g.Key.Name }

Si vous regardez attentivement ce code, vous pouvez voir que sa fonctionnalité principale est de faire un distinct sur l'ensemble des artistes de tous les albums.

Si bien que maintenant nous pouvons coder nos deux dernières méthodes comme ceci :

Il faut bien comprendre un truc : ça ne coûte rien de faire des requêtes avec Raven, parce que Raven ne permet les requêtes que sur des index et que ces index sont créés en tache de fond, ce qui contribue à rendre les requêtes très rapides.

Et cela remet en cause la façon dont vous allez concevoir votre système et de votre modèle de données. Avec Raven, vous cherchez à axer la plupart de vos traitements sur des index et interroger ces index, parce que c'est ce qui est le moins gourmand.

lundi 31 mai 2010

MVC Music Store / Raven DB : StoreManagerController

Ceci est la traduction du billet "Porting MVC Music Store to Raven: StoreManagerController", le douzième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

La dernière partie du portage de MVC Music Store sous Raven concerne toute la partie administration, implémentée au niveau du contrôleur StoreManagerController. Je vais commencer par une rapide comparaison de toutes les méthodes où le passage sous Raven n'apporte rien de nouveau puis je mettrai l'accent sur une différence de conception plutôt intéressante entre les deux implémentations.

Code d'origine

Portage sous Raven

Le code pour Raven est beaucoup plus court pour la bonne et simple raison que j'ai fait disparaitre tout la pseudo gestion d'erreur qu'il contenait.

Code d'origine

Portage sous Raven

Là encore, le fait de faire disparaitre une gestion d'erreur qui n'est là que pour la gallerie a un impact plus que certain sur la taille du code.

Code d'origine

Portage sous Raven

Ici nous avons une différence plus intéressante. Le code d'origine a pour effet d'effacer les commandes qui contiennent l'album supprimé. Ce que ne fait pas le code porté sous Raven.

La notion d'intégrité référentielle n'existe pas sous Raven (ou de façon plus générale sous les bases de données document). Cela peut être un avantage ou un inconvénient. Mais dans ce cas précis, cela s'avère être un avantage puisque nous pouvons supprimer un album sans perdre de commandes. Je ne sais pas pour vous, mais en ce qui me concerne ça ne me déplait pas de conserver toutes mes commandes :)

Avec Raven, les documents sont indépendants les uns des autres. Par conséquent, le fait de modifier un document n'a aucun impact sur les autres documents.

Il reste encore deux méthodes à étudier en ce qui concerne le contrôleur StoreManagerController, mais je verrai ça dans mon prochain billet.

dimanche 30 mai 2010

MVC Music Store / Raven DB : Gérer le règlement

Ceci est la traduction du billet "Porting MVC Music Store to Raven: Porting the checkout process", le onzième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Dans MVC Music Store, la gestion du règlement est composée de deux parties : renseigner l'adresse et le mode de paiement puis terminer la commande.

Le code pour gérer l'adresse et le mode de paiement dans la version d'origine :

Et ce même code correspondant à la version portée sous Raven :

Comme vous pouvez le constater, ils sont presque identiques. Mais le code pour Raven n'est pas tout à fait complet.

Si vous vous souvenez, nous avions décidé de stocker une propriété CountSold dans le document Album, pour pouvoir plus facilement faire un classement en fonction de ce compteur. Il nous reste donc à réaliser l'incrémentation de ce compteur, ce que j'ai codé immédiatement après l'appel à CreateOrder :

Le truc essentiel dans ce code, c'est que nous avons chargé tous les documents albums en une seule requête. Et lorsque nous sauvegardons, Raven va effectuer un seul appel (batch) au serveur.

Et maintenant, pour être tout à fait complet, voyons ce que donnait la méthode Complete() :

Et dans la nouvelle version sous Raven :

Je pense qu'arrivé à ce point, vous êtes en mesure de comprendre comment fonctionne les deux versions.

Mon prochain billet concernera la partie administration de l'application.

vendredi 28 mai 2010

MVC Music Store / Raven DB : ShoppingCartController

Ceci est la traduction du billet "Porting MVC Music Store to Raven: ShoppingCart", le dixième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Le contrôleur ShoppingCartController est considérablement impacté par tous les changements que nous avons apportés à la classe ShoppingCart. Penchons-nous sur ces modifications, en commençant par l'action Index() dans le code d'origine :

Celui-ci exécute deux requêtes différentes pour gérer la commande alors que la version pour Raven exécute seulement une requête au niveau de la méthode FindShoppingCart () :

Ce code implémente seulement la logique pour charger le panier depuis Raven ou créer un nouveau panier (pour l'identifiant spécifié). Notez-bien que nous n'enregistrons pas le nouveau panier dans la base de données, mais associons seulement ce nouveau panier avec la session. Nous n'avons pas besoin de sauvegarder étant donné que pour l'instant il ne contient rien de significatif. Lorsque nous appellerons SaveChanges(), le nouveau panier sera envoyé vers Raven pour stockage.

Maintenant, voyons-voir l'action AddToChart d'origine :

Et la version portée sous Raven :

Elles sont très similaires, si ce n'est que dans le cas de Raven, la méthode AddToCart() de la classe ShoppingCart est uniquement concerné par l'ajout d'un nouvel article au panier ou par la mise à jour de la quantité d'un article existant. Ainsi, il n'y a absolument aucun accès à la base de données dans la version pour Raven de la méthode ShoppingCart.AddToCart().

La différence c'est donc que dans l'approche pour Raven, nous appelons la méthode session.SaveChanges() au niveau de l'action. Pour la bonne et simple raison que c'est le bon endroit où faire cela étant donné que le code appelant est en charge de l'environnent, y compris la sauvegarde lorsque cela est nécessaire.

Je pense que le code pour Raven est plutôt facile à suivre. Il y a juste un truc un peu étrange à faire remarquer au niveau de la dernière ligne : id.Split(). Pourquoi diable fait-on ça ?

Et bien c'est parce que Raven utilise des identifiants de la forme "albums/616" et que la valeur DeleteId sera utilisé par le code Javascript appelant pour retrouver un élément à partir de son identifiant. Et comme l'identifiant d'un élément HTML ne peut pas contenir de "/", nous ne renvoyons que la partie numérique de l'identifiant. Ce n'est pas un problème puisque dans ce cas précis nous ne gérons que des albums.

Encore une fois, on ne peut pas faire plus simple. Par conséquent, je ferai seulement remarquer qu'avec l'approche suivie par Raven on peut profiter du cache de l'unit of work alors qu'avec le code d'origine non.

Dans mon prochain billet, je m'occuperai de la gestion de la commande.

jeudi 27 mai 2010

MVC Music Store / Raven DB : ShoppingCart

Ceci est la traduction du billet "Porting MVC Music Store to Raven: ShoppingCart", le neuvième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

ShoppingCart

La classe ShoppingCart de MVC Music Store est ma tête à claque du moment. C'est plus fort que moi.

Vous pouvez admirer à quoi elle ressemble dans l'illustration de droite. Le souci avec elle, c'est que c'est le genre de code qui fait l'amalgame entre deux responsabilités différentes :

  • Les opérations au sujet du panier
    • GetCart
    • GetCartId
    • GetCartItems
    • GetCount
  • Les traitements du contenu du panier
    • AddToCart
    • CreateOrder
    • EmptyCart
    • MigrateCart
    • RemoveFromCart

Vous avez sans doute remarqué que toutes les opérations au sujet du panier correspondent à des fonctions get. Tous les traitements du contenu du panier sont relatifs aux articles du panier, ils représentent la logique métier du panier et sa raison d'être. Les opérations Get ne sont pas rattachées aux articles du panier, elles dépendent d'un autre genre d'objet qui gère les instances de paniers.

Dans la plupart des applications, cet objet s'appellerait un Repository. Je ne suis pas certain que cela nous soit utile dans le cas présent. Si on étudie les méthodes Get de plus près, on se rend compte que la seule justification de leur existence vient du fait que l'on a choisi de stocker uniquement les articles du panier. Comme il n'existe pas d'entité panier, nous sommes obligé d'effectuer des requêtes explicites pour obtenir ces données.

Avec Raven, nous procèderons différemment, ce qui fait que la seule chose dont nous devrions avoir besoin est GetCart() et peut-être GetCartId().

Voici donc à quoi un document panier ressemblera :

Ce qui donnera en tant qu'entité :

La méthode GetTotal a été remplacée par une propriété Total. Contrairement à la méthode GetTotal qui va générer un accès à la base de données, cette propriété fonctionne uniquement à partir des données en mémoire. Cela constitue une autre différence majeure de Raven par rapport à une autre solution ORM : on ne va pas faire de lazy loading. C'est quelque chose d'inhérent aux bases de données documents : les données du modèle ont rarement besoin de parcourir d'autres données en dehors de leur propre document. Parcourir le document avec Raven ne risque pas de provoquer du lazy loading ou de nous entrainer dans de redoutables problèmes de type SELECT N+1.

Et maintenant, occupons nous de gérer les opérations au sujet du panier. Les plus importantes sont GetCartId et GetCart. Je considère que ces méthodes n'ont rien à faire là. J'ai donc créé une nouvelle classe ShoppingCartFinder qui ressemble à ceci :

Vous pouvez voir que nous n'exposons plus la méthode GetCartId. Il s'agit d'un élément interne qui n'a pas à être connu des clients de cette classe. La méthode SetShoppingCartId() est là parce que nous devons gérer l'initialisation de l'identifiant d'un panier étant donné que nous voulons gérer le transfert d'un panier (lorsqu'un utilisateur anonyme se connecte). Comme nous n'avons pas besoin des autres méthodes, je les ai supprimées.

Continuons avec les traitements du contenu du panier. Pour mémoire, voici la méthode AddToCart() d'origine :

Et voici cette méthode portée sous Raven :

Le code pour Raven fonctionne entièrement en mémoire et sans se soucier de tout l'aspect persistance. Le code d'origine se charge explicitement de tout ce qui est persistance. Ce n'est pas un problème en soi, mais ce n'est pas le bon endroit pour s'occuper de persistance.

Passons à RemoveFromCart() :

Vous pouvez voir que sa taille diminue de façon très significative et qu'une fois encore, il ne s'agit plus que d'un traitement en mémoire. La méthode EmptyCart() n'est pas implémentée puisqu'avec Raven cela correspond juste à un Lines.Clear().

Un truc intéressant à voir, c'est que l'ancienne implémentation de EmptyCart() aurait généré N requêtes (N étant le nombre d'articles dans le panier) alors qu'avec Raven cela engendre une seule requête.

La méthode CreateOrder() d'origine :

Et celle pour Raven pour laquelle il n'y a pas grand chose à dire si ce n'est que l'ancien code exacuterait N * 2 requêtes là où le code pour Raven continuera de se contenter d'une seule requête :-)

MigrateCart() est plus intéressant parce que son implémentation est complètement différente. Dans le code d'origine, on met à jour tous les articles du panier un par un :

Avec Raven, nous allons faire quelque chose de radicalement différent :

L'identifiant du panier sert pour définir la clé du document et donc, en initialisant cet identifiant (soit avec le nom de l'utilisateur soit avec une valeur stockée en session), nous pouvons charger le panier à l'aide d'une méthode Load (sur la clé primaire pour faire une comparaison avec le monde des bases relationnelles). Le transfert du panier est alors une opération toute simple. Tout ce que vous avez à faire, c'est de changer sa clé. Etant donné que Raven ne permet pas de la renommer, nous allons faire une suppression puis une insertion qui s'exécuteront dans la même transaction.

Le code pour appeler la méthode MigrateCart() est le suivant :

Etant donné que SaveChanges est atomique et transactionnel, cela a le même effet que de faire un Rename.

Et c'est tout pour le panier. Je consacrerai mon prochain billet au contrôleur ShoppingCartController qui utilise cette classe.

mercredi 26 mai 2010

MVC Music Store / Raven DB : StoreController

Ceci est la traduction du billet "Porting MVC Music Store to Raven: StoreController", le huitième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Commençons par la méthode Index() :

Il y a un truc dans ce code qui me gène, c'est qu'il va exécuter deux requêtes sur la base de données. Mais je ne vais pas m'appesantir étant donné que nous allons modifier tout ça.

Et voici ma version portée sous Raven :

Comme vous pouvez le constater, c'est à peut près pareil et donc pas très intéressant. Voyons voir ce que nous avons d'autre :

Ce qu'il faut bien voir, c'est que ce code cherche à faire une recherche sur le libellé d'un genre. Le problème c'est que le libellé du genre n'est pas la clé primaire, et que pour couronner le tout, il n'y a même pas d'index sur cette colonne libellé. Bon, c'est vrai que la table genre ne contient que 10 lignes, mais c'est une question de principe (si vous êtes très sympa, vous n'aurez droit qu'à un sermon du DBA pour avoir osé faire une requête sans index sur la base de production).

Avec Raven, il nous serait donc très simple d'implémenter ça en suivant la même approche, mais je ne vois pas d'excuse pour faire ça. Le genre que nous récupérons dans la méthode Browse() dépend des données que nous avons renvoyées avec la méthode Index(). Il n'y a donc pas de raison pour ne pas faire passer directement l'identifiant du genre. J'ai donc modifié l'action Index() pour renvoyer l'objet genre complet et pas seulement son libellé et par la suite renvoyer l'identifiant à l'action Browse() au lieu du libellé.

J'avais donc commencé à implémenter ça mais je me suis retrouvé coincé par l'association entre les albums et les genres.

Normalement, les bases de données documents n'ont pas d'associations et pas de jointures non plus. Alors, comment gérer ça ?

Depuis le temps vous devez commencer à vous douter de la réponse : en créant un un index :)

// AlbumsByGenre
from album in docs.Albums
where album.Genre != null
select new { Genre = album.Genre.Id }

Et cet index nous permet d'écrire le code suivant :

Et pour finir, il nous reste l'action GenreMenu :

Que nous pouvons facilement porter de la façon suivante :

Et nous en avons terminé avec StoreController.

MVC Music Store / Raven DB : Faire une migration plus poussée

Ceci est la traduction du billet "Porting MVC Music Store to Raven: Advanced Migrations", le septième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Je me suis rendu compte qu'à cause d'une faute de frappe lorsque j'avais fait la reprise des données, les informations sur l'artiste étaient enregistrées en tant que "Arist" au lieu de "Artist". Cela va me donner l'occasion de montrer comment faire une migration du modèle de données un peu plus sophistiquée.

using (var documentStore = new DocumentStore { Url = "http://localhost:8080" })
{
    documentStore.Initialise();

    var count = 0;

    do
    {
        var queryResult = documentStore.DatabaseCommands.Query("Raven/DocumentsByEntityName", new IndexQuery
        {
            Query = "Tag:`Albums`",
            PageSize = 128,
            Start = count
        });


        if (queryResult.Results.Length == 0)
            break;

        count += queryResult.Results.Length;
        var cmds = new List<ICommandData>();
        foreach (var result in queryResult.Results)
        {
            var arist = result.Value<JObject>("Arist");
            if(arist == null)
                continue;
                        
            result["Artist"] = arist;
            result.Remove("Arist");

            cmds.Add(new PutCommandData
            {
                Document = result,
                Metadata = result.Value<JObject>("@metadata"),
                Key = result.Value<JObject>("@metadata").Value<string>("@id"),
            });
        }

        documentStore.DatabaseCommands.Batch(cmds.ToArray());

    } while (true);
    
}

Je ne pense pas que le code soit très compliqué à suivre. Vous pouvez voir comment on peut manipuler les documents en travaillant directement au niveau du document JSON plutôt que de passer par une couche objet.

mardi 25 mai 2010

MVC Music Store / Raven DB : Faire évoluer le modèle de données

Ceci est la traduction du billet "Porting MVC Music Store to Raven: Migrations", le sixième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Dans mon dernier billet, j'ai indiqué que nous devions ajouter une propriété CountSold à tous les albums, généralement quelque chose d'assez pénible à faire dans l'univers des bases de données SQL. La commande pour ajouter une colonne est toute simple, mais c'est une vrai galère d'en venir à bout, de la déployer et de la versionner. Avec Raven, quand vous ajoutez une propriété, elle sera automatiquement ajoutée à votre document la prochaine fois que vous le sauvegarderez. Vous n'avez rien d'autre à faire. Et même, c'est pareil si vous décidez de supprimer une propriété. Raven s'occupera de fera le ménage après vous.

Mais comment faire quand on veut initialiser cette propriété avec une valeur définie, et pas se contenter de la valeur par défaut ? Dans ce cas là, il faut un peu mettre la main à la pâte, mais ça reste très simple :

using (var documentStore = new DocumentStore { Url = "http://localhost:8080" })
{
    documentStore.Initialise();
    using (var session = documentStore.OpenSession())
    {
        IDictionary<string,int> albumToSoldCount = new Dictionary<string, int>();
        int count = 0;

        do
        {
            var results = session.Query<SoldAlbum>("SoldAlbums")
                .Take(128)
                .Skip(count)
                .ToArray();

            if (results.Length == 0)
                break;
            count += results.Length;
            foreach (var soldAlbum in results)
            {
                albumToSoldCount[soldAlbum.Album] = soldAlbum.Quantity;
            }
        } while (true);

        count = 0;
        do
        {
            var albums = session.Query<Album>()
                .Skip(count)
                .Take(128)
                .ToArray();
            if (albums.Length == 0)
                break;

            foreach (var album in albums)
            {
                int value;
                albumToSoldCount.TryGetValue(album.Id, out value);

                album.CountSold = value;
            }

            count += albums.Length;

            session.SaveChanges();
            session.Clear();
        } while (true);
    }
}

Pour ceux d'entre-vous qui n'ont pas pris la peine de lire le code, cette fonction parcours l'index SoldAlbums que nous avons créé auparavant et mémorise ses valeurs. Puis nous parcourons les albums par lot de 128 et nous mettons à jour leur compteur CountSold. L'un dans l'autre, c'est plutôt facile.

Un des autres avantages de ce script, c'est que vous pouvez l'exécuter autant de fois que vous le voulez sans que cela fausse vos données.

MVC Music Store / Raven DB : Refaire HomeController, la bonne méthode

Ceci est la traduction du billet "Porting MVC Music Store to Raven: Porting the HomeController, the Right Way", le cinquième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Comme je l'ai indiqué dans le billet précédent, nous pouvons solutionner le problème de la méthode GetTopSellingAlbums() grâce au map/reduce, mais cela n'est pas vraiment la bonne façon de faire les choses. Le problème en procédant de la sorte (en plus des regards effrayés et des cris de détresse que vous suscitez dès que vous mentionnez cette solution), c'est qu'on essaie de résoudre le problème selon une logique relationnelle. Et d'ailleurs, la solution précédente est quasiment identique à la façon dont une base de données relationnelle pourrait traiter ce genre de requête. Voyons plutôt quelle serait l'approche d'une base de données documents pour résoudre ce genre de problème.

La réponse est évidente à trouver : rappelez-vous que les documents sont indépendants et réfléchissez à nouveau à la question. Ce que nous cherchons à savoir, c'est quels sont les albums les plus vendus. Si nous ajoutions une propriété CountSold à l'album, cela deviendrait immédiatement bien plus simple de répondre à cette question. Et pour cela, il nous suffit de mettre à jour les différents albums qui font parti de la commande lorsque celle-ci est validée. C'est quelque chose de tout à fait acceptable et ce genre d'opération est couramment effectuée, y compris avec des bases de données SQL.

Pour l'instant, laissons de côté la façon de créer la propriété CountSold et de l'initialiser avec les bonnes valeurs (je verrai ça dans mon prochain billet). Nous supposerons donc que c'est déjà fait et qu'il ne nous reste plus qu'à trouver comment résoudre le problème de notre méthode GetTopSellingAlbums().

Et bien, c'est plutôt simple. Tout ce que nous avons à faire, c'est de définir un index sur CountSold.

// AlbumsByCountSold
from album in docs.Albums
select new { album.CountSold };

Avec ça, nous pouvons implémenter la fonction GetTopSellingAlbums() de la façon suivante :

C'est fait : simple, efficace et même élégant (même si c'est moi qui le dit).

vendredi 21 mai 2010

MVC Music Store / Raven DB : Refaire HomeController, méthode map/reduce

Ceci est la traduction du billet "Porting MVC Music Store to Raven: Porting the HomeController, the map/reduce way", le quatrième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Actuellement, le contrôleur HomeController contient le code suivant :

Je n'aime vraiment pas quand un contrôleur se charge de faire des requêtes, mais ça n'est pas le sujet pour l'instant.

Grâce à EF Prof, on peut voir à quoi ressemble cette requête :

Et là on se trouve face à un problème très intéressant : il ne nous est pas possible de reproduire cette requête. En effet, cette requête porte sur plusieurs tables que dans notre modèle nous avons réparties dans différents documents.

Il existe plusieurs méthodes pour résoudre cela. Une des façon de faire serait de définir un index map / reduce au niveau des documents orders.

Note: Oui, je sais ce que vous allez dire.

La méthode que je suis sur le point de vous montrer n'est pas celle que je conseillerais dans la réalité. Mais je veux malgré tout vous la présenter. Dans mon prochain billet, je vous expliquerai la façon dont Raven permet de gérer ça dans les formes.

Avec Raven, le map / reduce consiste simplement en quelques requêtes Linq. Il n'y a donc pas de raison de s'affoler. Pour mémoire, nous avons défini les documents suivant dans notre base de données :

Nous créons l'index "SoldAlbums" à l'aide des requêtes suivantes :

// map
from order in docs.Orders
from line in order.Lines
select new{ line.Album, line.Quantity }

// reduce
from result in results
group result by result.Album into g
select new{ Album = g.Key, Quantity = g.Sum(x=>x.Quantity) }

Comme vous pouvez le voir, il s'agit de deux requêtes Linq toute simples.

Leur résultat devrait être le suivant :

Dès lors que nous avons cela, c'est un jeu d'enfant d'en faire découler GetTopSellingAlbums. En fait, la fonction ci-dessous implémente exactement la même logique et renvoie le même résultat que l'implémentation d'origine :

La façon dont elle fonctionne est très simple. Nous sélectionnons les albums les plus vendus (en triant les quantités par ordre décroissant), puis nous les chargeons depuis la base de données. Et dans le cas où nous aurions moins d'albums vendus que ce que nous comptons afficher, nous complétons avec d'autres albums normaux.

Au final ce code exécute 2 ou 3 requêtes. Je n'aime vraiment pas ça, mais sur ma machine, cela prend environ moins de 10 ms pour faire ces trois requêtes, ce qui est tout à fait supportable.

Je vous ai présenté cette solution parce que je voulais vous montrer que c'était une approche du problème, mais pas la solution recommandée pour le résoudre. Nous verrons une meilleure approche dans le billet suivant.

jeudi 20 mai 2010

MVC Music Store / Raven DB : Migrer les données

Ceci est la traduction du billet "Porting MVC Music Store to Raven: Data migration", le troisième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Voici le code nécessaire pour lire les données dans la base de données de MVC Music Store et les transformer en documents comme attendu par Raven :

using (var documentStore = new DocumentStore { Url = "http://localhost:8080" })
{
    documentStore.Initialise();
    using (var session = documentStore.OpenSession())
    {
        foreach (var album in storeDB.Albums.Include("Artist").Include("Genre"))
        {
            session.Store(new
            {
                Id = "albums/" + album.AlbumId,
                album.AlbumArtUrl,
                Arist = new { album.Artist.Name, Id = "artists/" + album.Artist.ArtistId },
                Genre = new { album.Genre.Name, Id = "genres/" + album.Genre.GenreId },
                album.Price,
                album.Title,
            });
        }
        foreach (var genre in storeDB.Genres)
        {
            session.Store(new
            {
                genre.Description,
                genre.Name,
                Id = "genres/" + genre.GenreId
            });
        }
        session.SaveChanges();
    }
}

Comme vous pouvez le constater, c'est plutôt simple. Et même si c'est moi qui le dit, plutôt bien foutu.

J'ai utilisé des types anonymes parce que je me contente de migrer les données. Je ne m'occupe pas vraiment de savoir comment gérer les types pour l'instant.

mercredi 19 mai 2010

MVC Music Store / Raven DB : Configurer l'application

Ceci est la traduction du billet "Porting MVC Music Store to Raven: Setting up the application", le deuxième de la série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Juste quelques mots sur la façon de configurer Raven pour l'utiliser avec l'application MVC Music Store avant de me lancer dans le développement du reste du code.

  • Le fonctionnement retenu est (délibérément) très proche de celui employé avec NHibernate. Nous initialisons un objet DocumentStore au démarrage de l'application.
  • Puis nous gérons l'ouverture / fermeture des sessions dans le cadre de la requête HTTP, complété par une méthode CurrentSession() pour accéder à la session en cours.
  • Si l'application avait employé un conteneur, j'aurais fait en sorte que les contrôleurs récupèrent une instance de la session par son intermédiaire. Mais comme il n'y en a pas, je m'en tiens à une méthode statique.
    • Si cela ne vous plait vraiment pas, n'hésitez pas à proposer autre chose.
public class MvcApplication : System.Web.HttpApplication
{
    private const string RavenSessionKey = "Raven.Session";
    private static DocumentStore _documentStore;

    protected void Application_Start()
    {
        _documentStore = new DocumentStore { Url = "http://localhost:8080/" };
        _documentStore.Initialise();

        AreaRegistration.RegisterAllAreas();

        RegisterRoutes(RouteTable.Routes);
    }

    public MvcApplication()
    {
        BeginRequest += (sender, args) => HttpContext.Current.Items[RavenSessionKey] = _documentStore.OpenSession();
        EndRequest += (o, eventArgs) =>
        {
            var disposable = HttpContext.Current.Items[RavenSessionKey] as IDisposable;
            if (disposable != null)
                disposable.Dispose();
        };
    }

    public static IDocumentSession CurrentSession
    {
        get { return (IDocumentSession)HttpContext.Current.Items[RavenSessionKey]; }
    }
}

Et c'est à peu près tout, du moins en ce qui concerne l'initialisation de Raven.

mardi 18 mai 2010

MVC Music Store / Raven DB : Modèle de données

Ceci est la traduction du billet "Porting MVC Music Store to Raven: The data model", le premier d'une série consacrée au portage de l'application MVC Music Store sous RavenDB par Oren Eini, alias Ayende Rahien.

Le tutoriel "MVC Music Store" est venu à point nommé pour moi. Je souhaitais faire une application de démonstration pour Raven DB et le fait que quelqu'un d'autre ait déjà fait tout le travail ingrat (l'interface utilisateur :) à ma place et que je n'ai plus qu'à refaire l'accès aux données est une situation rêvée. Mon objectif est de ne rien toucher du tout au Javascript ou au code HTML et de me contenter de remplacer les contrôleurs. Ca devrait être intéressant de voir si je peux y arriver.

Le modèle de base de données dans le tutoriel d'origine est le suivant :

On peut déjà remarquer deux ou trois choses intéressantes dans ce schéma :

  • Il serait plus correct que la table Cart soit nommée CartLineItem puisqu'elle stocke une ligne par article dans le panier
    • CartId n'est pas une clé étrangère, mais référence le nom de l'utilisateur ou l'identifiant de la session
  • La table Artist gère uniquement le nom de l'artiste et rien d'autre.

A partir de ces informations, je considère que le modèle de données suivant devrait convenir.

Albums

  • Le document Album contient à la fois une référence pour le Genre et pour le libellé du genre. Cela nous permet d'afficher l'album sans avoir à référencer le document Genre.
  • Et pour les mêmes raisons, le document Album contient aussi le nom et l'identifiant de l'artiste.
  • Il n'y a pas un ensemble de documents Artists dans la base de données. Nous ne gérons aucune information sur les artistes, si ce n'est leur nom, et je ne vois donc pas de raison pour définir un document Artist pour l'instant.

Genre

Le document Genre est la réplique exacte de la table Genre, rien d'extraordinaire à ce niveau.

Cart

Le document Cart suit un format de document plutôt classique. Nous avons un simple document qui contient un tableau d'éléments là où le modèle de données relationnel contient un ensemble de lignes. Vous pouvez voir que le UserIdentifier nous sert pour stocker l'identifiant de l'utilisateur ou celui de la session pour le panier.

Orders

Order constitue une autre document plutôt standard. Nous regroupons toutes les information de la commande dans un simple document et nous stockons les informations liées à celle-ci (Address) dans un noeud spécifique.

Artist

Il n'y a pas de document Artist.

Pourquoi un document pour Genre et pas pour Artist ?

Pour la bonne raison que l'application va faire quelque chose avec les Genres (en plus d'afficher leur description) alors que la seule chose que l'on fait avec les Artistes est d'afficher leur nom dans la cadre d'un album. Pour l'instant, je considère que Artist fait partie intégrante de Album et qu'il n'y a donc pas à définir un document spécifique pour lui. La seule raison au fait qu'il existe un identifiant artiste en plus du nom est que je suppose (d'après les données) que la source pour les artistes est un système externe qui fait quelque chose d'un peu plus utile que simplement stocker le nom de l'artiste.

Nous n'avons fait que la moitié du travail à faire. Nous avons défini le modèle de données, maintenant nous devons étudier comment il sera utilisé dans un contexte plus large et lui ajouter le modèle de requête en utilisant des index. Nous verrons cela dans un prochain billet.