geng CLI とテンプレートを活用した Go プロジェクトの初期化と開発効率化
Nest CLI に触発された geng は、Go の gin アプリケーションの初期化、開発、メンテナンスをサポートする、一連のコマンドを提供します。
弊社はこちらのテンプレートを多くのプロジェクトのベースとして使っています。
Django CLI、Nest CLI、Micronaut CLI を参考に、構造化されたプロジェクトの作成とモジュールやアプリの追加を容易にする機能を持ち、同時に弊社のテンプレートの独自のフォルダ構造を再現する CLI を設計しました。
次のセクションでは、CLI の使い方と技術的な要素について説明します。
使用方法
geng の使用はとても簡単です。Go 言語をインストールするか、こちらから直接バイナリをダウンロードし、バイナリディレクトリを PATH 変数に追加するだけです。
goを使用してインストール:
go install github.com/mukezhz/geng@latest
go/bin ディレクトリが環境変数 PATH に含まれていない可能性があります。
*nix系オペレーティングシステムで追加するには、以下のようにします:
// For zsh: [Open.zshrc] & For bash: [Open .bashrc]
// Add the following:
export GO_HOME="$HOME/go"
export PATH="$PATH:$GO_HOME/bin"
// For fish: [Open config.fish]
// Add the following:
fish_add_path -aP $HOME/go/bin
インストール後、プロジェクトを作成しましょう:
プロジェクトの作成:
geng new
CLI上で、対話形式で、プロジェクト名、プロジェクトモジュール、作者、プロジェクトの説明、Goのバージョン、ディレクトリを入力します。
プロジェクト名とプロジェクトモジュールは必須フィールドです。*マークが付いています。
作者、プロジェクトの説明、Goのバージョン、ディレクトリはオプションフィールドです。[オプション]と表示されています:
必要な情報の入力を終えると、geng コマンドを実行したディレクトリに新しいディレクトリが作成されます。
ディレクトが正しく作成されていることを確認します。
test geng news
次のプロジェクトを起動しまう。
go run main.go app:serve
注:2つのルートが作成されています:
/health-check
/api/hello
次に、ユーザー関連のモジュールを追加しましょう。
geng new
「user」と入力した後の結果:
プロジェクトを再起動すると、3つのルートが得られます:
ルート:/api/user が追加されました
フォルダ構造については、geng new を使用した後に生成される README.md で確認できます。
開発方法
すでにテンプレートがあり、text/template を使用して変数を簡単に追加
生成されたモジュールを import し、fx に注入する必要があるモジュールの生成する際に問題が発生します。問題は、Go の import が Python や多くの言語の import のようにパスを知るだけでは簡単にできないことです。Golang でインポートするにはモジュール名を知る必要があり、これは以下のコードで行われています。
func GetModuleNameFromGoModFile() (model.GoMod, error) {
file, err := os.Open("go.mod")
goMod := model.GoMod{}
if err != nil {
return goMod, err
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
panic(err)
}
}(file)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "module ") {
// Extract module name
parts := strings.Fields(line)
if len(parts) >= 2 {
goMod.Module = parts[1]
}
} else if strings.HasPrefix(line, "go ") {
// Extract Go version
parts := strings.Fields(line)
if len(parts) >= 2 {
goMod.GoVersion = parts[1]
}
}
}
if err := scanner.Err(); err != nil {
return goMod, err
}
return goMod, nil
}
これがトリッキーな部分です。go/ast を使用して module.go ファイルを解析し、生成されたパッケージを import することでこの問題を解決しました。詳細はこちらで確認できます。
func ImportPackage(node *ast.File, projectModule, packageName string) {
// currently I have only one template so currently it is hard coded
// projectModule:- name of the project module which in inside go.mod
// domain:- hard coded value
// features:- hard coded value
// packageName:- the name of module you want to generate
path := filepath.Join(projectModule, "domain", "features", packageName)
importSpec := &ast.ImportSpec{
Path: &ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf(`"%v"`, path),
},
}
importDecl := &ast.GenDecl{
Tok: token.IMPORT,
Lparen: token.Pos(1), // for grouping
Specs: []ast.Spec{importSpec},
}
// Check if there are existing imports, and if so, add to them
found := false
for _, decl := range node.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if ok && genDecl.Tok == token.IMPORT {
genDecl.Specs = append(genDecl.Specs, importSpec)
found = true
break
}
}
// If no import declaration exists, add the new one to Decls
if !found {
node.Decls = append([]ast.Decl{importDecl}, node.Decls...)
}
}
最後に、template/wesionary ディレクトリ内のすべてのファイルを Go プロジェクトのビルドに埋め込む。
//go:embed templates/wesionary/*
var templatesFS embed.FS
完全なソースコードは こちら:geng で入手可能です。
参考:
この記事は、2024年1月に弊社のバックエンドエンジニアである Mukesh Kumar が執筆した内容を日本語に翻訳したものです。
英語版はこちらをご覧ください。
https://articles.wesionary.team/gen-g-31c3ed7e1e4f
採用情報
私たちはプロダクト共創の仕組み化に取り組んでいます。プロダクト共創をリードするプロダクト・マネージャー、そして、私たちのビジョンを市場に届ける営業メンバーを募集しています!
開発パートナーをお探しの企業様へ
弊社は、グローバル開発のメリットを活かし、高い費用対効果と品質を両立しています。経験豊富で多様性のあるチームが、課題を正しく理解し、最適なシステムと優れた体験を実現します。業務システムの開発、新規事業の開発、業務効率化やDX化に関するお困りごと、ぜひ弊社にご相談ください。