概要

メールを送信する処理ひとつとっても、SMTP クライアントの接続・認証、テンプレートエンジンでの本文生成、監査ログの記録と、複数のサブシステムが関わります。呼び出し側がこれらを直接操作すると、手順の漏れや順序の間違いが起きやすく、同じ処理を複数箇所で書くことになります。Facade パターンは、複雑なサブシステム群の操作をひとつの窓口クラスにまとめ、呼び出し側には「ウェルカムメールを送る」のような高水準のメソッドだけを公開します。この記事ではメール送信システムを題材に Facade の構造を示し、Java 17 の record で設定値をまとめる方法と、Facade を適用すべき場面の判断基準を整理します。

使いどころ

メール送信で SMTP 接続・テンプレート適用・監査ログ記録を1メソッドにまとめ、呼び出し側は宛先と名前だけ指定する

帳票出力でデータ取得・フォーマット変換・PDF 生成・ファイル保存を Facade に隠蔽し、バッチからはワンコールで呼ぶ

ユーザー登録処理で DB 保存・メール送信・監査ログ・キャッシュ更新を Facade に集約し、Controller を薄く保つ

コード例

FacadePatternSample.java
import java.util.HashMap;
import java.util.Map;

public class FacadePatternSample {

    // 設定値を record でまとめる(Java 17+)
    record EmailConfig(String host, String user, String password) {}

    // サブシステム1: SMTP
    static class SmtpClient {
        void connect(String host) { System.out.println("[SMTP] 接続: " + host); }
        void authenticate(String user, String pass) {
            System.out.println("[SMTP] 認証: " + user);
        }
        void send(String to, String subject, String body) {
            System.out.println("[SMTP] 送信: " + to + " / " + subject);
        }
        void disconnect() { System.out.println("[SMTP] 切断"); }
    }

    // サブシステム2: テンプレートエンジン
    static class TemplateEngine {
        String render(String template, Map<String, String> vars) {
            var result = template;
            for (var e : vars.entrySet()) {
                result = result.replace("{{" + e.getKey() + "}}", e.getValue());
            }
            return result;
        }
    }

    // サブシステム3: 監査ログ
    static class AuditLogger {
        void logSend(String to, String subject) {
            System.out.println("[AUDIT] " + to + " / " + subject);
        }
    }

    // Facade: サブシステムを統合
    static class EmailFacade {
        private final SmtpClient smtp = new SmtpClient();
        private final TemplateEngine engine = new TemplateEngine();
        private final AuditLogger audit = new AuditLogger();
        private final EmailConfig config;

        EmailFacade(EmailConfig config) { this.config = config; }

        void sendWelcomeEmail(String to, String userName) {
            smtp.connect(config.host());
            smtp.authenticate(config.user(), config.password());
            var vars = new HashMap<String, String>();
            vars.put("userName", userName);
            var body = engine.render(
                "こんにちは、{{userName}} さん!", vars);
            var subject = "ご登録ありがとうございます";
            smtp.send(to, subject, body);
            smtp.disconnect();
            audit.logSend(to, subject);
        }
    }

    public static void main(String[] args) {
        var config = new EmailConfig("smtp.example.com", "user", "pw");
        var facade = new EmailFacade(config);
        facade.sendWelcomeEmail("[email protected]", "山田");
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

record で設定値を不変オブジェクトとしてまとめて管理でき、var(JEP 286)でサブシステムの呼び出しコードが簡潔になる。

Java 17
// Java 17: record で設定をまとめて管理
record EmailConfig(String host, String user, String pass) {}
var config = new EmailConfig("smtp.example.com", "user", "pw");
var facade = new EmailFacade(config);
facade.sendWelcomeEmail("[email protected]", "山田");

Library Comparison

標準 API(クラス + record)サブシステムの数が限られ、統合ロジックを自前で管理するとき。サブシステムが増えると Facade のメソッド数も増える傾向がある。
Spring(@Service)DI コンテナでサブシステムの依存を注入し、Service 層を Facade として使うとき。Spring の流儀に従う形になるが、設計の意図は同じ。

注意点

Facade にロジックを詰め込みすぎると God Class になる。Facade はサブシステムへの委譲に徹し、ビジネスロジック自体はサブシステムに持たせる

Facade はサブシステムへのアクセスを「隠す」のではなく「簡単にする」もの。必要に応じてサブシステムを直接使うことも許容する設計が望ましい

Facade が増えすぎると、どの Facade がどのサブシステムを管理しているかが不明確になる。1つの Facade が扱うサブシステムは3〜5つ程度に留める

FAQ

Facade パターンと Service 層は同じですか。

考え方は近いです。Service 層がサブシステムの操作をまとめて高水準のメソッドを提供するなら、それは Facade パターンの適用と見なせます。

Facade を経由せずサブシステムを直接使ってもよいですか。

構いません。Facade はアクセスの簡略化が目的であり、サブシステムの直接利用を禁止するものではありません。

Facade が肥大化してきた場合はどうすればよいですか。

責務ごとに Facade を分割してください。メール送信、帳票出力、ユーザー管理など、業務ドメインに沿った単位で分けるのが自然です。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます