Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11457] ファイルの概要

このコミットは、Goプロジェクトのダッシュボードアプリケーションにおけるパッケージ管理の改善とリファクタリングに関するものです。具体的には、Goのサブリポジトリをダッシュボードの初期化リストに追加し、Package構造体にKindフィールドを導入し、/packagesハンドラにkindパラメータを追加することで、異なる種類のパッケージ(メインGoツリー、サブリポジトリ、外部パッケージ)を区別して管理できるようにしています。

コミット

commit d87813b51c667cf5169695ca4dc893d1976af888
Author: Andrew Gerrand <adg@golang.org>
Date:   Mon Jan 30 11:59:06 2012 +1100

    dashboard: add sub-repositories to init list
    dashboard: add Kind to Package struct
    dashboard: add kind parameter to /packages handler
    
    R=rsc, bsiegert, rogpeppe
    CC=golang-dev
    https://golang.org/cl/5572062

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d87813b51c667cf5169695ca4dc893d1976af888

元コミット内容

このコミットの元のメッセージは以下の通りです。

  • dashboard: add sub-repositories to init list (ダッシュボード: サブリポジトリを初期化リストに追加)
  • dashboard: add Kind to Package struct (ダッシュボード: Package構造体にKindを追加)
  • dashboard: add kind parameter to /packages handler (ダッシュボード: /packagesハンドラにkindパラメータを追加)

これらの変更は、Goプロジェクトのビルドダッシュボードが、メインのGoリポジトリだけでなく、関連するサブリポジトリも適切に追跡し、表示できるようにするためのものです。

変更の背景

Goプロジェクトは、その初期からメインのGoリポジトリとは別に、特定の機能やライブラリを独立した「サブリポジトリ」として管理する方針を採用していました。例えば、go.netgo.imagego.cryptoなどがこれに該当します。これらのサブリポジトリもGoエコシステムの一部であり、メインリポジトリと同様にビルドやテストの状況を監視する必要がありました。

このコミット以前のダッシュボードは、主にメインのGoリポジトリのビルド状況を追跡するように設計されていたと考えられます。しかし、サブリポジトリの重要性が増すにつれて、ダッシュボードがこれらを区別し、それぞれのビルド状態を正確に反映できる機能が求められるようになりました。

この変更の背景には、以下の課題があったと推測されます。

  1. サブリポジトリの追跡不足: ダッシュボードがサブリポジトリのビルド状況を適切に追跡できていなかった。
  2. パッケージの種類の区別: メインGoツリー、サブリポジトリ、将来的な外部パッケージなど、異なる種類のパッケージをデータモデル上で区別する必要があった。
  3. APIの柔軟性: ダッシュボードのフロントエンドが、特定の種類のパッケージのみを取得できるように、APIにフィルタリング機能が必要だった。
  4. 初期化ロジックの整理: ダッシュボードが起動する際に、追跡すべきパッケージの初期化ロジックが散在していたか、不十分だった。

これらの課題を解決するために、Package構造体にKindフィールドを導入し、パッケージの初期化ロジックを整理し、APIにフィルタリング機能を追加する変更が行われました。

前提知識の解説

このコミットを理解するためには、以下の技術的知識が役立ちます。

  1. Go言語:

    • 構造体 (Structs): 関連するデータをまとめるためのユーザー定義型。Package構造体のように、複数のフィールドを持つことができます。
    • init() 関数: Goプログラム内で、パッケージがインポートされた際に自動的に実行される特殊な関数。このコミットでは、サブリポジトリの初期化に利用されています。
    • スライス (Slices): 可変長配列。append関数を使って要素を追加できます。
    • エラーハンドリング: Goにおけるエラーの扱い方(errorインターフェース、os.NewErrorなど)。
  2. Google App Engine (GAE):

    • Go App Engine Standard Environment: 2012年当時、GoアプリケーションをホストするためのGoogleのPaaS(Platform as a Service)。このダッシュボードはGAE上で動作していることがコードから読み取れます。
    • appengine.Context: App EngineのAPI呼び出しに必要なコンテキストオブジェクト。リクエストスコープの情報や、Datastoreなどのサービスへのアクセスを提供します。
    • Datastore: Google App Engineが提供するNoSQLドキュメントデータベース。キーと値のペアでデータを保存し、クエリで取得します。
      • datastore.NewQuery: Datastoreからエンティティを検索するためのクエリを作成します。
      • Filter: クエリに条件を追加します。
      • Run: クエリを実行し、結果をイテレータとして返します。
      • Get: 特定のキーを持つエンティティを取得します。
      • Put: エンティティをDatastoreに保存または更新します。
      • datastore.Done: クエリ結果の終端を示すエラー。
      • datastore.ErrNoSuchEntity: 指定されたキーのエンティティが見つからなかったことを示すエラー。
    • HTTPハンドラ: http.HandlerFunchttp.Requesthttp.ResponseWriterを使ったWebリクエストの処理。
    • キャッシュ (Cache): App EngineのMemcacheサービスなど、頻繁にアクセスされるデータを一時的に保存してパフォーマンスを向上させる仕組み。
  3. Goプロジェクトのサブリポジトリ:

    • Goプロジェクトは、メインのgolang/goリポジトリの他に、golang.org/x/配下に多数のサブリポジトリ(例: golang.org/x/net, golang.org/x/image, golang.org/x/cryptoなど)を持っています。これらはGoの標準ライブラリの一部ではないものの、Goチームによって公式にメンテナンスされています。2012年当時はcode.google.com/p/go.<name>のようなパスでホストされていました。

技術的詳細

このコミットは、Goダッシュボードのバックエンドにおけるデータモデル、API、および初期化ロジックにわたる複数の変更を含んでいます。

  1. Package構造体へのKindフィールドの追加:

    • misc/dashboard/app/build/build.go内のPackage構造体にKind stringフィールドが追加されました。
    • このフィールドは、パッケージの種類を識別するために使用されます。コメントには"subrepo", "external", またはメインGoツリーの場合は空文字列が想定されています。これにより、Datastoreに保存される各パッケージエンティティが、その種類に関するメタデータを持つことができるようになりました。
  2. Packages関数の変更:

    • misc/dashboard/app/build/build.go内のPackages関数は、以前はすべての非Goパッケージを返していましたが、変更後はkind stringパラメータを受け取るようになりました。
    • この関数は、受け取ったkindパラメータに基づいてDatastoreクエリをフィルタリングします(Filter("Kind=", kind))。
    • kindパラメータが"external"または"subrepo"のいずれかであることを検証し、それ以外の場合はエラーを返します。これにより、APIの利用がより厳密になります。
  3. init.goファイルの導入と初期化ロジックの集約:

    • 以前misc/dashboard/app/build/handler.goにあったdefaultPackages変数とinitHandler関数が、新しく作成されたmisc/dashboard/app/build/init.goファイルに移動されました。
    • init.goには、Goプロジェクトのサブリポジトリのリストを定義するsubReposスライスが追加されました(例: "codereview", "crypto", "image", "net")。
    • このファイル内のinit()関数(Goの特殊な初期化関数)が、subReposリストをループ処理し、それぞれのサブリポジトリに対応するPackageエンティティを作成してdefaultPackagesスライスに追加します。この際、Kindフィールドは"subrepo"に設定され、Pathフィールドはcode.google.com/p/go.<name>形式で設定されます。メインのGoツリーのパッケージはKind: "go"として初期化されます。
    • initHandlerは、これらのdefaultPackagesをDatastoreにPut(保存)する役割を担います。これにより、ダッシュボードが初めてデプロイされた際や、新しいサブリポジトリが追加された際に、必要なパッケージ情報が自動的にDatastoreに登録されるようになります。
  4. /packagesハンドラの変更:

    • misc/dashboard/app/build/handler.go内のpackagesHandlerは、HTTPリクエストのフォーム値からkindパラメータを取得するようになりました(r.FormValue("kind"))。
    • 取得したkindパラメータは、新しいPackages関数に渡され、特定の種類のパッケージのみが取得されるようになります。
    • キャッシュキーもkindパラメータを含むように変更され、異なる種類のパッケージリストが適切にキャッシュされるようになりました("build-packages-" + kind)。
  5. テストとUIの更新:

    • misc/dashboard/app/build/test.goでは、テスト用のPackageエンティティにKind: "subrepo"が追加され、/packagesエンドポイントへのテストリクエストも?kind=subrepoクエリパラメータを含むように更新されました。これにより、新しいAPIの動作がテストされます。
    • misc/dashboard/app/build/ui.goでは、TagState関数がPackages(c, "subrepo")を呼び出すように変更され、UIがGoのサブリポジトリのビルド状態を適切に表示できるようになりました。関連するコメントも「すべてのGoサブリポジトリ」を指すように更新されています。

これらの変更により、ダッシュボードはGoプロジェクトの進化する構造、特にサブリポジトリの管理に対応できるようになり、より正確で柔軟なビルド監視機能を提供できるようになりました。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は以下の通りです。

  1. misc/dashboard/app/build/build.go:

    • Package構造体にKindフィールドを追加:
      type Package struct {
      	Kind    string // "subrepo", "external", or empty for the main Go tree
      	Name    string
      	Path    string // (empty for the main Go tree)
      	NextNum int    // Num of the next head Commit
      }
      
    • Packages関数のシグネチャと実装の変更(kindパラメータの追加とDatastoreクエリのフィルタリング):
      // Packages returns packages of the specified kind.
      // Kind must be one of "external" or "subrepo".
      func Packages(c appengine.Context, kind string) ([]*Package, os.Error) {
      	switch kind {
      	case "external", "subrepo":
      	default:
      		return nil, os.NewError(`kind must be one of "external" or "subrepo"`)
      	}
      	var pkgs []*Package
      	q := datastore.NewQuery("Package").Filter("Kind=", kind) // ここでKindによるフィルタリング
      	for t := q.Run(c); ; {
      		pkg := new(Package)
      		if _, err := t.Next(pkg); err == datastore.Done {
      			break
      		}
      		// ... (エラーハンドリングなど)
      	}
      	return pkgs, nil
      }
      
  2. misc/dashboard/app/build/handler.go:

    • packagesHandlerでのkindパラメータの取得とPackages関数への渡し:
      func packagesHandler(r *http.Request) (interface{}, os.Error) {
      	kind := r.FormValue("kind") // kindパラメータの取得
      	c := appengine.NewContext(r)
      	now := cache.Now(c)
      	key := "build-packages-" + kind // キャッシュキーにkindを含める
      	var p []*Package
      	if cache.Get(r, now, key, &p) {
      		return p, nil
      	}
      	p, err := Packages(c, kind) // kindを渡してPackages関数を呼び出す
      	if err != nil {
      		return nil, err
      	}
      	// ...
      }
      
    • defaultPackages変数とinitHandler関数の削除(init.goへ移動)。
  3. misc/dashboard/app/build/init.go (新規ファイル):

    • defaultPackagessubReposの定義:
      var defaultPackages = []*Package{
      	&Package{Name: "Go", Kind: "go"},
      }
      
      var subRepos = []string{
      	"codereview",
      	"crypto",
      	"image",
      	"net",
      }
      
    • init()関数でのサブリポジトリの初期化ロジック:
      func init() {
      	for _, name := range subRepos {
      		p := &Package{
      			Kind: "subrepo",
      			Name: "go." + name,
      			Path: "code.google.com/p/go." + name,
      		}
      		defaultPackages = append(defaultPackages, p)
      	}
      }
      
    • initHandler関数の定義(Datastoreへの初期パッケージの保存):
      func initHandler(w http.ResponseWriter, r *http.Request) {
      	c := appengine.NewContext(r)
      	defer cache.Tick(c)
      	for _, p := range defaultPackages {
      		if err := datastore.Get(c, p.Key(c), new(Package)); err == nil {
      			continue
      		} else if err != datastore.ErrNoSuchEntity {
      			logErr(w, r, err)
      			return
      		}
      		if _, err := datastore.Put(c, p.Key(c), p); err != nil {
      			logErr(w, r, err)
      			return
      		}
      	}
      	fmt.Fprint(w, "OK")
      }
      

コアとなるコードの解説

このコミットの核心は、GoダッシュボードがGoプロジェクトの異なる種類のパッケージ(特にサブリポジトリ)をより構造化された方法で管理できるようにすることです。

  1. Package構造体のKindフィールド:

    • これはデータモデルの拡張であり、各パッケージがその「種類」を示すメタデータを持つことを可能にします。これにより、ダッシュボードは単にパッケージ名を追跡するだけでなく、それがメインのGoツリーの一部なのか、公式のサブリポジトリなのか、あるいは将来的に追加される可能性のある外部パッケージなのかを区別できます。この区別は、UIでの表示方法や、特定の種類のパッケージに対する処理ロジックを分岐させる際に非常に重要になります。
  2. Packages関数のkindフィルタリング:

    • この変更は、Datastoreからパッケージを取得する際の柔軟性を大幅に向上させます。以前はすべての非Goパッケージを取得していましたが、今後は特定のKindを持つパッケージのみを効率的に取得できるようになります。これは、例えばUIが「Goサブリポジトリのみを表示」といった機能を提供する場合に不可欠です。Datastoreのクエリレベルでフィルタリングを行うことで、アプリケーションレベルでのフィルタリングよりも効率的になります。
  3. init.goへの初期化ロジックの分離とinit()関数によるサブリポジトリの自動登録:

    • init.goの導入は、コードの関心事の分離(Separation of Concerns)の良い例です。パッケージの初期化ロジックがhandler.goから独立したファイルに移動されたことで、handler.goはHTTPリクエストの処理に集中できるようになり、コードの可読性と保守性が向上します。
    • 特に重要なのは、Goのinit()関数を利用してsubReposリストからPackageエンティティを自動的に生成し、defaultPackagesに追加している点です。これにより、新しいサブリポジトリがGoプロジェクトに追加された場合、subReposリストを更新するだけで、ダッシュボードが自動的にそのサブリポジトリを認識し、追跡対象に含めることができるようになります。これは、手動での設定やデータベースへの直接挿入の手間を省き、運用を簡素化します。initHandlerは、これらの初期パッケージがDatastoreに存在しない場合にのみ挿入を試みるため、冪等性も確保されています。

これらの変更は、GoダッシュボードがGoプロジェクトの成長と構造の変化に対応するための基盤を強化し、より堅牢でスケーラブルなパッケージ管理システムを構築する上で重要なステップでした。

関連リンク

参考にした情報源リンク