ありがとうございます。
ここから 第26段階:共通基盤・例外処理・バリデーション・通知設計 に進みます。
ここは業務機能ではありませんが、システム全体の品質・保守性・運用性を大きく左右する部分です。
これまで設計してきた内容を踏まえ、Spring Bootのベストプラクティスに合わせて設計します。
第26段階 全体構成
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 | Controller
│
▼
@Validated Form
│
▼
Service
│
┌──────────────┴──────────────┐
▼ ▼
BusinessException Repository / Mapper
│
▼
@ControllerAdvice
│
▼
ErrorView / API Response
│
▼
AuditLog + Notification
|
1. 共通例外設計
例外は用途ごとに分けます。
| common.exception
├ BusinessException
├ ValidationException
├ ResourceNotFoundException
├ AccessDeniedException
├ DuplicateException
├ FileStorageException
├ PublishException
└ SystemException
|
BusinessException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | @Getter
public class BusinessException extends RuntimeException {
private final String messageCode;
public BusinessException(String messageCode) {
super(messageCode);
this.messageCode = messageCode;
}
public BusinessException(String messageCode, String message) {
super(message);
this.messageCode = messageCode;
}
}
|
2. ResourceNotFoundException
| public class ResourceNotFoundException extends BusinessException {
public ResourceNotFoundException(String resource) {
super("error.notFound", resource + " が存在しません。");
}
}
|
3. PublishException
教材公開時専用です。
| public class PublishException extends BusinessException {
public PublishException(String message) {
super("error.publish", message);
}
}
|
例
| 公開できません。
・動画未登録
・教材未承認
・必須項目不足
|
4. @ControllerAdvice
全画面共通になります。
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 | @ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public String notFound(
ResourceNotFoundException ex,
Model model) {
model.addAttribute("message", ex.getMessage());
return "error/404";
}
@ExceptionHandler(BusinessException.class)
public String business(
BusinessException ex,
Model model) {
model.addAttribute("message", ex.getMessage());
return "error/business";
}
@ExceptionHandler(Exception.class)
public String system(
Exception ex,
Model model) {
log.error("System Error", ex);
model.addAttribute(
"message",
"システムエラーが発生しました。"
);
return "error/500";
}
}
|
5. エラー画面
| templates
error
├ 404.html
├ 403.html
├ business.html
└ 500.html
|
404
403
business
| 教材は公開できません。
・動画未登録です
・本文がありません
|
6. Bean Validation
Formだけで利用します。
Entityには基本的に付けません。
例
| @Data
public class ContentCreateForm {
@NotBlank
@Size(max = 200)
private String title;
@Size(max = 1000)
private String summary;
}
|
7. 独自Validation
例
教材公開前チェック
1
2
3
4
5
6
7
8
9
10
11
12
13 | @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PublishValidator.class)
public @interface ValidPublish {
String message()
default "公開条件を満たしていません。";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
|
Validator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | public class PublishValidator
implements ConstraintValidator<ValidPublish, ContentPublishForm> {
@Override
public boolean isValid(
ContentPublishForm form,
ConstraintValidatorContext context) {
if (form.getVideoId() == null &&
form.getBody() == null) {
return false;
}
return true;
}
}
|
8. Validationメッセージ
| src/main/resources
messages.properties
messages_ja.properties
messages_en.properties
|
例
| content.title.required=タイトルは必須です。
content.summary.max=概要は1000文字以内です。
|
Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | @PostMapping
public String save(
@Validated
ContentCreateForm form,
BindingResult result) {
if (result.hasErrors()) {
return "teacher/content/create";
}
service.create(form);
return "redirect:/teacher/content";
}
|
9. ログ設計
ログレベル
| レベル |
用途 |
| ERROR |
例外 |
| WARN |
業務警告 |
| INFO |
通常操作 |
| DEBUG |
開発用 |
例
| log.info(
"Content Created id={}, user={}",
contentId,
loginUser.getId()
);
|
10. 通知設計
イベント駆動にします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | 教材申請
↓
Event発行
↓
NotificationService
↓
メール
+
画面通知
|
11. ApplicationEvent
| public record ContentSubmittedEvent(
Long contentId,
Long teacherId
) {
}
|
発行
| publisher.publishEvent(
new ContentSubmittedEvent(
contentId,
teacherId
)
);
|
12. EventListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | @Component
@RequiredArgsConstructor
public class ContentSubmittedListener {
private final NotificationService notificationService;
@EventListener
public void handle(
ContentSubmittedEvent event) {
notificationService.notifyReviewer(
event.contentId()
);
}
}
|
13. 非同期処理
通知や動画変換は同期処理にしません。
1
2
3
4
5
6
7
8
9
10
11
12
13 | 教材登録
↓
保存完了
↓
@Async
↓
メール送信
|
設定
| @Configuration
@EnableAsync
public class AsyncConfig {
}
|
例
| @Async
public void sendMail(
MailMessage message) {
}
|
14. メール送信抽象化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | NotificationService
│
▼
MailSender
│
┌──────┴────────┐
SMTP
Amazon SES
SendGrid
Microsoft Graph
|
インターフェース
| public interface MailSender {
void send(MailMessage message);
}
|
これにより送信サービスを容易に切り替えられます。
15. 通知テーブル(推奨追加)
メールだけでなく、システム内通知も管理します。
| CREATE TABLE notifications (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
type VARCHAR(50) NOT NULL,
title VARCHAR(200) NOT NULL,
message TEXT NOT NULL,
link_url VARCHAR(500),
is_read BOOLEAN NOT NULL DEFAULT FALSE,
created_at DATETIME NOT NULL,
read_at DATETIME
);
|
通知例
| 教材「Java基礎」が承認されました。
教材「Spring Boot入門」の修正依頼があります。
コース「Java入門」の受講が完了しました。
|
16. ファイルアップロード例外
追加例外
| FileTooLargeException
↓
413画面表示
|
また
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | 対応拡張子
pdf
zip
xlsx
pptx
docx
txt
md
mp4
|
以外は禁止できます。
17. 共通レスポンス(将来REST API対応)
画面主体ですが、将来的なAPI公開を考慮し、共通レスポンスDTOを用意します。
| @Getter
@AllArgsConstructor(staticName = "of")
public class ApiResponse<T> {
private boolean success;
private T data;
private String message;
}
|
18. 共通ユーティリティ
| common.util
├ DateTimeUtils
├ MarkdownUtils
├ SecurityUtils
├ FileUtils
├ ValidationUtils
└ MessageUtils
|
SecurityUtils の例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | public final class SecurityUtils {
private SecurityUtils() {
}
public static Long getLoginUserId() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
CustomUserDetails user =
(CustomUserDetails) authentication.getPrincipal();
return user.getUser().getId();
}
}
|
これにより、Service層でログインユーザーIDを簡単に取得できます(必要に応じて、Controllerからユーザー情報を渡す設計との使い分けを行います)。
19. システム全体の共通基盤
ここまでで共通基盤は以下のようになります。
1
2
3
4
5
6
7
8
9
10
11
12 | common
├ audit
├ config
├ constant
├ dto
├ event
├ exception
├ message
├ notification
├ util
└ validation
|
20. 第26段階終了時点
実装可能になった機能
✅ 共通例外処理(@ControllerAdvice)
✅ 業務例外・システム例外の分離
✅ エラー画面(403・404・500・業務エラー)
✅ Bean Validation・独自バリデーション
✅ メッセージ国際化(i18n)
✅ イベント駆動通知(ApplicationEvent)
✅ 非同期処理(@Async)
✅ メール送信抽象化
✅ システム内通知テーブル
✅ ログ出力ルール・共通ユーティリティ
次の第27段階
ここからは、システム全体を実運用レベルへ引き上げる 管理機能・運用基盤 を設計します。
予定内容は以下です。
-
ユーザー・ロール・権限管理画面
-
メニュー・画面権限制御
-
システム設定(動画・ストレージ・メール等)
-
マスタ管理
-
バックアップ・リストア設計
-
ジョブ(Spring Scheduler)
-
キャッシュ(Spring Cache)
-
パフォーマンス最適化(JPA・MyBatis・DB設計)
-
運用監視(Actuator・Micrometer)
-
本番環境を見据えたデプロイ構成(Docker・Nginx・MySQL・Cloudflare R2)