Featured image of post 第8回-設定ファイルでパイプラインを組み立てよう(完成) - PerlとMooで学ぶDecorator

第8回-設定ファイルでパイプラインを組み立てよう(完成) - PerlとMooで学ぶDecorator

最終回は、YAML設定ファイルからDecorator構成を読み込み、動的にパイプラインを構築するPipelineBuilderを作成。開放閉鎖原則(OCP)を実現したシステムの完成です。

@nqounetです。

全8回にわたってお届けしてきた「PerlとMooで学ぶDecorator」も、今回で最終回となります。

前回までの記事で、ログ読み込みからフィルタリング、統計集計、アラート通知まで、全ての機能を「部品(Decorator)」として実装しました。

最後は、これらの部品をプログラムコードを変更することなく自由に組み替えられるようにして、このシリーズを締めくくりましょう。

今回のゴール:設定駆動パイプライン

pipeline.json という設定ファイルを書き換えるだけで、ログ解析パイプラインの構成(順序やパラメータ)を自由に変更できるシステムを完成させます。

コード例1: pipeline.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[
    {
        "module": "IPFilterDecorator",
        "args": {
            "target_ip": "127.0.0.1"
        }
    },
    {
        "module": "AlertDecorator",
        "args": {
            "threshold": 5
        }
    },
    {
        "module": "StatsAggregatorDecorator"
    }
]

コード例2: PipelineBuilder.pm

JSONを読み込んでDecoratorを積み上げていくビルダークラスです。

このコードでは File::SlurperModule::Load という2つのCPANモジュールを使用しています。事前にインストールしてください。

1
cpanm File::Slurper Module::Load
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package PipelineBuilder;
use Moo;
use experimental qw(signatures);
use JSON::PP qw(decode_json);
use File::Slurper qw(read_text);
use LogParser;
use Module::Load;

sub build ($self, $config_file, $log_file) {
    # 1. まず基本のパーサーを作る(一番芯の部分)
    my $processor = LogParser->new(filename => $log_file);

    # 2. JSON設定を読み込む
    my $json_text = read_text($config_file);
    my $steps     = decode_json($json_text);

    # 3. 設定順にDecoratorを積み上げていく
    for my $step (@$steps) {
        my $module = $step->{module};
        my $args   = $step->{args} || {};

        # モジュールを動的にロード(use $module と同じ)
        load $module;

        # Decoratorで現在のprocessorを包み込み、新しいprocessorにする
        $processor = $module->new(
            wrapped => $processor,
            %$args,
        );
    }

    return $processor;
}

1;

コード例3: main.pl(完成形)

これですべての準備が整いました。メインスクリプトは驚くほどシンプルになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use strict;
use warnings;
use lib '.';
use PipelineBuilder;

# ビルダーを使ってパイプラインを一発構築
my $pipeline = PipelineBuilder->new->build('pipeline.json', 'access.log');

# あとは回すだけ!
while (defined(my $log = $pipeline->next_log)) {
    # 処理はすべてDecoratorたちが勝手にやってくれる
}

# もし集計機能が含まれていれば、レポートを出せるかも?
if ($pipeline->can('report')) {
    $pipeline->report();
}

開放閉鎖原則(OCP)の達成

このシステムの素晴らしい点は、新しい機能を追加したい時に、既存のコード(PipelineBuilder や他のDecorator)を一切変更する必要がないことです。

  1. 新しいDecoratorクラスを作る(拡張に対して開いている)
  2. JSONに1要素追記する
  3. 既存のコードは修正不要(修正に対して閉じている)

これを「開放閉鎖原則(Open/Closed Principle)」と呼び、オブジェクト指向設計の重要な目標の一つです。Decoratorパターンは、このOCPを実現するための強力な武器なのです。

まとめ

このシリーズでは、以下のステップでDecoratorパターンを学びました。

  1. 基本クラスを作る(LogReader, LogParser)
  2. 継承で機能追加しようとして「クラス爆発」に遭遇する
  3. Roleで共通インターフェースを定義する
  4. Decoratorで機能を「包み込む」構造に変える
  5. 状態を持たせたり(統計)、副作用を持たせたり(アラート)する
  6. 設定ファイルから動的に構築する

みなさんの開発現場でも、「ちょっと機能を追加したいけど、継承だと影響範囲が怖いな…」と思った時は、ぜひDecoratorパターンを思い出してください。

きっと、あなたのコードを柔軟で強固なものにしてくれるはずです。

長い連載にお付き合いいただき、ありがとうございました!

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。