Modèles de conception avancés en Go : de la théorie à la pratique
Go (Golang) est un langage qui encourage la simplicité et la lisibilité du code. Cependant, lorsque le projet s'agrandit, même le code le plus concis peut se transformer en chaos sans une architecture appropriée. Au niveau avancé, les développeurs Go utilisent activement des modèles de conception adaptés aux spécificités du langage : goroutines, canaux, interfaces et génériques (depuis Go 1.18+).
Dans cet article, nous allons analyser 4 modèles puissants qui vous aideront à écrire du code Go évolutif, testable et performant. Chaque modèle est accompagné d'un exemple pratique.
1. Modèle Pipeline (Convoyeur) pour le traitement de données en flux
Pipeline est l'un des modèles les plus populaires en Go pour le traitement séquentiel des données. Il s'intègre parfaitement au modèle des goroutines et des canaux. Chaque étape de traitement est exécutée dans une goroutine distincte, et les données sont transmises via des canaux.
Quand l'utiliser : Traitement de logs, transformation de données, processus ETL, vidéo en flux.
Exemple : Convoyeur pour le filtrage et la transformation de nombres.
package main
import "fmt"
// Stage 1 : Génération de nombresfunc generate(nums ...int) <-chan int { out := make(chan int) go func() { for _, n := range nums { out <- n } close(out) }() return out}
// Stage 2 : Multiplication par 2func multiply(in <-chan int) <-chan int { out := make(chan int) go func() { for n := range in { out <- n * 2 } close(out) }() return out}
// Stage 3 : Filtrage (uniquement les pairs)func filterEven(in <-chan int) <-chan int { out := make(chan int) go func() { for n := range in { if n%2 == 0 { out <- n } } close(out) }() return out}
func main() { // Assemblage du convoyeur pipeline := filterEven(multiply(generate(1, 2, 3, 4, 5)))
for result := range pipeline { fmt.Println(result) // Sortie : 4, 8 (uniquement les pairs après multiplication) }}Avantages : Traitement parallèle naturel, faible consommation mémoire (les données ne s'accumulent pas), facilité d'ajout de nouvelles étapes.
2. Fan-Out / Fan-In : mise à l'échelle du traitement
Ce modèle est une évolution avancée du Pipeline. Fan-Out (ramification) lance plusieurs goroutines pour traiter en parallèle les données provenant d'un seul canal. Fan-In (fusion) rassemble les résultats de plusieurs canaux en un seul.
Quand l'utiliser : Analyse de pages web, traitement d'un grand nombre de fichiers, requêtes parallèles à une API.
Exemple : Vérification parallèle de la disponibilité d'URL.
package main
import ( "fmt" "net/http" "sync")
func worker(urls <-chan string, results chan<- string, wg *sync.WaitGroup) { defer wg.Done() for url := range urls { resp, err := http.Get(url) if err != nil { results <- fmt.Sprintf("Erreur : %s - %v", url, err) continue } resp.Body.Close() results <- fmt.Sprintf("OK : %s - %d", url, resp.StatusCode) }}
func fanOutFanIn(urls []string, numWorkers int) []string { urlChan := make(chan string, len(urls)) resultChan := make(chan string, len(urls)) var wg sync.WaitGroup
// Fan-Out : lancement des workers for i := 0; i < numWorkers; i++ { wg.Add(1) go worker(urlChan, resultChan, &wg) }
// Envoi des URL for _, url := range urls { urlChan <- url } close(urlChan)
// Attente de la fin de tous les workers wg.Wait() close(resultChan)
// Fan-In : collecte des résultats var results []string for res := range resultChan { results = append(