コンテンツにスキップ

ありがとうございます。

ここから 第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. 共通例外設計

例外は用途ごとに分けます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

1
2
3
4
5
6
7
public class ResourceNotFoundException extends BusinessException {

    public ResourceNotFoundException(String resource) {
        super("error.notFound", resource + " が存在しません。");
    }

}

3. PublishException

教材公開時専用です。

1
2
3
4
5
6
7
public class PublishException extends BusinessException {

    public PublishException(String message) {
        super("error.publish", message);
    }

}

1
2
3
4
5
公開できません。

・動画未登録
・教材未承認
・必須項目不足

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. エラー画面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
templates

error

├ 404.html

├ 403.html

├ business.html

└ 500.html

404

1
2
3
ページが見つかりません

[ホームへ戻る]

403

1
この画面を表示する権限がありません

business

1
2
3
4
教材は公開できません。

・動画未登録です
・本文がありません

6. Bean Validation

Formだけで利用します。

Entityには基本的に付けません。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@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メッセージ

1
2
3
4
5
6
7
src/main/resources

messages.properties

messages_ja.properties

messages_en.properties

1
2
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 開発用

1
2
3
4
5
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

1
2
3
4
5
public record ContentSubmittedEvent(
        Long contentId,
        Long teacherId
) {
}

発行

1
2
3
4
5
6
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

↓

メール送信

設定

1
2
3
4
@Configuration
@EnableAsync
public class AsyncConfig {
}

1
2
3
4
5
@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

インターフェース

1
2
3
4
5
public interface MailSender {

    void send(MailMessage message);

}

これにより送信サービスを容易に切り替えられます。


15. 通知テーブル(推奨追加)

メールだけでなく、システム内通知も管理します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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
);

通知例

1
2
3
4
5
教材「Java基礎」が承認されました。

教材「Spring Boot入門」の修正依頼があります。

コース「Java入門」の受講が完了しました。

16. ファイルアップロード例外

追加例外

1
2
3
4
5
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を用意します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Getter
@AllArgsConstructor(staticName = "of")
public class ApiResponse<T> {

    private boolean success;

    private T data;

    private String message;

}

18. 共通ユーティリティ

1
2
3
4
5
6
7
8
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段階

ここからは、システム全体を実運用レベルへ引き上げる 管理機能・運用基盤 を設計します。

予定内容は以下です。

  1. ユーザー・ロール・権限管理画面

  2. メニュー・画面権限制御

  3. システム設定(動画・ストレージ・メール等)

  4. マスタ管理

  5. バックアップ・リストア設計

  6. ジョブ(Spring Scheduler)

  7. キャッシュ(Spring Cache)

  8. パフォーマンス最適化(JPA・MyBatis・DB設計)

  9. 運用監視(Actuator・Micrometer)

  10. 本番環境を見据えたデプロイ構成(Docker・Nginx・MySQL・Cloudflare R2)