0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goでのコンポーネント管理: シンプルなアプリケーションフレームワークの実装

Last updated at Posted at 2025-03-19

はじめに

Goで大規模なアプリケーションを開発するとき、複数のサービスや依存関係を効率的に管理する必要があります。本記事では、シンプルな コンポーネント管理フレームワーク を実装し、Goアプリケーションの 依存管理・起動・停止 を統一的に扱う方法について書きます。

1. コンポーネント管理

コンポーネント管理とは、アプリケーションの各機能(例: ロギング、データベース、HTTPサーバーなど)を モジュール化 し、動的に登録・起動・停止できるようにする設計手法 です。

これにより、以下のメリットが得られます。

  • 依存関係の明確化: 各コンポーネントの関係性を明示的に管理
  • 初期化の統一: すべてのコンポーネントを統一的に Init() する
  • 動的な管理: 追加・削除が容易で、テストがしやすい
  • クリーンなシャットダウン: Close() を適切に呼び出し、リソースを確実に解放

2. コンポーネント管理フレームワークの設計

今回のフレームワークでは、以下の3種類のコンポーネントをサポートします。

インターフェース 役割
Component すべてのコンポーネントが実装すべき最小限のインターフェース
ComponentRunnable バックグラウンドで動作するコンポーネント(例: HTTP サーバー)
ComponentStatable 状態変更に応じた処理が可能なコンポーネント

3. Component インターフェースの定義

すべてのコンポーネントは、以下の Component インターフェースを実装します。

package app

import "context"

// Component: すべてのコンポーネントが実装するべきインターフェース
type Component interface {
	// アプリ起動時に最初に呼ばれる初期化処理
	Init(a *App) error
	// 一意のコンポーネント名を返す
	Name() string
}

この Component インターフェースを実装することで、アプリの起動時に統一的な初期化 (Init()) を適用できます。

4. ComponentRunnable: バックグラウンド処理の管理

ComponentRunnableComponent を継承し、Run()Close() を実装することで バックグラウンド処理 を持つコンポーネントになります。

// ComponentRunnable: バックグラウンドプロセスを持つコンポーネント
type ComponentRunnable interface {
	Component
	// バックグラウンド処理の開始
	Run(ctx context.Context) error
	// シャットダウン処理
	Close(ctx context.Context) error
}

例えば、HTTPサーバーを管理する HttpServer を実装する場合、以下のようになります。

type HttpServer struct{}

func (s *HttpServer) Init(a *App) error {
	fmt.Println("HTTPサーバーの初期化")
	return nil
}

func (s *HttpServer) Name() string {
	return "http-server"
}

func (s *HttpServer) Run(ctx context.Context) error {
	fmt.Println("HTTPサーバーを起動")
	go func() {
		<-ctx.Done()
		fmt.Println("HTTPサーバーをシャットダウン")
	}()
	return nil
}

func (s *HttpServer) Close(ctx context.Context) error {
	fmt.Println("HTTPサーバーを停止")
	return nil
}

5. App 構造体: コンポーネントを統括管理

アプリケーション全体を管理する App 構造体を実装します。

type App struct {
	components []Component
	mu         sync.RWMutex
}

各コンポーネントを リスト (components) に登録し、統一的に管理 します。

6. コンポーネントの登録 (Register())

App にコンポーネントを登録するための Register() メソッドを実装します。

// Register: コンポーネントを登録する
func (app *App) Register(s Component) {
	app.mu.Lock()
	defer app.mu.Unlock()

	// すでに登録されているかチェック
	for _, es := range app.components {
		if s.Name() == es.Name() {
			panic(fmt.Errorf("コンポーネント '%s' はすでに登録済み", s.Name()))
		}
	}
	app.components = append(app.components, s)
}

7. アプリケーションの起動 (Start())

すべてのコンポーネントを初期化し、バックグラウンド処理を開始します。

func (app *App) Start(ctx context.Context) error {
	app.mu.Lock()
	defer app.mu.Unlock()

	// 1. すべてのコンポーネントを初期化
	for _, s := range app.components {
		if err := s.Init(app); err != nil {
			return fmt.Errorf("コンポーネント '%s' の初期化に失敗: %w", s.Name(), err)
		}
	}

	// 2. ComponentRunnable をバックグラウンドで実行
	for _, s := range app.components {
		if runnable, ok := s.(ComponentRunnable); ok {
			go runnable.Run(ctx)
		}
	}

	fmt.Println("アプリケーション起動完了")
	return nil
}

8. アプリケーションの停止 (Close())

すべての ComponentRunnable を逆順で Close() し、リソースを適切に解放します。

func (app *App) Close(ctx context.Context) error {
	app.mu.Lock()
	defer app.mu.Unlock()

	// 逆順で Close() を実行
	for i := len(app.components) - 1; i >= 0; i-- {
		if closeable, ok := app.components[i].(ComponentRunnable); ok {
			closeable.Close(ctx)
		}
	}

	fmt.Println("アプリケーション停止完了")
	return nil
}

9. 実際に動かしてみる

コンポーネントの登録

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// アプリケーションを作成
	app := &App{}

	// コンポーネントを登録
	app.Register(&HttpServer{})

	// アプリを起動
	if err := app.Start(ctx); err != nil {
		fmt.Println("アプリ起動エラー:", err)
		return
	}

	// 5秒後に停止
	time.Sleep(5 * time.Second)
	app.Close(ctx)
}

実行すると、以下のような出力が得られます。

HTTPサーバーの初期化
HTTPサーバーを起動
アプリケーション起動完了
HTTPサーバーを停止
アプリケーション停止完了

Go Playgroundでのお試し

以下のコードをGo Playgroundにコピーして動作確認できます。
Go Playgroundでの動作手順

  1. Go Playground にアクセス
  2. 上記のコードをコピー&ペースト
  3. Run をクリックして実行
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

// Component: すべてのコンポーネントが実装するべきインターフェース
type Component interface {
	Init(a *App) error
	Name() string
}

// ComponentRunnable: バックグラウンドプロセスを持つコンポーネント
type ComponentRunnable interface {
	Component
	Run(ctx context.Context) error
	Close(ctx context.Context) error
}

// App: コンポーネントを管理する
type App struct {
	components []Component
	mu         sync.RWMutex
}

// Register: コンポーネントを登録する
func (app *App) Register(s Component) {
	app.mu.Lock()
	defer app.mu.Unlock()
	app.components = append(app.components, s)
}

// Start: すべてのコンポーネントを初期化・実行する
func (app *App) Start(ctx context.Context) error {
	app.mu.Lock()
	defer app.mu.Unlock()

	// すべてのコンポーネントを初期化
	for _, s := range app.components {
		if err := s.Init(app); err != nil {
			return fmt.Errorf("コンポーネント '%s' の初期化に失敗: %w", s.Name(), err)
		}
	}

	// ComponentRunnable をバックグラウンドで実行
	for _, s := range app.components {
		if runnable, ok := s.(ComponentRunnable); ok {
			go runnable.Run(ctx)
		}
	}

	fmt.Println("アプリケーション起動完了")
	return nil
}

// Close: すべてのバックグラウンドコンポーネントを停止する
func (app *App) Close(ctx context.Context) error {
	app.mu.Lock()
	defer app.mu.Unlock()

	for _, s := range app.components {
		if closeable, ok := s.(ComponentRunnable); ok {
			closeable.Close(ctx)
		}
	}

	fmt.Println("アプリケーション停止完了")
	return nil
}

// HttpServer: HTTP サーバーの模擬コンポーネント
type HttpServer struct{}

func (s *HttpServer) Init(a *App) error {
	fmt.Println("HTTPサーバーの初期化")
	return nil
}

func (s *HttpServer) Name() string {
	return "http-server"
}

func (s *HttpServer) Run(ctx context.Context) error {
	fmt.Println("HTTPサーバーを起動")
	go func() {
		<-ctx.Done()
		fmt.Println("HTTPサーバーをシャットダウン")
	}()
	return nil
}

func (s *HttpServer) Close(ctx context.Context) error {
	fmt.Println("HTTPサーバーを停止")
	return nil
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// アプリケーションを作成
	app := &App{}

	// コンポーネントを登録
	app.Register(&HttpServer{})

	// アプリを起動
	if err := app.Start(ctx); err != nil {
		fmt.Println("アプリ起動エラー:", err)
		return
	}

	// 5秒後に停止
	time.Sleep(5 * time.Second)
	app.Close(ctx)
}

HTTPサーバーの初期化
HTTPサーバーを起動
アプリケーション起動完了
HTTPサーバーを停止
アプリケーション停止完了
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?