Ligne de commande et structure de package
Vous pouvez trouver tout le code de ce chapitre ici
Notre product owner souhaite maintenant faire un pivot en introduisant une seconde application - une application en ligne de commande.
Pour l'instant, elle devra simplement être capable d'enregistrer la victoire d'un joueur lorsque l'utilisateur tape Ruth wins. L'objectif est qu'elle devienne éventuellement un outil pour aider les utilisateurs à jouer au poker.
Le product owner souhaite que la base de données soit partagée entre les deux applications afin que la ligue se mette à jour en fonction des victoires enregistrées dans la nouvelle application.
Un rappel du code
Nous avons une application avec un fichier main.go qui lance un serveur HTTP. Le serveur HTTP ne nous intéressera pas pour cet exercice mais l'abstraction qu'il utilise, oui. Il dépend d'un PlayerStore.
type PlayerStore interface {
GetPlayerScore(name string) int
RecordWin(name string)
GetLeague() League
}Nous avons implémenté cela avec un FileSystemPlayerStore qui sauvegarde les données dans un fichier JSON.
Quelques refactorisations de projet d'abord
Avant d'aller trop loin, nous devons faire une pause et réfléchir à la structure de notre projet. Jusqu'à présent, tout notre code était dans le package main qui était acceptable, mais maintenant que nous ajoutons une autre application, nous avons besoin de restructurer les choses.
Nous voulons partager le code FileSystemPlayerStore entre deux applications.
Créez un nouveau répertoire pour notre projet et créez quelques sous-répertoires :
cmd/clipour notre nouvelle applicationcmd/webserverpour notre application de serveur web existantepkg/pokerpour le code que nous allons partager
La convention en Go est de mettre votre code d'application dans le répertoire cmd et le code que d'autres développeurs peuvent importer dans le répertoire pkg. Pour plus de détails sur la structure des projets, consultez ce guide.
Déplaçons notre serveur existant et la logique de stockage dans le répertoire pkg/poker. Ces codes ne devraient plus faire partie du package main, renommez-les en package poker.
Ensuite, nous devrons mettre à jour les imports et le package dans ces fichiers. La partie facile est de renommer les packages ; la partie difficile est de s'assurer que tout est encore importé correctement (n'oubliez pas de renommer les fichiers _test pour utiliser le nouveau nom du package également). Il est préférable de mettre un nom simple correspondant au nom du répertoire, alors utilisons poker.
Une fois que vous avez déplacé le code et mis à jour les packages, vous pouvez maintenant vous concentrer sur l'application du serveur web en créant un petit fichier main dans cmd/webserver/main.go.
Le code ne change pas beaucoup mais nous devons maintenant importer notre propre code depuis le nouveau package que nous avons créé.
Vérifications finales
Vous devriez être capable de lancer le serveur web avec go run cmd/webserver/main.go et faire des requêtes curl comme avant pour vous assurer que tout fonctionne toujours.
Vous pouvez également exécuter les tests dans le répertoire pkg/poker pour vous assurer que nos tests fonctionnent toujours.
Application en ligne de commande
Maintenant que nous avons restructuré notre code, ajoutons notre application en ligne de commande qui rencontrera les exigences suivantes :
Pour l'instant, elle devra simplement être capable d'enregistrer la victoire d'un joueur lorsque l'utilisateur tape
Ruth wins. L'intention est qu'elle devienne éventuellement un outil pour aider les utilisateurs à jouer au poker.
Squelette de base
Créons d'abord un simple fichier main dans cmd/cli/main.go
Vous pouvez le lancer avec go run cmd/cli/main.go et il devrait afficher un message à l'écran. Nous avons notre squelette de base !
Écrivons d'abord le test
Notre application aura une fonction que nous appellerons lorsqu'elle démarre, qui lira la ligne utilisateur et l'enverra à quelque chose pour l'enregistrer. Pour faciliter les tests, il serait préférable de ne pas intégrer réellement la lecture de l'entrée standard (stdin) dans notre logique d'application. Injectons plutôt le io.Reader de l'entrée standard dans notre application pour pouvoir la tester avec différentes entrées.
Créons un nouveau fichier CLI_test.go dans notre package poker.
Essayons d'exécuter le test
Nous devrons créer les types et les fonctions mentionnés dans le test.
Écrivons la quantité minimale de code pour que le test s'exécute et vérifier la sortie du test échouant
Tout d'abord, créons notre type CLI dans un nouveau fichier CLI.go dans le package poker.
La compilation échoue toujours car nous n'avons pas de stub StubPlayerStore dans notre fichier de test. C'est parce que nous avions des fichiers de test différents qui avaient chacun leur propre version de StubPlayerStore et maintenant nous avons mis tout dans un même package.
C'est l'une des irritations liées au développement Go. Si deux fichiers de test ont des helpers comme StubPlayerStore, ils ne peuvent pas le voir les uns les autres. Vous avez alors le choix entre :
Déplacer ces helpers vers un fichier dans le package principal (et les exporter)
Déplacer les helpers dans un fichier dans le package
xxx_test.
Dans notre cas précédent, ce n'était pas trop un problème car nos tests étaient dans des fichiers distincts pour des packages distincts, mais maintenant que nous les avons tous réunis, nous devrions probablement résoudre ce problème. Nous reviendrons sur cette question plus tard, mais pour l'instant, ajoutons juste notre fonction d'aide de test dans le fichier principal.
Écrivons suffisamment de code pour le faire passer
Ce n'est pas la bonne logique, mais le test passe. Nous devons maintenant gérer les autres entrées possibles.
Écrivons d'abord le test
Mais maintenant étendons-le pour gérer d'autres entrées.
Essayons d'exécuter le test
Écrivons suffisamment de code pour le faire passer
Nous avons besoin de lire l'entrée de l'utilisateur et de l'analyser.
Le test passe maintenant.
Refactorison
Déplaçons notre fonction d'aide de test dans un fichier d'utilitaires :
Et utilisons cette fonction d'aide dans notre test :
Écrivons d'abord le test
Notre application ne tient pas compte des entrées incorrectes. Ajoutons un test pour cela.
Essayons d'exécuter le test
Le test passe déjà, car notre bufio.Reader.ReadString ne lit que jusqu'au premier \n.
Maintenant, intégrons notre code dans la fonction main de notre application.
Écrivons suffisamment de code pour le faire passer
Modifions notre structure CLI pour avoir un constructeur qui nous permet d'initialiser la structure facilement.
Et maintenant nous pouvons utiliser ce constructeur dans notre fichier main.go :
Si vous exécutez l'application maintenant avec go run cmd/cli/main.go, vous devriez pouvoir entrer un nom de joueur suivi de "wins" et cela enregistrera la victoire.
Refactorison
Nos deux applications ont besoin d'un accès au FileSystemStore. Dans les deux cas, nous écrivons :
Nous pouvons extraire cette logique en une seule fonction.
Cette fonction prend un chemin et renvoie :
*FileSystemPlayerStoreUne fonction pour fermer le fichier
Toute erreur qui pourrait survenir dans le processus de création
C'est une bonne utilisation de l'idiome Go d'encapsuler la gestion des ressources et de renvoyer une fonction qui peut être utilisée pour fermer cette ressource.
Maintenant, nous pouvons simplifier notre application CLI :
Et notre application de serveur web :
Conclusion
Structure de package
Nous avons restructuré notre code en utilisant plusieurs packages pour faciliter le partage de code entre nos deux applications. Les avantages de cette approche incluent :
Un seul endroit pour maintenir le code partagé
Séparation des préoccupations
Plus facile à tester
Plus facile à travailler en équipe
Cela rend notre code plus modulaire et plus facile à étendre à l'avenir.
Lecture des entrées utilisateur
L'entrée utilisateur est simplement de l'io.Reader et nous pouvons utiliser la bibliothèque standard Go pour la lire. Dans notre cas, nous avons utilisé bufio.NewReader pour lire une ligne de texte de l'entrée utilisateur.
Des abstractions simples mènent à une réutilisation de code plus simple
En utilisant des interfaces simples comme io.Reader et io.Writer, nous pouvons facilement tester notre code sans avoir à utiliser des entrées/sorties réelles. Cela rend nos tests plus rapides, plus fiables et plus faciles à écrire.
Par exemple, notre CLI peut être testée en utilisant un strings.Reader au lieu de dépendre de l'entrée réelle de l'utilisateur. De même, notre PlayerStore abstrait la manière dont les données sont stockées, ce qui nous permet de l'implémenter de différentes manières (en mémoire, système de fichiers, base de données, etc.).
Mis à jour