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
upsertplutot quefindOrCreatepour l'atomicite. - Ne contient pas de logique metier (validation, calcul, etc.).
Flux typique
Commande Discord
-> Service (logique metier)
-> Repository (requete Prisma)
-> PostgreSQLExemple 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