1. 概要

このチュートリアルでは、12要素のアプリの方法論について理解します。

また、Spring Bootを使用してマイクロサービスを開発する方法についても理解します。 その過程で、このようなマイクロサービスを開発するために12要素の方法論を適用する方法を見ていきます。

2. 12ファクターの方法論とは何ですか?

12要素の方法論は、サービスとして実行するために開発されたアプリケーションを開発するための12のベストプラクティスのセットです。 これは元々、2011年に、クラウドプラットフォームにサービスとしてデプロイされたアプリケーション用にHerokuによって作成されました。 時間の経過とともに、これは Software-as-a-Service (SaaS)開発にとって十分に一般的であることが証明されています。

では、サービスとしてのソフトウェアとはどういう意味ですか? 従来、私たちはソフトウェアソリューションを設計、開発、展開、および保守して、そこからビジネス価値を引き出してきました。 しかし、必ずしも同じ結果を達成するために、このプロセスに従事する必要はありません。 たとえば、適用される税金の計算は、多くのドメインで一般的な機能です。

現在、このサービスを自分で構築および管理するか、商用サービスの提供にサブスクライブするかを決定する場合があります。 このようなサービスの提供は、サービスとしてのソフトウェアとして知られているものです。

Software-as-a-Serviceは、それが開発されたアーキテクチャに制限を課しませんが、 いくつかのベストプラクティスを採用することは非常に便利です。

最新のクラウドプラットフォームでモジュール式、ポータブル、スケーラブルになるようにソフトウェアを設計する場合、それは当社のサービス提供に非常に適しています。 これは、12要素の方法論が役立つところです。 チュートリアルの後半で、それらの動作を確認します。

3. SpringBootを使用したマイクロサービス

Microservice は、ソフトウェアを疎結合サービスとして開発するためのアーキテクチャスタイルです。 ここでの重要な要件は、サービスをビジネスドメインの境界を中心に編成する必要があることです。 これは多くの場合、識別するのが最も難しい部分です。

さらに、ここでのサービスは、そのデータに対する唯一の権限を持ち、他のサービスに操作を公開します。 サービス間の通信は通常、HTTPなどの軽量プロトコルを介して行われます。 これにより、独立して展開可能でスケーラブルなサービスが実現します。

現在、マイクロサービスアーキテクチャとサービスとしてのソフトウェアは相互に依存していません。 ただし、サービスとしてのソフトウェアを開発する場合、マイクロサービスアーキテクチャを活用することが非常に有益であることを理解するのは難しくありません。 モジュール性やスケーラビリティなど、前に説明した多くの目標を達成するのに役立ちます。

Spring Boot は、Springをベースにしたアプリケーションフレームワークであり、エンタープライズアプリケーションの開発に関連する多くの定型文を取り除きます。 これにより、非常に意見の高いでありながら、マイクロサービスを開発するための柔軟なプラットフォームが提供されます。 このチュートリアルでは、Spring Bootを活用して、12要素の方法論を使用してマイクロサービスを提供します。

4. 12ファクター手法の適用

ここで、今説明したツールとプラクティスを使用して開発しようとする単純なアプリケーションを定義しましょう。 私たちは皆、映画を見るのが大好きですが、すでに見た映画を追跡するのは困難です。

さて、誰が映画を始めて、後でそれを放棄したいですか? 私たちに必要なのは、私たちが見た映画を録画してクエリするためのシンプルなサービスです。

これは、データストアとRESTエンドポイントを備えた非常にシンプルで標準的なマイクロサービスです。 永続性にもマッピングされるモデルを定義する必要があります。

@Entity
public class Movie {
    @Id
    private Long id;
    private String title;
    private String year;
    private String rating;
    // getters and setters
}

IDと他のいくつかの属性を使用してJPAエンティティを定義しました。 ここで、RESTコントローラーがどのように見えるかを見てみましょう。

@RestController
public class MovieController {
 
    @Autowired
    private MovieRepository movieRepository;
    @GetMapping("/movies")
    public List<Movie> retrieveAllStudents() {
        return movieRepository.findAll();
    }

    @GetMapping("/movies/{id}")
    public Movie retrieveStudent(@PathVariable Long id) {
        return movieRepository.findById(id).get();
    }

    @PostMapping("/movies")
    public Long createStudent(@RequestBody Movie movie) {
        return movieRepository.save(movie).getId();
    }
}

これは私たちのシンプルなサービスの基本をカバーしています。 次のサブセクションで12要素の方法論を実装する方法について説明するときに、アプリケーションの残りの部分について説明します。

4.1. コードベース

12要素アプリの最初のベストプラクティスは、バージョン管理システムで追跡することです。 Git は、現在使用されている最も人気のあるバージョン管理システムであり、ほぼユビキタスです。 原則として、アプリは単一のコードリポジトリで追跡する必要があり、そのリポジトリを他のアプリと共有してはなりません

Spring Bootは、コマンドラインツールや Webインターフェイスなど、アプリケーションをブートストラップするための多くの便利な方法を提供します。 ブートストラップアプリケーションを生成したら、これをgitリポジトリに変換できます。

git init

このコマンドは、アプリケーションのルートから実行する必要があります。 この段階のアプリケーションには、生成されたファイルのバージョン制御を効果的に制限する.gitignoreファイルがすでに含まれています。 したがって、すぐに最初のコミットを作成できます。

git add .
git commit -m "Adding the bootstrap of the application."

最後に、必要に応じてリモートを追加し、コミットをリモートにプッシュできます(これは厳密な要件ではありません)。

git remote add origin https://github.com/<username>/12-factor-app.git
git push -u origin master

4.2. 依存関係

次に、 12ファクターアプリは、常にすべての依存関係を明示的に宣言する必要があります。 これは、依存関係宣言マニフェストを使用して行う必要があります。 Javaには、MavenやGradleなどの複数の依存関係管理ツールがあります。 これらの1つを使用して、この目標を達成できます。

したがって、私たちの単純なアプリケーションは、REST APIを容易にし、データベースに接続するためのライブラリなど、いくつかの外部ライブラリに依存しています。 Mavenを使用してそれらを宣言的に定義する方法を見てみましょう。

Mavenでは、プロジェクトの依存関係をXMLファイル(通常はプロジェクトオブジェクトモデル(POM))で記述する必要があります。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

これは単純明快に見えますが、これらの依存関係には通常、他の推移的な依存関係があります。 これはある程度複雑になりますが、目標を達成するのに役立ちます。 現在、アプリケーションには、明示的に記述されていない直接の依存関係はありません。

4.3. 構成

通常、アプリケーションには多くの構成があり、その一部はデプロイメント間で異なる場合がありますが、その他は同じままです。

この例では、永続データベースがあります。 接続するには、データベースのアドレスとクレデンシャルが必要です。 これは、展開間で変更される可能性が最も高くなります。

12ファクターのアプリは、デプロイメント間で異なるそのような構成をすべて外部化する必要があります。 ここでの推奨事項は、そのような構成に環境変数を使用することです。 これにより、構成とコードが明確に分離されます。

Springは、そのような構成を宣言して環境変数にアタッチできる構成ファイルを提供します。

spring.datasource.url=jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/movies
spring.datasource.username=${MYSQL_USER}
spring.datasource.password=${MYSQL_PASSWORD}

ここでは、データベースのURLとクレデンシャルを構成として定義し、環境変数から選択する実際の値をマップしました。

Windowsでは、アプリケーションを起動する前に環境変数を設定できます。

set MYSQL_HOST=localhost
set MYSQL_PORT=3306
set MYSQL_USER=movies
set MYSQL_PASSWORD=password

AnsibleChefなどの構成管理ツールを使用して、このプロセスを自動化できます。

4.4. バッキングサービス

バッキングサービスは、アプリケーションが操作のために依存するサービスです。 たとえば、データベースやメッセージブローカー。 12ファクターのアプリは、このようなすべてのバッキングサービスを接続されたリソースとして扱う必要があります。これが効果的に意味するのは、互換性のあるバッキングサービスを交換するためにコードを変更する必要がないということです。 唯一の変更は構成にあるはずです。

このアプリケーションでは、永続性を提供するためのバッキングサービスとしてMySQLを使用しました。

Spring JPA を使用すると、コードは実際のデータベースプロバイダーにまったく依存しなくなります。 すべての標準操作を提供するリポジトリを定義するだけで済みます。

@Repository
public interface MovieRepository extends JpaRepository<Movie, Long> {
}

ご覧のとおり、これはMySQLに直接依存していません。 Springはクラスパス上のMySQLドライバーを検出し、このインターフェースのMySQL固有の実装を動的に提供します。 さらに、構成から他の詳細を直接取得します。

したがって、MySQLからOracleに変更する必要がある場合は、依存関係のドライバーを置き換え、構成を置き換えるだけです。

4.5. ビルド、リリース、実行

12要素の方法論は、コードベースを実行中のアプリケーションに変換するプロセスを3つの異なる段階として厳密に分離します。

  • ビルドステージ:ここで、コードベースを取得し、静的および動的チェックを実行してから、JARのような実行可能バンドルを生成します。 Maven のようなツールを使用すると、これは非常に簡単です。
     mvn clean compile test package
  • リリース段階:これは、実行可能バンドルを取得し、これを適切な構成と組み合わせる段階です。 ここでは、PackerAnsibleなどのプロビジョナーを使用して、Dockerイメージを作成できます。
     packer build application.json
  • 実行ステージ:最後に、これは、ターゲット実行環境でアプリケーションを実行するステージです。 アプリケーションをリリースするためのコンテナーとしてDockerを使用する場合、アプリケーションの実行は非常に簡単です。
     docker run --name <container_id> -it <image_id>

最後に、必ずしもこれらの段階を手動で実行する必要はありません。 ここで、Jenkinsが宣言型パイプラインで非常に便利になります。

4.6. プロセス

12ファクターのアプリは、ステートレスプロセスとして実行環境で実行されることが期待されます。つまり、リクエスト間で永続的な状態をローカルに保存することはできません。 それらは、1つ以上のステートフルバッキングサービスに保存する必要がある永続データを生成する場合があります。

この例の場合、複数のエンドポイントが公開されています。 これらのエンドポイントでのリクエストは、それ以前に行われたリクエストとは完全に独立しています。 たとえば、メモリ内のユーザーリクエストを追跡し、その情報を使用して将来のリクエストを処理する場合、12要素のアプリに違反します。

したがって、12要素のアプリはスティッキーセッションのような制限を課しません。 これにより、このようなアプリは非常にポータブルでスケーラブルになります。 自動スケーリングを提供するクラウド実行環境では、これはアプリケーションにとって非常に望ましい動作です。

4.7. ポートバインディング

Javaの従来のWebアプリケーションは、WARまたはWebアーカイブとして開発されています。 これは通常、依存関係のあるサーブレットのコレクションであり、Tomcatのような準拠したコンテナランタイムを期待します。逆に、12ファクターのアプリは、そのようなランタイム依存関係を期待しません。完全に自己完結型であり、必要なのはJavaのような実行ランタイム。

私たちの場合、Spring Bootを使用してアプリケーションを開発しました。 Spring Bootは、他の多くの利点とは別に、デフォルトの組み込みアプリケーションサーバーを提供します。 したがって、以前にMavenを使用して生成したJARは、互換性のあるJavaランタイムを使用するだけで、どのような環境でも完全に実行できます。

java -jar application.jar

ここでは、単純なアプリケーションがHTTPバインディングを介してエンドポイントを8080などの特定のポートに公開しています。 上記のようにアプリケーションを起動すると、HTTPなどのエクスポートされたサービスにアクセスできるようになります。

アプリケーションは、複数のポートにバインドすることにより、FTPやWebSocketなどの複数のサービスをエクスポートできます。

4.8. 並行性

Javaは、アプリケーションで同時実行性を処理するための古典的なモデルとしてThreadを提供します。 スレッドは軽量プロセスのようなものであり、プログラム内の複数の実行パスを表します。 スレッドは強力ですが、アプリケーションの拡張にどの程度役立つかという点で制限があります。

12要素の方法論は、スケーリングをプロセスに依存することをアプリに提案します。これが効果的に意味するのは、アプリケーションが複数のプロセスにワークロードを分散するように設計する必要があるということです。 ただし、個々のプロセスは、Threadのような並行性モデルを内部で自由に活用できます。

Javaアプリケーションは、起動されると、基盤となるJVMにバインドされた単一のプロセスを取得します。 効果的に必要なのは、アプリケーションの複数のインスタンスを、それらの間でインテリジェントな負荷分散を使用して起動する方法です。 アプリケーションはすでにDockerコンテナーとしてパッケージ化されているため、Kubernetesはそのようなオーケストレーションに最適です。

4.9. 使い捨て

アプリケーションプロセスは、意図的に、または予期しないイベントによってシャットダウンされる可能性があります。 いずれの場合も、12要素のアプリがそれを適切に処理することになっています。 言い換えれば、アプリケーションプロセスは、不要な副作用なしに完全に使い捨てである必要があります。 さらに、プロセスは迅速に開始する必要があります

たとえば、このアプリケーションでは、エンドポイントの1つは、映画の新しいデータベースレコードを作成することです。 現在、このような要求を処理するアプリケーションが予期せずクラッシュする可能性があります。 ただし、これはアプリケーションの状態に影響を与えることはありません。 クライアントが同じリクエストを再度送信する場合、レコードが重複することはありません。

要約すると、アプリケーションはべき等サービスを公開する必要があります。 これは、クラウド展開を目的としたサービスのもう1つの非常に望ましい属性です。 これにより、他の考慮事項なしに、いつでも新しいサービスを停止、移動、またはスピンする柔軟性が得られます。

4.10. 開発/製品パリティ

アプリケーションはローカルマシンで開発され、他のいくつかの環境でテストされ、最終的に本番環境にデプロイされるのが一般的です。 これらの環境が異なる場合がよくあります。 たとえば、開発チームはWindowsマシンで作業しますが、実稼働展開はLinuxマシンで行われます。

12要素の方法論は、開発環境と本番環境の間のギャップを可能な限り最小限に抑えることを提案します。これらのギャップは、長い開発サイクル、関与するさまざまなチーム、または使用中のさまざまなテクノロジースタックに起因する可能性があります。

現在、Spring BootやDockerなどのテクノロジーは、このギャップを大幅に埋めます。 コンテナ化されたアプリケーションは、どこで実行しても同じように動作することが期待されます。 データベースのような同じバッキングサービスも使用する必要があります。

さらに、このギャップをさらに埋めるのを容易にするために、継続的インテグレーションやデリバリーなどの適切なプロセスが必要です。

4.11. ログ

ログは、アプリケーションがその存続期間中に生成する重要なデータです。 これらは、アプリケーションの動作に関する貴重な洞察を提供します。 通常、アプリケーションは、さまざまな詳細を含む複数のレベルでログを生成し、複数の異なる形式で出力することができます。

ただし、12要素のアプリは、ログの生成とその処理から分離されます。 このようなアプリの場合、ログは時間順に並べられたイベントのストリームにすぎません。これらのイベントを実行環境の標準出力に書き込むだけです。 このようなストリームのキャプチャ、保存、キュレーション、およびアーカイブは、実行環境で処理する必要があります。

この目的のために私たちが利用できるツールはかなりたくさんあります。 まず、 SLF4J を使用して、アプリケーション内でログを抽象的に処理できます。 さらに、 Fluentd などのツールを使用して、アプリケーションやバッキングサービスからログのストリームを収集できます。

これをElasticsearchにフィードして、保存とインデックス作成を行うことができます。 最後に、Kibanaで視覚化するための意味のあるダッシュボードを生成できます。

4.12. 管理プロセス

多くの場合、アプリケーションの状態を使用して、1回限りのタスクまたはルーチンの手順を実行する必要があります。 たとえば、不良レコードを修正します。 さて、これを達成するためのさまざまな方法があります。 多くの場合必要ない場合があるため、別の環境とは別に実行するための小さなスクリプトを作成できます。

現在、 12要素の方法論では、このような管理スクリプトをアプリケーションコードベースと一緒に保持することを強くお勧めします。 その際、メインアプリケーションのコードベースに適用するのと同じ原則に従う必要があります。 また、実行環境の組み込みREPLツールを使用して、本番サーバーでこのようなスクリプトを実行することをお勧めします。

この例では、これまでに視聴した映画をアプリケーションにシードするにはどうすればよいですか? 甘い小さなエンドポイントを使用することはできますが、それは実用的ではないように思われるかもしれません。 必要なのは、1回限りのロードを実行するためのスクリプトです。 小さなJava関数を記述して、ファイルから映画のリストを読み取り、それらをバッチでデータベースに保存できます。

さらに、Javaランタイムと統合されたGroovyを使用して、このようなプロセスを開始できます。

5. 実用的なアプリケーション

これで、12要素の方法論によって提案されたすべての要素を見てきました。 アプリケーションを12ファクターのアプリとして開発することには、特にクラウドにサービスとしてデプロイする場合に、確かにメリットがあります。 しかし、他のすべてのガイドライン、フレームワーク、パターンと同様に、これは特効薬ですか?

正直なところ、ソフトウェアの設計と開発における単一の方法論が特効薬であると主張することはありません。 12要素の方法論も例外ではありません。 これらの要素のいくつかは非常に直感的であり、おそらくすでに実行していますが、他の要素は私たちに当てはまらない可能性があります。 私たちの目標を背景にこれらの要素を評価し、賢明に選択することが不可欠です。

これらすべての要素がモジュール式で、独立した、ポータブルで、スケーラブルで、監視可能なアプリケーションの開発に役立つことに注意することが重要です。 アプリケーションによっては、他の方法でより適切に達成できる場合があります。 また、すべての要素を一緒に採用する必要はありません。これらの要素の一部を採用することで、以前よりも良くなる可能性があります。

最後に、これらの要素は非常にシンプルでエレガントです。 これらは、ダウンタイムや障害が実質的に発生することなく、アプリケーションに高いスループットと低いレイテンシーを要求する時代において、より重要になります。 これらの要素を採用することで、最初から適切なスタートを切ることができます。マイクロサービスアーキテクチャとアプリケーションのコンテナ化を組み合わせると、適切な場所に到達したように見えます。

6. 結論

このチュートリアルでは、12要素の方法論の概念について説明しました。 SpringBootでマイクロサービスアーキテクチャを活用して効果的に提供する方法について説明しました。 さらに、各要素とそれらをアプリケーションに適用する方法について詳しく説明しました。 また、これらの個々の要素を効果的に効果的に適用するためのいくつかのツールについても検討しました。