@nqounetです。
前回は、生成処理のオーバーライドを実装しました。今回は、送信処理やログ出力など、全シナリオで共通する処理を基底クラスに集約します。
このシリーズについて
シリーズ全体の目次は以下をご覧ください。
前回の振り返り
前回までに、各シナリオがcreate_responseをオーバーライドして専用のレスポンスを生成するようになりました。
1
2
3
4
5
6
7
8
9
10
| package SuccessScenario {
use Moo;
extends 'Scenario';
sub create_response($self) {
return SuccessResponse->new(
data => { id => 1, name => 'サンプルアイテム' },
);
}
}
|
今回のゴール
共通処理(ログ出力、タイムスタンプ追加など)を基底クラスに集約し、サブクラスでは「レスポンス生成」だけに集中できるようにします。
flowchart TB
subgraph "Scenario基底クラス(共通処理)"
A["execute()"] --> B["開始時刻を記録"]
B --> C["log_request()"]
C --> D["create_response()"]
D --> E["処理時間を計算"]
E --> F["log_complete()"]
F --> G["response.render()"]
end
subgraph "サブクラス(個別処理)"
D -.->|オーバーライド| H["SuccessScenario"]
D -.->|オーバーライド| I["NotFoundScenario"]
end
style D fill:#f9f,stroke:#333
現状の問題
現在のexecuteメソッドは非常にシンプルです。
1
2
3
4
| sub execute($self) {
my $response = $self->create_response;
return $response->render;
}
|
しかし実際のAPIシミュレーターでは、以下のような処理も必要になるでしょう:
- リクエストのログ出力
- 処理時間の記録
- タイムスタンプの追加
これらの処理を各サブクラスに書くと、コードが重複してしまいます。
共通処理を基底クラスに追加する
Scenario基底クラスに共通処理を追加しましょう。
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
83
84
85
86
87
88
89
90
91
92
93
94
95
| #!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo, JSON, Time::HiRes(cpanmでインストール)
use v5.36;
package ResponseRole {
use Moo::Role;
requires 'render';
}
package SuccessResponse {
use Moo;
use JSON qw(encode_json);
with 'ResponseRole';
has data => (is => 'ro', required => 1);
sub render($self) {
my $body = encode_json({
success => JSON::true,
message => 'リクエストが正常に処理されました',
data => $self->data,
});
return "HTTP/1.1 200 OK\nContent-Type: application/json\n\n$body";
}
}
package ErrorResponse {
use Moo;
use JSON qw(encode_json);
with 'ResponseRole';
has status => (is => 'ro', required => 1);
has error_code => (is => 'ro', required => 1);
has message => (is => 'ro', required => 1);
sub render($self) {
my $body = encode_json({
success => JSON::false,
error => $self->message,
code => $self->error_code,
});
return sprintf(
"HTTP/1.1 %s\nContent-Type: application/json\n\n%s",
$self->status, $body,
);
}
}
package Scenario {
use Moo;
use Time::HiRes qw(gettimeofday tv_interval);
sub create_response($self) {
die "create_response must be implemented by subclass";
}
sub scenario_name($self) {
my $class = ref($self);
$class =~ s/Scenario$//;
return $class;
}
sub log_request($self) {
my $name = $self->scenario_name;
my $timestamp = localtime();
say STDERR "[$timestamp] Processing: $name";
}
sub log_complete($self, $elapsed) {
my $name = $self->scenario_name;
my $timestamp = localtime();
say STDERR "[$timestamp] Completed: $name (${elapsed}ms)";
}
sub execute($self) {
my $start = [gettimeofday];
# リクエストログ
$self->log_request;
# レスポンス生成
my $response = $self->create_response;
# 処理時間計算
my $elapsed = int(tv_interval($start) * 1000);
# 完了ログ
$self->log_complete($elapsed);
return $response->render;
}
}
|
executeメソッドは以下の流れになりました:
- 開始時刻を記録
- リクエストログを出力
- レスポンスを生成(サブクラスの
create_responseを呼ぶ) - 処理時間を計算
- 完了ログを出力
- レスポンスを返す
サブクラスはシンプルなまま
共通処理は基底クラスにあるので、サブクラスはレスポンス生成だけに集中できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package SuccessScenario {
use Moo;
extends 'Scenario';
sub create_response($self) {
return SuccessResponse->new(
data => { id => 1, name => 'サンプルアイテム' },
);
}
}
package NotFoundScenario {
use Moo;
extends 'Scenario';
sub create_response($self) {
return ErrorResponse->new(
status => '404 Not Found',
error_code => 'NOT_FOUND',
message => 'リソースが見つかりません',
);
}
}
|
サブクラスは何も変わっていません。共通処理を意識する必要がありません。
実行ログの例
1
2
3
4
5
6
| [Sat Jan 17 13:23:20 2026] Processing: Success
[Sat Jan 17 13:23:20 2026] Completed: Success (0ms)
HTTP/1.1 200 OK
Content-Type: application/json
{"data":{"id":1,"name":"サンプルアイテム"},"message":"リクエストが正常に処理されました","success":true}
|
ログが自動的に出力されるようになりました。
完成コード
今回の完成コードを1ファイルにまとめると、以下のようになります。
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
| #!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo, JSON, Time::HiRes(cpanmでインストール)
use v5.36;
package ResponseRole {
use Moo::Role;
requires 'render';
}
package SuccessResponse {
use Moo;
use JSON qw(encode_json);
with 'ResponseRole';
has data => (is => 'ro', required => 1);
sub render($self) {
my $body = encode_json({
success => JSON::true,
message => 'リクエストが正常に処理されました',
data => $self->data,
});
return "HTTP/1.1 200 OK\nContent-Type: application/json\n\n$body";
}
}
package ErrorResponse {
use Moo;
use JSON qw(encode_json);
with 'ResponseRole';
has status => (is => 'ro', required => 1);
has error_code => (is => 'ro', required => 1);
has message => (is => 'ro', required => 1);
sub render($self) {
my $body = encode_json({
success => JSON::false,
error => $self->message,
code => $self->error_code,
});
return sprintf(
"HTTP/1.1 %s\nContent-Type: %s\n\n%s",
$self->status,
'application/json',
$body,
);
}
}
package Scenario {
use Moo;
use Time::HiRes qw(gettimeofday tv_interval);
sub create_response($self) {
die "create_response must be implemented by subclass";
}
sub scenario_name($self) {
my $class = ref($self);
$class =~ s/Scenario$//;
return $class;
}
sub log_request($self) {
my $name = $self->scenario_name;
my $timestamp = localtime();
say STDERR "[$timestamp] Processing: $name";
}
sub log_complete($self, $elapsed) {
my $name = $self->scenario_name;
my $timestamp = localtime();
say STDERR "[$timestamp] Completed: $name (${elapsed}ms)";
}
sub execute($self) {
my $start = [gettimeofday];
$self->log_request;
my $response = $self->create_response;
my $elapsed = int(tv_interval($start) * 1000);
$self->log_complete($elapsed);
return $response->render;
}
}
package SuccessScenario {
use Moo;
extends 'Scenario';
sub create_response($self) {
return SuccessResponse->new(
data => { id => 1, name => 'サンプルアイテム' },
);
}
}
package NotFoundScenario {
use Moo;
extends 'Scenario';
sub create_response($self) {
return ErrorResponse->new(
status => '404 Not Found',
error_code => 'NOT_FOUND',
message => 'リソースが見つかりません',
);
}
}
for my $scenario_class (qw(SuccessScenario NotFoundScenario)) {
say "=== $scenario_class ===";
my $scenario = $scenario_class->new;
say $scenario->execute;
say "";
}
|
まとめ
今回は、共通処理を基底クラスに集約しました:
- ログ出力、処理時間計測を
Scenario基底クラスに追加 - サブクラスは
create_responseの実装だけに集中 - 共通処理の変更が1箇所で済むようになった
次回は、新しいシナリオ「レート制限」を追加して、既存コードを修正せずに拡張できることを体験します。