Skip to content

Services et repositories

Pattern service

Les services contiennent la logique metier. Ils ne connaissent ni Discord ni Prisma directement. Ils delegent l'acces aux donnees aux repositories.

typescript
// src/modules/<module>/services/<nom>.service.ts
import { myRepository } from '../repositories/my.repository.js';
import { logger } from '../../../config/logger.js';

const log = logger.child({ module: 'my-module' });

export const myService = {
  async processData(params: { guildId: string; value: string }) {
    // Logique metier (validation, calcul, transformation)
    const transformed = params.value.toUpperCase();

    // Delegation au repository
    const result = await myRepository.save({
      guildId: params.guildId,
      value: transformed,
    });

    log.info({ id: result.id }, 'Data processed');
    return result;
  },
};

Conventions :

  • Exporte un objet singleton (pas de classe).
  • Utilise un child logger avec le nom du module.
  • Ne manipule pas d'objets Discord (interaction, message, etc.).
  • Retourne les donnees, laisse la commande formater la reponse.

Pattern repository

Les repositories sont la couche d'acces aux donnees. Ils encapsulent les requetes Prisma.

typescript
// src/modules/<module>/repositories/<nom>.repository.ts
import { prisma } from '../../../shared/db/prisma.js';

export const myRepository = {
  async findByGuild(guildId: string) {
    return prisma.myModel.findMany({
      where: { guildId },
      orderBy: { createdAt: 'desc' },
    });
  },

  async save(params: { guildId: string; value: string }) {
    return prisma.myModel.create({
      data: params,
    });
  },

  async upsertByUser(discordId: string, guildId: string, data: object) {
    const profile = await prisma.userProfile.upsert({
      where: { discordId },
      create: { discordId, guildId, username: discordId },
      update: {},
    });

    return prisma.myModel.upsert({
      where: { userId: profile.id },
      create: { userId: profile.id, guildId, ...data },
      update: data,
    });
  },
};

Pattern UserProfile upsert

La plupart des tables ont une relation avec UserProfile. Avant d'inserer des donnees liees a un utilisateur, il faut s'assurer que le profil existe :

typescript
const profile = await prisma.userProfile.upsert({
  where: { discordId },
  create: { discordId, guildId, username: discordId },
  update: {},
});

Ce pattern est utilise dans xpRepository, birthdayRepository, eventRepository, etc.

Conventions :

  • Exporte un objet singleton.
  • Chaque methode correspond a une operation Prisma.
  • Utilise upsert plutot que findOrCreate pour l'atomicite.
  • Ne contient pas de logique metier (validation, calcul, etc.).

Flux typique

Commande Discord
  -> Service (logique metier)
    -> Repository (requete Prisma)
      -> PostgreSQL

Exemple concret avec le systeme d'XP :

/rank (commande)
  -> xpService.getRankStats(discordId, guildId)
    -> xpRepository.getUserXp(discordId)
    -> xpRepository.getRankPosition(discordId, guildId)
    -> xpRepository.getTotalMessageCount(discordId, guildId)
    -> xpRepository.getTotalVoiceMinutes(discordId, guildId)
  -> Construction de l'embed de reponse