Featured image of post 第4回-通知ルールを契約化 - Perlでハニーポット侵入レーダーを作ろう

第4回-通知ルールを契約化 - Perlでハニーポット侵入レーダーを作ろう

Moo::Roleを使って通知担当が満たすべき契約(インターフェース)を定義します。requiresで必須メソッドを宣言し、実装漏れをコンパイル時に検出できるようにしましょう。

@nqounetです。

前回は通知担当をRadarLogObserverThreatScoreObserverに分離しました。どちらもupdateメソッドを持っていて、統一的に呼び出せるようになりましたね。

でも、ここで疑問が生まれます。

updateメソッドを持っていること、どうやって保証するの?」

今回はMoo::Roleを使って、この契約を明文化します。

Roleで契約を定義する

Moo::Roleは「このメソッドを必ず持っていなければならない」というルールを定義できます。JavaやTypeScriptでいうインターフェースのようなものです。

 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo(cpanmでインストール)

use v5.36;

package IntrusionEvent;
use Moo;

has timestamp => (
    is       => 'ro',
    required => 1,
);

has source_ip => (
    is       => 'ro',
    required => 1,
);

has attack_type => (
    is       => 'ro',
    required => 1,
);

# 通知担当が満たすべき契約
package IntrusionObserver;
use Moo::Role;

requires 'update';  # updateメソッドを必須にする

package RadarLogObserver;
use Moo;
with 'IntrusionObserver';  # 契約に同意

sub update ($self, $event) {
    say "=== 侵入イベント検知 ===";
    say "時刻: " . $event->timestamp;
    say "発信元: " . $event->source_ip;
    say "攻撃種別: " . $event->attack_type;
    say "";
}

package ThreatScoreObserver;
use Moo;
with 'IntrusionObserver';  # 契約に同意

my %threat_scores = (
    'SSH Brute Force'       => 80,
    'Port Scan'             => 30,
    'SQL Injection Attempt' => 90,
    'XSS Attack'            => 70,
);

has total_score => (
    is      => 'rw',
    default => 0,
);

sub update ($self, $event) {
    my $score = $threat_scores{$event->attack_type} // 50;
    $self->total_score($self->total_score + $score);
    say "[脅威スコア] $score 加算 (累計: " . $self->total_score . ")";
}

package main;

my $log_observer   = RadarLogObserver->new;
my $score_observer = ThreatScoreObserver->new;

my @events = (
    IntrusionEvent->new(
        timestamp   => '2026-01-18T06:00:00+09:00',
        source_ip   => '192.168.1.100',
        attack_type => 'SSH Brute Force',
    ),
);

for my $event (@events) {
    $log_observer->update($event);
    $score_observer->update($event);
}

ポイントはIntrusionObserverというRoleです。

1
2
3
4
package IntrusionObserver;
use Moo::Role;

requires 'update';  # updateメソッドを必須にする

このRoleをwithで取り込んだクラスは、必ずupdateメソッドを実装しなければなりません

契約違反を検出する

試しにupdateメソッドを実装し忘れた場合を見てみましょう。

1
2
3
4
5
package BrokenObserver;
use Moo;
with 'IntrusionObserver';  # 契約に同意したけど...

# updateメソッドを実装し忘れた!

このコードを実行すると、以下のようなエラーが発生します。

1
Can't apply IntrusionObserver to BrokenObserver - missing update

コンパイル時にエラーが検出されます。実行してから「あれ、動かない」ではなく、プログラムを起動した時点で問題が発覚するのです。これは大きな安心感につながります。

実行結果

正しく実装されたコードは、前回と同じように動作します。

1
2
3
4
5
6
=== 侵入イベント検知 ===
時刻: 2026-01-18T06:00:00+09:00
発信元: 192.168.1.100
攻撃種別: SSH Brute Force

[脅威スコア] 80 加算 (累計: 80)

見た目は変わりませんが、設計の堅牢性が向上しています。

Roleの役割

Roleは「契約書」のようなものです。

  • requires 'update': 「このメソッドを持っていること」という契約条項
  • with 'IntrusionObserver': 「この契約に同意します」という署名

契約に違反すれば、すぐにエラーで教えてくれます。チームで開発するときも「このRoleを実装してね」と伝えれば、必要なメソッドが明確になります。

今回のまとめ

今回はMoo::Roleを使って、通知担当が満たすべき契約を定義しました。

  • IntrusionObserverロールでupdateメソッドを必須化
  • withで契約に同意、requiresで必須メソッドを宣言
  • 実装漏れはコンパイル時に検出

これで「updateを持っているはず」という期待が、明文化された契約になりました。

次回予告

次回は「通知担当を一元管理する司令塔」を作ります。現在は手動で各Observerを呼び出していますが、これを自動化しましょう。

イベントが発生したら、登録されたすべてのObserverに自動で通知する仕組みを作ります。

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