← Retour

L'approche pessimiste vs optimise pour l'asynchronicité

~3 min

Réfléchir à ces deux modèles en amont du lancement d'un projet est essentiel pour avoir un code cohérent et robuste. Ça change souvent la qualité du produit final

En développement, notamment dans les environnements asynchrones ou concurrents, la manière dont on gère les conflits ou les accès concurrents à des ressources partagées peut radicalement changer l’architecture d’un système.

Deux grandes philosophies s’affrontent : l’approche pessimiste et l’approche optimiste.

👉 Réfléchir à ces deux modèles en amont du lancement d’un projet est essentiel pour avoir un code cohérent et robuste. Ça change souvent la qualité du produit final.

🔍 Définition rapide

  • Approche pessimiste : on verrouille la ressource dès qu’un processus veut y accéder, pour éviter tout conflit potentiel.
  • Approche optimiste : on part du principe qu’il n’y aura pas de conflit, et on vérifie seulement à la fin si tout s’est bien passé.

Ces deux stratégies sont classiques en base de données, mais elles se retrouvent partout : dans les accès réseau, les traitements asynchrones, les mises à jour d’interface, etc.

🛡️ L’approche pessimiste

Principe

On anticipe les conflits. Dès qu’un utilisateur ou un processus accède à une ressource, on la bloque pour les autres. C’est le fameux lock.

Avantages

  • Sécurise les écritures : peu ou pas de conflits.
  • Prévisible : les règles sont strictes.

Inconvénients

  • Risque de blocage (deadlock).
  • Moins scalable dans des systèmes fortement concurrents.
  • Peut ralentir les performances à cause des verrous.

🎯 L’approche optimiste

Principe

On autorise tout le monde à accéder ou modifier la ressource, en supposant que les conflits seront rares. C’est à la validation que le système vérifie l’absence de problème (souvent via un hash ou une version).

Avantages

  • Très performant dans un environnement où les conflits sont rares.
  • Parfait pour les applications distribuées ou offline-first.
  • Plus fluide côté utilisateur (moins de blocages visibles).

Inconvénients

  • Demande de gérer les conflits à la main.
  • Moins intuitif si les utilisateurs modifient simultanément.
  • Peut être complexe à tester et à implémenter proprement.

🧠 Quand utiliser quoi ?

SituationApproche recommandée
Conflits fréquentsPessimiste
Conflits rares, besoin de fluiditéOptimiste
Système critique (financier, médical)Pessimiste
App distribuée ou mobileOptimiste

⚛️ Exemples en React avec Redux Toolkit

Prenons le cas d’une action asynchrone : supprimer un élément d’une liste côté serveur.

🔒 Approche pessimiste (confirmation après serveur)

// slice.js
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    setTodos: (state, action) => action.payload,
  },
  extraReducers: (builder) => {
    builder.addCase(deleteTodo.fulfilled, (state, action) => {
      return state.filter(todo => todo.id !== action.payload.id);
    });
  },
});

export const deleteTodo = createAsyncThunk(
  'todos/deleteTodo',
  async (id, thunkAPI) => {
    const response = await fetch(`/api/todos/${id}`, { method: 'DELETE' });
    if (!response.ok) throw new Error('Erreur lors de la suppression');
    return { id };
  }
);

Ici, on attend la réponse du serveur avant de mettre à jour le store. C’est plus lent mais plus sûr.


⚡ Approche optimiste (on met à jour le state tout de suite)

const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    removeTodo: (state, action) => {
      return state.filter(todo => todo.id !== action.payload);
    },
    restoreTodo: (state, action) => {
      state.push(action.payload);
    },
  },
  extraReducers: (builder) => {
    builder.addCase(deleteTodoOptimistic.rejected, (state, action) => {
      // On pourrait re-fetch les données ici si besoin
    });
  },
});

export const deleteTodoOptimistic = createAsyncThunk(
  'todos/deleteTodoOptimistic',
  async (todo, { dispatch, rejectWithValue }) => {
    dispatch(removeTodo(todo.id));
    const response = await fetch(`/api/todos/${todo.id}`, { method: 'DELETE' });
    if (!response.ok) {
      dispatch(restoreTodo(todo));
      return rejectWithValue(todo);
    }
    return todo;
  }
);

Ici, on met à jour l’UI immédiatement (UX rapide), et on gère le rollback si erreur. C’est l’approche optimiste typique.

🧩 Conclusion

Encore un truc à penser avant de lancer un projet, mais au moins vous savez que ça existe et que ça peut changer la donne.