Déployer un jeu à boire sur le Edge avec Nuxt-hub

By Hugo LassiègeJun 13, 20248 min read

En fait le titre de ce billet aurait dû être :

Comment utiliser Cloudflare D1 et Cloudflare KV avec nuxt-hub pour déployer un jeu à boire sur Cloudflare worker ?

Mais ça faisait un peu long. Et pour beaucoup de gens, ce titre n'aurait eu aucun sens.

Bon, en même temps, le titre actuel n'a pas plus de sens. Mais, jeu à boire, ça va attirer quelques lecteurs.

L'origin story

TIP

Ici c'est le moment où je vous explique pourquoi j'ai créé cette application. Vous pouvez aller directement à la partie technique mais, si comme moi, vous aimez comprendre à quoi sert la technique, vous pouvez rester le temps de quelques lignes supplémentaires :)

On dit souvent que les deux problèmes les plus difficiles en informatique sont :

  • le nommage des variables
  • l'invalidation de cache

Mais, si je peux me permettre, on devrait mettre à jour cette citation.

En 2024, trouver un nom de domaine libre relève de l'exploit. Tout est pris, même des noms qui n'ont ni queue ni tête.

Alors, ça ne veut pas dire que tout a déjà été créé. Beaucoup de ces noms de domaines ne sont même pas utilisés. C'est aussi, et surtout, la faute des squatteurs de domaines.

Bref, c'est un enfer, parce que de très nombreuses personnes se sont dit que la meilleure façon de s'enrichir sur internet c'était d'acheter des noms de domaines au hasard pour espèrer les revendre plus tard.

Je peux pas y faire grand chose, donc autant en rire. J'en ai donc fait un jeu à boire.

TIP

je précise que, évidemment, je n'encourage pas la consommation de boisson alcoolisée, donc vous pouvez boire n'importe quoi à la place. Il paraît que le kumis ou la chicha sont de bons candidats...

Le coin de la technique

Quitte à faire une application vite faite, autant apprendre quelque chose au passage. J'ai donc décidé d'utiliser des fonctionnalités expérimentales de Cloudflare, notamment D1.

Pour ne rien gâcher, j'ai choisi d'utiliser Nuxt-Hub qui donne une couche d'abstraction pour manipuler D1 et le key value store de Cloudflare depuis une application Nuxt.

Les CDN et le Edge

Traditionnellement, un utilisateur qui consulte un site web, va déclencher des appels d'APIs vers un serveur. Ce serveur peut parfois être très éloigné de lui/elle. Et dans ce cas, c'est long.
On appelle ça la latence.

APIWebsiteUserAPIWebsiteUserRequest for website (fast)API call (slow)

Ici l'appel au site web est rapide. Mais l'appel et la réponse à l'API sont lent. Car l'API est loin de l'utilisateur.

Parce que si vous consultez un site depuis le Japon, mais que pour chaque information affichée sur la page web, il faut aller demander à une base de données présente en France, ça fait quelques kilometres à parcourir.

On estime que chaque aller/retour entre la France et le Japon va faire environ entre 200 et 300ms. S'il y a beaucoup d'appels d'APIs, ça peut rapidement faire très mal et créer une expérience de navigation pas très agréable.

votre sensation devant l'exécution de la page
votre sensation devant l'exécution de la page

Bon, vous pourriez me dire que 300ms ça reste acceptable et c'est assez vrai. Mais ici, je n'ai même pas inclus le temps de traitement, et le temps d'affichage. Sans parler que, il suffit que le réseau ait des soucis ou que l'application web soit un peu mal foutue avec de nombreux appels séquentiels pour que l'expérience se dégrade terriblement.

C'est pour ça que depuis très longtemps, on utilise des CDN pour les ressources statiques : images, scripts js, fonts etc...

Un CDN c'est un réseau de distribution de contenu (content distribution network).
C'est un ensemble de serveur réparti dans le monde qui contient chacun une copie de la ressource à distribuer. Donc si l'utilisateur qui regarde une image est au Japon, le CDN va lui servir l'image qui est présente sur un serveur au Japon pour optimiser le chargement.

l'image est prise sur le serveur le plus proche
l'image est prise sur le serveur le plus proche

Mais dès qu'on parle de serveurs applicatifs, c'est plus complexe.

Un serveur applicatif va réaliser des traitements. Pendant très longtemps ce type de serveur était donc à un seul endroit, par exemple en France et l'utilisateur au Japon devait accepter d'attendre que la donnée revienne depuis la France.

Sur le principe des CDN, certains, comme Cloudflare ont commencé à proposer de déporter les traitements et les données sur le Edge (la périphérie en francais mais personne n'utilise ce mot).

Ca a un nom, ça s'appelle le Edge computing.
(Le principe est loin d'être nouveau. Dans le domaine de la data ça fait plusieurs années qu'on parle de Data Locality.

DnsDrink et le Edge

Justement dnsdrink est une application Nuxt, déployé sur le Edge.

Ça utilise Cloudflare et plus exactement des workers cloudflare.

Les workers, ce sont des "serveurs", mais pas dans la forme traditionnelle qu'on entend.

Un worker peut exécuter du code sur le point de présence le plus proche de l'utilisateur final pour éviter la latence. C'est ça, le fameux Edge computing.

Ok, mais il reste le souci des bases de données ?

La base D1 sur le Edge

C'est ici qu'interviennent des fonctionnalités de Cloudflare :

Le principal souci pour qu'une base de données puisse être distribuée sur le Edge, c'est la cohérence des données.

En lecture seule, c'est facile. On peut répliquer les données partout dans le monde et les serveurs Edge peuvent servir les données locales. Enfin, facile, c'est vite dit. On est bien content de le déléguer à Cloudflare.

En lecture, un utilisateur au Japon de dnsdrink aura le même temps de latence qu'un utilisateur en France pour ces accès à la base de données.

Les écritures, elles, sont "eventually consistent", c'est-à-dire que la donnée est répliquée partout dans le monde mais pas instantanément. C'est un compromis entre la cohérence et la performance.

Si vous voulez comprendre le mécanisme exact, je vous invite à lire leur article de blog à ce sujet.

Sachez que c'est pas magique :

  • L'écriture est donc un peu plus lente, mais au bénéfice d'une lecture rapide.
  • à un instant t, vous pouvez avoir des données différentes selon le point de présence où vous vous trouvez.

Nuxt-Hub

Nuxt-Hub c'est deux choses :

  • une plateforme de déploiement qui permet de piloter la création d'application sur cloudflare, avec la création des bases de données et l'association avec nos applications (le binding)
  • un ensemble d'api qui permet d'utiliser les fonctionnalités de cloudflare, D1, R1 et R2 et de simplifier le développement local.

Et c'est l'ensemble de toute ces fonctionnalités que j'ai voulu utiliser avec dnsdrink.

DNSDrink

L'application en elle-même n'est pas très complexe. Vous pouvez consulter son code source sur Github.

La configuration de Nuxt-Hub

Regardons la configuration de nuxt

nuxt-config.ts
export default defineNuxtConfig({
    modules: ['@nuxthub/core', '@nuxtjs/tailwindcss', '@nuxt/image'],

    hub: {
        database: true,
        kv: true,
        blob: true,
        cache: true,
    },
    nitro: {
        scheduledTasks: {
            '* * * * *': ['schema'],
            '0 0 1 * *': ['purge'],
        },
        experimental: {
            tasks: true
        }
    }
})

On note l'utilisation du module @nuxthub/core, l'activation des fonctionnalités dans hub et des tâches nitro.

Les tâches nitro

Les tâches nitro ne sont pas spécifiques à Nuxt-Hub et, pour être honnête, je n'ai pas réussi à les faire fonctionner sur Cloudflare. Elles fonctionnent uniquement sur mon poste local. Sur le principe, il s'agit de planifier des tâches qui s'effectuent à intervalles réguliers, comme par exemple le cleaning de la base de données.

Je n'ai pas spécialement creusé parce que cette application n'a aucune importance particulière pour moi. Mais voici à quoi ressemble une tâche :

purge.ts
export default defineTask({
    meta: {
        name: 'purge',
        description: 'Purge old quota entries from previous month',
    },
    async run({ payload, context }) {
        const db = hubDatabase()
        await db.prepare('DELETE FROM quota WHERE strftime(\'%Y-%m\', created_at) < strftime(\'%Y-%m\', \'now\')').run()
        return { result: 'Success' }
    },
})

Les appels à KV et D1

Dans le fichier index.post.ts vous pouvez voir les exemples d'appels.

index.post.ts
async function checkQuotaExceeded() {
    const db = hubDatabase()

    // check if the number of requests exceeds the quota for this current month
    const quota = 9500
    const count = await db.prepare('SELECT COUNT(*) as count FROM quota WHERE strftime(\'%Y-%m\', created_at) = strftime(\'%Y-%m\', \'now\')').first('count') as number

    if (count >= quota) {
        consola.warn('Quota exceeded')
        return true
    }

    return false
}

....
// check if result is already in cache
const responseFromCache = await hubKV().get(domain)
if (responseFromCache) {
    return responseFromCache
}
....
// store result in cache 
await hubKV().set(domain, summary, { expirationTtl: 60 * 60 * 24 * 30 }) // 30 days

My 2 cents

L'expérience de dev est plutôt agréable. En local, vous avez accès à une base SQLite et un mini key value store donc tout fonctionne de façon très fluide.

Le déploiement sur CF est également très simple.

Je n'ai pas réussi à faire fonctionner les tasks mais je suppose qu'en cherchant un peu, j'aurais pu y parvenir.

Pour des applications full stack sur Cloudflare, c'est une plutôt bonne option. C'est certainement moins abouti en termes de features et d'administration que du Supabase mais ça reste très clean et ça pourrait devenir un concurrent sérieux dans le futur (?).

Pour ma part je ne vais pas abandonner ma stack de prédilection avec du kotlin parce que je reste plus productif. Mais il faut bien comprendre que cette architecture est très peu couteuse. DnsDrink me coute 0. Donc c'est une option sérieuse. Selon mes besoins, je réutiliserais cette stack sans souci. Et peut-être qu'un jour ma stack pourra tourner dans le Edge ?

A vous de jouer maintenant :)


Share this:

Written by Hugo Lassiège

Software engineer, ex-freelance, ex-cofounder, ex-CTO. I love building things, sharing knowledge and helping others.

Copyright © 2024
 Eventuallycoding
  Powered by Bloggrify