X

A blog about Oracle Technology Network Japan

Recent Posts

Java Magazine

Java 15仮想ハンズオン・ラボ体験レポート

※本記事は、Alexa Morales による "How Java 15 taught me to love virtual hands-on labs" を翻訳したものです。 本記事をPDFでダウンロード (英語) 最新のJavaリリースとともに公開された3つのハンズオン・ラボをライブで体験。皆さんもぜひお試しを 2020年 10月 7日   新しいテクノロジーを体験する最善の方法は、実際に試してみることです。そして、現在の新しい未知の世界において、実際に試してみるとは、仮想ハンズオン・ラボ(HOL)を試してみることを意味します。 たとえば、2020年9月15日に開催されたOracle Developer Live—Javaバーチャル・イベントでは、データや計算を多用するワークロード向けのJava 15の新しいアーキテクチャについて、多くのプレゼンテーションが行われました。プレゼンテーションの後は、セルフガイド式のラボで誰もがその機能を試すことができました。しかも多くの場合、新しいJDKが正式に公開されたまさにその日に可能でした。今回、筆者が体験したのもまさにこれです。   新しいKubernetes 筆者は、Oracle Cloud Infrastructure Foundations 2020 Associateの認定資格を取得したばかりでした。この試験の対策のため、Oracle Free Tier Accountはすでに持っており、Oracle Cloud Infrastructure(OCI)コンソールにもかなり親しんでいました。 この日に試したセルフガイド式のラボは、オラクルのMonica RiccelliとMaciej Gruszkaによる「MicronautとOracle Cloud InfrastructureによるJavaクラウド・ネイティブ・アプリケーションの構築」(英語)でした。目的は、Kubernetes(ここではOracle Container Engine for Kubernetes)を使ってOracle WebLogicコンテナをデプロイするという貴重なスキルを学ぶことでした。 このプロセスがどれほど簡単かはすぐにわかりました。その手順は、左側のメニュー・バーを下にスクロールしてDeveloper Servicesを開き、「Kubernetes Clusters」を選んでから「Quick Create」をクリックして、新しいクラスタおよびそのクラスタに関連するネットワーク・リソース(仮想クラウド・ネットワーク、インターネット・ゲートウェイおよびNATゲートウェイ、ワーカー・ノード、ノード・プール、ロードバランサ)を作成するというものです。7分ほどで、アクティブなクラスタを示す緑色の大きな六角形がクラウド・コンソールに表示されました(図1参照)。 図1:アクティブな Kubernetes クラスタを示す緑色の大きな六角形 この後もチュートリアルは続き、GitHubからOracle WebLogic Server Kubernetes Operatorをクローンし、Helmを使ってインストールする方法が説明されました。KubeConに参加した経験を踏まえて言うと、雇用者はこのようなクラスタリング・ツールをわずかにでも知っている開発者を非常に求めています。 続くOracle WebLogicドメインのデプロイ、クラスタのスケーリング、新しいイメージの時間差再起動、2つのデータセンターにまたがるクラスタのシミュレーションは行いませんでしたが、こういった点はどれも、Oracle WebLogic Serverでアプリケーションを実行している多くの開発者が興味をそそられる考え方です。 Oracle WebLogicのプロダクト管理担当ディレクターであるGruszkaは、「こういった開発者の大半は、今まさに、最新化のための手法を探そうと考えています。コンテナは、そのような方々の多くにとって一番人気の手法でしょう。コンテナに移行すれば、中立的なアプローチでクラウドに移行できる道が開けます。これは、特定のベンダーにロックインされずに、皆さんを解放するテクノロジーなのです」と述べています。 その後、筆者は次のハンズオン・ラボに移りました。   クラウドで Java 15 をテストする 昨年のベルリンでもそうでしたが、オラクルのカンファレンスでは、Java開発者リレーション部門で豊富な経験を持つDavid Delabasséeの講演をよく聴いてきました。Java 15の新機能を紹介するDelabasséeのラボは、すべてOracle Cloud Infrastructureで実行されており、非常ににぎやかなものでした。しかし、後で本人から聞いたところ、「OCIによるJavaアプリケーションの構築」(英語)ラボの仮想エクスペリエンスを作るのは難しかったとのことでした。 ここでも、仮想クラウド・ネットワークを含むOCI環境を作成し、Javaアプリにインターネット・リクエストが届くように設定しました。セキュリティ・ルールを適切に構成してから、新しいJava機能をテストできるコンピュート・インスタンスをプロビジョニングしました。ここで、ラップトップのシェル・プログラムからアクセスできるように、インスタンスのパブリックIPアドレスを書き留めておきました。 Linuxコマンドをまったく知らない筆者でも、Macでターミナル・アプリを開き、チュートリアルの各ステップにあるコマンドをコピーして貼り付けることができました。こういった手順はどれも初歩的なものと思われるかもしれません。しかし翌日、Delabasséeは、受講者が正確に手順に従うことを前提にすることはできないと話してくれました。 「この15年、対面式のハンズオン・ラボを数多く行ってきたので、受講者がラボガイドを解釈する際に創造性を発揮できることを知っています」と話すDelabasséeは、当たり前のことを当たり前と考えないようにしています。 OCIでLinuxを実行する仮想マシンができたので、次は最新バージョンのOpenJDKと、Maven、Git、Helidonをインストールしました。インストールが大量だと感じるかもしれませんが、Delabasséeが書いてくれたスクリプトを実行したところ、大変な勢いで90秒ほどスクロールが続いてから、すべてのインストールが完了しました。コンソールを確認したところ、確かにOCIインスタンスにJava 15がインストールされたことがわかりました(図2参照)。 図2: DelabasséeのスクリプトがOracle Cloud InfrastructureへのJava 15のインストールをすべて行ってくれた その後、Delabasséeが作成した11の項目のうち、Helidonマイクロサービス・フレームワークでREST APIを公開するものと、レコード(Java 15で2回目のプレビュー・ラウンドを迎えているJava言語機能)を有効化するものの2つをさらに終えることができました(図3参照)。 図3:コンパイラ・フラグでプレビュー機能を有効化すると、Javaレコードの実験が可能になる   Micronautの現状 超人になった気分で、この日最後のラボに進みました。「MicronautとOracle Cloud InfrastructureによるJavaクラウド・ネイティブ・アプリケーションの構築」(英語)を進行したのは、Micronautフレームワークの作成者で、現在はOracle Labsでアーキテクトを務めるGraeme Rocherでした。 ここまでで一番難しいラボでしたが、最初の数ステップを行ってみただけでも、同様に多くのことを学べました。ラボを始めるには、クラウド・スタックを作成することが必要でした。Terraform構成ファイル(Oracle Cloud仮想マシンとOracle Autonomous Databaseインスタンスをセットアップするためのレシピ)をダウンロードしました。そして、「Resource Manager」で「Stacks」を選び、そこにそのTerraformファイルをドラッグすることができました。しかし、心配なことがありました。Javaはすべてうまく動作していたので、その状態に戻れるようにしておきたかったのです。このTerraformレシピによって、そのすべてが台なしになってしまう可能性はあるでしょうか。 ちょうどそのとき、ラボの学習をサポートしていた数人のエンジニアの1人、Todd Sharpからブレークアウト・ルームに招待されました。まるで直接顔を合わせているかのように、バーチャル会議室に迎えてもらいました。そこには、チュートリアルに取り組んでいる人が数人いましたが、少しばかり雑談した後、Terraformスクリプトを実行するとJava 15のラボがおかしくなってしまうことはあるかどうかを尋ねました。彼は「問題ない」と教えてくれたので、作業に戻りました。スクリプトを書いたのは彼自身だったこともわかりました。 その後の手順は、GitHubからMicronautアプリケーションをクローンし、そのアプリケーションをOracle Autonomous Databaseに接続するというものでした。しかし、これはすべて高度なプログラミングで、筆者の「頭の限界を超えている」ことがわかりました。 SharpとRocher、そしてOracle Labsのテクニカル・ディレクターであるEric Sedlarとともにこのラボのエクスペリエンス全体を作成したChris Bensenは、「ユーザー・エクスペリエンスとコンテンツの視点から見て、ハンズオン・ラボの設計は非常に難しいものです。100 %バーチャルなら、なおさらです」と述べています。 Bensenは、「ラボには、Micronaut、Helidon、GraalVMなどの優れたテクノロジーが欠かせません。また、開発者が容易についていける内容でなければなりません。そのため、経験を積んだエンジニアのグループがテストしたり、イベントをサポートしたりする必要があります」と述べています。そして、「数百人の受講者がラボで学ぶのであれば、数千時間をかけて作るだけの価値があるのです」と付け加えています。   まとめ 皆さんは、自分のコードが遠くのデータセンターで動いているのを見てインターネットを操っているように感じ、興奮したいと思うかもしれません。筆者もそうです。そういう方は、ハンズオン・ラボをぜひ試してみてください。ここで紹介したハンズオン・ラボはすべて、こちらから、完全無料のOracle Cloud Free Tierで利用できます。   Dig deeper Java 15の内側:5つのカテゴリに分かれた14個のJEP Java 15の必須リソースを集めた便利なリスト(英語) Oracle Developer Live—Java(英語) セルフガイド式のラボ:クラウドでJavaアプリケーションを開発するハンズオン体験(英語) セルフガイド式のラボ:Oracle Cloudで開発するハンズオン体験(英語) Oracle VMハンズオン・ラボ用の仮想アプライアンスのダウンロード(英語) Oracle VMのハンズオン・ラボ(英語) Oracle Cloud Infrastructureハンズオン・ラボのウェルカム・ページ(英語) Oracle WebLogic Server Oracle Cloud Free Tier   Alexa Morales オラクルの開発者コンテンツ担当ディレクター。Software Development誌の元編集長。15年超にわたり、テクノロジー・コンテンツ・ストラテジストおよびジャーナリストとして活動している。

※本記事は、Alexa Morales による "How Java 15 taught me to love virtual hands-on labs" を翻訳したものです。 本記事をPDFでダウンロード (英語) 最新のJavaリリースとともに公開された3つのハンズオン・ラボをライブで体験。皆さんもぜひお試しを 2020年 10月 7日   新しいテクノロジーを体験する最善の方法は、実際に試してみることで...

Cloud Security

Oracle Identity Cloud Service内のユーザーの管理 – パート2

※本記事は、Paul Toal による "Managing Users in Identity Cloud Service - Part 2" を翻訳したものです。 2021年 8月 2日   前回の記事では、Oracle Identity Cloud Service(IDCS)内でユーザー管理を自動化するためのさまざまなオプションについて説明しましたが、よくあるユースケースの1つについては取り上げませんでした。それはOracle Fusion CloudアプリケーションからのIDの同期方法についてです。このユースケース自体に多数のオプションがあり、それぞれ特有の考慮事項があるため、改めて説明の機会を設けるのが良いと考えたからです。そこで、このパート2ではOracle Fusion Cloudに焦点を当てようと思います。 人材管理(HCM)システムは、通常、組織内でユーザーのレコードが始まる場所であるため、データの発生源として一般的です。そのため、このシステムからID管理(IDM)プラットフォームにデータを入力すると合理的です。 事前にFusion内に存在する各レコードについて(IDMの視点から)理解し、要件に基づいて必要なレコードは何かを明らかにすることが重要になります。本記事では説明用に、workerとuserという2種類のレコードについて取り上げます。 Fusionにログインするには、userレコードが必要です。ユーザー名とパスワードが含まれるこのレコードは、FusionのIAMプラットフォームに保存されます。userレコードがなければFusionにアクセスできません。これに対して、workerレコードについては、従業員(あるいは請負業者など)を表すレコードとして考えます。人を雇用すると、workerレコードが作成されます。このレコードに、住所、銀行口座詳細、等級、部門、勤務地など、その人についてHCMで得られる全情報が格納されます。userレコードとworkerレコードの関係については、同僚のOlaf Heimburgerがこちらの記事でわかりやすく説明しています。Olafは本記事で、私がFusionに関する正しい知識に基づいて説明できるようサポートしてくれました。 workerレコードとuserレコードには異なる情報が格納されるため、これらのレコードの違いは重要です。workerは完全に人事管理(HR)のためのレコードである一方、userレコードはFusionへの認証済みアクセスができるようにするためのスケルトン・レコードに過ぎません。同期オプションについて検討する際には、この点を覚えておく必要があります。オプションは基本的に3つあり、以下の図にまとめています。 それぞれのオプションについて確認しましょう。   1 - プッシュ 1つ目のオプションは、Fusionで標準提供されているEnterprise Scheduler Service(ESS)の同期ジョブを使用するものです。このジョブでIDCSに必要な詳細情報を提供するためには、いくつか基本的な構成を行う必要があります。そうすれば後はその名のとおり、スケジュールに基づいて、ユーザーをロールやロール・メンバーシップとともにFusionからIDCSへ同期してくれます。 この方法で特に注意すべき事柄の1つが、このジョブはFusionのuser APIから同期を行うため、userスケルトン・レコードにしかアクセスできないということです。つまり、同期されるのは以下のユーザー属性だけということになります。 ユーザー名 名 姓 電子メール・アドレス アクティブ・ステータス しかも、このESSジョブ内ではユーザー・フィルタを利用できません。アクティブで、電子メール・アドレスがあり、Person構造を持つすべてのユーザーがFusionからIDCSへ同期されます。ただし、選択したロールとロール・メンバーシップのみをIDCSに同期するためのロール用フィルタは用意されています。 ESS同期ジョブを使用するメリットの1つは、ロールに接頭辞を付ける機能があることです。例として、1つのIDCSインスタンスで、2つのFusionインスタンスから、ロールとロール・メンバーシップの情報を同期しているとします。Fusionインスタンス名はPRODとTESTです。この両方にロールPayablesManagerが含まれている場合、どちらかでこのロールのメンバーシップが更新されるたびに、IDCSのPayablesManagerロールが上書きされることになります。この状況を回避するために、ロールの接頭辞付加機能を使用して、IDCSに同期される各ロールの先頭に値を追加することができます。つまり、PROD Fusion環境ではロールにPROD_という接頭辞を付け、TEST Fusion環境ではTEST_という接頭辞を付けるように構成できます。これで、1つのIDCSインスタンス内にPROD_PayablesManagerのロールとTEST_PayablesManagerのロールが存在することになり、各ロールのメンバーシップはそれぞれの環境から得た正しいものになります。   2- プル 2つ目のオプションは、IDCSアプリケーション・カタログの事前定義済みFusionアプリケーション・テンプレートを使用するというものです。この方法では、ユーザー、ロール、ロール・メンバーシップをIDCSにプルするために権限付き同期を使用して、IDCSでIDレコードの作成または更新のいずれかを行います。 アプリケーション・テンプレートもFusionのuser APIを使用するため、同期されるのはuserスケルトン・レコードのみです。 このプルの手法のおもなメリットは、フィルタがサポートされることです。設定時に、同期の対象とするFusionロール・サブセットを指定できます。指定したロールのメンバーであるユーザーのみがIDCSにプルされます。   3 - API これまでの2つのオプションではいずれも、FusionのID情報がuser APIから取得されていました。すでに説明したとおり、このAPIはuserスケルトン・レコードを提示するだけで、Fusion内にあるworkerレコード全体にアクセスすることはできません。多くのユースケースでは、IDCS内でuserリストとそのFusionロールにアクセスできれば十分であり、他のIDCS連携アプリケーションにそれらを割り当てることができます。しかし、workerレコードの他の情報がIDCS内で必要になるようなユースケースもあります。 私が最近関わったユースケースでは、workerの個人用電子メール・アドレスを取得して、それをIDCS内の電子メール・アドレスとして使用する必要がありました。Fusion内では個人用電子メール・アドレスはuser API経由では閲覧できないため、オプション1、2のいずれも実現不可能でした。 幸い、Fusionはworker APIを公開しており、このAPIを使用すれば、必要なすべての属性をworkerレコードから抽出できます。そのため、任意の連携ツールを使用してFusionのworker APIからデータをプルして、REST API経由でIDCSに入力することができます。 たとえば、Oracle Integration Cloudを使用すれば簡単にこれを実行できるでしょう。以下にサンプルの手順を示します。 1. Fusionのworkerレコードの変更内容(新規雇用イベント、従業員情報の更新、退職など)が、worker APIを使用して取得されます。このジョブはスケジュールが設定されており、前回の実行日時以降のすべての変更内容が取得されます。 2. 次に、それらの変更内容が変換され、REST APIを使用してIDCSに伝播されます。IDCS内でユーザーが存在するかどうかの検索が実行され、存在しない場合はユーザーが作成されます。 以下に各オプションの概要を示します。 FusionからIDCSへのユーザーやロールの同期には正しい方法や間違った方法というものはありません。それぞれの手法にそれ特有の考慮事項があるため、ユースケースと要件を検討することで、使用すべき手法について判断を促すことができます。詳しくは、Oracle Identity Cloud Serviceのドキュメントを参照してください。  IDCSについてはこちらのドキュメントで詳細をご覧いただけます。   Paul Toal OCI Security Specialist Senior Director Paulはセキュリティに20年以上携わり、現在はEMEA OCI Center of Excellenceセキュリティ部門のリーダーを務めています。Oracle Cloudの普及に取り組みながら、エンジニアリング・チームと緊密に連携して、お客様からのフィードバックを伝達し、新しいサービスの創出にも貢献しています。取締役からアーキテクトや開発者までの組織内のあらゆる階層で、リスク削減とデジタル・トランスフォーメーションの両方を目指したセキュリティの利用方法について説明し、実践しています。最近では、ソリューションのリード・セキュリティ・アーキテクトとして複数の大規模デジタル・トランスフォーメーション・プログラムに関わりました。 英国政府のアイデンティティ保証仕様(Gov.UK Verify)の原本著者の1人であり、この分野のイノベーションとソート・リーダーシップを常に先導しています。

※本記事は、Paul Toal による "Managing Users in Identity Cloud Service - Part 2" を翻訳したものです。 2021年 8月 2日   前回の記事では、Oracle Identity...

Cloud Security

Oracle Identity Cloud Service内のユーザーの管理 – パート1

※本記事は、Paul Toal による "Managing Users in Identity Cloud Service - Part 1" を翻訳したものです。 2021年 7月 29日   Oracle Identity Cloud Service(IDCS)について質問を受けることの多いトピックの1つがユーザー管理であり、具体的に言えば「IDCSでユーザーを管理するにはどのようなオプションがあるか」というものです。「IDCSが他のターゲット・システム内でどのようにIDを管理するか」という意味ではありません。「IDCSのサービスそれ自体の中で、どのようにIDを管理できるか」ということです。そこで私は、すべてのオプションを1か所にまとめて提示すべき頃合いだと思い、こちらで紹介することにしました。本記事では、IDCS内へのIDのオンボーディングに関するさまざまな方法について詳しく見ていきます。 ここでは、手動プロセスではなく自動化された手法に焦点を当てます。もちろん、IDCSには、ユーザーの追加、編集、有効化/無効化などのユーザー管理タスクを実行できるWebベースの管理コンソールが存在します。 このコンソールは変更を少し行うのに適していますが、数千ユーザーを追加するために利用しようとはまず思わないでしょう。同様に、IDCSは、自動登録などのセルフサービス機能も提供しており、ユーザーが自分のプロファイルを作成できるようになっています。 この機能も個人ユーザー向けのユースケースでは非常に便利なのですが、一般には従業員に自動登録させたくないものです。すでに1か所以上で従業員IDを管理している場合は特にそうでしょう。 では、どのようなオプションがあるでしょうか。ユーザー管理を自動化するための方法は6つあります。それらについて以下の図にまとめます。 では、それぞれのオプションについて見ていきましょう。   Active Directory (AD) ブリッジ 1つ目のオプションは、IDCS ADブリッジを使用して、ユーザーとグループをADから同期するというものです。ADブリッジは既存のADメンバー・サーバーまたはドメイン・コントローラにインストールして使用するサービスです。このブリッジがLDAPSでADサーバーと通信し、それをIDCSへのアウトバウンドのHTTPS RESTコールに変換します。ここではデータの流れが重要になります。ブリッジからの通信はアウトバウンドのみです。ブリッジへのインバウンド・ポートをオープンする必要はありません。また、設定を使用することで、同期対象のユーザー・セットやグループ・セット、および同期スケジュールを完全に制御できます。 なお、ADブリッジではADのパスワードがIDCSに同期されないことに注意してください。ADのユーザー名とパスワードでIDCSのユーザー認証を行う必要がある場合(かつ、フェデレーションが利用不可の場合)は、委任認証を使用できます。   LDAP IDCSのアプリケーション・カタログには、よく利用されるソース・システムやターゲット・システムと迅速に連携できるように、多数のアプリケーション・テンプレートが付属しています。その中に、Oracle Unified DirectoryおよびOracle Internet Directory用のテンプレートがあります。これらは、LDAP v3準拠ディレクトリからのユーザーの権限付き同期をサポートしています。この同期の実行にはIDCSのプロビジョニング・ブリッジが使用されます。このブリッジはADブリッジと同様に、LDAP(またはLDAPS)経由でディレクトリと通信して、変更内容をIDCSへのアウトバウンドのHTTPS RESTコールに変換します。 ただし、ADブリッジとは異なり、LDAPアプリケーション・テンプレートにはフィルタ・オプションがありません。LDAPサービス・アカウントが閲覧権限を持っているユーザーとグループが、すべてIDCSに同期されます。   アプリケーション・カタログ Oracle Unified DirectoryとOracle Internet Directoryのテンプレートに加えて、アプリケーション・カタログにも、人材管理(HCM)などのよく利用されるアプリケーションに対応した事前構成済みアプリケーション・テンプレートが多数含まれています。テンプレートを使用することでシングル・サインオン(SSO)が容易になるだけでなく、多くのテンプレートでプロビジョニングと同期もサポートされています。一部のテンプレートでは権限付き同期もサポートされており、その場合はソース・システムがIDCSに入力されるIDデータのマスターとなります。 すべてのアプリケーション・テンプレートでプロビジョニング・ブリッジを使用する必要はありません。使用する必要があるかどうかは、ターゲットのアプリケーションまたはサービス、そのインストール先、および使用可能なユーザー管理インタフェースによります。アプリケーション・カタログの完全版には、カスタム連携が必要な場合に使用できる汎用テンプレート(スクリプトやSCIMテンプレートなど)もあります。   API/SDK IDCSには、System for Cross-domain Identity Management(SCIM)プロトコルをベースとした非常に豊富なREST APIが存在します。また、.NET、Android、iOS、Java、Node.js、Python版のSDKも提供されています。そのため、REST APIのコールまたはSDKの使用が可能なすべてのクライアントで、IDCSのユーザーをプログラム的に管理できます。APIは個別操作と一括操作の両方に対応しています。 REST APIを使い始める方のために、Postmanコレクションがチュートリアルとともに提供されています。   CSV ユーザー管理の自動化の手段を持たない組織もあるでしょう。あるシステムのID情報を抽出して別のシステムに渡すための手段として、カンマ区切り値(CSV)もいまだによく使用されます。IDCSでは、管理コンソール経由か、プログラムからAPI経由でCSVファイルを取り込むことができます。その情報には、ユーザー、グループ、グループ・メンバーシップ情報が含まれます。   JIT フェデレーテッド認証を使用する場合は、Just-In-Time(JIT)プロビジョニングを使用できます。通常、アイデンティティ・プロバイダ(IdP)によって認証トークンが発行される場合、このトークンにはユーザー識別子のみが含まれています。一方、JITを使用する場合は、このトークンは認証済みユーザーに関する必要なすべての属性(グループ・メンバーシップを含む)を格納できるように構成されます。その後、IDCSによって、それらの属性に基づいてそのユーザー用のIDレコードが作成されます。 一致するユーザーがすでに存在する場合は、変更されている属性があれば、そのユーザー・レコードを更新できます。 これまでに説明したオプションには、それぞれ違った考慮事項があります。それらについて以下の表にまとめています。 さて、このトピックについては、一般的なユースケースがもう1つあります。Oracle Fusion SaaSからユーザーを同期できるというものですが、これについては次の記事で取り上げたいと思います。ぜひお見逃しなく。 これまで見てきたように、IDCSのユーザー管理の自動化には多くのオプションがあり、それを使用すれば管理の効率化と運用コストの削減が可能です。IDCSについて詳しく知りたい場合は、こちらからドキュメントをお探しください。このサイトでは上記の一部のオプションについて構成を進めるための、幅広いチュートリアルもご利用いただけます。 Paul Toal OCI Security Specialist Senior Director Paulはセキュリティに20年以上携わり、現在はEMEA OCI Center of Excellenceセキュリティ部門のリーダーを務めています。Oracle Cloudの普及に取り組みながら、エンジニアリング・チームと緊密に連携して、お客様からのフィードバックを伝達し、新しいサービスの創出にも貢献しています。取締役からアーキテクトや開発者までの組織内のあらゆる階層で、リスク削減とデジタル・トランスフォーメーションの両方を目指したセキュリティの利用方法について説明し、実践しています。最近では、ソリューションのリード・セキュリティ・アーキテクトとして複数の大規模デジタル・トランスフォーメーション・プログラムに関わりました。 英国政府のアイデンティティ保証仕様(Gov.UK Verify)の原本著者の1人であり、この分野のイノベーションとソート・リーダーシップを常に先導しています。

※本記事は、Paul Toal による "Managing Users in Identity Cloud Service - Part 1" を翻訳したものです。 2021年 7月 29日   Oracle Identity...

Java

Coherence Community Editionが登場(パート2):クライアントの構築

※本記事は、Aleks Seović による "Hello, Coherence Community Edition, Part 2: Building the client" を翻訳したものです。 本記事をPDFでダウンロード (PDF) ReactとJavaFXを使い、Coherence CEバックエンド・アプリケーションと連動可能なフロントエンド・クライアントを開発する方法 2020年 10月 9日   本シリーズの第1回の記事では、オラクルのオープンソース製品であるCoherence Community Edition(CE)に格納したTo Doリストのタスクを管理するREST APIの実装方法について説明しました。REST APIのテストにはcurlベースのインタフェースを使いましたが、curlは、この世でもっともユーザー・フレンドリなインタフェースというわけではありません。 本記事では、前回の記事のバックエンド・コードを利用する2つのクライアントを実装します。1つはReactベースのWeb UI(図1参照)、もう1つはJavaFXを使ったデスクトップUIです。 図1: To Do リスト・アプリケーションのユーザー・フレンドリなインタフェース   クライアント・アプリケーションと Coherence CE まずは、基本的な内容について確認しておきましょう。クライアント・アプリケーションでは、どのようにしてCoherence CEに接続し、データにアクセスすればよいのでしょうか。 Coherence CEでは、クラスタ・メンバー・クライアントとリモート・クライアントという2種類のクライアントをサポートしています。 前回の記事で実装したREST APIは、クラスタ・メンバー・クライアントの例です。クラスタ・メンバー・クライアントでは、ストレージを有効化することも、ストレージを無効化することもできます。この2種類のメンバーは本質的に同じものですが、ストレージを無効化したメンバーではローカルにデータが保存されないという点が異なります。 前回の記事で、このプロジェクトではデータ・ストアとアプリ・サーバーを分けることもできると述べましたが、それはこのことを指しています。ストレージを有効化したメンバーですべてのデータを管理し、それとは別に、ストレージを無効化したメンバーとしてHelidon Webサーバーを実行してREST APIを提供することもできました。このような構成が妥当な場合もあります。データ・ストアとアプリ・サーバーの独立性が高まり、個別にスケーリングできるようになるからです。簡略化するため、少なくとも今のところはそのような構成にはしないことにしました。 クラスタ・メンバー・クライアントを使う利点は、クライアントがクラスタ・トポロジとパーティションの割当てを完全に認識している点にあります。そのため、クライアントは、ネットワーク・ホップ1回だけで、Coherence CEに格納されているすべてのデータ・オブジェクトに直接アクセスすることができます。欠点は、クライアントが他のクラスタと同一の、高速で低遅延のネットワーク上に存在しなければならない点です。さらに、クライアントが暴走を始めて無応答になった場合、クラスタ全体を不安定にしてしまう可能性もあります。 一方、リモート・クライアントは少し異なる仕組みで動作します。他のクラスタ・メンバーに直接接続するのではなく、プロキシ・サーバーに接続します。通常、このプロキシ・サーバーはストレージを無効化したクラスタ・メンバーです。 Coherenceでは以前より、Coherence*ExtendとCoherence RESTプロキシという2種類のプロキシをサポートしています。そこに、3つ目の種類として、Coherence gRPCが加わりました。 Coherence*Extend : 独自仕様による、TCPベースのRPCプロトコルで、オラクルの既存のJava、.NET、C++のクライアント実装で使用します。Coherence*Extendはかなり前(2006年)から存在しており、下位互換性および上位互換性を保つ形で多くのバージョンのOracle Coherenceに対応しています。また、多くのミッション・クリティカルなアプリケーションで使用されている実績があります。その一方で、Coherence*Extendでは同期的な独自処理が使用されているため、最新のクラウド技術と相性がよいとは限りません。さらに、Coherence*Extendを使って実装されたクライアントには、やや古いため、言語でサポートされている最新機能をほとんど使用していないものもあります(特に.NETクライアント)。 Coherence REST プロキシ:このプロキシでは汎用REST APIを実装しており、Coherence CEで管理されているプラットフォームや言語データの大半からアクセスすることができます。ただし、RESTや、ベースとなるHTTPプロトコル自体の性質上、多少の制限があり、機能はネイティブ・クライアントほど充実していません。また、構成を行うのが少しばかり面倒かもしれません。 率直に言うなら、このRESTプロキシに意義があった時期もありましたが、今ではそれほどニーズや用途はないものと思われます。前回の記事で紹介しましたが、完全なJava APIを自由に使用し、Helidon統合(推奨)や組込みHTTPサーバーを通して公開する、アプリケーション固有のREST APIを実装することも、Coherence RESTプロキシと同じくらい容易です。 Coherence gRPC : 最新のCoherence CEリリース(20.06)で導入された3つ目のプロキシです。 Coherence gRPCは、転送にgRPCを使用するプロキシ実装で、Coherence*Extendの現実的な代替機能です。Helidon gRPC Server上に構築されているこのプロキシから、直接得られるメリットがいくつかあります。最新のクラウド技術との相性がはるかによく、さまざまなHTTPロードバランサや、KubernetesのIngressコントローラに対応しており、非同期的です。また、gRPC自体も、関連するほぼすべてのプラットフォームや言語でサポートされています。 現時点で、オラクルが提供しているのはCoherence gRPCのネイティブJavaクライアントのみで、後ほどJavaFXクライアントを実装するときはこのクライアントを使います。しかし、順調にいけば近いうちにネイティブのNode.js/JavaScriptクライアントが公開されるでしょう。また、最新の.NETおよびC++のクライアントに加え、新たにPython、Golang、Swiftのクライアントも登場する予定です。 公式にサポートされていないプラットフォーム用のクライアントが必要な場合は、自分で記述できるようになるでしょう。オラクルがサポートするサービスおよびメッセージのプロトコル・バッファ定義は、すでに一般公開されています。独自のクライアントを実装する場合に、既存のクライアント実装を参考にすることができるようになるでしょう。 リモート・クライアントの利点は、同じネットワーク上に存在しなくてもよいことです。また、必要な数だけ導入でき、クラスタのメンバー構成に影響を与えることなく、自由に追加したり削除したりすることができます。当然ながら、Java以外の言語で書くこともできます。欠点は、クライアントからのすべてのリクエストがプロキシを経由する必要があるため、すべての操作に対してネットワークのホップが1回追加され、待機時間も必要になることです。 前置きはこのくらいにして、クライアントの実装に取りかかりましょう。   React クライアントの実現 前回の記事でも触れましたが、筆者はフロントエンドの開発者ではありません。Reactを選んだことにも深い意味はなく、(多少)経験があったというだけの理由です。AngularやVue.jsなど、人気のある他のフロントエンド・フレームワークで同じようなフロントエンドを実装することも難しくないでしょう。 実は、筆者の同僚であるTim Middletonがすでに別のWebフロントエンドを実装しています。こちらはOracle JavaScript Extension Toolkit(Oracle JET)を使ったものですが、UIをデータ・モデルにバインディングし、イベントを通して更新するという考え方は同じです。Oracle JETクライアントのソース・コードはGitHubで公開されていますので、興味がある方はご覧ください。 ここで取り上げたすべてのフレームワークには、標準のNode.js開発ツールチェーンを使ってUIのビルドとテストを行うことができるという共通点があります。そしてその結果に納得したら、アプリケーションを「コンパイル」して一連の静的なHTML、JavaScript、CSSファイルを生成し、静的コンテンツの提供が可能な任意のWebサーバーで公開することができます。 そのため、サンプル・アプリケーションでは、REST APIを提供しているHelidon Web Serverを流用し、そこで静的なフロントエンドも併せて公開することができます。このアプローチでは、アプリケーションがかなり簡略化されます。別のサーバーのデプロイや管理を行う必要がありません。また、フロントエンドとREST APIが同じオリジン上に存在するので、クロスオリジン・リソース共有(CORS)の問題に対処する必要もありません。   React クライアントのセットアップ Reactクライアントを構築するために、いくつかのことを決める必要があります。 フロントエンドのソースをどこに配置するか 既存の Maven ビルドの一部としてどのようにフロントエンドを構築するか Helidon で提供するためには、「コンパイルした」フロントエンドをどのようにパッケージングすればよいか これらを逆の順番で決めていくことにします。 Helidonでは、静的コンテンツをファイル・システムから提供することも、クラスパスから提供することもできます。後者の方がはるかに管理しやすいので、ここではフロントエンドのすべての静的ファイルをサーバーJARファイルにパッケージングし、そこからコンテンツを提供するようにHelidonを構成します。具体的には、以下の内容のMETA-INF/microprofile-config.propertiesファイルをプロジェクトに追加します。 server.static.classpath.location=/web server.static.classpath.welcome=index.html 必要な作業はこれだけです。これで、クラスパスのwebディレクトリから静的コンテンツを提供し、index.htmlをデフォルトのウェルカム・ファイルとして使うようにHelidonが構成されました。 Mavenビルドの一部としてフロントエンドを構築し、Helidonが想定する場所にフロントエンドの静的コンテンツが格納されるようにするには、どうすればよいでしょうか。これを行うために、筆者が作成したnpm-maven-pluginを次のようにして使用します。このプラグインにより、npmベースのビルドをMavenビルドに埋め込みます。 <plugin> <groupId>com.seovic.maven.plugins</groupId> <artifactId>npm-maven-plugin</artifactId> <version>1.0.4</version> <executions> <execution> <id>build-frontend</id> <goals> <goal>run</goal> </goals> <phase>generate-resources</phase> <configuration> <workingDir>${project.basedir}/src/main/web</workingDir> <script>build</script> </configuration> </execution> </executions> </plugin> 上記のコードによって、Mavenビルドのgenerate-resourcesフェーズに、package.jsonで定義されたビルド・スクリプトが実行されます。また、このコードでは、フロントエンドのソース・コードをサーバー・プロジェクトのsrc/main/webディレクトリ以下に格納することが明示されています。図2をご覧ください。 図2:Maven プロジェクトの一部になったフロントエンド・アプリケーション フロントエンド・アプリケーション(とその開発者)は、アプリケーションを取り囲むMaven構造をまったく意識していないことに注意してください。フロントエンド・アプリケーションの開発者にとっては、上記の図で選択されているwebディレクトリがJavaScriptプロジェクトのルートです。 この構成なら、フロントエンド開発者がMavenに習熟していなくても作業を進めやすくなります。ただしこれは、フロントエンドをビルドした後、生成された静的ファイルをMavenが認識している構造にコピーしなければならないということを意味します。この点は、標準のmaven-resources-pluginアーティファクトIDを使えば簡単に解決します。 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>copy-frontend</id> <phase>generate-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory> ${project.build.directory}/classes/web </outputDirectory> <resources> <resource> <directory>${project.basedir}/src/main/web/build</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> 以上で完成です。これでプロジェクト構造が完成したので、いよいよ実装に入ることにしましょう。     React クライアントの実装 本記事でフロントエンド実装のすべてを説明することはできません。UIに関連するコードの大半はCoherence CEとは無関係で、すぐに本の1章分を費やすことにもなってしまうからです。コードは、アプリケーションの他の部分と合わせてGitHubで公開しているので、興味のある方はリポジトリをクローンして詳細を確認してみてください。 ここでは、クライアント・アプリケーションの状態管理の側面に加え、フロントエンド・アプリと、前回の記事で作成したバックエンドREST APIとの間の相互作用に着目します。 クライアントでのローカルの状態管理には、Reduxを使います。Reduxが唯一の選択肢というわけではありませんが、今回のアプリケーションで実装するイベント駆動アーキテクチャにはうまく適合します。Reduxでは、reducerにアクションをディスパッチすることで、アプリケーションの状態を更新します。厳密に言えば、Reduxでは状態を不変として扱うので、実際には何も更新してはいません。正確には、既存の状態と、受け取ったアクションに基づいて、新しい状態を作成しています。 まだよくわからない方のために(筆者自身も、完全に理解するまでしばらく時間がかかりました)、To Doリストのクライアントサイド表現のreducerを見てみることにしましょう。うまくいけば、理解の助けになるはずです。 export default function todos(state = [], action = null) { switch (action.type) { case INIT_TODOS: return action.todos || []; case ADD_TODO: return [ { id: action.id, createdAt: action.createdAt, completed: false, description: action.description }, ...state ]; case DELETE_TODO: return state.filter(todo => todo.id !== action.id ); case UPDATE_TODO: return state.map(todo => todo.id === action.id ? { ...todo, description: action.description } : todo ); case COMPLETE_TODO: return state.map(todo => todo.id === action.id ? { ...todo, completed: action.completed } : todo ); default: return state; } } 上記の関数では、To Doリストの状態を扱うreducerを定義しています。switch文の各条件は、それぞれ異なるアクション・タイプを扱っており、現在の状態と、受け取ったアクションのペイロードに基づいて新しい状態を返します。UIは、状態が変わるたびにその変化に反応し、適切に自身を更新します(そのため、このフレームワークは「反応」を表すReactと呼ばれています)。 理解すべき重要なことは、この状態はフロントエンド・アプリのローカルであり、この時点ではCoherence CEバックエンドで管理されている状態とは関係がないことです。この点を解決し、2つの状態を結びつけるためには、次のことが必要です。 アプリケーションがロードされたときに、ローカルの状態を初期化する サーバーから受け取ったイベントに基づいてローカルの状態を更新する ReactアプリケーションのメインのApp.jsコンポーネントでこの2つのタスクを実現するコードは、次のようになります。 let initialized = false; function init(actions) { actions.fetchAllTodos(); // サーバーサイド・イベントの登録 let source = new EventSource('/api/tasks/events'); source.addEventListener("insert", (e) => { let todo = JSON.parse(e.data); actions.addTodo(todo.id, todo.createdAt, todo.description); }); source.addEventListener("update", (e) => { let todo = JSON.parse(e.data); actions.updateTodo(todo.id, todo.description, todo.completed); }); source.addEventListener("delete", (e) => { let todo = JSON.parse(e.data); actions.deleteTodo(todo.id); }); source.addEventListener("end", (e) => { console.log("end"); source.close(); }); initialized = true; } const App = ({todos, actions}) => { if (!initialized) { init(actions); } return ( <div> <Header /> <TodoInput addTodo={actions.addTodoRequest}/> <MainSection todos={todos} actions={actions}/> </div> ) }; 先ほどの2つのタスクには、どちらも上記のコードのinit関数で対処しています。この関数では最初に、次に示すfetchAllTodosアクションを呼び出します。するとREST APIが呼び出され、その結果がreducerにディスパッチされます。 export const initTodos = (todos) => ({type: types.INIT_TODOS, todos}); export function fetchAllTodos() { return (dispatch) => { request .get('/api/tasks') .end(function (err, res) { console.log(err, res); if (!err) { dispatch(initTodos(res.body)); } }); } } 次に、サーバーで実装された、イベントのエンドポイントを使ってイベント・ソースを登録します。受け取ったそれぞれのイベントは、Reduxのreducerにディスパッチして処理されます。 アクション自体は、2つのグループに分かれています。1つはReduxが管理するローカルの状態を更新するものです。もう1つは、REST APIへのリモート呼出しを行い、Coherence CEでサーバーサイドの状態を更新するものです。 その他のローカル・アクションは、上記のinitTodosアクションと同様で、対応するアクション・タイプをペイロードに追加しているだけです。これにより、reducerでそのアクションを適用できます。たとえば、addTodoアクションは次のように定義されています。 export const addTodo = (id, createdAt, description) => ({type: types.ADD_TODO, id, createdAt, description}); 一方、リモート・アクションでは、単純にサーバーに対してREST呼出しを行います。Reduxの状態を直接更新するのではなく、先ほど登録したイベント・リスナーを使って、サーバーサイドの状態の変更をローカルの状態に適用します。 たとえば、上記のTodoInputコンポーネントに渡されるaddTodoRequestアクションでは、単純にリクエストを送信してレスポンスをログに出力します。 export function addTodoRequest(text) { return (dispatch) => { request .post('/api/tasks') .send({description: text}) .end(function (err, res) { console.log(err, res); }); } } 続いて、先ほど定義したinsertイベント・ハンドラで、JSON形式で受け取った実際のタスクをReduxの状態に追加します。 source.addEventListener("insert", (e) => { let todo = JSON.parse(e.data); actions.addTodo(todo.id, todo.createdAt, todo.description); }); このリアクティブなイベント駆動アプローチは、次の2つの重要な結果につながります。 サーバーサイドの状態を変更する場合、サーバーにリクエストを送信することだけを考えればよいので、アクションの実装がしやすくなる。サーバーサイドの状態と、クライアントサイドの状態の動機について心配する必要はない。 どのくらいアンドが変更を行ったかにかかわらず、クライアント UI がサーバーサイドの状態の変化に反応することになる。 よく使われているデータ・ストアの実装の大半では、サーバーにポーリングを行ったり、クライアントの機能でUIを最新に保たなければならなかったりします。Coherence CEが実現するこのイベント駆動アプローチは、それよりもはるかに効率がよいことがおわかりいただけたかと思います。 Reactフロントエンドの実装についての説明は、これで終わりです。次は、JavaFXクライアントを実装します。   JavaFX クライアントの実現 JavaFXクライアント(図3参照)のほとんどは、前回の記事で説明した、サーバーサイドのREST API実装とよく似たものになります。同じNamedMap APIを使い、Coherence CEイベントを監視して、まったく同じデータ・アクセス手法を多く使用します。 図3:JavaFX クライアント ただし、いくつか違う点があるので、その点について説明します。その前に、プロジェクトのセットアップを行う必要があります。   JavaFX クライアント・プロジェクトのセットアップ ここでは、同じMavenプロジェクト内の別のモジュールとしてJavaFXクライアントを実装します。そこで、最初にクライアントのPOMファイルを作成します。 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.oracle.coherence.examples</groupId> <artifactId>todo-list-client</artifactId> <version>1.0.0-SNAPSHOT</version> <properties> <coherence.groupId>com.oracle.coherence.ce</coherence.groupId> <coherence.version>20.06</coherence.version> </properties> <dependencies> <dependency> <groupId>${coherence.groupId}</groupId> <artifactId>coherence-java-client</artifactId> <version>${coherence.version}</version> </dependency> <dependency> <groupId>${coherence.groupId}</groupId> <artifactId>coherence-json</artifactId> <version>${coherence.version}</version> </dependency> <!-- JavaFX の依存性 --> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>14.0.2.1</version> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> <version>14.0.2.1</version> </dependency> <!-- CDI サポート --> <dependency> <groupId>de.perdoctus.fx</groupId> <artifactId>javafx-cdi-bootstrap</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.jboss.weld.se</groupId> <artifactId>weld-se-core</artifactId> <version>3.1.4.Final</version> </dependency> </dependencies> </project> 上記のコードに、驚く点や非常に興味深い点は何もありません。Coherence JavaクライアントとJSONシリアライズのサポートに加え、JavaFXとContexts and Dependency Injection(CDI)のサポートに必要な依存性がコードに含まれていることに注意してください。 ただし、これだけでは不十分です。サーバーサイドとの通信を可能にするプロキシがクライアントにはないからです。この点を修正するため、サーバーのPOMファイルに次の依存性を追加します。 <dependency> <groupId>${coherence.groupId}</groupId> <artifactId>coherence-grpc-proxy</artifactId> <version>${coherence.version}</version> </dependency> <dependency> <groupId>${coherence.groupId}</groupId> <artifactId>coherence-json</artifactId> <version>${coherence.version}</version> </dependency> まとめると、ここには次の2つの依存性を記述しています。 Coherence gRPC プロキシ:gRPC クライアントが必要とする gRPC サービス実装が含まれる。 Coherence JSON : クライアントと同じ依存性。クライアントとサーバーとの間で使用する JSON シリアライズをサポートする。 これでほぼ十分です。Coherence gRPCプロキシはHelidon gRPC Serverを用いて構築されており、これは推移的依存性として追加されます。Helidon Web Serverと同じく、Helidon gRPC Serverがクラスパス内に存在すれば、CDIによって起動時に読み込まれます。そして、検出されたすべてのgRPCサービスが自動的にデプロイされます。他には何も必要ありません。このプロジェクトの目的においては、Helidon gRPC Serverのデフォルトの構成で問題なく動作するからです。 Mavenプロジェクトの作成に加え、META-INF/beans.xmlファイルを作成してCDIを有効化する必要があります。 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" version="2.0" bean-discovery-mode="annotated"/> これでクライアント・プロジェクトの準備が済み、サーバー・プロジェクトに必要な依存性を追加しましたので、クライアントの実装に着手できます。   JavaFX クライアントの実装 Reactクライアントと同じように、UIの実装について詳しく述べることはせず、Coherence CEと通信するコードについて重点的に説明します。最初の手順は、タスク用のデータ・モデル・クラスの作成です。このクラスは、サーバー側のクラスとほとんど同じです。 package com.oracle.coherence.examples.todo.client; public class Task { private String id; private long createdAt; private String description; private Boolean completed; /** * Task インスタンスの作成 * * @param description タスクの説明 */ public Task(String description) { this.id = UUID.randomUUID().toString().substring(0, 6); this.createdAt = System.currentTimeMillis(); this.description = description; this.completed = false; } // 簡潔さを優先し、アクセッサは省略 } サーバーサイドとクライアントサイドのTaskクラスの実装の違いは2点だけです。1つ目の違いは、クライアントのクラスでSerializableインタフェースを実装していないことですが、この点はさほど重要ではありません。しかし、2つ目の違いは重要なので、説明する必要があります。 2つのクラスには同じ一連のフィールドがあり、ほとんど同じように見えますが、違うパッケージに格納されているので、同じクラスではありません。つまり、Javaのシリアライズを使ってクライアントとサーバーとの間でデータをマーシャル(Javaオブジェクト化)することはできません。これを行うには、同じデータ・クラスを共有する必要があるからです。 クライアントとサーバーとの間でJSONを使ってデータをマーシャルする理由の1つは、そこにあります。そうすることにより、クライアントとサーバーの両方に対応したペイロードを、パイプの両側で別々のクラスにデシリアライズできます。Portable Object Format(POF)の使用を選択した場合も同じことが言えるでしょう。パフォーマンスの観点からすれば、こちらの方が優れた選択かもしれませんが、このサンプル・アプリケーションではパフォーマンスをそれほど気にしていません。 しかし、まだ1つの問題が残っています。前回のREST API実装では、JSONペイロードをデシリアライズするときに、強く型付けされたJAX-RSのメソッド・シグネチャから使用するクラスを推測できます。今回はそれとは異なり、Coherence CEのNamedMap<K,V>はジェネリック・インタフェースなので、型を推測することはできません。この問題を解決するために、CoherenceのJSONでは、シリアライズを行う際に、デフォルトで型情報をJSONペイロードに含めるようになっています。この型情報は、JSONペイロードの@classメタプロパティの部分にあります。 たとえば、サーバーサイドのインスタンスをシリアライズしたJSONペイロードは、次のようになります。 { "@class": "com.oracle.coherence.examples.todo.server.Task", "id": "a3f764", "completed": true, "createdAt": 1596105656378, "description": "Write an article" } 問題に気づいたでしょうか。JSONペイロードに埋め込まれているクラス名は、クライアントには存在しません。ありがたいことに、Javaのシリアライズとは異なり、CoherenceのJSONでは型エイリアスがサポートされています。この仕組みを使うと、同じエイリアスを使ってサーバーとクライアントで別々のクラスを登録し、両方に対応したJSONペイロードを生成することができます。 これを実現するには、クライアントとサーバーの両方でGensonBundleProviderを実装します。 public class JsonConfig implements GensonBundleProvider { @Override public GensonBundle provide() { return new GensonBundle() { public void configure(GensonBuilder builder) { builder.addAlias("Task", Task.class); } }; } } 上記のコードにはパッケージ名を含めていませんが、パッケージ名を除けばクラスはまったく同じです。クラスパス上で利用できるものが、Taskの実装として使われます。 次に、META-INF/servicesディレクトリにcom.oracle.coherence.io.json.GensonBundleProviderファイルを追加します。これは、サービス・ローダーがカスタム・プロバイダを検出できるようにし、サーバーサイドではcom.oracle.coherence.examples.todo.server.JsonConfig、クライアントサイドではcom.oracle.coherence.examples.todo.client.JsonConfigの内容をもとにカスタム・プロバイダを構成できるようにするためです。 必要なJSON構成を配置すると、先ほどのペイロードは次のようになります。 { "@type": "Task", "id": "a3f764", "completed": true, "createdAt": 1596105656378, "description": "Write an article" } このペイロードは、クライアントとサーバーで、ローカルに登録されたTaskクラスの実装を使ってデシリアライズできます。 なお、CoherenceのJSONでは、大幅にカスタマイズされた組込みバージョンのGenson JSONシリアライザを使っていることに注意してください。この組込みのGensonシリアライザは、アプリケーションが公式のGensonリリースも使う場合に競合しないよう、別のパッケージに格納されています。 ほとんどの場合、この点について気にかける必要はありません。必要に応じてデータ・クラスにJSON-BまたはJacksonのアノテーションを付加していれば、すべて問題なく動作するはずです。また、その他の目的で別のJSON実装を使うこともできます。実際には、以上が完了すると、REST APIではJSON-Bのリファレンス実装であるEclipse Yasson(Helidonに含まれています)を使います。 それでは、その他のクライアント実装の確認を続けましょう。重要なロジックは、すべてTaskManagerクラスに含まれています。 @ApplicationScoped public class TaskManager { /** * 完了したタスクを取得する {@link Filter} */ private static final Filter<Task> COMPLETED = Filters.equal("completed", true); /** * アクティブなタスクを取得する {@link Filter} */ private static final Filter<Task> ACTIVE = Filters.equal("completed", false); /** * タスクのマップ */ @Inject @Remote private NamedMap<String, Task> tasks; ... } 上記のコードはおなじみのはずです。さまざまなクエリーに使用する2つのフィルタに対して静的フィールドを定義し、タスクを含むNamedMapを注入しています。 ただし、重要な違いが1つあります。Coherence Java gRPCクライアントが取得したNamedMapを注入するには、注入ポイントに@Remote修飾子を追加しなければなりません。これを行わないと、Coherence CDI拡張機能はデフォルトのNamedMap実装を注入し、JavaFXクライアントがクラスタに参加しようとするでしょう。これは望む動作ではありません。 上記のコードを動作させるには、正しいセッションを使うようにgRPCクライアントを構成する必要があります。 セッションでは、サーバーとの接続に使用するgRPCチャネルや、使用するシリアライザを定義します。それでは、クライアント・モジュールのsrc/main/resourcesディレクトリに次のapplication.yamlファイルを追加しましょう。 coherence: sessions: - name: default serializer: json channel: default このファイルでは、クライアントがJSONシリアライザとデフォルトのgRPCチャネルを使用するように構成しています。これにより、クライアントはlocalhost:1408のgRPCサーバーに接続を試みます。 テストで使用するセットアップはまさにこのとおりであるため、今のところはこれで問題ありません。しかし、サーバーをKubernetesにデプロイしたら、クライアントから接続するために追加の構成が必要になります。次回の記事ではこれを行います。 朗報なのは、構成にHelidon MP Configを使っているため、上記の値はすべて、システム・プロパティや環境変数を使って簡単に上書き(または新しい値を追加)できることです。この点も、次回の記事で説明したいと思います。 なお、クライアントはJSONシリアライザを使うように明示的に構成していますが、プロキシでそのようなことはしていない点に注意してください。ありがたいことに、そうする必要はありません。プロキシは、利用可能なシリアライズ形式(CDIまたはサービス・ローダーで検出されます)をすべてサポートし、クライアントから指示されたシリアライザを使用します。 理論的には、クライアントがリクエストごとに異なる形式を使用する可能性もあります。しかし実際のところ各クライアントは、プロキシに接続している間、上記のセッションで構成されている形式を使い続けることになるでしょう。 TaskManagerの実装についての説明を続けましょう。次は、データ・アクセス・メソッドに注目します。 public void addTodo(String description) { Task todo = new Task(description); tasks.put(todo.getId(), todo); } public Collection<Task> getAllTasks() { return tasks.values(); } public Collection<Task> getActiveTasks() { return tasks.values(ACTIVE); } public Collection<Task> getCompletedTasks() { return tasks.values(COMPLETED); } public void removeTodo(String id) { tasks.remove(id); } public void removeCompletedTasks() { tasks.invokeAll(COMPLETED, Processors.remove(Filters.always())); } public void updateCompleted(String id, Boolean completed) { tasks.invoke(id, Processors.update("setCompleted", completed)); } public void updateText(String id, String description) { tasks.invoke(id, Processors.update("setDescription", description)); } 上記のコードはどこも、REST APIで実装したコードによく似ています。このコードでは、tasksマップに対する基本的な作成、読取り、更新、削除(CRUD)操作を実装するために、標準のMap API(put、remove、valuesなど)と、NamedMap API(invoke、invokeAll、そしてフィルタを受け取る、valuesのオーバーロードなど)を使っています。 Coherenceのアグリゲータ:TaskManagerの2つの追加メソッドでは、まだ説明していないCoherence CEの機能を使っています。それがアグリゲータです。 public int getActiveCount() { return tasks.aggregate(ACTIVE, Aggregators.count()); } public int getCompletedCount() { return tasks.aggregate(COMPLETED, Aggregators.count()); } Coherence CEのアグリゲータを使うと、クラスタ全体を対象にMapReduceスタイルの並列集計を行うことができます。 上記の例では、とても基本的なCountアグリゲータを使っています。このアグリゲータは指定されたフィルタに該当するエントリの数を返すだけです。しかし、他にも多くの組込みアグリゲータが搭載されており、特定の属性値の最大値や最小値の検索や平均値の計算、さらにはある属性でエントリをグループ化したり、エントリのグループごとに別々の集計を行ったりすることもできます。また、専用のカスタム・アグリゲータの実装も可能です。 アグリゲータは並列に実行されるので、非常に効率的です。また、正しく実装されていれば、ほぼ線形的にスケールアップすることができます。さらに、クラスタのメンバーが変更されたり、データのリバランスが行われたりすると、アグリゲータは自動的に再実行されます。そのため、クライアント・アプリケーションがその点を意識する必要はありません。 上記の例では、各メンバーが指定された条件を満たすローカルのエントリの数を算出し、クライアント(この場合は、gRPCプロキシ)で実行されているルート・アグリゲータに部分結果を返します。その後、ルート・アグリゲータが部分結果を結合して最終結果を計算します。 この例の最終結果はスカラー値ですが、実際はどんな結果でも構いません。 ところで、Coherence CEでは、Java 8で導入されたStream APIのカスタム実装も提供されていますが、この実装にはアグリゲータが使用されています。Oracle Coherence Remote(詳細はこちらのビデオをご覧ください)を使うと、カスタム・アグリゲータによってすべてのクラスタ・メンバーにストリーム・パイプライン定義が送信され、並列に実行されます。この場合、1つのJVMやいくつかのCPUコアではなく、おそらく数百のJVMと数千のコアで実行されます。 前回の記事で、REST APIでCoherence CEイベントを監視し、Web UIが受け取れるServer-Sent Events(SSE)に変換する必要があったように、ここでも同じようなことを行う必要があります。異なる点は、Coherence CEのMapEventsをSSEイベントに変換するのではなく、標準のCDIイベントに変換することです。こうすることで、JavaFXのUI実装は、完全にCoherence CEに依存しない状態を維持でき、発行されるCDIイベントを監視するだけで済みます。 @Inject private Event<TaskEvent> taskEvent; /** * Coherence の Map events を CDI イベントに変換 */ void onTaskEvent(@Observes @MapName("tasks") MapEvent<String, Task> event) { taskEvent.fire(new TaskEvent(event.getOldValue(), event.getNewValue())); } JavaFXのクライアント実装の説明は以上です。次は、新しいクライアントが期待どおりに動作し、curlを使わなくて済むようになるかどうかを確認しましょう。   クライアントの実行 アプリケーションの実行に必要なコードを本記事ですべて説明したわけではないため、GitHub上のサンプル・コードをまだ取得していない場合は、ここで取得してください。 Reactクライアントにアクセスするには、サーバーをビルドして実行する必要があります。このサーバーでは、フロントエンド・アプリケーションと、フロントエンドが使用するREST APIが提供されます。 前回の記事では、IDEでそのままサーバーを実行できました。しかし今回は、フロントエンドをビルドしてサーバーのJARファイルにパッケージングするために、Mavenを使ってサーバーをビルドする必要があります。最新バージョンのNode.jsとnpmをインストールしている場合、この操作はサーバー・ディレクトリでmvn installを実行するだけで簡単に実現できます。 まず、server/src/main/webディレクトリでnpm installを実行し、フロントエンドに必要な依存性をインストールする必要があります。これは一度だけ行う必要があります。 すると、以前と同じようにIDEでサーバーを実行できるようになります。または、コマンドラインからmvn exec:execを実行しても構いません。すべてがうまくいけば、サーバーの起動が表示され、数秒後には第1回の記事で見たものと同じHelidonのログ・メッセージが表示されるはずです。 2020.08.11 03:16:00 INFO io.helidon.microprofile.server.ServerCdiExtension Thread[main,5,main]: Server started on http://localhost:7001 (and all other host addresses) in 11967 milliseconds (since JVM startup). これで、上記のログ・メッセージからわかるように、http://localhost:7001/にアクセスするだけで、Reactのフロントエンド(図4参照)にアクセスできるようになっています。 図4:React クライアントの UI アプリケーションを触ったり、タスクをいくつか作ったりしてみてください。アプリケーションをもっとおもしろくしたい場合は、複数のブラウザ・ウィンドウでアプリケーションを開き、変更を行った際に、イベントを通してすべてのウィンドウの同期が保たれる様子を確認してください。 最後に、クライアント・ディレクトリでmvn installを実行し、続いてmvn javafx:runを実行して、JavaFXクライアントを起動します。図5に示すものと同様のUIが表示されるはずです。 図5:JavaFX クライアントの UI すぐにわかることですが、タスク・リストに初期表示されているのは、先ほどReactクライアントを使って作成したタスクです。ここでも同じように、タスクをいくつか追加したり、既存のタスクを変更したりしてみてください。そして、変更を行った際に、両方のアプリケーションの同期が保たれる様子を確認してください。 自分でコードを実行したくない方は、こちらのビデオをご覧ください。   まとめ 長い記事になってしまいましたが、今回はここまでです。ローカルで実行できるTo Doリスト・アプリケーションを作ることができました。ただし、最初に言っておきますが、これはさほど便利なものではありません。 第3回となる最後の記事では、このちょっとしたデモ・アプリケーションをKubernetesクラスタにデプロイし、本番環境向けの高可用性アプリケーションにします。このアプリケーションは、簡単にスケールアウトできるだけでなく、PrometheusやGrafana、Jaegerを使って監視することができます   さらに詳しく Coherence Community Edition が登場:スケーラブルでクラウド・ネイティブなステートフル・アプリケーションの作成(パート1) Oracle Coherence Community Edition (英語) GitHub上の Helidon (英語) Coherence Remote Stream API (英語) Redux 初めての Coherence アプリケーションの構築(英語) Coherence Community Edition についてのオラクルの発表(英語)   Aleks Seović Aleks Seović(@aseovic):オラクルのアーキテクト。主要なインメモリ・データ・グリッド製品であるOracle Coherenceに携わるとともに、Helidonマイクロサービス・フレームワークにも貢献。最近では、HelidonのgRPCフレームワークや、CoherenceのCDIおよびEclipse MicroProfileサポートの設計と実装を主導した。現在は、Coherenceネイティブ・クライアント、GraphQLサポート、Spring統合の実装を率いている。2016年にオラクルに入社する前は、専門のコンサルタント会社を率いて、世界中の顧客がCoherenceを用いたミッション・クリティカルなアプリケーションを実装する作業をサポートしていた。『Oracle Coherence 3.5』(Packt Publishing、2010年)の著者であるほか、業界のカンファレンス、Javaや.NETユーザー・グループのイベント、Coherence SIGで頻繁に講演を行い、Coherenceの普及に努めている。

※本記事は、Aleks Seović による "Hello, Coherence Community Edition, Part 2: Building the client" を翻訳したものです。 本記事をPDFでダウンロード (PDF) ReactとJavaFXを使い、Coherence CEバックエンド・アプリケーションと連動可能なフロントエンド・クライアントを開発する方法 2020年 10月...

津島博士のパフォーマンス講座 第79回 Real Application Clustersの待機イベントについて

津島博士のパフォーマンス講座 indexページ▶▶  皆さんこんにちは、今年の7月は急な雨や雷雨が多いですが、これが公開される頃には関東でも梅雨が明けていますね。 今回は、第32回で説明できなかったReal Application Clusters(RAC)でのクラスタ待機イベントについてキャッシュ・フュージョンの動作を含めながら説明しますので、参考にしてください。   1. キャッシュ・フュージョン まずは、RACで難しいと感じている方も多いキャッシュ・フュージョンから説明します。 キャッシュ・フュージョンとは、複数インスタンス間でデータの一貫性を自動的に維持し、一つのキャッシュのように扱うことができる機能です。グローバル・キャッシュ・サービス(GCS)とグローバル・エンキュー・サービス(GES)によって、GRD(Global Resource Directory)に格納されたリソースの管理を行いますが、問題になるのはバッファ・キャッシュ上のブロックを管理するGCSになるので、ここではGCSの動作について解説します。 ローカル・キャッシュ(SQLを発行したインスタンスのバッファ・キャッシュ)にデータが存在しない場合に、マスター・インスタンスにデータを要求しますが、そのときデータの存在によって、以下の三つの動作を行います(これがキャッシュ・フュージョンの基本動作になります)。 2-wayブロック転送 3-wayブロック転送 Grantメッセージ転送   マスター・インスタンスについて ご存知ない方のために、ここでマスター・インスタンスについて簡単に説明します。RACには、Distributed Lock Managerという概念があり、すべてのグローバル・リソース(バッファ・キャッシュ・ブロックやエンキューなど)には、クラスタ内のインスタンスに分散されたリソースの状態に責任をもつマスター・インスタンス(マスター)が存在します。そのため、新しいリソースにアクセスしたい場合は、マスターにアクセスしてロックを取得する必要がありますが、ブロック・アドレスなどから算出するので、インスタンスが何台あっても1ホップでマスターに到達できようになっています(つまり、要求は3-way通信までとなります)。また、キャッシュ・フュージョンができるだけ発生しないように、頻繁にアクセスするインスタンスをマスターにするDRM(Dynamic Re-mastering)機能も提供されています。   (1)2-way / 3-way ブロック転送 マスターとホルダー(データ保有インスタンス)が同じリモート(またはホルダーだけリモート)であれば、そのリモート・インスタンスのLMSプロセスだけとの2-way通信(左側)、マスターとホルダーが別々のリモートであれば、マスターとホルダーのLMSプロセスとの 3-way通信(右側)になります。 要求インスタンスは、マスターにデータ(DMLのときCurrentモードのデータ・ブロック、クエリのときはCRモードのデータ・ブロック)を要求している間、PlaceHolderイベント("gc cr/current block request")で待機し、データ・ブロックをホルダーから受け取るとFixed-upイベント("gc cr/current block 2-way/3-way"などの実際の待機イベント)に変更します。PlaceHolderイベントは、AWRの「Top 10 Foreground Events by Total Wait Time(Top 10 Events)」には現れません。   (2)Grantメッセージ転送 すべてのインスタンスにデータが存在しない場合、マスターからLock Grant(ロック権限)を受け取り、ディスクから読み込んでアクセスします(マスターでデータの存在が判断できるので3-wayはありません)。これもPlaceHolderイベントで待機し、Grantを受け取るとFixed-upイベント("gc cr/current grant 2-way"など)に変更されます。   2. Real Application Clusters の待機イベント 次に、RAC特有の待機イベントと解決方法について説明します。 RAC特有の待機イベントは、グローバル・リソースに対する待機イベントになります。グローバル・エンキュー(バッファ・キャッシュ・ブロック以外のリソース)の待機イベントは、シングル・インスタンス環境の待機と同じ対処になるので、ここではグローバル・キャッシュ(GC)待機イベントについて取り上げます(ただし、GE待機イベントは、シングル・インスタンス環境よりもより多く発生するので、不要なパースなどは避ける必要があります)。   (1)一般的なGC待機イベント GC待機イベントは、ブロックがローカル・キャッシュに存在するかしないかで異なります。待機イベントの関係は、以下のようになります(インスタンス1が要求で、インスタンス2がマスターとホルダーです)。この待機イベントが最も多く発生するので、これだけは改善できるようになりましょう。 (a)ローカル・キャッシュに存在しないときの待機イベント 以下は、ローカル・キャッシュに存在しないときの一般的なGC待機イベントです。これが受信時のメッセージによって変換されるFixed-upイベントになり、動作が判断できるようになっています。 gc [cr|current] [block|grant] [2-way|3-way|busy|congested] gc:Global Cacheの略 cr/current:要求ブロック・タイプ block/grant:サーバーから返されるもの   ・2-way/3-way(ブロックがImmediateで返ってきた) マスターやホルダーからすぐに(ブロック競合などが発生することなく)返事が返ってきたことを示しています。AWRの「Top 10 Events」に最も多く現れますが、"Avg Wait" が1ms以内で完了していれば問題はありません。問題のときには、インターコネクトの混雑(AWRのInterconnect Statistics)、LMSプライオリティの変化(AWRのリアルタイムLMSプロセス数)、ホルダーと要求側のCPU負荷状況(Run queue length/load average)などを確認してください。 ・busy(競合が発生した) ブロック競合が発生して、すぐに送信できなかったことを示しています("gc cr grant busy"は理論的には発生しません)。これは、ホルダーのPin Time(ロックの獲得)、Build Time(CRブロックの生成)、Flush Time(ダーティ・バッファのRedoログ・フラッシュ)の問題で発生するので、AWRの「Global Cache Transfer Statistics」で受信が多いインスタンスやブロック・クラスを確認して、「Global Cache and Enqueue Services - Workload Characteristics」や「Global Current Server Statistics」で問題を特定します。セグメント統計の「Segments by CR Blocks Received/Current Blocks Received」を見ると、リモートからの受信が多いオブジェクトが分かるので、第4回の「ブロック競合について」を参考に、ブロック競合の削減も検討してください。 ・congested(2-way/3-wayと同じように競合はないが混雑が発生した) LMSプロセスにメッセージが送られてから、処理の開始がしきい値(1ms)を超えたため、混雑と判断されたことを示しています。LMSにCPUが割り当たらない、メッセージ数に対してLMSが不足しているなどの問題が考えられるので、LMSのCPU使用率などを確認して、CPUの追加、ロード・バランシング、別の時間帯や新しいノードへのオフロードなどを検討してください。   (b)ローカル・キャッシュに存在するときの待機イベント ローカル・キャッシュに存在するが、以下のようなアクセス中のため、アクセスできないGC待機イベントです。 ・gc buffer busy acquire アクセスするバッファを、別セッションがリモート・キャッシュから取得中のためアクセスできない。 ・gc buffer busy release アクセスするバッファを、別インスタンスから要求されて解放中のためアクセスできない。 「待機イベントの関係」を見ると分かるように、別セッションのデータ要求の完了を待っているので、"gc xxx block busy"などにより長くなるような場合もあります。対処方法は、"gc xxx block busy"と同じように、ブロック競合やRedoログ・フラッシュを削減することになります。セグメント統計の「Segments by Global Cache Buffer Busy」を見ると"gc buffer busy"が多いオブジェクトが分かります。   (2)その他のGC待機イベント その他の代表的なGC待機イベントについても参考のために載せておきます。 待機イベント 説明 gc [cr|current] multi block [request|grant|mixed] リモートにフル・スキャン(マルチ・ブロック)要求を行った request: すべてがブロック転送によって満たされた grant: すべてがLock Grantによって満たされた(その後にディスク・リード) mixed: ブロック転送とディスク・リードが混在した gc [cr|current] block direct read リモート・バッファのRDMAリード(Oracle18cからのExadataのみ) gc [cr|current] block lost ブロック損失による待機(これもFixed-upイベント) ブロック転送のしきい値0.5秒を超えると、この待機として計上されて再要求する ・パケットの損失が頻繁に発生していないかネットワークの監視を行う Exadataでは、ハードウェアの進化を活用して、このような待機イベントを削減する機能が追加され、より利用しやすくなっています(ExadataのRAC機能については、「Oracle RAC Cache Fusion on Exadataパフォーマンス最適化」を参照してください)。待機イベントで苦労されている方は利用を検討してみてください。   3. AWRレポートのRAC Statistics 最後に、AWRレポートのRAC Statisticsについて説明します。 RACの待機イベントもAWRの「Top 10 Events」から確認しますが、RACでは根本的な原因が他のインスタンスに隠れていて現れない可能性もあります。そのため、一つのインスタンスだけでは問題を特定できない場合もあるので、RACのパフォーマンス統計や待機イベントに基づいてまとめられたRAC Statisticsを利用します(Oracle Database 12cからADDMが出力されるので、最初に参考になるものがないかを確認してください)。ここでは、以下の代表的な情報について取り上げます。 RAC Report Summary(RAC全体の情報) Global Current Server Statistics(ホルダー・サイト) Global Cache Transfer Statistics(要求サイト) Interconnect Statistics(インターコネクト情報)   (1)RAC Report Summary RAC全体のキャッシュ・フュージョンの稼働状況として、RAC Report Summaryの「インスタンス数とLMSプロセス数」、「Global Cache Load Profile(負荷状況)」、「Global Cache Efficiency Percentages(アクセスの割合)」、「Global Cache and Enqueue Services - Workload Characteristics(ワークロードの特徴)」を見ていきます。   (a)インスタンス数とLMSプロセス数 RAC Report Summaryの最初に、開始と終了スナップショット時点でオープンしているインスタンス数とLMSプロセス数が出力されているので、スナップショット間で変化していないかを確認します。Oracle Database 12cR2からの"Number of realtime LMS's"(リアルタイムLMSプロセス数)で、すべてのLMSプロセスがリアルタイムで動作しているかも確認してください。すべてリアルタイムでない場合はその問題から解決してください。   (b)Global Cache Load Profile このセクションは、インターコネクトを介したトラフィック(GCSが送受信したブロック、GCSとGESが送受信したメッセージ)の概要になります。「Report Summary」の「Load Profile」と同じように、何かが正しく機能していないことを示すものではないので、負荷に変化があったかどうかの確認(正常値との比較など)に使用します。 統計 意味 Global Cache blocks received 受信したブロック数(Currentブロック + CRブロック) Global Cache blocks served 送信されたブロック数(Currentブロック + CRブロック) GCS/GES messages received リモート・インスタンスから受信したメッセージ数(GCS + GES) GCS/GES messages sent リモート・インスタンスに送信されたたメッセージ数(GCS + GES) DBWR Fusion writes フュージョン・ライト数(Currentブロックをクラスタ全体レベルで書込み要求した数) Estd interconnect traffic (KB) インターコネクト・トラフィックの推定値   (c)Global Cache Efficiency Percentages (Target local+remote 100%) ディスク、ローカル・キャッシュ、リモート・キャッシュから受信したバッファの割合を示しています。理想的には、ディスク・バッファ・アクセスの割合はゼロに近いことが望ましいです(リモートが多い場合も改善の余地があります)。 統計 意味 Buffer access - local cache % ローカル・キャッシュからブロックを取得した割合 Buffer access - remote cache % リモート・キャッシュ(キャッシュ・フュージョン)からブロックを取得した割合 Buffer access - disk % ディスクからブロックを取得した割合   (d)Global Cache and Enqueue Services - Workload Characteristics GCSとGESに関するインスタンスの全体的なパフォーマンス(レイテンシ)を示しています。このインスタンスは、どの時間が多いかを確認するのに使用します。 統計 意味 Avg global cache cr block receive time (us) キャッシュ・フュージョンの要求側として、CR/Currentブロックを要求してから受け取るまでの平均時間(通常は1ms以内) Avg global cache current block receive time (us) Avg global cache cr block build time (us) キャッシュ・フュージョンのホルダー側として、CRブロックの作成、ログ・フラッシュにかかった平均時間(これが多いと他インスタンスでgc cr xxx busyが発生していると言うことです) Avg global cache cr block flush time (us) Global cache log flushes for cr blocks served % CRブロック総数に対するログ・フラッシュした割合 Avg global cache current block pin time (us) キャッシュ・フュージョンのホルダー側として、Currentブロックの排他制御、ログ・フラッシュにかかった平均時間(これが多いと他インスタンスでgc current xxx busyが発生していると言うことです) Avg global cache current block flush time (us) Global cache log flushes for current blocks served % Currentブロック総数に対するログ・フラッシュした割合 Avg LMS process busy % LMSプロセスの平均ビジー率 Avg global enqueue get time (us) グローバル・エンキューの平均取得時間   (2)Global Current Server Statistics このセクションは、ホルダー側の詳細で、"gc current block busy"の理由を説明しています。そのため、この待機イベントが多い場合、他のインスタンス(ホルダー側)でこの統計を確認してください。 統計 意味 Pins CurrentブロックのPin操作数と各時間帯(<100usから<10s)の割合 時間が長い場合は、ホルダーのCPU負荷によりピン(ロック)解放が遅くなっている可能性がある Flushes Currentブロック送信前のRedoフラッシュ操作数と各時間帯(<100usから<10s)の割合 時間が長い場合は、ログI/Oに問題があることを示している   (3)Global Cache Transfer Statistics このセクションは、要求(受信)側の詳細で、クラスタに参加しているインスタンス間のブロック転送情報(Immediate、Busy、Congestedごとのブロック転送の割合や平均時間など)を提供します。待機イベント(2-way/3-way、busy、congested)に対して、受信が多いインスタンスや不均衡なインスタンスなどの確認に使用します。 以下は、「Global Cache Transfer Statistics」と「Global Cache Transfer Times」で、Immediate(滞りなく転送したブロック)、Busy(競合が発生したブロック)、Congested(システム負荷で混雑が発生したブロック)ごとのブロック転送の割合と平均時間になります。 Inst No 受信したインスタンス番号 Block Class ブロックのクラス(data block, undo header, undo block, other) CR / Current Blocks Received 受信したブロック数 CR / Current Avg Time (us) All すべての平均時間 % Immed Immediateブロックの割合 Immed Immediateの平均時間 % Busy Busyブロックの割合 Busy Busyの平均時間 % Congst Congestedブロックの割合 Congst congestedの平均時間 以下は、「Global Cache Transfer (Immediate)」と「Global Cache Times (Immediate)」で、Immediateの2ホップ/3ホップのブロック割合と平均時間、ブロックロストのブロック数と平均時間になります。 Src Inst# 受信したインスタンス番号 Block Class ブロックのクラス(data block, undo header, undo block, other) Blocks Lost ブロックロストのブロック数 Lost Time ブロックロストの平均時間 CR / Current Immed Blks Received Immediateで受信したブロック数 CR/Current Avg Time (us) Immed Immediateの平均時間 % 2hop 2ホップ・ブロックの割合 2hop 2ホップの平均時間 % 3hop 3ホップ・ブロックの割合 3hop 3ホップの平均時間   (4)Interconnect Statistics このセクションは、インターコネクトに対する情報(Ping時間、スループット、デバイス統計)を提供します。 以下は、「Interconnect Ping Latency Stats」で、このインスタンスからターゲット・インスタンス(自分自身も含む)へのPing時間を示します(500バイトと8Kバイトのメッセージに対するパケット転送時間で、平均時間が1ms未満で完了するのが一般的です)。これでインターコネクトの混雑状態を確認します。 Target Instance ターゲットのインスタンス番号 500B Ping Count 500バイト・メッセージのPING回数 Avg Latency 500B msg 500バイト・メッセージのPINGレイテンシ(パケット転送時間)の平均 Stddev 500B msg 500バイト・メッセージのPINGレイテンシの標準偏差 8K Ping Count 8Kバイト・メッセージのPING回数 Avg Latency 8K msg 8Kバイト・メッセージのPINGレイテンシ(パケット転送時間)の平均 Stddev 8K msg 8Kバイト・メッセージのPINGレイテンシの標準偏差   4. おわりに 今回は、Real Application Clustersの待機イベントについて説明しましたが、少しは参考になりましたでしょうか。これからも頑張りますのでよろしくお願いします。 それでは、次回まで、ごきげんよう。           津島博士のパフォーマンス講座 indexページ▶▶ 

津島博士のパフォーマンス講座 indexページ▶▶  皆さんこんにちは、今年の7月は急な雨や雷雨が多いですが、これが公開される頃には関東でも梅雨が明けていますね。 今回は、第32回で説明できなかったReal Application Clusters(RAC)でのクラスタ待機イベントについてキャッシュ・フュージョンの動作を含めながら説明しますので、参考にしてください。   1. キャッシュ・フュージョン ま...

Always Free

オラクル、Oracle Cloud Infrastructure Always Free サービスのポートフォリオを構築

※本記事は、"Oracle builds out their portfolio of Oracle Cloud Infrastructure Always Free services" を翻訳したものです。 オラクルは、Oracle Cloud Free Tierに13個のAlways Free(常時無償)サービスを追加することをお知らせします。これにより、開発者やオペレータは、Oracle Cloud Infrastructure(OCI)上のワークロードを認識し、点検し、デプロイすることができるようになります。2019年9月に、オラクルは、Compute、Storage、Autonomous Databaseを含むAlways Freeサービスの提供を開始しました。最新のAlways Freeサービスには、Ampere A1 Compute、Autonomous JSON Database、NoSQL、APEX Application Development、Logging、Service Connector Hub、Application Performance Monitoring(APM)、柔軟なロードバランサ、柔軟なネットワーク・ロードバランサ、VPN Connect V2、Oracle Security Zones、Oracle Security Advisor、およびOCI Bastionが含まれ、これらによってオラクルのサービスとリソースのAlways Freeポートフォリオは業界でもっとも豊富なものとなっています。 オラクルの13個の新しいAlways Freeサービスは、既存のAlways FreeのCompute仮想マシン(VM)、Object Storage、Block Storage、Load Balancing、Autonomous Data Warehouse、およびAutonomous Transaction Processingサービスを補完します。たとえば、ローコードAPEXアプリのデプロイ、WordPress Webサイトの構築、OCIへのMinecraftサーバーのデプロイなどがすべて制限期間なく無償で実行できるようになりました。オラクルのAlways Freeサービスは、将来、有償のOCIアカウトで拡張できるOCIのプリミティブと機能を学習するのにも役立ちます。OCIのすべてのAlways Freeサービスおよびリソースは、アクティブなOracle Cloudアカウントを使って利用できます。   Always Free の新しい Compute リソース サーバーに導入されるArmアーキテクチャをサポートするため、Oracle OCIでは、クラウドにAmpere A1 Computeインスタンスを導入しました。オラクルは、開発者がOCI Armインスタンス上でアプリケーションをシームレスに移行、構築、実行できるようにするために、Arm開発者エコシステムを作成しました。OCIで使用されている最新のAmpereプロセッサは、ほとんどのサーバー・ワークロードを従来のプロセッサよりも効率的に実行します。Ampere A1 Computeシェイプがご自分に合ったものなのかを判断できる唯一の方法が、お試しいただくことです。オラクルは、(“hello world”デモだけではなく)意味のあるアプリを開発者が構築してテストできるように、業界最大のAlways FreeのArmリソース(4つのOCPU、24-GBメモリ)を提供しています。 OCI Arm開発者エコシステムについては、こちらをご確認ください。   Always Free の新しい Database リソースおよびサービス Always Freeの新しいAutonomous JSON DatabaseリソースをOracle Cloud上で起動することにより、ワークロード・タイプを選択して、Autonomous Transaction Processing、Autonomous Data Warehouse、およびAutonomous JSON Databaseから最大2つのデータベースを実行できるようになりました。また、単純なクエリに対して予測可能な1桁台のミリ秒の待機時間での応答を必要とするデータベース操作のために設計された、完全管理型のNoSQLデータベース・クラウド・サービスも使用できます。 Oracle Autonomous JSON Database Always FreeのOracle Autonomous JSON Databaseは、オラクルの有償版Autonomous JSON Databaseが提供するメリットを、必要とする限り無償で提供します。Autonomous JSON Databaseは、JSON中心のアプリケーションの開発とJSONワークロードの処理を簡単にするクラウド・ドキュメント・データベース・サービスです。これには、シンプルなJSONドキュメントAPI、完全なSQLアクセス、サーバーレス・スケーリング、高パフォーマンスのACIDトランザクション、および包括的なセキュリティの機能が含まれます。このサービスはOracle Autonomous Databaseファミリーに含まれるため、データベースのプロビジョニング、構成、チューニング、スケーリング、パッチ適用、暗号化、および修復を自動化し、ほとんどのデータベース管理タスクを排除しながら、高可用性と1桁台のミリ秒の応答時間を実現します。無償のJSON Databaseは、最大1つのOCPU(共有)と20 GBのストレージを提供するため、小中規模の広範囲にわたるJSON中心のアプリケーションに適しています。 Always FreeのAutonomous JSON Databaseについては、こちらをご確認ください。   NoSQL Oracle NoSQL Database Cloudサービスにより、開発者は、ドキュメント、列、Key-Value型データベース・モデルを使用してアプリケーションを簡単に構築でき、高可用性のためのデータ・レプリケーションによって予測可能な1桁台のミリ秒の応答時間を実現します。このサービスは、オンプレミスのOracle NoSQL Databaseとの完全な互換性を含む、原子性、一貫性、独立性、永続性(ACID)トランザクション、サーバーレス・スケーリング、包括的なセキュリティを従量課金による手頃な価格で提供します。Always FreeのNoSQLリソースには、1か月あたり最大1億3,300万の読取り、1か月あたり1億3,300万の書込み、および1つの表あたり25 GBのストレージを含む3つの表が含まれます。Oracle Cloudのお客様(Airbusなど)は、NoSQL Cloud Serviceを使用することでその価値、および継続して提供される実証済みの信頼性、柔軟性、高パフォーマンスを利用しています。 Always FreeのOracle Cloud NoSQLサービスについては、こちらをご確認ください。   Always Free の新しいアプリケーション開発サービス Always Freeの新しいOracle APEXアプリケーション開発により、新規のプロ開発者は、必要とする限り無償でOracle Cloudでローコード・アプリを迅速に構築してデプロイできます。Oracle APEXにより、開発者はデータ主導の最新のアプリケーションを最大でコーディングよりも38倍速く作成できます。開発者は、リレーショナル、JSON、空間、およびグラフ・データを統合するコンバージド・アプリケーションを構築することにより、データ資産を最大限に活用できます。含まれているOracle Autonomous Database、またはほぼすべてのRESTエンドポイントからデータを引き出せます。 また、開発者は、オラクルとサード・パーティのAPEXプラグインを組み込むか、またはカスタムのJavaScript、SQL、およびPL/SQLロジックを追加することで、アプリケーションを拡張することもできます。Always FreeのAPEXサービスは、Oracle Cloudで実行されるOracle APEXを、最大1つのOCPU(共有)と20 GBのストレージを含むすべてを備えた完全管理型のサービスとして提供します。ここでは、3~6ユーザーが同時にサービスにアクセスでき、アプリケーション、開発者アカウント、エンドユーザー・アカウントが数に制限なくサポートされます。 Always FreeのローコードのAPEXアプリケーション開発については、こちらをご確認ください。   Always Free の新しい可観測性および管理のリソースとサービス Logging OCI Loggingサービスは、非常にスケーラブルで、テナンシーのすべてのログを完全に管理できる単一画面を備え、1か月あたり10 GBものリソースを必要とされる限り常時無償で提供します。ログの取込みと検索も無償です。Loggingは、OCIリソースからの監査、サービス、およびカスタム・ログへのアクセスを提供します。これらのログには、リソースがどのように実行され、アクセスされるかを記述する重要な診断情報が含まれます。ログの確認には、優れたログ検索エンジンを使用できます。また、すべてのログ・ラインをほぼリアルタイムで使用可能にして、ログに基づく動作、エクスポート、アーカイブを実行し、OCI Service Connector Hubサポートを用いて無期限に保存できます。 OCIログは、OCI Loggingに取り込まれる過程で処理中に暗号化されます。ログは、システムに取り込まれたのち、商用環境向けにディスクレベルの暗号化で暗号化されます。また、ログは、アーカイブ時や保存中にも暗号化されるため、24時間365日対応の強固なログ・セキュリティがいつでも提供されます。相互運用性のために、OCI Loggingは、ログの取込みにはCloud Native Computing Foundation(CNCF)Fluentdエージェントを使用し、ログ・イベントにはCNCF CloudEvents標準を使用しています。 Always FreeのOracle Cloud Infrastructure Loggingについては、こちらをご確認ください。 Service Connector Hub Service Connector Hubは、OCI内とOCIからサード・パーティ・ツールへの両方において、データをサービス間で移動させる無償サービスです。ログなどのデータをLoggingからObject Storage、Streaming、Monitoringなどのサービスへ移動し、追加の処理やコンプライアンスのストレージのニーズに対応します。カスタム・データ処理用の関数をトリガーし、クラウド・リソースへの変更に関する通知を送信します。Service Connector Hubは、サービス間のデータを移動するための複雑なアプリケーションの構築および維持に関連するオーバーヘッドを排除します。完全管理型で耐久性に優れており、バックエンドへのプレッシャーという実世界のシナリオに対応するように拡張できます。 Service Connector Hubを使用すると、サード・パーティ・ツールとのシームレスな統合による処理および分析の改善、コンプライアンス目的でのObject Storageへのデータの保存、特定の条件(アラーム)に基づくログからのメトリックの発行、関数による転送中のデータ処理、クラウド・リソースへの変更に関する通知の取得、およびデータの移動の完全な可視化が可能になります。 Always FreeのService Connector Hubについては、こちらをご確認ください。 Application Performance Monitoring (APM) Application Performance Monitoringが提供する4つの個別の機能により、アプリケーション管理者やDevOpsエンジニアは、すべてのユーザーのエクスペリエンスの実像を知ることができます。APMは、堅牢な分散トレース・システムをサービスとして実装し、マイクロサービスベースのアプリケーションや従来の多層アーキテクチャでのサンプリングや集計を行わずに、すべてのトランザクションのすべての手順を追跡します。APMはエンドユーザー監視を提供し、ブラウザの計測、セッション診断、サーバー側のトレースとの組合せを通じて、アプリケーションへアクセスする場所や方法に関係なく、各エンドユーザーの実際のエクスペリエンスを常に追跡します。 合成監視は、開発者やオペレータが、ユーザーが影響を受ける前に問題を防いで事前に対応するのに役立ちます。合成監視をスケジューリングして記述し、アプリケーションの可用性などの要件に対応できます。サーバー監視により、APMは、サービスの提供に関わるあらゆるコンポーネントからメトリックを収集できます。 APMによるデータの収集は、OpenMetricsやOpenTelemetryなどのクラウド・ネイティブのオープン標準と互換性があります。Always Freeバージョンには1時間あたり1,000のイベントが含まれるため、分散トレース、エンドユーザー監視、およびサーバー監視機能を利用して、サービスを試行したり、少量のアプリケーションを監視したりできます。現在、合成監視はAlways Freeオプションには含まれません。 Always FreeのApplication Performance Monitoringについては、こちらをご確認ください。   Always Free の新しいインフラストラクチャおよびネットワーキング・サービス 柔軟なロードバランサ OCI Flexible Load Balancingを使用すると、固定された帯域幅のロードバランサ・シェイプや一般的なトラフィック・パターンのみに基づくスケーリングに制限されなくなります。カスタムの最小帯域幅とオプションの最大帯域幅を、10 Mbpsから8,000 Mbpsの間のいずれかから選択できます。最小帯域幅は常に使用可能で、ワークロードにすぐに対応できます。オプションの最大帯域幅の設定により、コストを管理する必要がある場合は、予期しないピークの間でさえも帯域幅を制限できます。受信トラフィック・パターンに基づいて、使用可能な帯域幅はトラフィックが増加するにつれて最小値からスケールアップします。オラクルのAlways Freeアカウントでは、最小帯域幅と最大帯域幅が10 Mbpsの柔軟なロードバランサを1つ取得します。 Always FreeのOracle Cloud Infrastructure Flexible Load Balancerについては、こちらをご確認ください。 柔軟なネットワーク・ロードバランサ OCIの柔軟なネットワーク・ロードバランサは、レイヤー3およびレイヤー4(TCP/UDP/ICMP)ワークロードのパススルー・ロードバランシングを実行する、非プロシキ・ロードバランシング・ソリューションです。最小帯域幅または最大帯域幅の構成要件なしにクライアントの通信量に基づいてスケールアップまたはスケールダウンできる、柔軟に拡張可能なリージョナルVIPアドレスを提供します。また、フローの高可用性、ソースとターゲットのIPアドレス、およびポートの保持の利点も提供します。頻繁に変更されるトラフィック・パターンや数百万のフローに対応するために設計されており、極めて短い待機時間を維持しながら高スループットを実現します。ネットワーク・ロードバランサは、数日または数か月にわたる長時間実行の接続向けに最適化されているため、データベースやWebSocketタイプのアプリケーションに最適です。 Always FreeのOracle Cloud Infrastructure Flexible Network Load Balancerについては、こちらをご確認ください。 VPN Connect V2 OCIのVPN接続では、VPN Connectサービスを通じて、IPSec VPNとしても知られる業界標準のIPSecプロトコルを使用します。VPN Connectは、オンプレミス・ネットワークと仮想クラウド・ネットワーク(VCN)の間にサイト間のIPSec VPNを提供します。IPSecプロトコル・スイートは、パケットが送信元から宛先に送られる前にIPトラフィックを暗号化し、トラフィックが到着した際に復号化します。オラクルの新しいサービス提供では、VPN Connect V2によってIPSec接続の可用性と信頼性が向上しています。2つのIPSec接続を無償で使用して、軽量のワークロードを無期限で実行できるようになりました。 Always FreeのVPN Connectについては、こちらをご確認ください。   Always Free の新しいセキュリティ・リソース Oracle Security Zones と Oracle Security Advisor Oracle Security ZonesとOracle Security Advisorを使用して、OCIのコンパートメント用のセキュリティ・ポリシーを自動的に設定して実施できます。Oracle Security Zonesサービスには、クラウド・セキュリティ態勢の管理を可能にする豊富なポリシー・ライブラリが含まれているため、管理者はコンパートメントが安全であることをすぐに確認できます。Security ZoneとSecurity Advisorは補完的であり、OCIに組み込まれている他のセキュリティ機能と組み合わせることで、クラウド・インフラストラクチャを自動化された安全な方法で簡単に構築できるようにします。これらの機能により、製品のセキュリティ面を心配することなく、ソフトウェアにおける卓越したカスタマー・エクスペリエンスの構築に集中できます。どちらもOCIのAlways Freeサービスとして分類され、アクティブなOracle Cloudアカウントをお持ちのお客様には、ユースケースに合った最先端のセキュリティが提供されます。 Security ZonesとSecurity Advisorについては、こちらをご確認ください。   OCI Bastion OCI Bastionは、OCIのプライベート・リソースに安全で一時的なSSHアクセスを提供する完全管理型のサービスで、プライベート・リソースへアクセスするための複雑なネットワーキングやジャンプ・ホストのデプロイを不要にします。ユーザーの認証および認可はIDおよびアクセス管理(IAM)サービスと統合されているため、認可されたユーザーは、SSHでサポートされているソフトウェアやプロトコルを使用して特定のIPアドレスからターゲット・リソースに接続できます。OCI BastionはOCIのAlways Freeサービスとして分類されており、1つの踏み台サーバーあたり20セッションと各テナンシーで5つの踏み台サーバーをサポートします。 Oracle Cloud Infrastructure Bastionについては、こちらをご確認ください。   Oracle Cloud を始める Oracle Cloud の無償トライアルアカウントを登録して、30日間無償トライアルと常時無償サービスをお試しください Oracle Cloud Infrastructure ドキュメントを見る Oracle Cloud Infrastructure Core Service Workshop を見る Oracle Quick Starts で素早く簡単に OCI へ導入する方法を確認する Oracle Cloud Free Tier および常時無償枠について Always Free-eligible resources で Oracel Cloud のプロビジョニングする方法を学ぶ Oracle Cloud Free Tier のよくある質問

※本記事は、"Oracle builds out their portfolio of Oracle Cloud Infrastructure Always Free services" を翻訳したものです。 オラクルは、Oracle Cloud Free Tierに13個のAlways Free(常時無償)サービスを追加することをお知らせします。これにより、開発者やオペレータは、Oracle...

Java Magazine

Javaにおけるリファクタリング(パート1):テスト駆動開発でアジャイル開発を促進する

※本記事は、Mohamed Taman による "Refactoring Java, Part 1: Driving agile development with test-driven development" を翻訳したものです。 本記事を PDF でダウンロード(英語) リファクタリングで組織のコードがシンプルになり、バグの減少と容易なメンテナンスにもつながる October 9, 2020   リファクタリングとは、コードをシンプルにして一貫性を高めることです。シンプルなコードにすれば、融通が利くようになるほか、すばやく変更したり、新しい機能を導入したり、組織において変化を続けるニーズに合わせたりすることが容易に可能になります。 本シリーズでは、リファクタリングについて、練習を通して学びます。その目的は、コードを書いてリファクタリングや最適化を行い、その過程で新機能を追加することにあります。コードを適切にリファクタリングすることで、開発者がより迅速に問題を解決できるようになり、顧客に喜んでもらえる高品質なソフトウェア製品を短時間で開発できるようになります。   学習する内容 本シリーズ記事では、実践的な「形」(かた)に基づいて、アジャイル開発に適したリファクタリングの基本について説明します(「形」については、後ほど説明します)。最初の記事となる今回は、テスト駆動開発(TDD)環境をセットアップし、変数名の変更、メソッドの抽出、メソッドのインライン化などの基本的なリファクタリング・テクニックを紹介します。 2回目の記事では、明らかな技術的負債(不注意なプログラミングによって紛れ込んだ効率の悪さやエラー)に対処することで、どうすればレガシー・コードを安定させることができるかについて取り上げたいと思います。 3回目の記事では、リファクタリングを行って、コードをシンプルにし、重複を削除し、再利用可能なオブジェクトを増やす実例を紹介する予定です。シンプルになったコードベースにすばやく新機能を追加する方法を実際に紹介するので、リファクタリングによってアジャイル・ワークフローがどのように補完されるかをわかっていただけると思います。 まずは、基本的な定義や考え方から紹介します。 リファクタリングとは:リファクタリングはコードを改善するプロセスです。リファクタリングの目的は、品質を改善し、ソフトウェア製品の変更を容易にすることにあります。リファクタリングを行うと、コードはシンプルになります。コードの行数が少なくなれば、潜在的なバグの数も少なくなります。 ここで適用できる、アジャイル・ソフトウェア開発に関する2つの原則は、シンプルさと技術的優位性です。 コードをシンプルにすれば、技術的な俊敏性を高めることができます。たとえば、コードをすばやく変更して新機能を追加できるようになります。 技術的な俊敏性を高めれば、ビジネスの俊敏性を高めることができます。つまり、ユーザーなどのステークホルダーが持つ、変化を続けるニーズを満たすために、テック製品を簡単に変更できるチャンスが増えます。 そのため、適切にリファクタリングされたコードベースからは、ビジネスの問題が迅速に解決されるとともに、皆に愛される高品質なソフトウェア製品が短時間で開発されて提供されるという柔軟性がもたらされます。 品質第一のテスト駆動開発:リファクタリングの土台となるのは、確かなテストです。コードの動作が正しいことを検証するために適切に定義したテスト・ケースがなければ、リファクタリングによってコードの外部動作が変わっていないことを保証できません。リファクタリングのプロセスは、コードの外部動作に影響を与えずに、その内部構造を変えることです。 本記事では、TDDのテスト記述法に従い、コードが正しく動作することを証明する自動テストを記述します。新しくコードを書くとき、TDDは正しいコードを書くための指針となります。そしてリファクタリングする場合、すなわち現在のコードを改善する場合には、リファクタリングを安全なものにするためのガイド・レールがTDDから提供されます。 新しい機能を記述する場合、TDDはインタラクティブなプロセスになります。このプロセスは、赤、緑、リファクタリングという3ステップで表されます。 新機能のテストを新しく記述します。もちろん、新機能はまだ動作しないので、コードのテストは失敗します。TDDでは、テストは赤の状態になったと言います。 新機能を実装するJavaコードの記述を、テストに合格するまで続けます。他のすべてのテストにも合格し続けている必要があります。つまり、テストは緑の状態になっています。 リファクタリングにより、実稼働環境のコードをクリーンアップして改善します。これによって、よりよいコードとなります。すべてのテストに合格し続けている必要があります。   図1に示すように、新機能に満足するまで、この手順を何度でも繰り返します。 図1:テスト工藤開発の反復ステップ TDDについてさらに詳しく知りたい方は、筆者の記事「テスト駆動開発:実はそれは設計技術です」をご覧ください。 コードの「形」を学ぶ:コードの「形」とは、新しいコーディング・スキルを学ぶためのテクニックです。武道において、繰り返し練習する一連の動きを「形」と呼びます。ある動きを何度も繰り返すと、体がその動きを覚え、やがては考えることなく、その動きを行うようになります。 コードの「形」も武道と同様ですが、対象となるのはコーディングの問題です。簡単な問題を取り上げ、その問題を解決するための一連の動きに従います。一連の動きを何度も繰り返すと、体がその動きを覚えます。その結果、体と脳は、次に同じようなコーディングの問題に出会ったときにどうすればよいのかを覚えています。 ここで言う一連の動きとは、テスト駆動開発の赤-緑-リファクタリングのループです。赤-緑-リファクタリングのループを何度も繰り返すと、優れたリファクタリングを体が覚えます。目標は、実際の実稼働環境のコードを書くときにリファクタリングのことを考える必要がなくなり、無意識で行うようになることです。 知っておくべきこと:本シリーズ記事から最大限に学ぶには、普段からコードを書いている必要があります。リファクタリングは、どんなプログラミング言語にも適用できます。ここでは、すべての開発とリファクタリングをJavaで行いますが、Javaについて詳しくなくても理解できます。 すべてのソース・コードのファイルは、筆者のGitHubリポジトリから取得できます。または、次のようにしてクローンしてください。 ~$ git clone https://github.com/mohamed-taman/Agile-Software-Dev-Refactoring.git コーディングはせずに読み進めたい方は、本記事の手順をそのままお読みください。実際にコードを追ってみたい方のために言うと、各記事は複数の手順に分かれており、それぞれの手順でTDDの赤-緑-リファクタリングの状態が変わるごとにgitにコミットしています。そのため、コミットをたどれば、差分や、最終的な「形」の要件に向けて行われたリファクタリングを確認できます。 最大限の価値を得るには、お気に入りのIDEを立ち上げて、記事の手順に従って実際にコードを書き、リファクタリングしてみてください。これを行う場合、以下のものが必要です。 最新のJava Development Kit。ここでは、Java SE 15を使います。 任意のIDE。ここでは、自動テスト用のJUnitとコード・カバレッジ・ツールを使います。 IntelliJ IDEA Ultimateエディション。本記事でこのエディションを使うのは、JUnitとコード・カバレッジ・ツールが組み込まれているためです。   これで、最初の演習に向けたセットアップが可能になります。この演習では、ローマ数字をアラビア数字に変換するソフトウェアを書きます。アラビア数字とは、私たちが毎日使っている1、2、3などの数字です。ローマ数字とは、I、V、Xのように文字で表される数字です。この「形」のユーザー・ストーリーは、「生徒が宿題の評価をすばやく確認できるように、ローマ数字をアラビア数字に変換する教師用のツールが求められている」というものです。   手順1:新しいコードの「形」をセットアップする このサンプルでは、全部の要件を深く追求することはしません。その代わり、TDDのリファクタリング・プロセスに関する重要な考え方とテクニックを説明するために、簡単な事例をいくつか提示します。したがって、この簡単なサンプルでプロセスを判断しないでください。数多くの考え方やヒント、秘けつはその次に登場します。 TDDを使ったローマ数字コンバータのサンプルの全体について詳しく知りたい方は、先ほど紹介した、筆者の記事をご覧ください。 今回の「形」を始めるには、以下の手順に従います。ここではIntelliJを使っていますが、任意のIDEを使用できることを思い出してください。 IntelliJを起動し、新しいプロジェクトを作成します。左側で「Maven」を選択し、「Next」をクリックします。 今回はローマ数字の「形」なので、NameにはRomanNumeralsなどと入力します。GroupIdにはcom.siriusxi.javamag.kata、ArtifactIdにはRomanNumerals-Converterと入力します。「Finish」をクリックします。 画面右下隅に、Maven projects need to be importedというウィンドウが表示されます。「Enable auto-import」をクリックします。 プロジェクト・ナビゲータでRomanNumeralsを開き、ソース(src)フォルダを開いてから、testフォルダを開きます。javaフォルダを右クリックし、「New Package」をクリックします。パッケージ名はMavenのGroupIdに合わせます。com.siriusxi.javamag.kataと入力して「OK」をクリックします。 「com.siriusxi.javamag.kata」パッケージを右クリックし、「New Java Class」を選択します。新しいクラス名にRomanNumeralsConverterTestsと入力し、「OK」をクリックします。 IntelliJによって最初の数行のコードが自動生成されることにお気づきでしょう(図2参照)。実行可能なコードがないので、これは完璧なソフトウェアです。つまり、コードがないのでバグもありません。 図2:IDEで自動生成された、バグのない 0 行のコード 今回の「形」の残りの部分における目標は、コードを前述の理想に可能な限り近づけることです。コードの行数は、可能な限り少なくすべきです。そうすれば、バグが最小限に抑えられ、技術とビジネスの俊敏性が最大限に高まるからです。   手順2: TDD のテスト環境をセットアップする 今回の「形」を先に進めるため、テスト・フレームワークとしてJUnitをセットアップします。赤-緑-リファクタリングの3ステップを覚えているでしょうか。まずは、JUnitが存在して問題なく動作することを確認するため、失敗するテストを作成します。 コード・エディタの4行目に@Testと入力し、[Enter]キーを押します。 続いて、メソッド名public void isJunitWorking()と左波括弧を入力し、[Enter]キーを押します。 6行目にassertTrue(false)と入力します。 画面上の赤い部分のそれぞれに注目してください。赤い部分が多いので、コンパイルもできません。ここでの問題は、まだ、JUnitフレームワークを使うようにプロジェクトを構成していないことです。この問題をすばやく解決して「形」の解決にさらに集中できるように、IntelliJに構成を修正してもらいます。 4行目の@Testの上にカーソルを移動し、macOSでは[Option]+[Enter]キー(Windowsでは[Alt]+[Enter]キー)を押し、「JUnit 5.4」を選択してクラスパスに追加します。内部的には、IntelliJによってプロジェクト構成にJUnitが追加されています。さらに、3行目にimport文が追加されていることに注意してください。 8行目のassertTrueがまだ赤いままであることに注目してください。IntelliJの提案に従って[Option]+[Enter]キーを押すと、5行目にimport文が追加されます。赤い部分がすべてなくなったことにも注目してください。コードがコンパイルできるようになりました。 ここで、単体テストを実行します。「RomanNumeralsConverterTests」を右クリックし、「Run RomanNumeralsConverterTests」を選択します。画面下部のJUnitウィンドウに、isJunitWorkingが失敗したと表示されます。この動作はまったく問題ありません。これは失敗するテストで、TDDの赤-緑-リファクタリングのステップの1つ目です。 ここでの目標は、最小限のコードを書いて、このテストを緑の状態にすることです。10行目のfalseをtrueに変更します。 テストを再実行すると、すべて緑の状態になります。これで、赤-緑-リファクタリングのステップにおける緑の部分に到達しました。コードを見てみると、ここでリファクタリングできるものは何もないように見えます。つまり、改善の余地はないということです。したがって、これで完了です(図3参照)。   図2:何もしないものの、最初の単体テストには少なくとも合格する Java コード   ところで、ここではIntelliJにプロジェクトの構成を行ってもらいました。最新のIDEは、プロジェクトの構成などの作業を自動で行うのが得意です。今回の「形」を進めるにあたっては、プロジェクトの構成などの作業を可能な限りIDEに任せて自動化します。   手順3:最初のリファクタリング - 変数名を変更する ローマ数字の「形」を解決するため、まずは一番シンプルなテスト・ケースとして、1桁のローマ数字から始めます。たとえば、ローマ数字のIはアラビア数字の1に変換されます。また、Vは5、Xは10を意味します。最初の動作テストを記述するところから、問題の解決を始めます。 11行目で[Enter]キーを2回押します。@Testと入力して[Enter]キーを押してから、public void convertsSingleRomanDigit(){}と入力します。 TDDは問題を解決するソリューションを設計する際の指針であり、記述するテストはソリューションのコードの指針です。ここで必要になるのは、整数を返すメソッドです。このメソッドは、convertのような名前で、変換したいローマ数字を入力文字列として受け取ります。したがって、このメソッドはint arabic = convert("I");のようになります。 テスト・ケースを完成させるため、1に等しいことをアサートします。「1」は、ローマ数字のIをアラビア数字の整数値に変換したときに期待される結果で、convertメソッドの戻り値になります。つまり、assertEquals(1, arabic);とします。 この時点では、赤い部分が多数あります。それで問題ありません。赤-緑-リファクタリングのステップの1つ目に戻っています。再び、コードに赤い部分が多く、コンパイルもできない状態になっています。このコードを修正していきましょう。 IntelliJにヒントが表示されています。[Option]+[Enter]キーを押すと、5行目にimport文が挿入されます。これで、assertEqualsはコンパイルできるようになります。 convertが赤くなっているのは、Javaコードにconvertメソッドが実際には存在しないからです。赤い電球のアイコンをクリックし、提案を確認します。ここでは、「Create method convert」を選びます。すべてデフォルトのままとして、[Enter]キーを何度か押します。 テストを実行すると、結果は赤くなります。コードはコンパイルできますが、新しいテストは失敗します。これから、最小限のコードを書いて、テストを緑の状態にします。 21行目に戻り、1を返すように変更します。テストを再実行すると、今度はすべて緑の状態になります。次は、3ステップの3つ目に入ります。このコードを改善する余地はあるでしょうか。   手順3.1:コードの改善 最初に目につくのは、20行目にあるiという名前の引数です。1文字というのは説明が十分ではなく、長期的なメンテナンスには向きません。そこで、もう少しわかりやすく変更します。 iを選択して右クリックし、「Refactor」を選択してから「Rename」を選びます。この引数の名前を、わかりやすく実態に即したものに変更します。ここでは、romanNumeralのような名前にします。[Enter]キーを押し、テストを再実行して、外部動作が変化しなかったことを確認します。引き続き、すべて緑の状態です。 これが最初のリファクタリングです。変数の名前を変更して、目的がわかりやすくなるようにしました。   手順4:一連のマイクロリファクタリングを行う コードを見てみると、他にも改善できる点があることがわかります。テスト・コードの一部であるかのように、テスト・クラスに新しいメソッドを作成していますが、これは正しい方法ではありません。この新しいメソッドは、ソリューションの一部なので、別のクラスに移動する必要があります。そのクラスは、ソース・コードのテスト領域ではなく、メイン領域の一部になるようにします。 これから行うのは、一連のリファクタリングという優れたプロセスです。時として、リファクタリングの規模が大きいこともあります。大規模なリファクタリングは、新たな欠陥が紛れ込む可能性があるために、リスクを伴うことがあります。そのため、1つの大きなリファクタリングではなく、一連のマイクロリファクタリング(細かなリファクタリング)を行います。それぞれのマイクロリファクタリングの後にテストを再実行し、問題がないことを確認します。 手順4.1:convert()をstaticに変更する:最初のマイクロリファクタリングとして試みるのは、convertメソッドを専用のクラスに移動することです。 convertを右クリックし、「Refactor」→「Move」の順にクリックします。ここでIntelliJに、このメソッドを専用のクラスに移動できるようにするには、まずメソッドをstaticにする必要があるというメッセージが表示されます。そのため、これが最初のマイクロリファクタリングになります。 「Yes」をクリックしてから「Refactor」ボタンをクリックし、変更内容が適切であることを確認します。 テストを再実行し、直前の操作で外部動作が変化しなかったことを確認します。すべて緑の状態のままなので、次のマイクロリファクタリングに移ることができます。 手順4.2:convert()を専用のクラスに移動する:2つ目のマイクロリファクタリングとして、convertを専用のクラスに移動します。 「convert」を右クリックし、「Refactor」→「Move」の順にクリックしてから、新しいクラスの完全修飾クラス名を入力します。 クラスは、同じパッケージであるcom.siriusxi.javamag.kataに格納します。このクラスをRomanNumeralsConverterとします。これがソリューションの「形」のクラスになります。「Refactor」ボタンをクリックして、変更内容が適切であることを確認します。 テストを再実行し、直前の操作で外部動作が変化しなかったことを確認します。すべて緑の状態のままなので、次のマイクロリファクタリングに移ることができます。 手順4.3:RomanNumeralsConverterをソースのmainフォルダに移動する:RomanNumeralsConverterクラスをテスト用のソース・コード領域からメインのソース・コード領域に移動します。これを行うには、以下の手順に従います。 プロジェクト・ナビゲータでRomanNumeralsConverterクラスをドラッグし、testのjavaフォルダからmainのjavaフォルダに移動します。パッケージ名を尋ねるプロンプトが表示されるので、同じパッケージ名com.siriusxi.javamag.kataを指定します。 注:IntelliJではデフォルトでクラスがソースのtestディレクトリに格納されますが、ここではソースのmainディレクトリに格納したいので、そのように選択します。「OK」をクリックし、クラスを正しいディレクトリに格納します。 「Refactor」をクリックします。テストを再実行し、直前の操作で外部動作が変化しなかったことを確認します。すべて緑の状態のままなので、次のマイクロリファクタリングに移ることができます。 これで3つのマイクロリファクタリングが終わりました。これらを合わせれば、大きなリファクタリングになります。   手順5:1桁のローマ数字のテスト・ケースを新しく追加する 「形」の解決を続けましょう。次に扱う文字は、ローマ数字のVとXになります。 まず、Vが5を返すことを確認するテストを書きます。すべてのテストを実行すると、予想どおり、テストは赤の状態になります。 これを解決するために、最小限のコードを書いて、このテスト・ケースを緑の状態にします。たとえば、ローマ数字がIであれば1を返し、そうでなければ5を返すように記述します。テストを再実行すると、すべてのテストが緑の状態になります。問題ありません。ここで改善できることはありそうにないため、次のテスト・ケースに移ります。 赤の状態になるテストをさらに追加しましょう。上記の1と2の手順を繰り返しますが、新しいテスト・ケースはローマ数字のXです。「X」が10に等しいことをアサートします。テストを実行します。まだソリューションのコードを実装していないので、このテストは赤の状態になります。そこで、テストが緑の状態になる一番単純なコードを書きます。 else if分岐を追加し、ローマ数字がVであれば5を返し、その他であれば10を返すようにします。テストを再実行すると、すべて緑の状態になります。これから、赤-緑-リファクタリングの3ステップのうち、リファクタリングのステップに入ります。 最初のリファクタリングとして、コードの繰り返しに対処します。この作業は、DRY(Don't Repeat Yourself)とも言われます。重複のあるコードは、時がたつにつれてメンテナンスが難しくなります。繰り返されているコード行のいずれかを少し変更する必要がある場合、その変更を何度も行わなければならないからです。この作業には時間がかかり、技術的な俊敏性が低下します。さらに、変更に漏れがあると、見つけにくい欠陥が紛れ込む可能性もあります。 コードの一部をインライン化するリファクタリングを行います。23行目の「arabic」を右クリックし、「Refactor」→「Inline」をクリックするか、[Option]+[Command]+[N]キーを押します。その結果、23行目の変数arabicが、22行目でarabicに代入されている値で置き換えられ、22行目が削除されます。 「Refactor」をクリックし、リファクタリングを適用します。テストを再実行し、直前の操作で外部動作が変化しなかったことを確認します。すべて緑の状態のままであり、問題ありません。 20行目と17行目で同じ手順を繰り返します。それぞれの手順においてテストを再実行し、外部動作が変化しなかったことを確認します。 1点補足ですが、17行目と18行目を削除して、コードを少し整えておくべきでしょう。この行削除によって実際のコードが変更されることはありませんが、それでもテストを再実行する必要があります。変更のたびにテストを実行する習慣をつけてください。すべて緑の状態のままなので、次の段階に進みます。   手順6:コードの「形」の解決を続ける ここまでで、1桁のローマ数字のあらゆるケースを網羅する、テストとソリューション・コードをすべて追加しました。自分でこの部分の「形」を完成させることも、筆者が用意したソリューション・コードを見ることもできます。 それではいよいよ、次のテスト・ケースに入っていきましょう。ローマ数字では、多くの場合、新しい桁の追加が加算のように見えます。 IIは1+1と同じです。コードでは値2を返します。 IIIは1+1+1と同じです。コードでは値3を返します。 VIは5+1と同じです。コードでは値6を返します。 お気づきと思いますが、この例は単純化したものです。というのも、IVは6ではなく4を表すからです。左側に小さい数がある場合、加算ではなく減算を表します。ただし、ここでの目的は、ローマ数字の算術を習得することではなく、TDDとリファクタリングの実例を示すことです。 そのため、最初は単純な加算のテスト・ケースを記述します。 23行目で[Enter]キーを2回押し、@Test public void romanNumeralAddition(){}と入力します。最初のアサーションは、assertEquals(2, RomanNumeralsConverter.convert("II"))となります。 テストを実行すると、失敗します。それでまったく問題ありません。現在は、赤-緑-リファクタリングのループにおける赤のステップです。 最小限のコードを書いて、テストを緑の状態にします。4行目で[Enter]キーを押し、if(romanNumeral.equals("II")) return 2と入力します。コードがコンパイルできるように、次の行のifの前にelseを追加します。 テストを実行し、すべてが緑の状態であることを確認します。 言うまでもありませんが、これは問題を解決するための最終的なソリューションではありません。このソリューションは大変素朴で、文字列IIを受け取ったときに2を返しているだけです。実際の加算すらしていません。しかし、「形」ではそれで構いません。今回の「形」で重要な点は、ローマ数字の問題を解決することでは必ずしもありません。 重要なのは、動きを練習することです。ここで練習している動きは、テスト駆動開発の赤-緑-リファクタリングという3ステップです。赤から緑に移るときは、最小限のコードを書くことになります。テストを実行して、緑の状態であることを確認しましょう。問題ありません。テストはすべて緑の状態です。 それでは、IIIとVIのアサーションも追加しましょう。今度のアサーションも同じ手順に従います。 3とRomanNumeralsConverter.convert("III")が等しいことをアサートします。後者は1+1+1を表します。テストを実行し、このテストが失敗することに注目します。それで問題ありません。最小限のコードを書いて、このテストを緑の状態にします。 このテストを緑の状態にするための最小限のコードは、else if (romanNumeral.equals("III")) return 3;のようになります。これもベストなソリューションではありませんが、目的は動きを練習することです。テストを実行すると、緑の状態になります。 最後に、6についても同じことをしましょう。ローマ数字ではVIになります。 実際のTDDで、すべてのローマ数字をアラビア数字に変換する問題に対する完全なソリューションを確認したい方は、先ほども触れた記事「テスト駆動開発:実はそれは設計技術です」をご覧ください。 手順6.1:次のリファクタリングをしやすくするためにコードを整える:convertメソッドは長すぎて、1画面に収まっていません。メソッド名は画面最上部よりも上で、スクロールしなければ確認できません。メソッドの一番下も見えません。エディタの最下部より下にはみ出てしまっています。 いくつかのマイクロリファクタリングを行うと、この状況が改善されます。最初のマイクロリファクタリングとして、1桁のローマ数字を扱うコード・セグメントを抜き出して、メソッドの上部に移動します。 1桁のローマ数字かどうかを判定するため、if(romanNumeral.length() == 1)と入力します。この条件に一致する場合、1桁のローマ数字の変換操作を行います。一方、複数桁のローマ数字の場合は、複数桁の加算操作を行います。 このマイクロリファクタリングを行うために、1桁のローマ数字に対応するコードを手動で切り取って貼り付けます。15行目から29行目までを選択し、そのコードを切り取って7行目に貼り付けます。コードのコンパイルと実行が可能となるように、29行目に移動して、elseを削除します。 コードの最後に、どの ifにも繋がらない場合の return文も必要です。最後のifを削除し、else return 6に修正します。テストを実行すると、まだ緑の状態です。したがって、このマイクロリファクタリングは成功しました。 メソッドが長すぎる点は変わりませんが、これで次のマイクロリファクタリングがしやすくなりました。 手順6.2:メソッドを抽出するリファクタリングを行う:次のマイクロリファクタリングとして、7行目から21行目までを抽出して別のメソッドにします。7行目から21行目までを選択し、右クリックして「Refactor」→「Extract Method」を選択するか、[Option]+[Command]+[M]キーを押します。新しいメソッドの名前は、convertSingleDigitなどとします。その他はデフォルトのままにして、「Refactor」をクリックします。すると、18行目に新しいメソッドが作成されます。 これで、メソッド全体が1画面に収まるようになりました。この方がわかりやすく、技術的な俊敏性も高まります。 ここで忘れずにテストを実行しましょう。それが3ステップにおけるこの次の動きです。すべて緑の状態なので、外部動作は何も変化していないことが確認されます。このリファクタリングは成功です。 ここでは、新たなリファクタリングを学びました。このリファクタリングはメソッドの抽出と呼ばれており、大きなメソッドの一部から新しいメソッドを作成するものです。皆さん自身でも試してみましょう。 ぜひ、この「形」の取り組みを続けてください。「形」を解決できたかどうかを気にすることはありません。重要なのは、赤-緑-リファクタリングのステップを練習することです。   まとめ コードのリファクタリングは、質の高いコードを書くことにつながります。質の高いコードは、変化にすばやく対応したり、新機能を追加したり、パフォーマンスの優れた製品を提供したりするために必要な土台です。 本記事では、実践的な「形」に基づき、ローマ数字をアラビア数字に変換する問題を題材として、アジャイル開発に適した基本的なリファクタリング・テクニックを紹介しました。 今回は、新しいコードでテスト駆動開発環境をセットアップする方法を学びました。そして、マイクロリファクタリングを通して、変数名の変更、コードの移動、メソッドの抽出、メソッドのインライン化といった基本的なリファクタリング・テクニックを体験しました。重要なのは、赤-緑-リファクタリングの動きを練習することです。 次回の記事では、明らかな技術的負債(不注意なプログラミングによって紛れ込んだ効率の悪さやエラー)を含むレガシー・コードを安定させる方法について取り上げたいと思います。 そして3回目の記事では、リファクタリングを使用して、レガシー・コードをシンプルにし、重複を削除し、再利用可能なオブジェクトを増やす予定です。さらに、シンプルになったコードベースにすばやく新機能を追加する方法を実際に紹介するので、リファクタリングによってアジャイル・ワークフローがどのように補完されるかをわかっていただけると思います。   さらに詳しく テスト駆動開発:実はそれは設計技術です Java MagazineのJUnit 5特別号 JUnitを生み出し、TDDを唱えたKent Beck氏のインタビュー JUnitで行うアプリケーションの単体テスト Oracle Visual Builderでシンプルになるテスト駆動開発 書籍2冊:Joshua Kerievsky著『Refactoring to Patterns』およびMartin Fowler著『Refactoring:Improving the Design of Existing Code』   Mohamed Taman Mohamed Taman(@_tamanm):SiriusXI InnovationsのCEOであり、Effortel Telecommunicationsの最高ソリューション・アーキテクト。セルビアのベオグラードを拠点とするJava Champion、Oracle Groundbreaker、JCPメンバーであり、Jakarta EEのAdopt-a-SpecプログラムとOpenJDKのAdopt-a-JSRのメンバーを務める。  

※本記事は、Mohamed Taman による "Refactoring Java, Part 1: Driving agile development with test-driven development" を翻訳したものです。 本記事を PDF でダウンロード(英語) リファクタリングで組織のコードがシンプルになり、バグの減少と容易なメンテナンスにもつながる October 9, 2020   リフ...

Java Magazine

コマンドライン・ユーティリティを書く楽しさ(パート2):重複ファイルを高速に判別する方法

※本記事は、Andrew Binstock による "The joy of writing command-line utilities, Part 2: The souped-up way to find duplicate files" を翻訳したものです。 本記事を PDF でダウンロード(英語) 総当たり方式でも問題なく機能するが、スマートなアルゴリズムを使えば、パフォーマンスが桁違いに向上する September 25, 2020   本シリーズの前回の記事では、1つまたは複数のディレクトリに含まれる重複ファイルを特定するコマンドライン・ユーティリティについて紹介しました。LinuxやUNIXのfindコマンドとは異なり、このユーティリティでは、同じ内容のファイルが検索されます。ファイルの名前が違っていても検索結果には影響しません。 ユースケース:このユーティリティ(FileDedupe)は、ファイルの内容に注目するので、ミュージック・ライブラリや写真コレクションから重複ファイルを削除するときにとても役立ちます。FileDedupeの最初の実装では、総当たり方式を採用しました。つまり、ユーザーが指定したディレクトリに含まれるすべてのファイルの一覧を作成し、そのファイルすべてに対してJavaのチェックサム関数を実行し、生成されたチェックサムをテーブルに格納し、複数のファイルから生成されたチェックサムを見つけ、、チェックサムが同一のファイルをグループ化した一覧を出力します。 この総当たりバージョン(プロジェクトのWebサイトのリリース1.0)は問題なく機能しますが、動作は遅く、大量のI/Oが必要です。ファイルのチェックサムを生成するためには、すべてのバイトを読み取る必要があります。多数の音楽ファイルや高解像度動画が入った大規模なディレクトリの場合、チェックサム生成は膨大な作業となります。 今回は、前回予告したとおり、総当たり方式から進んで、すべてのファイルですべてのバイトを読み取らなくてもよいバージョンを実現します。前回の記事では設計について詳しく説明しましたが、今回は主に実装に注目します。   パフォーマンスの大幅な改善 バージョン1.0で実装したチェックサム・ソリューションは、I/Oの制約を非常に強く受けます。アプリケーションが実行されている間、CPUはほとんど動作していないように見えますが、ディスク・ドライブ(筆者のシステムではSSD)のランプは途切れることなく点滅を続けています。 したがって、明らかにわかる最適化には、ファイルI/Oの量の削減が必要でしょう。そのためには、チェックサムを生成しなければならないファイル数の減少に役立つファイル特性を見つけることが必要です。その意味で有用なデータ項目の1つが、ファイル・サイズです。サイズが異なる2つのファイルが重複ファイルということはありえません。 さらに、ファイル・サイズはファイルシステムでファイル自体のエントリに格納されるので、ディレクトリ・ツリーをたどる処理の中で高速に取得できます。そして取得したサイズをテーブル(この実装ではHashMap。図1参照)に格納し、そのサイズに対応するファイル名のリストにリンクします。ユーザーが指定したディレクトリをすべてスキャンし終えたら、ファイル・サイズのテーブルを調べて、サイズに対応するファイルが1つしかないエントリはすべて破棄できます。この作業を行うことで、一意であることがわかっているファイルのチェックサム計算を、1バイトも読み取ることなく回避しています。 ここで行うことをしっかりと理解するため、図1に示すテーブルをご覧ください。エントリのキーは、ファイル・サイズを保持するlongです。エントリの値部分は、ファイル名のArrayList(すなわち、文字列のArrayList)です。 図1:ファイル・サイズと、それぞれのサイズに対応するファイル名を格納したテーブル データが格納されたテーブルを調べると、1つ目、3つ目、5つ目のエントリはすべて、項目が1つしかないので、スキップすることができます。しかし、2つ目のエントリには複数のファイルが格納されているので、ユーティリティではチェックサム・ルーチンを実行し、ファイル名が異なっていたとしても、本当に重複ファイルであるかどうかを判定します。チェックサム(偶然ですが、これもlongです)も、図1と同じような構造のテーブルに格納します。それぞれのチェックサムが、そのチェックサムを生成したファイルの名前のArrayListを指しています。 このデータ構造は概念的には単純ですが、実装の特徴についてはさらに解説する価値があります。   主なデータ構造を探る テーブル(図1)を宣言するJavaコードでは、HashMapクラスを使っています。 HashMap<Long, ArrayList<String>> table; しかし、ここまで説明してきたコードには、このデータ構造に直接アクセスするものはありませんでした。その理由は、このテーブルをラッパー・クラスの中に入れているからです。単純な名前ではありますが、このラッパー・クラスはLongStringListTableとしています。この手順をはさむことで、特定のメソッド以外ではテーブルにアクセスできなくなります。皆さんが使用するほぼすべての主要なデータ構造において、これは優れた手法であり、大規模なコードベースを扱うチームで作業している場合には欠かせません。 一般的な用語を使用すれば、このクラスは「ラッパー・クラス」です。しかし、Javaで「ラッパー・クラス」と言えば、プリミティブ型に当てはまる概念がつきまといます(たとえば、Integerはintのラッパーです)。このクラスの一番の利点は、データ構造に対して他人が実行可能な操作を制限できることです。たとえば、今回のラッパー・クラスでは、エントリの追加とエントリのリストの取得しか許可していません。そうすると、他人がエントリを削除したり、既存のエントリを変更したりすることを防止できます。HashMapをそのまま使い、直接アクセスしているなら、このような制限を強制することはできないでしょう。 先ほども述べたように、もう1つのLongStringListTableインスタンスに、重複の疑いがあるファイルのチェックサムを格納します。2つの異なる目的で同じデータ構造を使用するプログラムにおいてそのデータ構造を宣言する場合、他にもいくつかの方法があります。その1つは継承を使う方法です。他の言語を経験したOOP開発者にとっては自然な方法でしょう。たとえば、LongStringListTableをスーパークラス、そしてFileSizeTableとChecksumTableを2つの派生クラスにすることが考えられます。Javaでは、extendsキーワードを使用して実現できます。しかし、継承には、派生クラスの一部はスーパークラスとは異なるという暗黙の前提があります。 (昔から使われている教育的な例で言えば、AnimalというスーパークラスにCatおよびDogという派生クラスがあり、それぞれの派生クラスにおけるmakeNoise()の実装は異なるというものです。) しかし、今回のアプリケーションでは、派生クラスとスーパークラスに違いはないため、継承を使うと誤解を生む可能性があります。また、その点とは無関係ですが、Javaでの継承は好まれなくなっています。そのため、明らかに適切な解決策でない限り、継承は使わない方がよいでしょう。 今回は、単純な方法を採用しました。すなわち、Main.javaでテーブルのインスタンスを2つ宣言し、内部データ構造がたまたま同じである2つの異なるオブジェクトとして使用します。 先に進む前に、LongStringListTableの内部でテーブルにエントリを追加する手順をざっと見てみることにしましょう。チェックサムと、そのチェックサムに対応するファイル名を追加する場合について考えます。前回の記事のコードでは、明示的な手順を使いました。 ArrayList tableEntry = dupesTable.get( checksum ); if( tableEntry == null ) { // テーブル内でエントリがまだ見つからない場合 ArrayList<String> entry = new ArrayList<>(); entry.add( filename ); dupesTable.put( checksum, entry ); } else { tableEntry.add( filename ); } このコードでは、テーブルに含まれるチェックサムを検索しています(1行目)。チェックサムがテーブル内にまだ存在しない場合は、ファイル名のArrayListを新しく作成し、そこにファイル名を追加して、そのArrayListとチェックサムを1つのエントリとしてテーブルに追加します。チェックサムがテーブル内にすでに存在する場合(else句を参照)は、既存の、ファイル名のArrayListにファイル名を追加するだけです。 このコードは明快ですが、すべてが「Javaらしい」というわけではありません。読者のMorten Hindsholm氏が賢明にも、次の機会にはJavaらしい書き方をした方がよいと提案してくれました。 public void insertEntry( String filename, Long numeric ) { ArrayList<String> tableEntry = table.computeIfAbsent( numeric, c -> new ArrayList<>() ); tableEntry.add( filename ); } computeIfAbsent()メソッドは、HashMapの標準APIの一部です。このコードでは、キー(ここではnumeric)がまだテーブルにない(すなわち、absent)である場合にtableEntryを作成します。短いラムダ式は、エントリの2つ目の要素を作成する方法を表しています。この場合は、ArrayListのコンストラクタを呼び出すだけです。コンパイラでは、ArrayListの要素がStringであることを認識しています。この時点でArrayListは空ですが、次の文でArrayListにファイル名が挿入されます。エントリがすでに存在する場合は、computeIfAbsent()の手順がスキップされ、次の文にあるファイル名追加の処理が行われます。 このコードがもともとの実装よりもわかりやすいとは思わない方もいるでしょう。しかし、組み込みのAPIをJavaらしく使うことには価値があります。組み込みのAPIを使っていない場合、コードを読む次の開発者に、期待されるAPIではできない特殊なことをやっているという合図を出していることになります。その理由はいくつかあるでしょう。 実際に何か特殊なことをやっている API に気づかなかった(この場合は、コード・レビューでそっと指摘する必要があります) Java8 より前(つまり、言語にラムダ式が追加される前)のJDKでコンパイルするコードを書いている   テーブルの内容のストリーム 両方のテーブルとも、ある時点ですべてのエントリをたどる処理が必要です。テーブル全体をたどる処理は、2つの方法で実現できます。従来型の方法では、テーブルのすべての行をたどるイテレータを使います。新しい方法では、テーブルをストリームに変換し、ラムダ式で表現したストリーム操作を使って必要な作業を行います。ストリームによるアプローチの方が、Javaらしい方法です。行っているすべての操作を、1つの文ですばやく確認できるからです。次に示すのは、sizesTableを読み取るコードです。このコードが実行される時点で、sizesTableにはすべてのファイル・サイズと、それぞれのファイル・サイズに対応するファイル名が格納されています。 sizesTable.getFilenames().stream() // それぞれのサイズに該当するファイルのリストを取得 .filter( s -> s.size() > 1 ) // エントリとして複数のファイルを含むリストを抽出 .forEach( s -> new FilesChecksum( s, dupesTable ).go() ); // ファイルのチェックサムを計算 sizesTableに対してgetFilenames()を呼び出しています。getFilenames()は、LongStringListTableクラスに作成したメソッドの1つで、テーブルに含まれる、ファイル名のArrayListすべてからなるコレクションを返します。1行目では、このArrayListコレクションをストリームに変換しています。このストリームからエントリ数が1より多いArrayListを抽出し、該当した各ArrayListをFilesChecksumに送っています。FilesChecksumでは、ArrayList内のファイルすべてのチェックサムを計算し、その計算結果をdupesTableに格納しています。 その後、同じようなストリーム操作を使ってdupesTableをたどり、複数のファイルに関連付けられたチェックサムを探します。チェックサムが同一のファイルはすべて重複ファイルであるとわかっているので、該当するファイル名をstdoutに出力します。dupesTableをすべてたどり終えれば、処理は完了です。   まとめ ファイル・サイズを使った処理を追加しましたが、新しくコーディングを行うだけの価値はあったでしょうか。7,201枚の写真(うち434枚が重複)を格納したテスト・ディレクトリでは、もともとのバージョンの実行に119秒かかりました。一方、ファイル・サイズの処理を組み込んだ改訂版の実行時間は16秒でした(時間はすべて、timeユーティリティで計測しました。Windowsをお使いの方には、ptimeユーティリティをお勧めします)。この最適化により、実行時間が87 %減少しました。十分に価値のある作業です。小規模なテストでも大規模なテストでも同様の結果が得られ、重複ファイルが少なかった場合(もう少し厳密に言うなら、チェックサムを計算するバイト数が少なかった場合)により良い結果が得られました。 この最適化は、FileDedupeを実装する2種類の方法を紹介できるようにするために最初から計画していたわけではありません。実際、筆者は最初のバージョンでかなり満足していました。動作が遅い点は気に入りませんでしたが、頻繁な実行は想定していませんでした。ただし、2つのちょっとした点には引っかかっていました。 その1つ目は、サイズがゼロのファイルをどう扱うかです。このようなファイルは、本質的にすべて重複ファイルです。しかし、サイズがゼロのファイルは特別な役割を持っていることがあるので、理由なく削除しない方がよいでしょう。ユーザーに警告できるように、個別に扱うべきかどうかについて悩みました。 2つ目の懸念は、チェックサムのアルゴリズムでした。Java SEの標準ライブラリでは、2つのチェックサム・アルゴリズムが提供されています。今回使わなかったのは、高速ですが100 KB未満のファイルでは信頼できないアルゴリズムです。そこで、ファイル・サイズを確認し、そのサイズをもとにどちらのアルゴリズムを使うかを決めることも考えました。どちらの点も、ファイル・サイズに関わることです。そして、ファイル・サイズの役割について考える中で、チェックサム計算の必要性を大幅に低下させるという目的においては、サイズ自体が十分に一意であるかもしれないという考えが浮かびました。それが、今回の最適化の背景にある洞察につながりました。 このユーティリティのパフォーマンスをさらにチューニングするとすれば、2つのチェックサムを使うアプローチの効果を確認することになるでしょう。さらに、テーブルに対して(現在の1つの逐次ストリームではなく)並列ストリームを使うと効果が得られるかどうかも検討するでしょう。 機能面では、デフォルトのstdoutではなく、コマンドラインで出力ファイルを指定する方法を提供してもよいでしょう。また、出力をHTMLファイルにすることも考えるでしょう。可能な場合にファイルへのクリッカブル・リンクを含めると、ユーザーは重複ファイルのコンテンツをレポートから直接調べることができます。 最後の可能性として考えられるのは、FileDedupeを実行している現在のシステムに加え、別のデスクトップやサーバーにあるリモート・ディレクトリのファイルをチェックする方法を含めることです。これはバックアップを検証する際に便利でしょう。ただし、その正しい方法は、ユーティリティで重複のないファイルのみが報告されるように、(コマンドライン・スイッチで)現在の分析を反転させることです。そうすれば、別のシステム上にあるバックアップが完璧であることを確信できます。   本シリーズの今後について 本記事と前回の記事は、Java Magazineの新シリーズの一部です。このシリーズでは、実際に役立ち、完成させることができる小さなプロジェクト、しかもサードパーティ製ライブラリを呼び出すのではなく、主にJava APIを使っているものを取り上げます。 自分のプロジェクトで好みの設計方法やコーディング方法を選べるように、プロジェクトの設計方法や実装方法をさまざまなやり方で紹介したいと考えています。今回触れたコーディング・スタイルの進化はその一例です。今回のような記事をもっと読みたい方や、取り上げてほしいプロジェクトのアイデアがある方は、ぜひTwitterでお知らせください。   さらに詳しく コマンドライン・ユーティリティを書く楽しさ(パート1):重複ファイルの検索 Javaチュートリアル:コマンドライン引数 JavaクラスHashMap JavaメソッドcomputeIfAbsent JavaメソッドgetFileName Linuxのfindコマンド   Andrew Binstock Andrew Binstock(@platypusguy):Java Magazineの前編集長。Dr.Dobb's Journalの元編集長。オープンソースのiText PDFライブラリを扱う会社(2015年に他社によって買収)の共同創業者。16刷を経て、現在もロング・テールとして扱われている、Cでのアルゴリズム実装についての書籍を執筆。以前はUNIX Reviewで編集長を務め、その前にはC Gazetteを創刊し編集長を務めた。妻とともにシリコン・バレーに住んでおり、コーディングや編集をしていないときは、ピアノを学んでいる。  

※本記事は、Andrew Binstock による "The joy of writing command-line utilities, Part 2: The souped-up way to find duplicate files" を翻訳したものです。 本記事を PDF でダウンロード(英語) 総当たり方式でも問題なく機能するが、スマートなアルゴリズムを使えば、パフォーマンスが桁違いに向上する...

Java Magazine

JUnit 5でのネストしたテスト、動的テスト、パラメータ化テスト、拡張機能の詳細解説(上級編)

※本記事は、Juliette de Rancourt、Matthias Merdes による "Beyond the simple: An in-depth look at JUnit 5’s nested tests, dynamic tests, parameterized tests, and extensions" を翻訳したものです。 本記事を PDF でダウンロード(英語) 人気のJUnitフレームワークは、新しいJUnit Jupiterテスト・エンジンの登場により、柔軟性が格段に増し、テストのニーズを満たすための自在なカスタマイズも可能となっている August 28, 2020   オリジナル版のJUnitは、1997年に飛行機でペアプログラミングされたという伝説があります。Kent Beck氏とErich Gamma氏はその開発から離れましたが、JUnitがとても活発である点は変わりません。それから時は流れ、いくつかのバージョンのJUnitが登場しました。最新版のJUnit 5は、完全に新しいアーキテクチャになっているだけでなく、テスト作成者向けの新機能が多数導入されています。 JUnit 5で刷新されたアーキテクチャにより、JUnitプラットフォームという考え方が登場しています。簡単に言えば、この新しいプラットフォームには、IDEおよびビルド・ツールにテスト・フレームワークを組み込むためのAPIや、テスト・エンジンを実装するための抽象化が含まれています。 このプラットフォームでは、1回のテスト実行で複数のテスト・エンジンを実行できます。代表は、古いJUnit Vintageテスト・エンジン(JUnit 3とJUnit 4のテスト用)と、まったく新しいJUnit Jupiterエンジンです。この新しいエンジンでは、JUnit 5のプログラミング・モデルを実装しています。本記事では、JUnit Jupiterテストの作成と実行に関するいくつかの高度な機能に注目します。具体的には、ネストしたテスト、動的テスト、パラメータ化テストです。 本記事は、日々の作業において何らかの形態でコードをテストしているプログラマーを対象にしています。たとえば、JUnit 4やTestNGのテストなどです。JUnit 5を使った経験があれば理解しやすくなりますが、絶対に必要なものではありません。 一般的な情報については、JUnit 5を特集したJava Magazineをご覧ください。JUnit 5の最近の進化については、Mert Çalişkan氏の記事で詳しく説明されています。   ネストしたテスト ネストしたテストについてJUnit 5チームが初めて検討したのは2015年であり、当時はJUnit Jupiterテスト・エンジンの初期計画段階でした。しかし、全員がすぐに納得したわけではありません。このテストにはネスト・クラスが必要です。そのため、標準のネストしていないクラスで定義したテストよりも、はるかに複雑な実装になります。最終的に、チームはネストしたテストをフレームワークに含めることにしました。他の手段では容易には実現できない特別な表現力が提供されるからです。 ネストしたテストは、機能を階層的に分割する方が、直線的に分割するよりも見通しがよくなるユースケースすべてに適しています。特に、後ほど紹介する例のような、実装のプロトコルや状態オートマトンをチェックするテストなどです。JUnit Jupiterでは、@Nestedアノテーションを使うことで、ネストしたテストの定義を直接的にサポートしています。大まかに言えば、この機能はJUnit 4の拡張機能であるHierarchicalContextRunnerに似ています。 インナー・クラスを使ってテストをネストさせると、テスト構造を階層的に考えやすくなりますが、この仕組みはまず何よりもレイアウト・ツールです。内側のテストは当然ながら外側のテストに依存するため、原理的には、内側のテストでは外側のテストが設定した事前条件を2つの方法で再利用できます。 一番シンプルに実行できるのは、外側のライフサイクル・メソッドによるセットアップを内側のテストから再利用する方法です。パラメータ付きで@BeforeEachなどのアノテーションを付加した、外側のライフサイクル・メソッドは、包含階層のすべてのレベルに配置でき、常に内側のテストのために実行されます。この状況では、ネストしたテストにおいてクラス・レベルの@BeforeAllメソッドはサポートされないことに注意してください。Javaではインナー・クラス内の静的メソッドが許可されないためです。 階層的なセットアップ・メソッドを持つネストしたテストの例を示します。 class NestedTestWithHierarchicalSetupMethods { String state = ""; @BeforeEach void outerSetup() { state = state + "outer"; } @Nested class InnerClass { @BeforeEach void innerSetup() { state = state + "-inner"; } @Test void checkSetup() { assertEquals("outer-inner", state); } } } 上記の例で使われている標準のインスタンス化ライフサイクルでは、テスト・メソッドを実行するたびにテスト・クラスが最初からインスタンス化されることになります。これは通常のテストでは確かに優れた方法で、すべてのバージョンのJUnitでデフォルトの動作になっています。別のやり方として、テスト・クラス全体を一度だけインスタンス化することもできます。これを行うには、@TestInstance(TestInstance.Lifecycle.PER_CLASS)を使ってテスト・インスタンスのライフサイクルを調整します。これにより、内包される側のテストにおいて、内包する側のテストで設定された状態を使用できるようになります。次の例では、内側のテストにおいて、外側のテストで設定された状態を参照し、その値のアサートを行うことができます。 @TestInstance(TestInstance.Lifecycle.PER_CLASS) class NestedTestWithInstantiationPerClass { String outerState = null; @Test void setState() { this.outerState = "outer"; } @Nested class InnerClass { @Test void checkState() { assertEquals("outer", outerState); } } } 上記の例では、クラスごとのライフサイクルを使っています。こうすると、Jupiterテスト・エンジンで、ネストしたテスト全体のインスタンスが一度だけ作成されるようになります。テスト・メソッドが実行されるたびに何度も作成されることはありません。このやり方は、テスト・メソッドの順番が影響すべきでない通常のテストでは推奨されませんが、ネストしたテストでは役立つ場合があります。セットアップ・コードなしに、プロトコル的な呼出しシーケンスを階層中に広げることができるからです。なお、順番は包含階層によってのみ決まり、ネストしたクラスの中では固定されない点に注意してください。どちらのテクニックも、プロトコル的な呼出しシーケンスの単純で自然なモデリングに使用できます。 以上の例はかなり理論的で作為的なものでしたが、次はもう少し現実的なユースケースを取り上げましょう。次の例は、セットアップ・コードで階層的ライフサイクル・メソッドを使用することにより、外側のテストの事前条件を内側のテストで使用できることを示しています。この例には、customerオブジェクトのCRUD(作成、読取り、更新、削除)メソッドを提供するデータ・アクセス・オブジェクト(DAO)が登場します。簡略化するため、最初に格納するオブジェクトにID 1を割り当てています。 @DisplayName("A customer object") class DAOTest { CustomerDAO dao = new CustomerDAO(); @Test @DisplayName("can be created with the dao") void canBeCreated() { dao.create("Alice"); } @Nested @DisplayName("when created") class WhenCreated { Customer customer; @BeforeEach void setup() { customer = dao.create("Alice"); } @Test @DisplayName("it must be saved to the dao") void mustBeSaved() { assertEquals(1L, dao.save(customer)); } @Nested @DisplayName("after saving a customer") class AfterSaving { @BeforeEach void setup() { dao.save(customer); } @Test @DisplayName("it can be fetched from the dao") void canBeFetched() { assertTrue(dao.fetch(1L).isPresent()); } @Test @DisplayName("it cannot be deleted by wrong id") void cannotBeDeletedByWrongId() { assertThrows(IllegalArgumentException.class, () -> dao.delete(-77L)); } } } } 上記の例は、内側のテストが実行される前に、外側のテストのセットアップ・コードが実行されることを示しています。外側のテストにすでに存在している機能が内側のテストに含まれる場合、内側のテストのセットアップ・コードで何らかの重複が生じてしまう可能性はありますが、すべてのテストを独立して実行することができるようになります。外側のテストのセットアップ・コードは常に実行されるので、外側のテストを実行せずに内側のテストだけを実行することもできます。外側のテスト・メソッドで設定された状態を内側のテストで使いたい場合は、セットアップ・コードを省略できます。しかし、前述した、クラスごとのライフサイクル・セマンティックを使用する必要があります。 この例では、@DisplayNameアノテーションを多用しています。このアノテーションはJUnit 5の汎用ツールであり、Javaのメソッドの厳格なネーミング規則よりも柔軟にテストの名前付けを行うことができます。ネストしたテストを実行すると、結果は見やすい形でIDEに表示されます(図1参照)。適切な表示名を設定すれば、この階層構造はかなり仕様に近い表現になり、ビヘイビア駆動型開発フレームワークの表現力に匹敵します。 図1:IDEでのネストしたテストの実行 ネストしたテストを使うと、関連した複数のテストを階層的にまとめることができますが、関連した複数のテストを作成する仕組みは他に2つあります。具体的には、動的テストとパラメータ化テストですが、どちらも階層構造は持ちません。以降のセクションでは、この2つについて説明します。   動的テストと TestFactory パラメータ化テストとネストしたテストはJUnit 4でも何らかの形で使えましたが、動的テストという概念はJUnit 5でまったく新しく登場しました。この新機能は、@TestFactoryアノテーションという形態になっています。 このアノテーションが付加されたメソッドの中にテストはなく、メソッド自体もテストではありません。このメソッドでは、テスト・ケースのストリームまたは集合を生成します。TestFactoryという名前はそこから来ています。このストリームは実行時にのみ生成されます。作成時やコンパイル時には長さがわからないからです。ただしIDEでは、JUnitプラットフォームで公開されるリスナーを通じて、実行ツリーのストリームに含まれるテスト・ケース1つにつき、1つのエントリを表示できます。 JUnitプラットフォームのイベント・リスナーは、ツール・ベンダー向けの新しいJUnit 5 APIの一部であり、JUnitプラットフォームとIDEなどのツールとの統合が容易になります。動的テストでは名前も動的にできるので、柔軟で表現力が高いIDE表示を実現できます。IDEには動的テストの数を事前に知る手段はなく、動的な名前も当然わからないので、実行表示ツリーのエントリを実行時に生成しなければなりません。この処理は、それぞれの動的テストの開始イベントで始まります。 実行時にイベントを受け取るという柔軟性を実現できるのは、検出フェーズと実行フェーズを区別するというJUnitプラットフォームの考え方があるからです。検出時にはテスト・ファクトリのみが認識されますが、個々のテストは実行フェーズの、あるタイミングで表示されます。 ここではまず、非常にシンプルな例を紹介します。次に示すリストのコードは、2つのテストを生成するテスト・ファクトリを示しています。1つ目は成功するテスト、2つ目は失敗するテストです。この場合、2つのDynamicTestインスタンスのリストは固定であり、テストを書くときにわかっています。ここではあえて短い例にしましたが、動的テストの作成の基本を示すには十分です。実際のユースケースでは、少数の固定のテストを作成するなら標準(@Test)のテストを使う方がシンプルでしょう。 @TestFactory List<DynamicTest> simple() { return Arrays.asList( dynamicTest("first", () -> assertTrue(true)), dynamicTest("second", Assertions::fail) ); } 次の例もまだ多少作為的ではありますが、長さがわからないストリームを使ってテスト・ケースのリストを生成する機能を示しています。乱数のストリームを生成し、そのストリームの数値が10億を超えない限り取り出し続けます。その後に作成する実際のテストでは、正の数かどうかのチェックだけを行います。 Random random = new Random(); @TestFactory Stream<DynamicTest> withStream() { return Stream.generate(random::nextInt) .takeWhile(n -> n < 1000000000) .map(n -> dynamicTest("is positive? " + n, () -> isPositive(n))); } private void isPositive(Integer n) { assertTrue(n > 0); } このテスト・ファクトリを1回実行したときの結果を図2に示します。テスト・ファクトリは純粋に(そして多少恣意的に)動的に作成したので、もう一度実行すると、テスト結果は多くなるかもしれませんし、まったくなくなるかもしれません。 図2:IDEでの動的テストの実行 初歩的な例は純粋に技術的なものでしたが、次は金融領域でのユースケースについて考えてみましょう。動的テストは、多くの場合、外部リソースとの相互作用を検証する結合テストに利用できます。次の例では、支払いの有効性をチェックするタスクを与えられたと考えてください。まず、List<Customer> getCustomers()を使い、リモートの可能性があるサービスから複数のCustomerオブジェクトをフェッチします。次に、それぞれのカスタマーについて、Stream<Payment> findAllPayments(Customer customer)を使ってデータベース・リポジトリから個々の支払いのストリームを受け取ります。 次に示すのは、2つのストリームを動的に結合するテスト・ファクトリのコードです。 CustomerService customerService = new CustomerService(); PaymentRepository paymentRepository = new PaymentRepository(); @TestFactory Stream<DynamicTest> withCombinedStreams() { return customerService.getCustomers() .stream() .flatMap(c -> paymentRepository.findAllPayments(c)) .map(p -> dynamicTest("valid payment? " + p, () -> validate(p))); } void validate(Payment payment) { //いくつかのアサート } 上記のテスト・ファクトリ・メソッドでは、flatMap()を使ってカスタマーのストリームと支払いのストリームを結合しています。リモート・サービスから受け取るカスタマーの数も、各カスタマーの支払いの数もわからない可能性があるので、当然ながら結合ストリームのサイズはわかりません。つまり、この例は、生成されるテストの数が作成時にもコンパイル時にもテスト生成時にもわからない結合テストのユースケースを示していることになります。 もちろん、アサートを行うメソッドを繰り返すだけで、こういったテストに近いことを実現できます。しかし、その方法では、一連の論理テストが1つの物理テストにまとまってしまうことになります。単一テストで多数のアサートを繰り返せば、結果表示の本質が失われ、失敗時のデバッグははるかに難しくなります。 以上の例はすべて、このような機能はラムダ式を使ってエレガントかつ簡潔に実装できることを示しています。JUnit 5の初期計画フェーズにおいて、Java 5との下位互換性を維持しない理由の1つになったのが、この機能に代表される機能の表現力です。実のところ、JUnitの新バージョンの元になったプロジェクトは、まさに「JUnit Lambda」と呼ばれていました。 結局のところ、実行時に動作し、数が不明なテストにおいて、名前を柔軟に生成したいというユースケースに対応した軽量ツールが動的テストです。テスト・ファクトリで生成される、ストリーム内の個々のテスト・ケースは、成熟した標準のテストとして使うことを意図したものではありません。そのため、きめ細かなライフサイクル・メソッドはサポートされず、ライフサイクル・メソッドはテスト・ファクトリ・メソッドのみに適用されます。複数のテストのシーケンスでそれぞれのライフサイクル・メソッドを使う必要がある場合は、次に紹介するパラメータ化テストの方がよいでしょう。   パラメータ化テスト パラメータ化テストは、テスト・フレームワークにかかわらず頻繁に使われ、重宝されている機能です。朗報なのは、JUnit Jupiterテスト・エンジンがこの分野に絶大な進化をもたらしたことです。この種のテストはJUnit 4よりも冗長でなくなり、はるかに書きやすく、読みやすくなっています。また、新たな方法でパラメータ化テストを作成することもできるので、機能全体が緻密で強力なものになっています。 パラメータ化テストを試してみたい場合、その第一歩はjunit-jupiter-params依存性を追加することです。それよりも簡単なのが、バージョン5.4以降でjunit-jupiterアグリゲートを使用する方法です。このアグリゲートには、junit-jupiter-apiおよびjunit-jupiter-paramsのアーティファクトが両方とも含まれます。 繰り返しますが、新しいパラメータ化テストは、以前よりもはるかに冗長でなくなっています。ランナーもフィールドへのアノテーションも必要ありません。必要なのは@ParameterizedTestだけで、パラメータはメソッドの引数として渡します。次に示すのは、考えられる最小のパラメータ化テストの例です。 @ParameterizedTest @ValueSource(strings = {"Java", "JUnit"}) void a_very_concise_test(String name) { assertTrue(name.startsWith("J")); } 値は@ValueSourceアノテーションで提供されていることに注意してください。このアノテーションは、1つのプリミティブ・パラメータがある場合に便利です。値としてnullを使う必要があるテストでは、テストに@NullSourceアノテーションを付加します。また、空の値(文字列の場合は"")をテストする場合は、@EmptySourceや@NullAndEmptySourceを使うこともできます。 複数のパラメータを使うテストでも心配は無用です。@CsvSourceが役立ちます。このアノテーションを使うと、CSVのような形式で必要な数だけパラメータを渡すことができます。次の例をご覧ください。データが実際のファイルに格納されている場合は、@CsvFileSourceで注入することもできます。 @ParameterizedTest @CsvSource({ "Java, 4", "JUnit, 5" }) void a_slightly_more_complex_test(String name, int expectedLength) { assertEquals(expectedLength, name.length()); } ここでおもしろい点は、expectedLength引数の型です。@CsvSourceで渡せるのはStringパラメータだけであるにもかかわらず、この引数の型はintです。ここには、JUnit内部のちょっとしたマジックが働いています。この仕組みには、Stringをintなどの別の型として解釈できる暗黙的なコンバータが使われています。この例はさほど優れているようには見えないかもしれませんが、この仕組みは、File、Path、BigDecimal、URLなど、よく使われる多くの便利な型でも機能します。さらに、Java Data and Time APIのほとんどをサポートし、列挙型にも対応しています。すべての暗黙的コンバータのリストは、ユーザー・ガイドに掲載されています。 @ParameterizedTest @CsvSource({ "2020-06-01, JUNE", "2019-04-15, APRIL" }) void putting_those_converters_to_good_use(LocalDate date, Month expectedMonth) { assertEquals(expectedMonth, date.getMonth()); } 概して言えば、必要な定型コードがコンバータのおかげで少なくなり、テストは実際のテスト・ロジックに的が絞られるようになります。しかし、これが暗黙的コンバータであるなら、明示的コンバータもあるはずです。 実は、次の例のように、SimpleArgumentConverterを拡張して@ConvertWithの引数として渡すと、独自のコンバータを作成できます。 同様に、ArgumentsAggregatorを実装して@AggregateWithとともに使用すると、複数のパラメータを1つの引数にまとめるアグリゲータを書くこともできます。 @ParameterizedTest @ValueSource(strings = {"JANUARY", "FEBRUARY", "MARCH"}) void a_test_with_explicit_conversion( @ConvertWith(MonthToNumberConverter.class) int month) { assertTrue(month <= 3); } class MonthToNumberConverter extends SimpleArgumentConverter { @Override protected Object convert(Object source, Class<?> targetType) { Month month = Month.valueOf((String) source); return month.getValue(); } } パラメータのソースに話を戻します。さらに複雑なオブジェクトを作成する必要がある場合でも、メソッドを経由してパラメータのソースを提供するという方法があります。具体的には、Stream<Arguments>を返すメソッドを指すようにして@MethodSourceを使用します。Argumentsは、Jupiterでのパラメータのラッパーです。このソースは、前のセクションで行ったように、動的にテスト実行を生成するために使用できます。主な違いは、パラメータ化したテスト実行は動的テストとは違って、それぞれがライフサイクル・メソッドに対してスタンドアロンのテストとして振る舞うことです。 パラメータ化したテスト実行の表示名やテスト名は、カスタマイズすることができます。@ParameterizedTestのname属性をそのために利用できます。次の例に示すように、波括弧で囲んで位置を指定することで、名前にパラメータを挿入することもできます。@EnumSourceを使っている点にも注意してください。この方法もパラメータ化テストの作成に便利です。 @DisplayName("There are 12 months in a year") @ParameterizedTest(name = "Month #{index} --> {0}") @EnumSource(Month.class) void all_the_months(Month month) { // ... } There are 12 months in a year ├─Month #1 --> JANUARY ├─Month #2 --> FEBRUARY ... ├─Month #12 --> DECEMBER 拡張機能 これまでとは違い、JUnit Jupiterには、フレームワークとテスト作成者の両方が利用できる独自の拡張ポイントが存在します。その拡張ポイントがExtension APIです。このAPIは開発者にとって非常に使いやすいものになっており、直接実装できるいくつかのインタフェースで構成されています。実装が済んだら、あとはテスト・クラスで@ExtendWithを使って実装した拡張機能を登録するだけです。内部的には、このモデルの実装が相当複雑なのは間違いありません。しかしここでは、テストを書くときにどのように活用できるかについて紹介します。 結合テストで、メモリ内データベースを起動したり、外部サーバーをモックしたりする必要があるとしましょう。そのようなことは、おそらく@BeforeAllメソッドか@BeforeEachメソッドの中で行うでしょう。しかし、同じセットアップを必要とするテストが多数あるとしたらどうでしょうか。そのような場合は、ライフサイクル・コールバックが便利です。ライフサイクル・コールバックとは、BeforeEachCallbackやAfterTestExecutionCallbackといったもので、テスト・ライフサイクルの1ステップごとに1つの拡張機能が提供されます。次の例では、最初のテストの前と最後のテストの後にそれぞれフックするライフサイクル・コールバックを使用しています。 @ExtendWith(MyCoolExtension.class) class MyIntegrationTests { @Test void test() { // この時点で、すべてがすでに起動し、実行されている } } class MyCoolExtension implements BeforeAllCallback, AfterAllCallback { @Override public void beforeAll(ExtensionContext context) { // 実際のセットアップを行う } @Override public void afterAll(ExtensionContext context) { // すべてをシャットダウンする } } ParameterResolverも非常に便利な拡張機能です。この拡張機能は、オブジェクトをテストやライフサイクル・メソッドに引数として注入するものです。 class MyObjectResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == MyObject.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new MyObject(); } } @ExtendWith(MyObjectResolver.class) class MyTests { @Test void test(MyObject myObject) { assertNotNull(myObject); } } ランナーとは違い、1つのクラスに対して複数の拡張機能を登録できます。もちろん、カスタムの拡張機能を、他のフレームワークが実装するサードパーティ製の拡張機能とともに使うこともできます。現在のJUnitはメタアノテーションをサポートしているので、こういった拡張機能すべてを再編成したカスタム・アノテーションを作ることもできます。次に例を示します。 @ExtendWith({ MyCoolExtension.class, MyObjectResolver.class, SpringExtension.class }) @Retention(RetentionPolicy.RUNTIME) @interface CustomTest { } まとめ 本記事は、JUnitの拡張機能で実現できるすべてのことのうち、ごく一部を紹介したにすぎません。この簡単な概要によって好奇心を刺激されたという方は、ユーザー・ガイドをご覧になるとよいでしょう。すべての拡張機能が完全にドキュメント化されています。 本記事では、JUnit 5の3つの特別なテストである、ネストしたテスト、動的テスト、パラメータ化テストについて説明しました。そのすべては、テストをまとめたり、整理したりするために使用されます。さらに、表示名などのJUnit Jupiterの新機能と完全に統合されています。 JUnit 5では、数多くの重要なテスト機能が導入されたり、刷新されたりしています。JUnit 5チームは、このフレームワークを外部に開放するポイントを設けた一方で、APIを見つけることができて使いやすいという状態を保っています。その結果、フレームワークの柔軟性が格段に増し、テストのニーズを満たすための自在なカスタマイズが可能となっています。 まだJUnit 4を使っていてJUnit 5に移行したい方は、最近のJava Magazineに掲載された、Brian McGlauflin氏による移行に関する記事をご覧になるとよいでしょう。ぜひテストを楽しみましょう。 本記事のレビューを担当し協力してくれたJohannes Link氏に感謝をささげたいと思います。   さらに詳しく •    JUnit 5ホーム・ページ •    テストを容易にするJUnit 5.6の新機能 •    JUnit 4からJUnit 5に移行する:重要な違いと利点 •    JUnit 5:特別号 •    コード・レビューでの5つのアンチパターン •    PactでJavaマイクロサービスをテストする •    活動規則:ソフトウェアのテストを成功させるためのヒント •    クラウド・インフラストラクチャの目に見えないコスト •    テストを自動化すべきタイミング(と見送るべきタイミング)     Juliette de Rancourt フランスのパリを拠点とするCarbon ITのソフトウェア・エンジニア。好奇心旺盛で、日々新しいことを学ぼうとしている。JUnit 5チームのメンバーであり、Java、テスト、オープンソース、そしてすばらしい人々がそろったお気に入りの環境に囲まれている。 Matthias Merdes ドイツのハイデルベルクに拠点を置く、物理学者から転じたエンジニア。決済プロバイダheidelpayのシニア・ソフトウェア・アーキテクトで、5年前からコアJUnit 5チームに参加している。20年超にわたってサーバー・サイドJavaに携わり、Webシステムやストリーミング・システムのテスト中心開発に注力。プログラミングでは、ソース・コードのシンプルさと可読性をもっとも重視している。    

※本記事は、Juliette de Rancourt、Matthias Merdes による "Beyond the simple: An in-depth look at JUnit 5’s nested tests, dynamic tests, parameterized tests, and extensions" を翻訳したものです。 本記事を PDF でダウンロード(英語) 人気のJUnit...

Java Magazine

特定のクラウドに依存しないサーバーレスJavaをFn ProjectとGraalVMで構築する

※本記事は、Anton Epple による "Cloud-agnostic serverless Java with the Fn project and GraalVM" を翻訳したものです。 本記事を PDF でダウンロード(英語) 言語の選択はアプリケーションの移植性に重大な影響を与えるものであり、サーバーレス・アプリケーションを構築する新興企業にとって非常に重要となり得る September 25, 2020   クラウド・プラットフォーム、とりわけFunction-as-a-Service(FaaS)サービスは、新興企業にとって大きな味方です。使い始めるのが容易で、使った分だけ支払えばよく、高可用性と低メンテナンス性が保証されています。また、アプリケーションは需要の伸びに応じて自動的にスケーリングされます。 アーキテクチャに注目するときは、移植性について考えてください。移植性のあるクラウド・アプリケーションの標準を設けようという取り組みはありますが、ほとんどのプラットフォームで提供されているのは、独自の専用ツール群が組み込まれた統合システムです。これによって、使用を始めるのは簡単になる反面、使用をやめるのは難しくなります。 本記事は2回シリーズの1回目で、マルチクラウドでのアプリケーションの移植性を保つために役立つツールやベスト・プラクティス、テクノロジーに注目します。今回は、Fn ProjectとGraalVMネイティブ・イメージによるJavaネイティブ・サーバーレス・ファンクションの利点を中心に説明します。 本記事で紹介するツールやテクニックのデモとなるサンプル・プロジェクトを作成しています。記事の中で説明していますが、このコードをOracle Cloud Infrastructureにデプロイしたい場合のために、チュートリアルとヘルパー・スクリプトも作成しました。   クラウドを使ってみる 2018年のことでした。オフグリッド環境でも動作する電子鍵のアクセス権管理を行う新興企業のアイデアを持つ古い友人から、意見を求められました。ネットワーク接続機能のない、顧客の鍵やセンサーに対し、モバイル・アプリによってバックエンドへの接続を提供するという仕組みです。私たちは多少のプロトタイピングを行い、そう時間をかけることなく、このアイデアは実現可能であると判断しました。筆者もチームに参加することを決め、2018年11月に共同でSmart Access Solutions(SAS)を設立しました。 このような新興企業にとってIaaSは、データセンターの構築について心配せずに、すべての労力をプロダクト開発に振り向けることができるという点で重宝します。調査の結果、スケーラビリティとコスト管理のバランスが最適なIaaSオプションはFaaSモデルでした。FaaSモデルはサーバーレス・アーキテクチャとも呼ばれます。中核プロダクトはクラウドベースのシステムにすることを計画していたので、図1に示すようなサーバーレス方式を採用することにしました。 図1:Smart Access Solutions のセキュア・サーバーレス・クラウド   コードの移植性を維持する サーバーレスの世界は、サーバーを中心とした従来型のJavaの世界とはまったく異なります。広く採用されている基準がないこともあり、具体的なツールやテクノロジーを選ぶことが難しくなっていて、特定のプラットフォームへのロックインが起こりやすい状況です。さらに、専用のフレームワークやツール、サービスと密接に統合されたプラットフォームを使えば、開発の初期コストは驚くほど低下します。しかし、あるプラットフォームに特化したプラットフォームを選べば、後から別のプロバイダに移動するのは難しくなるかもしれません。 新興企業や多くの既成組織では、可能な限りプラットフォーム独立を維持するというのが優れた戦略です。つまり、クラウド・プロバイダが、あるサービスの提供を終了した場合などに備えて、移植性を考慮した設計をするということです。さらに優れたテクノロジーや魅力的な価格モデルを提供する別のプロバイダへの移動が、いつでも自由で、しかも比較的容易であるべきです。 アプリケーションの移植性には多くの要素が影響しますが、2つのクラウド・プロバイダを見て、それぞれのプラットフォームでアプリケーションを実行するために必要な設計を考えるという原則に従うとよいでしょう。 データやファイルの保存、ネットワーク、メッセージング、認証と認可、監視などのサービスは、プロバイダによって異なります。実際に設計を考えてみるという過程は、使用中のプラットフォーム固有サービスを特定するうえで役立ちます。そうすれば、プラットフォーム固有のサービスを、両方のプラットフォームで利用できる別のサービスで置き換えることにより、負の影響を最小限にとどめることができます。 移植性は、ビジネス・コードを独自仕様のAPIから守るための抽象化にも役立ちます。こういった抽象化を行ったり、APIコントラクトを強制したりすることがどのくらい簡単であるかは、プログラミング言語次第かもしれません。ほとんどのプラットフォームでは多くの言語ランタイムを提供しているので、どのプログラミング言語を使うかはそれほど重要ではないように思えます。しかし実際のところ、言語の選択は移植性に重大な影響を与えます。   プログラミング言語を意識する 例として、JavaScriptランタイムのNode.jsについて考えてみます。当社でも、Node.jsはいくつかの小規模な顧客プロジェクトで大変良好に動作しました。通常、サーバーレス・ファンクションは、リクエストごとにコンテナ化されたランタイムが起動してファンクションが実行されるような実行環境で動作します。JavaScriptには、Javaに比べてコールド・スタートの待機時間が短く、メモリのフットプリントが小さいという特徴があります。 その結果、サーバーレス・ファンクションがたまにしか起動しない場合でも、JavaScriptはうまく動作します。サーバーレス・ファンクションは実行時間とメモリ使用量によって課金されるため、JavaScriptを使う方がJavaを使うよりも安価になる可能性があります。さらなるメリットとして、JavaScriptの開発は、コンパイルの手順がなく、非常に高速であることが挙げられます。ファンクションは小さな独立したコード・ユニットなので、厳密なAPIコントラクトを定義して強制するという考え方が言語になくても、さほど問題とはならないはずです。そのため、Node.jsは優れた選択肢であるように思えました。しかし、私たちが選択したものは違います。 熟慮の結果、私たちは中核プロダクトのSAS Secure Cloud Core(アクセス権、センサー・データ、予想分析の管理を行うマルチテナント・システム)にJavaを使うことにしました。 その理由は、アプリケーションの移植性を可能な限り維持できるようにする必要があるためです。私たちの狙いは、希望したときにクラウド・プロバイダを切り替えられること、そしてアプリケーションをマルチクラウド環境でもオンプレミス環境でも実行できるようにすることにあります。 別のデータ・ストレージ、別のメッセージング・キュー、別のサービスにすばやく切り替えることができるように、ベンダー固有の部分はすべてビジネス・ロジックから分離し、抽象化の背後に隠蔽しておく必要があります。そこで登場するのがJavaです。 Javaには、パッケージ、メソッド、フィールドの可視性を細かく制御する仕組みがあります。インタフェースや抽象クラスを使うことで、APIやサービス・プロバイダ・インタフェース(SPI)の定義が容易になります。コンパイラでは、APIコントラクトが自然な形で強制され、意図しない使用を防ぎます。この特徴は、大規模なチームが協調して作業する際にぴったりです。 このような抽象化の典型的な例として挙げられるのが、データの永続化です。Javaでは、インタフェースを作るだけで、キーバリュー・ストア、リレーショナル・データベース、グラフ・データベースなどのどこにデータが格納されているかにかかわらず、透過性を保つことができます。次に示すのは、私たちが使用しているものを簡略化した例です。 public abstract class Repository <T>{ private final Class t; protected Repository (Class<T> t){ this.t = t; }; public abstract Optional<T> create(T entity); public abstract Optional<T> get(String id); public abstract List<T> getAll(); public abstract boolean update(T entity); public abstract boolean delete(String id); public final boolean canHandle(Class v) { return t.isAssignableFrom(v); } } ビジネス・コードには、ServiceLoader を提供できます。たとえば、次のようにします。 public class RepositoryServiceLoader { public static <T> Repository<T> getRepository(Class<?> t) { ServiceLoader< Repository> loader = ServiceLoader.load(Repository.class); for (Repository repository : loader) { if (repository.canHandle(t)) { return repository; } } throw new UnsupportedOperationException( "No Repository for "+t); } } これで、実際のサービス実装では、クラウド・プロバイダがサポートするものであれば、どんなリレーショナル・データベースやキーバリュー・ストアでも使用できるようになります。次に示すのは、テスト用に使用できるHashMapに基づいたダミーの実装です。 public class MockRepository extends Repository<MockElement>{ private static Map<String, MockElement> users = new HashMap<>(); public MockRepository() { super(MockElement.class); } @Override public Optional<MockElement> create(MockElement entity) { MockElement put = users.put(entity.id, entity); return Optional.of(entity); } @Override public Optional<MockElement> get(String id) { return Optional.ofNullable(users.get(id)); } @Override public List<MockElement> getAll() { return new ArrayList<>(users.values()); } @Override public boolean update(String id, MockElement entity) { MockElement get = users.get(id); return get == null ? false : users.put(id, entity) == null? false: true; } @Override public boolean delete(String id) { return users.remove(id)==null ? false: true; } } 実装は、Javaモジュールとして登録するか、またはクラスパスを使用して登録することができます。サービス・ロケータ・パターンが適切だと思わないなら、代わりにインジェクション・フレームワークを使ってサービスを提供することもできます。どちらも、Javaで十分にサポートされている単純で容易な方法です。 JavaScriptなどの動的言語では、このような機能が不十分です。1人か2人の開発者が携わる小さなプロジェクトでライフスパンが限られていれば、JavaScriptを使用しても安心です。しかし、ビジネスの中核を形成する大規模なマルチモジュール・プロジェクトの場合、サーバーレス空間での欠点はあるものの、Javaの方がはるかによく適合すると判断しました。 APIコントラクトが厳密に強制されることも大きな利点です。というのも、開発者を導いてくれるだけでなく、望まない依存性が意図せずに紛れ込むことを防いでくれるからです。APIのユーザーが、たとえ内部動作について何も知らなくても、APIを使用できることが必要です。 この条件を満たすうえで、Javaのような強く型付けされた言語が持つ価値は計り知れません。コントラクトに従ったコードを開発者が書くときにIDEによるアシストが得られ、コーディング中にドキュメントがIDEからエディタに直接提供されるからです。加えて、大規模プロジェクトで利用できるツールも非常に充実しています。Apache Mavenプロジェクト管理システムを使えば、マルチモジュール・ビルドの設定も簡単で、ビルド・アーティファクトの共有もMavenリポジトリを使って管理することができます。すばらしい機能を持つこのツールは、ほとんどのDevOpsプラットフォーム(中には無料のものもあります)に組み込まれています。   サーバーレス Fn Project Java(やその他の言語)でサーバーレス・ファンクションを記述するうえで、興味深いプラットフォームがFn Projectです。Fn Projectは、Dockerをベースにしたオープンソースのコンテナ・ネイティブなサーバーレス・プラットフォームで、Function Development Kit(FDK)を使ってビジネス・コードをDockerコンテナとしてパッケージ化します。 Fnは、特定のクラウドに依存しないことを目的としており、ランディング・ページには、「Fn Projectは、オープンソースのコンテナ・ネイティブなサーバーレス・プラットフォームで、どんなクラウドでも、オンプレミスでも、どこでも実行できます」と書かれています。そのため、移植性のツールボックスに追加するにふさわしいものだと考えました。 Fnのインストール・プロセスはシンプルです。まず、Dockerをインストールする必要があります。macOSとLinuxでは、シェルからFnサーバーとコマンドライン・インタフェース(CLI)をインストールすることができます。Windowsでは、Dockerのセットアップが難しいので、少なくともテスト用にはLinuxを実行する仮想マシンを使うとよいでしょう。 $ curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh $ fn start [ // 数行を省略 ] ______ / ____/___ / /_ / __ \ / __/ / / / / /_/ /_/ /_/ v0.3.339 time="2020-05-13T12:23:16Z" level=info msg="Fn serving on `:8080`" type=full ローカル・インストールが完了し、開発サーバーが稼働したら、FDKを使ってファンクションの開発を始めることができます。次のコマンドを実行すると、functionという名前の新しいディレクトリにMavenプロジェクトが生成されます。 $ fn init --runtime java function src フォルダの中に、次のデモ・コードがあります。 public class HelloFunction { public String handleRequest(String input) { String name = (input == null || input.isEmpty()) ? "world" : input; System.out.println("Inside Java Hello World function"); return "Hello, " + name + "!"; } } 生成されたソース・コードは、入力を文字列として受け取り、結果を文字列として返すメソッドです。フレームワーク自体には何の依存性もないので、抽象化を追加して、漏れ出してくる依存性からビジネス・コードを守る必要はありません。コードは完全な移植性をすでに備えています。デプロイを行うには、アプリを作成する必要があります。アプリとは、所属するファンクションをグループ化する単位です。その後、Fn CLIからファンクションを呼び出します。次の例をご覧ください。 $ fn create app java-app $ fn deploy --app java-app --local $ fn invoke java-app function time="2020-05-17T07:47:58Z" level=info msg="starting call" action="server.handleFnInvokeCall)-fm" app_id=01E88VPC6ANG8G00GZJ0000002 call_id=01E8GSN4RBNG8G00GZJ0000007 container_id=01E8GSN4RENG8G00GZJ0000008 fn_id=01E88VSCE1NG8G00GZJ0000003 Hello, world! ファンクションを HTTP エンドポイントから起動することもできます。まず、アドレスを確認する必要があります。たとえば、次のようにします。 $ fn inspect function java-app function { "annotations": { "fnproject.io/fn/invokeEndpoint": "http://localhost:8080/invoke/01E88VSCE1NG8G00GZJ0000003" }, "app_id": "01E88VPC6ANG8G00GZJ0000002", "created_at": "2020-05-14T05:51:18.465Z", "id": "01E88VSCE1NG8G00GZJ0000003", "idle_timeout": 30, "image": "function:0.0.2", "memory": 128, "name": "function", "timeout": 30, "updated_at": "2020-05-14T05:51:18.465Z" } $ curl -X "POST" -H "Content-Type: application/json" http://localhost:8080/invoke/01E88VSCE1NG8G00GZJ0000003 time="2020-05-18T10:55:10Z" level=info msg="starting call" action="server.handleFnInvokeCall)-fm" app_id=01E88VPC6ANG8G00GZJ0000002 call_id=01E8KPRMMPNG8G00GZJ000000F container_id=01E8KPRMMQNG8G00GZJ000000G fn_id=01E88VSCE1NG8G00GZJ0000003 Hello, world!% JSONによる入力を受け取るには、Plain Old Java Object(POJO)を受け取るように、メソッドのシグネチャを変更します。Fnでは、受け取ったJSONメッセージを自動的にPOJOのフィールドにマッピングし、そのフィールドをオブジェクトとしてハンドラ・メソッドに渡します。ここでも、コードに追加の依存性は必要ありません。   Kubernetes にデプロイする Fnのプロジェクトは、ソース・コードのレベルで移植性を備えているだけではありません。プラットフォーム自体も、任意のクラウド環境で実行できます。 Fnを本番環境で使用するために、プロジェクトでTerraformモジュールが提供されています。このモジュールは、監視やメトリックを含む本番対応環境をさまざまなクラウドにデプロイするためのものです。 唯一の前提条件となるのが、Kubernetesクラスタです。このクラスタはオンプレミスでも実行できます。当社では、ほとんどの顧客がホステッド・ソリューションを使います。しかし、中にはインターネットに接続しない顧客も存在するため、オンプレミスでファンクションを実行できることも1つの要件でした。 Kubernetesクラスタは、大部分のクラウド環境においてセットアップが容易であり、将来的な選択肢としては興味深いかもしれませんが、私たちが求めていた真のサーバーレス・アプローチではありません。ありがたいことに、真にサーバーレスな選択肢もあります。Oracle Functionsです。   Oracle Functions Oracle Functionsは、Oracle Cloud Infrastructureの一部として提供されています。 Oracle Functionsを使うと、Fnのファンクションをマネージド環境にデプロイすることができます。この作業は、Fn CLIから直接行うことができます(図2参照)。自分で試してみたい方は、無償のOracle Cloud Infrastructureテスト・アカウントがあれば十分です。セットアップとデプロイについては、前述のとおり、筆者が作成したチュートリアルで説明しています。 図2:セルフ・マネージドKubernetesクラウドを使ったローカルへのFnのデプロイと、フル・マネージドOracle FunctionsへのFnのデプロイ Fn Projectは、オンプレミスでも任意のクラウドでも実行できます。ホステッド・サーバーレス・ソリューションとしてOracle Functionsを使うこともできます。このプラットフォームではファンクションのコードに依存性が紛れ込むことがないので、別の環境でコードを再利用するのも簡単です。Fnがアプリケーションの移植性を保つうえで手堅い選択だと言えるのはそのためです。しかし、この時点で、Javaのコールド・スタートとメモリ使用量の問題はまだ解決されていません。この点について、別のプロジェクトが大変有用であることが判明します。GraalVMです。   GraalVM ネイティブ・イメージ Javaでサーバーレス・ファンクションの開発を始めたとき、私たちにとってコールド・スタートの問題は非常に顕著でした。ファンクションによっては、ベンダーが設定した実行タイムアウトのデフォルト値を増やさなければならなかったからです。 ストレージのサイズとメモリ使用量も問題でした。ここでも、デフォルト値を増やしてメモリ不足の問題を回避する必要がありました。そのため、GraalVMがリリースされたときは非常に興奮しました。 GraalVMはOpenJDKをベースとしたJVMおよびJDKです。また、高パフォーマンスなJVMベースの言語や、多言語アプリケーションを改善することを目的としたツールのエコシステム全体でもあります。オーストリアのリンツにあるOracle Labsによるこの偉大なプロジェクトに、筆者はずっと注目してきました。10年近く前にSun Microsystemsの「Maxine」プロジェクトからこのプロジェクトが生まれたときからです。特に注目すべき点は、GraalVMではJavaのコールド・スタート問題に対する解決策も提供されていることです。 通常、Javaアプリケーションは優れたパフォーマンスを発揮します。その理由は、HotSpot JVMのJust-In-Time(JIT)コンパイラでランタイムの動作を分析し、その情報に基づいて、コードのもっとも重要な部分のバイトコードをネイティブ命令にコンパイルしているからです。ただし、このプロセスにはウォームアップ期間が必要です。ウォームアップは、対象のコードが初めて実行されるときに行われます。さらに、アプリケーションの起動時には、クラスのロードと検証を行うためにいくらかの時間がかかります。新しいサーバーレス・コンテナが起動してリクエストに対応するたびに、無視できない遅延や、さらにはタイムアウトが発生することもあるのはそのためです。 それでは、どうすればサーバーレス・ファンクションがウォームアップされた状態を保つ、すなわち、コールド・スタートによる遅延を避けることができるのでしょうか。もっとも一般的な解決策は、ファンクションを実行するイベントを定期的に作成することです。これは基本的に、ステートレスなサーバーレス・ファンクションをステートフルなサーバーに変えることになります。 このアプローチには欠点もあります。各コンテナは一度に1つのリクエストにしか対応しないので、リクエストが増加すると新しいコンテナが起動することになります。そのためいずれにせよ、いくつかの同時リクエストに対応するには、コンテナのプールをウォームアップする必要があります。実際のリクエストをブロックせずに処理を早期終了できるように、コードを調整してウォームアップ・イベントを識別しなければならない場合もあるでしょう。そのため、使用していないときは無料であるサーバーレス・ファンクションのようなわけにはいかず、コンテナ化したサーバーのクラスタがアイドル状態であっても課金されることになります。 うれしいことに、GraalVMがリリースされたことで、このような細工を省くことができます。GraalVMでは、ネイティブ・イメージと呼ばれるネイティブの実行可能ファイルをJavaコードから生成できます。このプロセスの中で、native-imageツールにおいてコードの静的分析が行われ、必要な依存性のみがイメージに追加されます。そのため、イメージのサイズが必要最小限に抑えられます。   Fn で GraalVM を使用する Fn CLIで次のコマンドを実行すると、Fn Projectでネイティブ・イメージを使うように設定することができます。 $ fn init --init-image fnproject/fn-java-native-init graalfunc これで、Dockerfileを含む必要なリソースが作成されます。GraalVMで生成される実行可能ファイルはプラットフォーム固有なので、ターゲット・プラットフォームでイメージを実行するには、同じオペレーティング・システムでコンパイルする必要があります。たとえば、Apple macOSやMicrosoft Windowsで作成した実行可能ファイルは、当社のファンクション・コンテナで使っているBusyBox Linuxイメージでは動作しません。Fnでは、マルチステージDockerビルドを使ってこの問題を回避しています。 必要なツールはコンテナにすべて搭載されているので、開発者のマシンには何もインストールする必要がありません。さらに、非常に便利だと筆者が思うのは、このビルドではリフレクション用の構成、Javaネイティブ・インタフェース(JNI)、動的プロキシ、ランタイム自体のリソース・ローディングがすでに提供されていることです。 ちなみに、ネイティブ・イメージを別のクラウド・プロバイダのサーバーレス・プラットフォームで試してみましたが、すべてを適切に設定するためには、かなりの時間がかかるでしょう。Fnなら、「Hello World」サンプルは追加構成を必要とすることなく、すぐに動作しました。Fnとのシームレスな統合を行えば、作業が相当楽になるのはそのためです。コールド・スタートも高速化でき、メモリ使用量も減ります。 それでは、GraalVMを使って先ほどの「Hello World」ファンクションを呼び出す場合と、もともとのバージョンを使う場合を比較してみましょう。ローカルでの測定結果では、GraalVMを使ったときのコールド・スタート時間は約30%減少し、720ミリ秒から476ミリ秒になりました。同時に、メモリ使用量は18 MBから1 MBと、およそ20分の1になりました。 最後の1滴までパフォーマンスを完全に絞り出したいのであれば、GraalVMでアプリケーションを実行して最適化データを収集し、後でネイティブ・イメージにコンパイルする際に役立てることもできます。   まとめ 当社のSAS Secure Cloud Coreマルチテナント・システムでは、ツールとフレームワークの選定において、移植性と安定性を最重要視しました。その結果、Javaがサーバーレス・アプリケーションのすばらしい選択肢であることがわかりました。 成熟したビルド・ツールやテスト・ライブラリ、コード・リポジトリがあるJavaでは、大規模アプリケーションのメンテナンス性を保つためのすばらしい環境が提供されます。API設計者は、静的な型付け、きめ細かなアクセス修飾子、モジュール・システムによって、動的言語の場合よりも綿密にAPIを制御できます。 同時に、そういったAPIは、APIユーザーにとってのガイドにもなり、意図しない誤用を防いでくれます。さらに、言語に組み込まれたサービス・インフラストラクチャを含む、シンプルな階層化と抽象化によってビジネス・コードの移植性を保つ方法も提供されます。 Fnは、サーバーレス・プロジェクトの移植性と特定のクラウドに依存しない状態を保つ優れた手法です。このフレームワークでは、追加の依存性が紛れ込むことはないので、ソース・コードの移植性やテスト可能性が保たれます。ファンクションは、ホステッド・サービスにも、任意のクラウド環境にも、オンプレミスにもデプロイできます。GraalVMネイティブ・イメージとのシームレスな統合によってコールド・スタートの問題も解決され、メモリ・フットプリントも減少します。 Java、Fn、GraalVMは高度に一体化していながら、それぞれ独立して使用できます。どんな状況で使うのも自由です。 本シリーズの次回記事では、プラットフォームに依存しない、マルチクラウド・インフラストラクチャ管理ツールを導入して、このツールボックスを完成させたいと思います。   さらに詳しく Fn Project(英語) GraalVM:コンテナにネイティブ・イメージを格納する クラウド用Javaフレームワーク:高速起動の限界に迫る GraalVMネイティブ・イメージ(英語) プロファイルによる最適化でGraalVMネイティブ・イメージのパフォーマンスを改善する(英語) Fn Projectの概要(英語) Fn ProjectのTerraformモジュール(英語) Oracle Functionsの概念 Oracle FunctionsとFn Projectの相違点 Oracle FunctionsでのFn Project CLIの使用  

※本記事は、Anton Epple による "Cloud-agnostic serverless Java with the Fn project and GraalVM" を翻訳したものです。 本記事を PDF でダウンロード(英語) 言語の選択はアプリケーションの移植性に重大な影響を与えるものであり、サーバーレス・アプリケーションを構築する新興企業にとって非常に重要となり得る September...

Java Magazine

生産性向上に役立つ最新のJavaツール:型推論からテキスト・ブロックまで

※本記事は、Angie Jones による "Modern Java toys that boost productivity, from type inference to text blocks" を翻訳したものです。 本記事を PDF でダウンロード(英語) 古いバージョンのJavaプラットフォームを使っている開発者は損をしている October 23, 2020   Javaは業界で特に広く使われているプログラミング言語の1つですが、長年にわたり、冗長で不活性だという不当な悪評を買ってきました。確かに、もっとも基本的なことをするために大量のコードを書かなければならないこともあります。そして確かに、Java 7、8、9のリリース間隔はそれぞれ約3年でした。ソフトウェア開発の世界で3年というのは、とてつもなく長い時間です。 ありがたいことに、その声は聞き届けられました。Javaは待望の変化を遂げ、新しいバージョンの言語仕様が6か月ごとにリリースされるようになりました。最新バージョンは、2020年9月にリリースされたJava 15です。(※編集部注:2021年3月にJava 16がリリースされています。) Javaプラットフォームには非常に多くの機能があるので、ついていくのが難しいかもしれません。特に、アプリケーションを高速化し、書きやすくすることができる機能を見つけるのは大変でしょう。そこで本記事では、筆者が特に便利だと感じたJavaの新機能をいくつか紹介します。 ローカル変数の型推論 次の例では、Javaの規則に従い、クラスとオブジェクトの両方にわかりやすい名前を付けています。 AccountsOverviewPage accountsOverviewPage = page.login(username, password); しかし、見ればわかるように、同じ2つの名前が繰り返されています。これは冗長で、多くの文字を入力することになります。 Javaのバージョン10では、ローカル変数の型推論が導入されました。つまり、オブジェクト、すなわち変数の型を明示的に宣言するのではなく、varキーワードを使用できるというものです。すると、代入するものからJavaが型を推論してくれます。次に例を示します。 var accountsOverviewPage = page.login(username, password); この機能により、開発者による文字入力の手間が多少省けるとともに、言語の冗長性がいくらか減少します。 Javaが静的に型付けされた言語である点は変わらない:型推論を使っても、JavaがJavaScriptやPythonのような動的に型付けされた言語になるわけではありません。型が存在することは変わらず、型が文の右辺から推論されるだけのことです。つまり、varを使用できるのは、実際に変数を初期化する場合のみです。それ以外の場合、Javaでは型を推論できません。次の例をご覧ください。 var accountsOverviewPage; //コンバイル・エラーが発生 型推論ではグローバル変数の型は推論できない:「ローカル変数の型推論」という名前からわかるとおり、この機能はローカル変数でのみ動作します。varは、メソッド、ループ、決定構造の中で使用できます。しかし、変数を初期化している場合でも、グローバル変数でvarを使用することはできません。次のコードはエラーになります。 public class MyClass { var accountsOverviewPage = page. login(username, password); //コンバイル・エラーが発生 } 型推論はヘッダーでは許可されない:ローカル変数の型推論をローカル構造の本体で使うことはできますが、次の例で示すように、メソッドやコンストラクタのヘッダーで使うことはできません。これは、送信する引数のデータ型をコール元が知る必要があるからです。 public class MyTests { public MyTests(var data) {} //コンバイル・エラーが発生 } 型推論によってネーミングが今までに増して重要になっている:次の変数名とそれに続くメソッド名をご覧ください。xに対して推論されるデータ型が何になるかは、まったくわかりません。 Javaが型を知ることができるのは、getX()が返す内容に基づいて型を推論できるからです。しかし、コードを読む人は、getX()が何であるかを簡単に知ることはできません。そのため、この変数を扱うのが難しくなります。 適切な変数名を常に使うべきですが、varを使うとコンテキストの一部が削除されることになるので、ネーミングはさらに重要になります。 var x = getX(); すべてをvarにする必要はない:型推論は、一度使い始めればお気に入りの機能になるでしょう。しかし、過剰な利用は避けてください。 たとえば、次のようにvarを使っても、何の役にも立ちません。適切な理由なくコンテキストを削除することになるからです。 var numberOfAccounts = 5; 曖昧さにつながる可能性に注意する:次の宣言で推論される型は何でしょうか。 var expectedAccountIdsList = new ArrayList(); ObjectのArrayListだと思った方は、正解です。 これでも問題ない場合は多いものの、コレクションの要素に対してドット演算子(.)を使いたい場合は、Objectクラスで利用できるメソッドしか使えないことになります。 もう少し具体的な推論を行うには、代入の右辺にできる限り多くの情報を含めます。たとえば、ダイヤモンド演算子を使ってString型を指定すると、expectedAccountIdsListがStringのArrayListとして定義されるようになります。 var expectedAccountIdsList = new ArrayList<String>(); ストリームの効率を向上させる新しい操作 Java 9では、Stream APIに2つの新しい操作、takeWhileとdropWhileが導入されました。 takeWhile()操作では、コレクションのアイテムを処理し、与えられた条件(predicate)がtrueである間、そのアイテムを保持します。dropWhile()操作では、その逆となり、条件がtrueである間はコレクションのアイテムを無視します。 下記の例では、口座のリストを取得した後、takeWhile()を使って、タイプがCHECKINGである口座をすべて保持しています。ただし、コードがこのタイプでない口座に到達すると、そこで処理が終了します。 var accountsList = APIUtil.getAccounts(customerId); var checkingAccountsList = accountsList .stream() .takeWhile(account -> account.type().equals("CHECKING")) .collect(Collectors.toList()); 図1に示す口座リストを対象にした場合、タイプがCHECKINGに等しいことを条件としてtakeWhile()を呼び出すと、最初の3つのエントリが保持されます。条件に一致するエントリは他にもありますが、条件を満たさなくなった時点でストリームが終了します。4つ目の要素のタイプはSAVINGSなので、この要素に到達した時点でストリームは終わりになります。 図1:銀行口座のリスト 同じように(ただし逆の状況です)、このストリームに対してdropWhile()を呼び出すと、インデックスが3~10の要素が保持されます。dropWhile()では、最初の3つのエントリを削除します。この3つは条件に一致したからです。そしてタイプがSAVINGSである4番目の要素に到達すると、ストリームが始まります。 var accountsList = APIUtil.getAccounts(customerId); var checkingAccountsList = accountsList .stream() .dropWhile(account -> account.type().equals("CHECKING")) .collect(Collectors.toList()); 予測可能な結果を得るためにコレクションをソートする:条件に一致するすべての要素を収集または削除したい場合は必ず、takeWhile()またはdropWhile()を呼び出す前にストリームをソートします。たとえば、次のようにします。 var accountsList = APIUtil.getAccounts(customerId); var checkingAccountsList = accountsList .stream() .sorted(Comparator.comparing(Account::type)) .takeWhile(account -> account.type().equals("CHECKING")) .collect(Collectors.toList()); 4行目のようにしてコレクションをソートすると、条件に一致するすべての要素が、期待したとおりに保持または削除されます。   takeWhileとfilterの違い:よくある質問に、「takeWhileとfilterはどう違うのか」というものがあります。この2つでは、どちらも条件を使ってストリームを絞り込みます。 違うのは、filter()操作ではコレクション全体を参照して条件に一致するすべての要素を収集するのに対し、takeWhile()では条件に一致しない要素が見つかると操作が中止され、収集プロセスが一部省かれる点です。そのため、takeWhile()の方が高速です。 並列ストリームでtakeWhileまたはdropWhileを使用する:takeWhile()またはdropWhile()を並列ストリームで使用すると、ストリームがソートされている場合でも、パフォーマンスが損なわれます。 最高のパフォーマンスを得るために、これらの操作はスタンドアロンのストリームで使用することをお勧めします。   switch式 Java 12で、switch式が導入されました。これにより、switchを使って直接値を変数に代入することができるようになっています。次の例で、変数idを初期化するために、文の右辺にswitchを使っていることに注目してください。 String id = switch(name) { case "john" -> "12212"; case "mary" -> "4847474"; case "tom" -> "293743"; default -> ""; }; このコードは、名前がjohnであれば変数idに12212を代入することを表しています。 switch式ではcase文にコロンは必要ありませんが、代わりに矢印を使います。 switch式におけるフォールスルー:switch式にbreak文は必要ありません。switch式にはフォールスルーがないからです。これはswitch式を使うメリットの1つです。switch文でbreak文を忘れるというのはよくあるエラーで、予期しない動作につながります。switch式では、このエラーを回避できます。 しかし、1つのブロックで複数の条件値に対処した方がよいこともあります。その場合は、switch式の各条件値を、カンマで区切ったリストの形で指定します。次の例をご覧ください。 return switch(name) { case "john", "demo" -> "12212"; case "mary" -> "4847474"; case "tom" -> "293743"; default -> ""; }; 最初のケースでは、名前がjohnまたはdemoである場合に12212が返されることに注目してください。 switch式で追加のロジックを実行する:switch式の主な目的は値の代入ですが、その値を決定するための追加のロジックが必要になる場合もあるでしょう。 これを実現するために、switch式のcase文の中にコード・ブロック(波括弧で囲んだ文)を含めることができます。 ただし、switch式の最後の文はyield文である必要があります。この文では、代入する値を指定します。下記の例のjohnに対応するcase文をご覧ください。 return switch(name) { case "john" -> { System.out.println("Hi John"); yield "12212"; } case "mary" -> "4847474"; case "tom" -> "293743"; default -> ""; }; switch式から例外をスローする:任意のcase文で例外をスローすることができます。 return switch(name){ case "john" -> "12212"; case "mary" -> "4847474"; case "tom" -> "293743"; default -> throw new InvalidNameException(); }; 当然ですが、デフォルトのケースではフロー全体が例外によって中断されるので、値は返されません。 デフォルト以外のケースで例外をスローしても構いません。次に示すように、任意のcase文で例外をスローすることができます。 return switch(name){ case "john" -> "12212"; case "mary" -> throw new AccountClosedException(); case "tom" -> "293743"; default -> throw new InvalidNameException(); }; switch式を使うタイミング:switch 式 は言語に追加されたものであり、switch 文 を置き換えるものではありません。もちろんswitch文は引き続き使用できます。switch文の方がswitch式より適切な場合もあるでしょう。 経験則としては、値を代入するためにこの構造を使う場合はswitch式を使い、値を代入せずに条件に応じて文を呼び出す必要がある場合はswitch文を使います。   レコード レコードは新しいタイプのクラスであり、Java 14でプレビュー機能として導入されました。レコードは、フィールドと、そのフィールドにアクセスする方法だけが必要となるシンプルなクラスに適した機能です。Accountのモデルとすることができるレコードを示します。 public record Account( int id, int customerId, String type, double balance) {} classの代わりにrecordを使っていることに注意してください。また、クラス宣言に含まれる丸括弧内でフィールドが定義されており、その後に波括弧が続いています。 要するに、このシンプルな宣言により、丸括弧内で定義したフィールドを持つレコードが作成されます。getterやsetterを作成する必要はありません。equals()、hashCode()、toString()といった継承メソッドをオーバーライドする必要もありません。これらは、すべて自動的に行われます。 ただし、何かをオーバーライドしたい場合やメソッドを追加したい場合は、波括弧内で行うことができます。次に例を示します。 public record Account( int id, int customerId, String type, double balance ) { @Override public String toString(){ return "I've overridden this!"; } } レコードのインスタンス化:レコードは、クラスと同じようにインスタンス化することができます。次の例では、Accountがレコードの名前であり、newキーワードを使ってコンストラクタを呼び出し、すべての値を渡しています。 Account account = new Account(13344, 12212, "CHECKING", 4033.93); レコードは不変:レコードのフィールドはfinalなので、setterメソッドは生成されません。もちろん、レコードの波括弧内にsetterを追加することはできますが、フィールドはfinalで変更することはできないので、そのようにするもっともな理由はありません。 Account account = new Account(13344, 12212, "CHECKING", 4033.93); account.setType("SAVINGS"); //コンバイル・エラーが発生 同じ理由で、レコードをbuilderクラスとして直接使用することはできません。レコードのfinalフィールドを変更しようとすると、コンパイル・エラーになります。次の例をご覧ください。 public record Account( int id, int customerId, String type, double balance) { //コンバイル・エラーが発生 public Account withId(int id){ this.id = id; } } アクセッサ・メソッド:レコードにはアクセッサ・メソッドがありますが、getで始まる名前ではありません。アクセッサ・メソッドの名前は、フィールド名と同じになります。下記で、account.getBalance()ではなく、account.balance()を呼び出している点に注意してください。 Account account = new Account(13344, 12212, "CHECKING", 4033.93); double balance = account.balance(); 継承はサポートされない:レコードはfinalなので、他のクラスやレコードから継承することはできません。次の例のように、レコードの宣言でextends句を使おうとすると、コンパイル・エラーになります。 public record CheckingAccount() extends Accounts {} //コンバイル・エラーが発生 レコードはインタフェースを実装可能:しかし、レコードではインタフェースを実装できます。クラスと同じように、レコードの宣言でimplementsキーワードを使用し、実装するインタフェースを指定すると、レコードの波括弧内でメソッドを実装できます。次の例をご覧ください。 public interface AccountInterface { void someMethod(); } public record Account( int id, int customerId, String type, double balance) implements AccountsInterface { public void someMethod(){ } } テキスト・ブロック Java文字列で長く複雑なテキストのブロックを表現するのは、かなり面倒な作業になることがあります。次の例をご覧ください。どのようにしてすべての引用符をエスケープし、行末に改行文字を追加し、プラス記号で各行を結合する必要があるかに注目してください。 String response = "[\n" + " {\n" + " \"id\": 13344,\n" + " \"customerId\": 12212,\n" + " \"type\": \"CHECKING\",\n" + " \"balance\": 4022.93\n" + " },\n" + " {\n" + " \"id\": 13455,\n" + " \"customerId\": 12212,\n" + " \"type\": \"CHECKING\",\n" + " \"balance\": 1000\n" + " }\n" + "]"; Java 13で導入されたテキスト・ブロックを使うと、3つの引用符によって、長いテキストのブロックを開始および終了することができます。次の例をご覧ください。 return """ [ { "id": 13344, "customerId": 12212, "type": "CHECKING", "balance": 3821.93 }, { "id": 13455, "customerId": 12212, "type": "LOAN", "balance": 989 } ] """; 何もエスケープする必要がないことに注目してください。フィールドは個々に引用符で囲まれたままとなり、改行も保持されます。 開始の引用符と同じ行からテキストを始めることはできない:同一行にテキスト・ブロック全体を含めることはできません。そうすると、コンパイル・エラーになります。次の例をご覧ください。 return """ Hey y'all! """; //コンバイル・エラーが発生 次の例で示すように、開始の引用符の後で改行する必要があります。下記のようにすることは認められていますが、推奨される形ではありません。 return """ Hey y'all!"""; 推奨される形は、開始および終了の引用符の両方を、その間にある対象テキストとは異なる行にそれぞれ配置し、それらの引用符の位置と対象テキストの開始位置をそろえるというものです。次の例をご覧ください。 return """ Hey y'all! """;     まとめ 本記事では、最近のバージョンのJavaで登場した、筆者お気に入りの新機能の中からほんの一部を紹介しました。おわかりのように、冗長性の回避やプログラミングの最新トレンドの採用という領域で、Java言語は確かに進化しています。愛すべきJavaに乾杯。 さらに詳しく •    varとJava 10で拡張された型推論 •    Java 9で更新されたコア・ライブラリ:CollectionとStream •    Java 13のswitch式と再実装されたSocket APIの内側 •    Javaにレコードが登場 •    Javaにテキスト・ブロックが登場     Angie Jones Angie Jones:Java Champion。テスト自動化の戦略や技法を専門とする。世界中のソフトウェア・カンファレンスでの講演や指導を通して豊富な知識を発信するとともに、オンライン学習プラットフォームTest Automation Universityを主宰。革新的、独創的な思考スタイルを持つことでも知られ、米国と中国で25を超える特許を取得した発明の達人でもある。余暇には、テック業界に加わる女性やマイノリティを増やすための取り組みとして、少女向けのプログラミング・ワークショップBlack Girls Codeのボランティアとして指導に当たっている。  

※本記事は、Angie Jones による "Modern Java toys that boost productivity, from type inference to text blocks" を翻訳したものです。 本記事を PDF でダウンロード(英語) 古いバージョンのJavaプラットフォームを使っている開発者は損をしている October 23, 2020   Javaは業界で特に広く使われて...

Java Magazine

Helidon SEとWebSocketによるリアクティブ・ストリーム・プログラミング

※本記事は、Daniel Keo による "Reactive streams programming over WebSockets with Helidon SE" を翻訳したものです。 本記事を PDF でダウンロード(英語) Helidon SEを使用し、リモートのパブリッシャが一度に送信するデータの量をクライアントからパブリッシャに通知することで、クライアント・アプリケーションから非同期トラフィックを制御できる September 11, 2020   リアクティブ・ストリーム・プログラミングは、非同期ストリーム処理を扱う方法として人気が上昇しており、リアクティブなアプローチを採用するAPIは増加を続けています。リアクティブなアプローチでは、すべてが非同期でノンブロッキングでなければなりません。また、パブリッシャのペースが速い場合にサブスクライバが飽和しないように、フィードバックを行ってデータ・フローを制御する仕組みを実装する必要があります。 しかし、最適なソリューションはReactive Streams APIを使うことだと筆者は考えます。このAPIでストリームによる双方向通信を行えば、アプリケーションがどのくらいの負荷に耐えられるかをパブリッシャに伝えることができます。つまり、必要なバックプレッシャを適用して、ニーズや要求に応じてストリームを開始したり停止したりすることができます。図1をご覧ください。 この非同期双方向通信というニーズがあることで、ネットワーク経由でデータをリモートでストリーミングするという開発者の選択肢が限られてしまう可能性があります。もちろん、この問題に対処できる高耐久性のメッセージ・システムは存在します。そういったシステムでは、ネットワークを流れる大量の非同期トラフィックを扱えますが、かなり大がかりなインフラストラクチャが必要な場合が多く、おいそれと使えるものではありません。しかし、サーバーからクライアントへのリアクティブ接続という単純なユースケースで、大量のデータが送信されるわけではなく、サーバーによる送信データ量がクライアントからサーバーに通知される場合はどうでしょうか。 図1:Reactive Streams APIのパブリッシュ/サブスクライブ・モデル ネットワーク経由でパブリッシャとサブスクライバを接続するソリューションはすでに存在します。たとえば、RSocket、Reactive gRPC、ServiceTalkといったものです。これらのソリューションは仕様に準拠しており、すぐに使用できます。 しかし、すでにHelidon SEマイクロサービスのJavaライブラリでWebSocketなどの双方向ネットワーク・プロトコルを使っている方の場合、単純にWebSocketをそのまま使ってクライアント・サブスクライバをリモートのパブリッシャに接続するのはどれほど大変なことでしょうか。本記事では、その点に迫ります。 誤解のないように言っておきますと、Reactive Streams for JVM APIを自力で実装するのはかなり厄介な場合があり、通常は推奨されません。というのも、この一見単純そうに見えるAPIには、複雑な仕様があるからです。しかしそれと引き替えに、ストリームのアイテムのシリアル化を完全に制御でき、ネットワークの問題から回復する際の選択肢を広げることができます。 実際、WebSocketプロトコルはこのようなタスクに非常に適しています。Helidon SEでWebSocketを使う利点と欠点については、後ほど述べたいと思います。本記事では、Helidon SEを使用し、リモートのリアクティブ・サブスクライバにパブリッシュするためのWebSocketサーバーを作成します。そして、カスタムのリアクティブ・シグナルを定義し、そのシグナルをJSONにシリアライズし、JavaおよびJavaScriptベースのサブスクライバから(ストリーム経由で)サーバーに接続します。 そして、そのすべてがエンド・ツー・エンドでリアクティブです。 本記事のサンプルは、すべてGitHubに掲載しています。 先へと進む前に、Helidonでマイクロサービスを書く場合、2つのプログラミング・モデルがサポートされている点に注意してください。 Helidon SEは、リアクティブ・プログラミング・モデルをサポートするマイクロフレームワークです。今回はこちらを使用します。 Helidon MPは、Jakarta EEコミュニティがポータブルな形でマイクロサービスを実行できるようにするEclipse MicroProfileランタイムです。   サブスクライバとしての WebSocket サーバー・エンドポイント アプリケーションからネットワーク経由でリアクティブ・ストリームに接続するには、サーバー側に中間リアクティブ・サブスクライバ、クライアント側にパブリッシャが必要です。実際にはサーバー側がパブリッシュするので、少しわかりにくいかもしれませんが、このWebSocketネットワーク・ブリッジをストリームの中間で処理を行うプロセッサだと考えてください。つまり、サーバー側は上流ストリームのサブスクライバとして振る舞う必要があります。一方、クライアント側は下流ストリームのパブリッシャとして振る舞います。図2をご覧ください。 図2:WebSocketネットワーク・ブリッジの上流ストリームと下流ストリーム WebSocket APIでは、常にクライアント・エンドポイントからサーバー・エンドポイントに接続します。この2種類のエンドポイントにおける最大の違いは、サーバー・エンドポイントのインスタンスがWebSocketの実装によってすべてのクライアント接続に対してデフォルトで作成されることです。そのため、サーバー・エンドポイントをサブスクライバとして使うには、接続後にそのインスタンスにアクセスし、そこでリアクティブ・ストリームをサブスクライブする必要があります。 まず、このサンプル・アプリケーションのパブリッシャに対してStreamFactoryクラスを使い、クライアントが接続されるたびにパブリッシャを供給します。このファクトリでは、0.5秒間隔で10個の文字列アイテムを発行するというシンプルな新しいストリームを返します。 public class StreamFactory { private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4); public Multi<String> createStream() { return Multi.interval(500, TimeUnit.MILLISECONDS, scheduledExecutorService) .limit(10) .map(aLong -> "Message number: " + aLong); } } 次は、WebSocketで送信するシグナルを区別するための、シンプルなカスタム・ラッパーを作成します。この例では、文字列のストリームであると仮定します。簡素化するため、この例では、サブスクライブ・シグナル、すなわちonSubscribeシグナルは送りません。この簡素化の影響で、アプリケーションではWebSocket接続の準備ができるまで待機してからリクエスト・シグナルを送信しなければなりません。そのため、抽象化はさほど完璧ではありません。 public class ReactiveSignal { public Type type; public long requested; public String item; public Throwable error; public enum Type { REQUEST, CANCEL, ON_NEXT, ON_ERROR, ON_COMPLETE } public static ReactiveSignal request(long n) { ReactiveSignal signal = new ReactiveSignal(); signal.type = Type.REQUEST; signal.requested = n; return signal; } public static ReactiveSignal cancel() { ReactiveSignal signal = new ReactiveSignal(); signal.type = Type.CANCEL; return signal; } public static ReactiveSignal next(String item) { ReactiveSignal signal = new ReactiveSignal(); signal.type = Type.ON_NEXT; signal.item = item; return signal; } public static ReactiveSignal error(Throwable t) { ReactiveSignal signal = new ReactiveSignal(); signal.type = Type.ON_ERROR; signal.error = t; return signal; } public static ReactiveSignal complete() { ReactiveSignal signal = new ReactiveSignal(); signal.type = Type.ON_COMPLETE; return signal; } } この例では、WebSocketメッセージをJSON-Bでエンコードする方法が最適です。そうしておくと、後ほどJavaScriptからサーバーに接続する際に報われることになります。 public class ReactiveSignalEncoderDecoder implements Encoder.TextStream<ReactiveSignal>, Decoder.TextStream<ReactiveSignal> { private static final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withAdapters(new ThrowableAdapter())); @Override public ReactiveSignal decode(final Reader reader) { return jsonb.fromJson(reader, ReactiveSignal.class); } @Override public void encode(final ReactiveSignal object, final Writer writer) throws IOException { writer.write(jsonb.toJson(object)); } @Override public void init(final EndpointConfig config) { } @Override public void destroy() { } } 次に、WebSocketエンドポイントを準備します。このエンドポイントもFlow.Subscriberとなるので、StreamFactoryで作成されたパブリッシャを直接サブスクライブすることができます。このコードでは、エンドポイントでWebSocketメッセージがインターセプトされるためには、前提としてサブスクリプションが実現している必要があるものとしています。 public class WebSocketServerEndpoint extends Endpoint implements Flow.Subscriber<String> { private static final Logger LOGGER = Logger.getLogger(WebSocketServerEndpoint.class.getName()); private Session session; private Flow.Subscription subscription; @Override public void onOpen(Session session, EndpointConfig endpointConfig) { this.session = session; System.out.println("Session " + session.getId()); session.addMessageHandler(new MessageHandler.Whole<ReactiveSignal>() { @Override public void onMessage(ReactiveSignal signal) { System.out.println("Message " + signal); switch (signal.type) { case REQUEST: subscription.request(signal.requested); break; case CANCEL: subscription.cancel(); break; default: throw new IllegalStateException("Unexpected signal " + signal.type + " from upstream!"); } } }); } @Override public void onError(final Session session, final Throwable thr) { LOGGER.log(Level.SEVERE, thr, () -> "WebSocket error."); super.onError(session, thr); } @Override public void onClose(final Session session, final CloseReason closeReason) { super.onClose(session, closeReason); subscription.cancel(); } @Override public void onSubscribe(final Flow.Subscription subscription) { this.subscription = subscription; } @Override public void onNext(final String item) { sendSignal(ReactiveSignal.next(item)); } @Override public void onError(final Throwable throwable) { sendSignal(ReactiveSignal.error(throwable)); try { session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage())); } catch (IOException e) { LOGGER.log(Level.SEVERE, e, () -> "Error when closing web socket."); } } @Override public void onComplete() { sendSignal(ReactiveSignal.complete()); try { session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Completed")); } catch (IOException e) { LOGGER.log(Level.SEVERE, e, () -> "Error when closing web socket."); } } private void sendSignal(ReactiveSignal signal) { session.getAsyncRemote().sendObject(signal); } } WebSocketのAsyncRemoteを使ってメッセージを非同期式に送信している点に注意してください。これが必要なのは、リアクティブ・パイプラインではスレッドをブロックすることが禁止されているからです。 ここで唯一足りないものは、WebSocketサーバーとなるHelidon SEの起動です。起動後は、クライアントが接続してきたときに、作成したすべてのエンドポイント/サブスクライバが、StreamFactoryによって供給される新しいパブリッシャをサブスクライブします。こうすれば、下流ストリームのクライアントから最初のリクエスト・シグナルがWebSocketで着信したときに、上流ストリームはサブスクリプションの準備ができた状態になっています。 StreamFactory streamFactory = new StreamFactory(); TyrusSupport tyrusSupport = TyrusSupport.builder() .register( ServerEndpointConfig.Builder.create( WebSocketServerEndpoint.class, "/messages") .encoders(List.of(ReactiveSignalEncoderDecoder.class)) .decoders(List.of(ReactiveSignalEncoderDecoder.class)) .configurator(new ServerEndpointConfig.Configurator() { @Override public <T> T getEndpointInstance(final Class<T> endpointClass) throws InstantiationException { T endpointInstance = super.getEndpointInstance(endpointClass); if (endpointInstance instanceof WebSocketServerEndpoint) { WebSocketServerEndpoint endpoint = (WebSocketServerEndpoint) endpointInstance; //Endpoint is instantiated for every connection; lets subscribe it to the upstream streamFactory.createStream().subscribe(endpoint); } return endpointInstance; } }) .build()) .build(); Routing routing = Routing.builder() .register("/ws", tyrusSupport) .build(); WebServer.builder(routing) .build() .start(); サーバー側はこれで完成です。   パブリッシャとしての WebSocket クライアント・エンドポイント 次の手順は、クライアント側でFlow.Publisherを使い、アプリケーションでサブスクライブを行えるようにすることです。パブリッシャが従わなければならない仕様上のルールは多数ありますが、一番優先されるのは、サブスクライバのメソッドに逐次的にシグナルを送ること(JVMのReactive Streams仕様のルール1.3)と、下流ストリームからのシグナルをブロックしないこと(ルール3.4および3.5)です。ここでは、WebSocketで押し寄せてくる非同期シグナルからサブスクライバ自体を守れるように、Helidon SEのSequentialSubscriberクラスを実際のサブスクライバのラッパーとして利用します。リクエストやキャンセルのシグナルが、確実にノンブロッキングで処理を妨害しないようにするため、サーバー側と同じように、WebSocketのAsyncRemoteをそのまま使ってシグナルを上流ストリームに送信します。 public class WebSocketClientEndpoint extends Endpoint implements Flow.Publisher<String>, Flow.Subscription { private static final Logger LOGGER = Logger.getLogger(WebSocketClientEndpoint.class.getName()); private Session session; private Flow.Subscriber<? super String> subscriber; @Override public void onOpen(final Session session, final EndpointConfig endpointConfig) { this.session = session; session.addMessageHandler(new MessageHandler.Whole<ReactiveSignal>() { @Override public void onMessage(ReactiveSignal signal) { switch (signal.type) { case ON_NEXT: subscriber.onNext(signal.item); break; case ON_ERROR: subscriber.onError(signal.error); break; case ON_COMPLETE: subscriber.onComplete(); break; default: subscriber.onError(new IllegalStateException("Unexpected signal " + signal.type + " from upstream!")); } } }); } @Override public void onError(final Session session, final Throwable thr) { Optional.ofNullable(subscriber).ifPresent(s -> s.onError(thr)); LOGGER.log(Level.SEVERE, thr, () -> "Connection error"); super.onError(session, thr); } @Override public void onClose(final Session session, final CloseReason closeReason) { subscriber.onComplete(); super.onClose(session, closeReason); } @Override public void subscribe(final Flow.Subscriber<? super String> subscriber) { Objects.requireNonNull(subscriber, "subscriber is null"); // Notice usage of Helidon's SequentialSubscriber as a wrapper // to get around difficulties with specification rules 1.3, 1.7 this.subscriber = SequentialSubscriber.create(subscriber); subscriber.onSubscribe(this); } @Override public void request(final long n) { sendAsyncSignal(ReactiveSignal.request(n)); } @Override public void cancel() { sendAsyncSignal(ReactiveSignal.cancel()); } private void sendAsyncSignal(ReactiveSignal signal) { try { //reactive means no blocking session.getAsyncRemote().sendObject(signal); } catch (Exception e) { subscriber.onError(e); } } } あとは接続して何かをリクエストするだけです。その際には、同じエンコーダを再利用してメッセージをシリアライズします。今回接続するのはテスト・アプリケーション1つだけであるため、クライアント・エンドポイントは自分でインスタンス化することができます。 public class Client { private static final Logger LOGGER = Logger.getLogger(Client.class.getName()); public static void main(String[] args) throws URISyntaxException, DeploymentException, InterruptedException, ExecutionException { ClientManager client = ClientManager.createClient(); WebSocketClientEndpoint endpoint = new WebSocketClientEndpoint(); Future<Session> sessionFuture = client.asyncConnectToServer(endpoint, ClientEndpointConfig.Builder .create() .encoders(List.of(ReactiveSignalEncoderDecoder.class)) .decoders(List.of(ReactiveSignalEncoderDecoder.class)) .build(), new URI("ws://localhost:8080/ws/messages")); //Wait for the connection sessionFuture.get(); //Subscribe to the publisher and wait for the stream to end Multi.create(endpoint) .onError(throwable -> LOGGER.log(Level.SEVERE, throwable, () -> "Error from upstream!")) .onComplete(() -> LOGGER.log(Level.INFO, "Complete signal received!")) .forEach(s -> System.out.println("Received item> " + s)) .await(); } } 出力は次のようになります。0.5秒ごとに10個のアイテムが着信した後に、完了シグナルを受け取ります。 Received item> Message number: 0 Received item> Message number: 1 Received item> Message number: 2 Received item> Message number: 3 Received item> Message number: 4 Received item> Message number: 5 Received item> Message number: 6 Received item> Message number: 7 Received item> Message number: 8 Received item> Message number: 9 Jul 30, 2020 5:34:22 PM io.helidon.fs.reactive.Client lambda$main$2 INFO: Complete signal received! おわかりのように、forEachでWebSocketのパブリッシャをサブスクライブしています。その際にLong.MAX_VALUEをリクエストしていますが、これはサブスクライバがアイテムをいくつでも確実に消費できるという意味です。ありがたいことに、上流ストリームはアイテムを10個だけ送信して完了しました。   Java によるエラー・シグナル処理 何か問題が発生したらどうすればよいでしょうか。それについて考えてみましょう。まず、クライアントのコードが確実にエラー・シグナルのログを出力し、問題を報告するようにします。 Multi.create(endpoint) .onError(throwable -> LOGGER.log(Level.SEVERE, throwable, () -> "Error from upstream!")) .onComplete(() -> LOGGER.log(Level.INFO, "Complete signal received!")) .forEach(s -> System.out.println("Received item> " + s)) .await(); 次に、障害を発生させるために、4つ目のアイテムとしてエラー・シグナルを生成するようにStreamFactoryに指示します。 public class StreamFactory { public Multi<String> createStream() { return Multi.concat(Multi.just(1, 2, 3), Single.error(new Throwable("BOOM!"))) .map(aLong -> "Message number: " + aLong); } } コードを実行すると、次の結果が表示されます。スタック・トレースが生成されているのはJSON-Bアダプタのおかげです。 Received item> Message number: 1 Received item> Message number: 2 Received item> Message number: 3 Jul 31, 2020 4:30:11 PM io.helidon.fs.reactive.Client lambda$main$1 SEVERE: Error from upstream! java.lang.Throwable: BOOM! at app//io.helidon.fs.reactive.StreamFactory.createStream(StreamFactory.java:9) at app//io.helidon.fs.reactive.Server$1.getEndpointInstance(Server.java:67) at app//org.glassfish.tyrus.core.TyrusEndpointWrapper$1.getEndpointInstance(TyrusEndpointWrapper.java:225) これで、Java同士をリアクティブ・ストリームでつなぐソリューションが完成しました。次に、多言語環境に少し足を踏み入れたらどうなるでしょうか。   JavaScript によるフルスタック・リアクティブ・コーディング バックエンドにリアクティブWebSocketエンドポイントがある場合、そのエンドポイントをフロントエンドのリアクティブ・パイプラインに接続してみてはいかがでしょうか。 ここでは、アプリケーションをReactive Extensions for JavaScript(RxJS)ストリームに接続してみます。エンド・ツー・エンドでリアクティブ性を維持するため、リアクティブな命令を使ってカスタム・シグナルをRxJSストリームにマッピングします。以下のスニペットでは、takeWhile命令を利用して完全なシグナルを検出しています。ON_COMPLETEタイプが着信すると、RxJSストリームが完了します。 エラー・シグナルをマッピングするために最適なのは、flatMapと同じ機能をRxJSで持つmergeMapです。mergeMapを使ってON_ERRORタイプの任意のアイテムをストリーム・エラーにマッピングし、エラーをメイン・ストリームにフラット化します。エラーがON_NEXTだった場合は、アイテムを単にアンラップし、of(msg.item)を使ってフラット化します。 const { Observable, of, from, throwError} = rxjs; const { map, takeWhile, mergeMap } = rxjs.operators; const { WebSocketSubject } = rxjs.webSocket; const subject = new WebSocketSubject('ws://127.0.0.1:8080/ws/messages'); // Now I have to map the custom signals to RxJS subject.pipe( // Map the custom ON_COMPLETE to RxJS complete signal takeWhile(msg => msg.type !== 'ON_COMPLETE'), // Map the custom ON_ERROR to RxJS error signal or unwrap next item mergeMap(msg => msg.type === 'ON_ERROR' ? throwError(msg.error) : of(msg.item)) ) .subscribe( // invoked for every item msg => onNext(msg), // invoked when error signal is intercepted err => console.log(JSON.stringify(err, null, 2)), // invoked when complete signal is intercepted () => console.log('complete') ); これを接続しても何も起こりません。その理由は、バックエンドがバックプレッシャに対応しており、アイテム数のリクエストが届くのを待っているからです。そこで、カスタムのリクエスト・シグナルを送信するボタンを追加します。 const input = $("#input"); const submit = $("#submit"); submit.on("click", onSubmit); function onSubmit() { subject.next({"requested":input.val(),"type":"REQUEST"}); } 実際に動作する完全なサンプルをGitHubで公開しています。このサンプルを使って任意の数のアイテムをリクエストし、結果を視覚的に確かめてみてください。ストリームは新しい接続のたびに初期化されるので、10個のアイテムがなくなってストリームが完了しても、ページをリロードするだけで再度開始されます。図3にユーザー・インタフェースを示します。 図3:JavaScriptフロントエンドのユーザー・インタフェース   JavaScript によるエラー・シグナル処理 もう一度StreamFactoryを変更し、4つ目のアイテムとしてエラー・シグナルを生成します。 public class StreamFactory { public Multi<String> createStream() { return Multi.concat(Multi.just(1, 2, 3), Single.error(new Throwable("BOOM!"))) .map(aLong -> "Message number: " + aLong); } } カスタムのエラー・シグナルがJSONにエンコードされます。図4は、フロントエンドがコンソールに表示される様子を示しています。 図4:JavaScriptとJSON-Bによるリアクティブ・ストリームのエラー処理 おわかりのように、このアプリケーションではJavaスタック・トレース付きの完全な例外が出力されます。カスタム・シグナルをエンコードするためにJSON-Bが使われているからです。 まとめ WebSocketは、リアクティブ・ストリームによる通信に対応するほど十分に強力です。この点は、比較的小規模なアプリケーションや、制約のあるアプリケーションのユースケースではメリットです。長時間続くことが想定されるストリームや、数百万件の項目が流れてくる可能性があるストリームでは、WebSocketよりも大がかりなツールが必要です。 このトピックに興味を覚えた方には、Helidon MPで利用できるMicroProfileリアクティブ・メッセージングをお勧めします。また、バージョン2.0.0以降のHelidon SEに導入されている非CDI APIもご覧ください。   さらに詳しく 「Helidonが飛び立つ」(英語) 「マイクロサービスの開発からデプロイまで(パート1):Helidonを使ってみる」(英語) Project Helidon(英語) リアクティブ・プログラミングの概要 「JAX-RSによるリアクティブ・プログラミング」 「JDK 9のFlow APIによるリアクティブ・プログラミング」(英語) 「Eclipse Vert.xとRxJavaでリアクティブ・プログラミング」 WebSocket API(英語) GitHubのHelidon Full Stack Reactive(英語)     Daniel Kec Daniel Kecはプラハのオラクルに勤務するJava開発者。Helidon Projectに注力している。    

※本記事は、Daniel Keo による "Reactive streams programming over WebSockets with Helidon SE" を翻訳したものです。 本記事を PDF でダウンロード(英語) Helidon SEを使用し、リモートのパブリッシャが一度に送信するデータの量をクライアントからパブリッシャに通知することで、クライアント・アプリケーションから非同期トラフィ...

Cloud Security

3つの簡単なステップで多要素認証をアプリケーションに追加

※本記事は、Paul Toal による "Add Multi-Factor Authentication to applications in 3 easy steps" を翻訳したものです。 May 4, 2021 ほとんどの人が在宅ワークをしている今、セキュリティに関する会話で真っ先に認証が話題にのぼらないことはありません。ゼロトラスト・セキュリティへの関心と、ユーザーを理解し、ユーザーが認証され認可される方法を理解するための要件への関心が高まっていることと相まって、多要素認証(MFA)について、そしてどうすればOracle Identity Cloud Service(IDCS)を使用してこれをきわめて迅速かつ簡単にアプリケーションに追加できるかについてお話しするのに、今が良いタイミングだと思いました。 これを行うことで、認証プロセスのセキュリティが強化されるだけでなく、エンタープライズクラスのIdentity-as-a-Service(IDaaS)プラットフォームに認証を一元化し、認証を1か所で管理し、一貫性のある単一のフローをエンドユーザーに提供できます。 素晴らしいことに、たった3つの簡単なステップでこれを実行できます。今からその方法をご紹介しましょう。さあ、始めましょう。   ステップ1 – IDCSを使用してアプリケーションを統合する まず、アプリケーションの認証をIDCSに委任する必要があります。これを実行するには、アプリケーションと、アプリケーションが何をサポートしているかによって、多数の異なるアプローチがあります。これについては以前にお話ししましたが、要約するとおもに次の3つのオプションがあります。 SAML、OpenID Connect、OAuthなどのオープン標準ベースの統合 外部化された認証を把握するアプリケーション向けのヘッダーベースの統合 オプション1または2をサポートできないアプリケーションを対象にしたフォーム・フィル統合 多数の一般的なアプリケーション用にテンプレートが事前構築されている、IDCSのApp Catalogを使用して統合を加速させることができます。 このブログのために、上記のオプション1を使用し、App Catalogを用いてSalesforceをIDCSと統合しました。これは、SAMLベースの統合です。 私のテスト・ユーザー、Bobby Darrenを使用して、Salesforceへの標準のSSOをテストできるようになりました。 基本のSSOが機能してユーザーがIDCSに認証され、その後、ユーザーのIDがSalesforceに渡され、ユーザーがログインします。私のテスト・ユーザー、Bobby Darrenを使用して、Salesforceへの標準のSSOをテストできるようになりました。   ステップ2 – 使用したいさまざまな要素を有効にする SSOが機能するようになり、MFAをフローに重ねることができます。MFAポリシーをSalesforceに適用する前に、IDCSテナンシー内で使用したいさまざまな要素をステップ2で有効にします。 IDCSでは、下記のように広範囲の要素をサポートしています。 ここで必要なのは、Security → MFAメニューから使用したい要素を有効にすることだけです。この段階では、これらの要素はSalesforceアプリケーションに固有のものではありません。そうではなく、これらはIDCSインスタンス内でグローバルに有効にしたい要素です。ステップ3で示すように、アプリケーションごとにこれらのサブセットを選択できます。 有効にしたい要素にチェックを入れます。必要な場合は、Configureリンクを使用して各要素の構成を変更できますが、ほとんどの場合はデフォルトの構成でまったく問題ありません。信頼できるデバイスをここから有効化できることにも注意してください。これについては後ほど説明します。 場合によっては、チェック・ボックスがグレーに表示され、1つまたは複数のオプションが選択できないことがあります。その場合は、その機能を有効にするようOracle Supportにサービス・リクエストを送信してください。上記のインスタンスの例では、Duo Securityが有効化されていません。 ステップ3 – Salesforceのサインオン・ポリシーを作成する Salesforceアプリケーションを登録し、要素を有効化しました。最後のステップでは、SalesforceにMFAを適用するサインオン・ポリシーを構成します。 IDCS管理コンソールで、Security → Sign-on Policiesに進みます。デフォルトのポリシーが表示されます。当然のことながら、これは“Default Sign-On Policy”と呼ばれます。これは、IDCSで認証を受けたすべてのユーザーに適用される一般的なポリシーです。このポリシーを変更する場合、すべてのアプリケーションのすべてのユーザーに影響します(IDCS管理コンソールを含む)。 特定のアプリケーションにMFAを適用したいだけなので、デフォルトのポリシーはそのままにしておき、"Salesforce-MFA”という新しいポリシーを作成します。 この新しいポリシー内で、AppsタブからSalesforceアプリケーションにこれを割り当てます。 次に、MFAを実行するためのルールを定義します。これは、Sign-On Rulesタブから行います。このタブ内で、新しいルールを追加します。このルールがいつ実行されるかを決定するためのさまざまな条件を追加できます。この例では、すべてのSalesforceユーザーがSalesforceへのアクセス時にMFAを要求されるようにします。該当するユーザー全員を含むグループ(SFと呼ばれます)があるため、これをグループ・メンバーシップ条件に追加します。 ルールにAdaptive Security Conditionsを指定することもできることに注意してください。これにより、MFAの要求はリスクベースになり、静的条件ではなく動的条件に基づくものとなります。この詳細については、別の記事で取り上げます。 最後に、Actionsセクションに入力して、これらのユーザーが要求を受ける方法を決定します。この例では、要素のサブセットを1つだけ使用できるようにしています。具体的には、Mobile App Passcodeです。他のオプションはすべてチェックを外しています。また、FrequencyとEnrollmentの設定もデフォルトのままにしています。以下に、完成したルールを示します。   ここで、SFグループに属さないためにMFAによる認証を受けないユーザーへのアクセスを拒否するための2番目のルールを追加します。 完成したポリシーは以下のようになります。 ルールは順番に処理されることに注意する必要があります。ルールを組み直す必要がある場合は、下の3番目の列のグリッドを使用して、ルールを上下にドラッグできます。 ここで必要なことは以上です。これでポリシーをテストできます。 MFAポリシーを有効化した後に初めて認証を行う場合、必要とされる要素で登録されていないユーザーは、許可されている要素の登録を行うことになります。下記の例では、モバイル・アプリ要素のみが有効化されているため、Bobbyはモバイル・アプリに登録する必要があります。 ただし、Bobbyが最初の登録をすると、以降の認証で2番目の要素を求められます。この場合は、Bobbyはモバイル・アプリを使用しているため、下記のようにプッシュ通知がそのデフォルトになります MFA設定の中で信頼できるデバイスが許可されたため、Bobbyには現在使用中のデバイスを15日間信頼させるというオプションもあります。これは、そのデバイスからSalesforceにアクセスする場合、次の15日間はMFAを求められないことを意味します。信頼できるデバイスを有効にするかしないかについてサインオン・ルールの中で構成できることがお分かりでしょう。 これで完了です。3つの簡単なステップで、ターゲット・アプリケーションの1つに多要素認証を有効化できました。もちろん、この記事に示した通りにステップアップ認証を行う必要はありません。こちらで説明したように、FIDO2サポートによってパスワード不要の認証を使用して、当初のユーザー名とパスワードを置き換えるすることもできます。 このブログが皆さまのお役に立てば幸いです。もっと詳しく知りたいIDCSの特定の機能(またはいずれかのOCIセキュリティ機能)はありますか。もしくは、特定のユースケースの構成手順をご覧になりたいですか。遠慮なくお問い合わせください。それについて記事を作成いたします。 IDCSについて詳しくは、Oracle Identity Cloud Serviceを参照してください。  

※本記事は、Paul Toal による "Add Multi-Factor Authentication to applications in 3 easy steps" を翻訳したものです。 May 4, 2021 ほとんどの人が在宅ワークをしている今、セキュリティに関する会話で真っ先に認証が話題にのぼらないことはありません。ゼロトラスト・セキュリティへの関心と、ユーザーを理解し、ユーザーが認証され認...

Oracle Technology Network

Amazon AuroraからMySQL Database Serviceへの移行と初期コスト削減

※本記事は、Michel Gerin による "Migrate from Amazon Aurora to MySQL Database Service and Start Saving" を翻訳したものです。 オリジナルの記事はこちら(英語) May 4, 2021   データの管理にAmazon Auroraを使用している場合、同じデータをOracle Cloudで管理すれば、Amazonでのコストよりも⅓安く済むことを知っていますか? また、ETLを必要としないAnalyticsのクエリなど、Amazon Auroraに比べて1100倍の速度でクエリを実行できることを知っていますか? OLTPとOLAPのクエリに同じMySQL Database Serviceを使用すればよいのです。 最も重要なことは、MySQL Database Serviceは、OLAPのためのETLを必要としない唯一のMySQLのデータベースサービスであるということです。   Amazon RDSからMySQL Database Serviceへの移行方法に関するウェビナーをご覧いただけます 日本語版:オンデマンド・ウェビナー 〜Amazon RDSからOracle MySQL Database Serviceへの移行手順解説〜 ※登録必須 日本語版:パワーポイントのプレゼンテーションをダウンロードする(ログインが必要)   Amazon AuroraからMySQL Database Serviceへの移行方法に関するウェビナーをご覧いただけます 英語版:プレゼンテーション・パワーポイントをダウンロードする ※ログイン必須     ベンチマークを見る MySQL Database Service vs Amazon Aurora     MySQL Database ServiceとAmazon Auroraの比較について、Larry Ellison のコメントをご覧ください   さらに詳しく Heatwave の詳細はこちら HeatWave の MySQL データベースサービスをお試しください

※本記事は、Michel Gerin による "Migrate from Amazon Aurora to MySQL Database Service and Start Saving" を翻訳したものです。 オリジナルの記事はこちら(英語) May 4, 2021   データの管理にAmazon...

Java Magazine

内部動作に迫る:Javaのラムダ式は実際にはどのようにして動いているのか

※本記事は、Ben Evans による "Behind the scenes: How do lambda expressions really work in Java?" を翻訳したものです。 本記事を PDF でダウンロード(英語) バイトコードを調べて、 Java でラムダ式がどのように処理されているかを確認する September 25, 2020   Javaコードの内部、そしてJVMの内部で、ラムダ式はどのように見えるのでしょうか。何らかの種類の値であることは間違いありませんが、Javaではプリミティブタイプとオブジェクト参照という2種類の値しか許可されていません。ラムダ式がプリミティブタイプではないのは明らかであるため、オブジェクト参照を返す何らかの式であるに違いありません。 例を見てみます。 public class LambdaExample { private static final String HELLO = "Hello World!"; public static void main(String[] args) throws Exception { Runnable r = () -> System.out.println(HELLO); Thread t = new Thread(r); t.start(); t.join(); } } インナー・クラスに精通しているプログラマーなら、ラムダ式は実際のところ、Runnableの匿名実装を表すシンタックス・シュガーにすぎないと考えるかもしれません。しかし、先ほどのクラスをコンパイルしても、生成されるのはLambdaExample.classという1つのファイルだけです。インナー・クラスに対応する、その他のクラス・ファイルはありません。 つまり、ラムダ式はインナー・クラスではなく、別の仕組みであるはずです。実際、javap -c -pでバイトコードを逆コンパイルしてみると、2つのことがわかります。その1つは、次に示すように、ラムダ式の本体がプライベートな静的メソッドにコンパイルされ、メイン・クラスに含まれていることです。 private static void lambda$main$0(); Code: 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #9 // String Hello World! 5: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return このプライベートな本体メソッドと先ほどのラムダ式は、同じシグネチャを持つのではと思われるかもしれません。実はそのとおりです。次のようなラムダ式があるとします。 public class StringFunction { public static final Function<String, Integer> fn = s -> s.length(); } このラムダ式からは、次のような本体メソッドが生成されます。文字列を受け取って整数を返すので、関数型インタフェース・メソッドのシグネチャに一致します。 private static java.lang.Integer lambda$static$0(java.lang.String); Code: 0: aload_0 1: invokevirtual #2 // Method java/lang/String.length:()I 4: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 7: areturn このバイトコードに関して注目すべきもう1つの点は、mainメソッドが次に示すような構造になっていることです。 public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; 5: astore_1 6: new #3 // class java/lang/Thread 9: dup 10: aload_1 11: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V 14: astore_2 15: aload_2 16: invokevirtual #5 // Method java/lang/Thread.start:()V 19: aload_2 20: invokevirtual #6 // Method java/lang/Thread.join:()V 23: return このバイトコードがinvokedynamic呼出しで始まっている点に注目してください。この命令コードは、バージョン7でJavaに追加されたものです(そして、これまでにJVMバイトコードに追加された唯一の命令コードです)。メソッド呼出しについては、「ASMによるバイトコード処理の実際」と「Javaでのinvokedynamicによるメソッド呼出しを理解する」という記事で説明しました。いずれも本記事を読むうえで参考となるものです。 このコードのinvokedynamic呼出しを一番簡単に理解する方法は、特殊な形式のファクトリ・メソッドを呼び出していると考えることです。このメソッド呼出しでは、Runnableを実装した何らかの型のインスタンスを返します。バイトコードで厳密な型は指定されていませんが、その点はまったく問題とはなりません。 実際の型はコンパイル時には存在せず、実行時にオンデマンドで作成されます。もう少しわかりやすく説明するために、この機能を実現している3つの仕組みについて説明することにしましょう。その仕組みとは、コール・サイト、メソッド・ハンドル、ブートストラップの3つで、これらの仕組みが連携し合って動作しています。   コール・サイト バイトコードの中でメソッド呼出しが起こる場所のことを、コール・サイト(呼出し場所)と呼びます。 従来、Javaのバイトコードには各種の状況に応じてメソッド呼出しを扱う4つの命令コードがありました。その4つとは、静的メソッド、「通常の」呼出し(メソッドのオーバーライドを伴う可能性がある仮想呼出し)、インタフェースのルックアップ、「特殊な」呼出し(スーパークラスの呼出しやプライベート・メソッドなど、オーバーライドの解決が必要ない場合)です。 動的な呼出しであるinvokedynamicは、前述の4つよりもはるかに進化した機能で、実際にどのメソッドを呼び出すかをプログラマーがコール・サイトごとに判断する仕組みが提供されています。 その際、Javaヒープでは、invokedynamicのコール・サイトをCallSiteオブジェクトとして表現します。これは奇妙なことではありません。Javaでは、Java 1.1以降、Method、さらにはClassなどの型を使用して、Reflection APIで同様のことが行われてきました。Javaは実行時に多くの動的な振る舞いをします。そのため、現在のJavaが実行時の型情報(コール・サイトなど)をモデリングしていると知っても、何ら驚くことはないはずです。 invokedynamic命令に到達すると、JVMではその命令に対応するコール・サイト・オブジェクトを探します(初めて到達したコール・サイトの場合は、JVMでコール・サイト・オブジェクトが新規作成されます)。コール・サイト・オブジェクトには、メソッド・ハンドルが含まれています。メソッド・ハンドルは、実際に呼び出すメソッドを表すオブジェクトです。 このコール・サイト・オブジェクトはいわば必要なレベルの回り道で、関連付けられた呼出しターゲット(つまり、メソッド・ハンドル)を時間によって変更できるようにする仕組みです。 CallSiteは抽象クラスですが、利用できるサブクラスが3つあります。そのサブクラスが、ConstantCallSite、MutableCallSite、VolatileCallSiteです。ベース・クラスにはパッケージ・プライベートなコンストラクタしかありませんが、3つのサブタイプにはパブリックなコンストラクタがあります。つまり、ユーザーのコードでCallSiteを直接サブクラス化することはできませんが、サブタイプをサブクラス化することはできます。たとえばJRuby言語では、実装の一部にinvokedynamicを使っており、MutableCallSiteをサブクラス化しています。 注:invokedynamicのコール・サイトの中には、事実上、遅延計算されるだけというものもあります。その場合、ターゲット・メソッドは一度実行されると、その後に変更されることはありません。これはConstantCallSiteの非常によくあるユースケースで、そこにラムダ式も含まれています。 つまり、定数でないコール・サイトは、プログラムの生存期間を通して、多数の異なるメソッド・ハンドルがターゲットとして含まれる可能性があります。   メソッド・ハンドル リフレクションは、実行時に特殊な操作を行う際の強力なテクニックですが、そこには設計的欠陥が多数あります(もちろん、後からであれば何とでも言えます)。そして、間違いなく老朽化しています。リフレクションが抱える大きな問題の1つがパフォーマンスです。特に、Just-In-Time(JIT)コンパイラがリフレクションの呼出しをインライン化するのは困難です。 これが不都合なのは、インライン化がJITコンパイルにとってさまざまな面で非常に重要だからです。その理由の中でも大きいのが、一般的にインライン化は最初に適用される最適化であり、他のテクニック(エスケープ分析やデッド・コードの削除など)につながる扉を開くものであるためです。 2つ目の問題は、Method.invoke()のコール・サイトが登場するたびに、リフレクションの呼出しがリンクされることです。つまり、たとえばセキュリティ・アクセス・チェックが行われるということです。これは非常に無駄です。通常、このようなチェックは最初の呼出しで成功または失敗するもので、一度成功すればプログラムの生存期間を通して成功し続けるからです。それでも、リフレクションではこのリンク処理が何度も繰り返されます。そのため、リフレクションでは再リンクによる多くの不要なコストがかかり、CPUタイムが無駄に消費されます。 以上のような(そしてその他の)問題を解決するため、Java 7で新しいAPI、java.lang.invokeが導入されました。導入された主要なクラスの名前から、通常はメソッド・ハンドルと呼ばれています。 メソッド・ハンドルは、Java版のタイプ保証された関数ポインタです。メソッド・ハンドルは、JavaのリフレクションのMethodオブジェクトと同じように、コードで呼び出される可能性があるメソッドを参照する方法です。メソッド・ハンドルにはinvoke()メソッドがあり、実際にはこのメソッドがリフレクションと同じ方法で下層のメソッドを実行しています。 ある意味、メソッド・ハンドルは実際のところ、効率がよくなった、低レベルのリフレクションの仕組みでしかありません。Reflection APIのオブジェクトで表現されるものはすべて、等価なメソッド・ハンドルに変換することができます。たとえば、リフレクションのMethodオブジェクトは、Lookup.unreflect()を使ってメソッド・ハンドルに変換することができます。作成したメソッド・ハンドルを使用すると、大抵の場合、下層のメソッドにアクセスする際の効率が向上します。 メソッド・ハンドルに対してMethodHandlesクラスのヘルパー・メソッドを使うと、コンポジションやメソッド引数の部分バインディング(カリー化)などの方法によって、さまざまな適応処理を施すことができます。 通常、メソッドをリンクするには、型記述子が厳密に一致する必要があります。しかし、メソッド・ハンドルのinvoke()メソッドにはポリモーフィックで特殊なシグネチャが存在し、呼び出されているメソッドのシグネチャによらず、リンクを続行できるようになっています。 実行時、invoke()コール・サイトのシグネチャは、参照先のメソッドを直接呼び出しているように見えるため、型変換やオートボクシングのコストは回避されます。このようなコストは、リフレクションの呼出しでは一般的です。 Javaは静的に型付けされる言語であるため、このように根本的に動的な仕組みが使われる場合に、どの程度のタイプ保証が維持可能であるのかという疑問が生じます。メソッド・ハンドルのAPIでは、MethodTypeと呼ばれる型を使ってこれに対処しています。この型は、メソッドが受け取る引数、すなわちメソッドのシグネチャの不変表現です。 メソッド・ハンドルの内部実装は、Java 8の時代に変更されました。新しい実装はラムダ形式と呼ばれています。ラムダ形式により、パフォーマンスが劇的に向上しました。今では多くのユースケースで、メソッド・ハンドルのパフォーマンスがリフレクションのパフォーマンスを上回っています。   ブートストラップ バイトコードの命令ストリームの中で、あるinvokedynamicコール・サイトが初めて登場した場合、JVMではターゲットとするメソッドを認識していません。実は、その命令には何のコール・サイト・オブジェクトも関連付けられていません。 コール・サイトには、ブートストラップを行う必要があります。JVMでブートストラップ・メソッド(BSM)を実行し、コール・サイト・オブジェクトを生成してそのオブジェクトを返すというのが、ブートストラップの処理です。 それぞれのinvokedynamicのコール・サイトに、BSMが関連付けられています。BSMはクラス・ファイル内の専用領域に格納されます。ユーザーのコードにおいて実行時にプログラムによってリンクを決定できるのは、このメソッドのおかげです。 最初のRunnableの例を含め、invokedynamic呼出しを逆コンパイルすると、次のパターンが含まれていることがわかります 0: invokedynamic #2, 0 そして、クラス・ファイルの定数プールのエントリ#2が、CONSTANT_InvokeDynamic型の定数であることに注目してください。定数プールの関連部分は、次のとおりです。 #2 = InvokeDynamic #0:#31 ... #31 = NameAndType #46:#47 // run:()Ljava/lang/Runnable; #46 = Utf8 run #47 = Utf8 ()Ljava/lang/Runnable; 手がかりは定数に0が存在することです。定数プールのエントリは1から始まるので、0であることにより、実際のBSMがクラス・ファイル内の別の部分に存在すると気づきます。 ラムダ式では、NameAndTypeエントリが特殊な形式になっています。名前は任意ですが、型シグネチャには有用な情報が含まれています。 戻りタイプはinvokedynamicファクトリの戻りタイプに一致し、ラムダ式のターゲット型となります。同様に、引数リストも、ラムダ式が受け取る要素の型で構成されます。ステートレスなラムダ式の場合、戻りタイプは常に空になります。引数が存在するのは、Javaクロージャのみです。 BSMでは少なくとも3つの引数を受け取り、CallSiteを返します。標準の引数の型は次のとおりです。 MethodHandles.Lookup:コール・サイトが存在するクラスのルックアップ・オブジェクト String:NameAndType で示されている名前 MethodType: NameAndType の型記述子を解決したもの BSMで必要とされる追加の引数が、上記の引数に続きます。ドキュメントで、この追加の引数は追加静的引数と呼ばれています。 一般的なBSMでは非常に柔軟な仕組みが可能であり、非Java言語の実装者はこの仕組みを使用しています。しかし、Java言語では、任意のinvokedynamicコール・サイトを作成する言語レベルの構造は提供されていません。 ラムダ式のBSMは特殊な形式になっています。この仕組みがどのように動作するかを完全に理解するために、さらに細かく説明しましょう。   ラムダ式のブートストラップ・メソッドの解読 ブートストラップ・メソッドを確認するためには、javapで-v引数を使用します。これが必要なのは、ブートストラップ・メソッドがクラス・ファイル内の特殊な部分に存在しており、メイン定数プールを逆方向に参照しているからです。今回の単純なRunnableの例では、BootstrapMethodsセクションに1つのブートストラップ・メソッドが存在します。 BootstrapMethods: 0: #28 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String; Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #29 ()V #30 REF_invokeStatic LambdaExample.lambda$main$0:()V #29 ()V 少し読みにくいですが、解読していきましょう。 このコール・サイトのブートストラップ・メソッドは、定数プールのエントリ#28です。このエントリはMethodHandle型(Java 7で標準に追加された定数プールの型)です。それでは、次に示す、先ほどのStringFunctionの場合と比較してみましょう。 0: #27 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String; Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #28 (Ljava/lang/Object;)Ljava/lang/Object; #29 REF_invokeStatic StringFunction.lambda$static$0:(Ljava/lang/String;)Ljava/lang/Integer; #30 (Ljava/lang/String;)Ljava/lang/Integer; BSMとして使われているメソッド・ハンドルは、同じ静的メソッドLambdaMetafactory.metafactory( ... )です。 違っている部分はメソッドの引数です。この引数はラムダ式用の追加静的引数で、全部で3つあります。この追加静的引数が表しているのは、ラムダ式のシグネチャと、実際にラムダ式の最終的な呼出しターゲットとなるラムダ式本体へのメソッド・ハンドルです。3番目の静的引数は、型消去された形式のシグネチャです。 次は、java.lang.invokeのコードをたどってみましょう。そして、プラットフォームがメタファクトリを使って、ラムダ式のターゲット型を実際に実装したクラスを動的に生成する方法を確認してみます。   ラムダ式のメタファクトリ BSMではこの静的メソッドを呼び出します。この静的メソッドでは、最終的にコール・サイト・オブジェクトを返します。invokedynamic命令が実行されると、コール・サイトに含まれるメソッド・ハンドルでは、ラムダ式のターゲット型を実装したクラスのインスタンスを返します。 メタファクトリ・メソッドのソース・コードは、比較的単純です。 public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) throws LambdaConversionException { AbstractValidatingLambdaMetafactory mf; mf = new InnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType, implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY); mf.validateMetafactoryArgs(); return mf.buildCallSite(); } ルックアップ・オブジェクトは、invokedynamic命令が存在するコンテキストに一致します。この場合、そのコンテキストはラムダ式が定義されたクラスと同じクラスであるため、ルックアップ・コンテキストはラムダ式本体がコンパイルされたプライベート・メソッドにアクセスできる適切なパーミッションを持つことになります。 呼び出される名前と型を提供するのはVMですが、これは実装の詳細です。最後の3つのパラメータは、BSMの追加静的引数です。 現在のメタファクトリの実装では、シェードされた、ASMバイトコード・ライブラリの内部用コピーを使用するコードに処理を委譲することによって、ターゲット型を実装したインナー・クラスを生成しています。 ラムダ式がその包含スコープから何のパラメータも取得していない場合、結果として生成されるオブジェクトはステートレスです。そのため、実装では事前に1つのインスタンスを計算しておくことで最適化を行います。この場合、ラムダ式の実装クラスは実質的にシングルトンになります。 jshell> Function<String, Integer> makeFn() { ...> return s -> s.length(); ...> } | created method makeFn() jshell> var f1 = makeFn(); f1 ==> $Lambda$27/0x0000000800b8f440@533ddba jshell> var f2 = makeFn(); f2 ==> $Lambda$27/0x0000000800b8f440@533ddba jshell> var f3 = makeFn(); f3 ==> $Lambda$27/0x0000000800b8f440@533ddba ドキュメントにおいて、Javaプログラマーはラムダ式の識別セマンティックをいかなる形でも使用すべきではないと強調されていますが、その理由の1つはこの点にあります。   まとめ 本記事では、JVMでラムダ式のサポートが正確にはどのように実装されているかについて、かなり細かく説明しました。この機能は、今後目にするプラットフォーム機能の中でも特に複雑と言えるものの1つです。言語実装者の領域に深く踏み込んでいるからです。 説明の中で、invokedynamicとメソッド・ハンドルAPIについて触れました。この2つは、最新のJVMプラットフォームの主要な部分を占める重要なテクニックです。どちらの仕組みも、エコシステム全体で使用が増加しています。たとえば、invokedynamicは、Java 9以降において新しい形態での文字列連結の実装に使われています。 こういった機能を理解することで、Javaアプリケーションが依存するプラットフォームや最新フレームワークのもっとも内側の動作を見通す重要な手がかりが得られます。   さらに詳しく Java 8:ラムダ式(パート1)(英語) Java 8:ラムダ式(パート2)(英語) ASMによるバイトコード処理の実際 ASMバイトコード・フレームワーク(英語) ループ展開 進化するJavaインタフェース OpenJDK Project Lambda(英語) 第6章:Java仮想マシン命令セット(英語)

※本記事は、Ben Evans による "Behind the scenes: How do lambda expressions really work in Java?" を翻訳したものです。 本記事を PDF でダウンロード(英語) バイトコードを調べて、 Java でラムダ式がどのように処理されているかを確認する September 25, 2020   Javaコードの内部、そしてJVMの内部で、...

Java Magazine

世界を変えるJavaアプリケーション―ブラジルの医療システムを支えるコードなど

※本記事は、Alexa Morales による "The code underpinning the Brazilian healthcare system—and other world-changing Java applications" を翻訳したものです。   本記事をPDFでダウンロード(英語) Java の偉大なアプリ 25選に対する読者の反応 August 28, 2020   Javaの偉大なアプリケーション、フレームワーク、プラットフォーム、ライブラリ25選の掲載にあたっては、少しばかり不安がありました。何と言っても、開発者の皆さんは要求の厳しい方々です。しかしこの記事には、Reddit、Slashdot、Hacker News、Twitterなどで数多くのコメントが寄せられ、編集長宛てのメッセージも多数届きました。また、米国国家安全保障局やa.i. solutionsなど、25選に掲載された方々がソーシャル・メディアに喜びの声を投稿していました。 米国国家安全保障局は、本誌がGhidraバイナリ逆コンパイル・ツールに気づいたことをひそかに喜んでいました。 a.i. solutionsのチームも、DSTE軌道設計ツールが掲載されたことに満足していました。   その内容は、ポジティブで丁重なものでした。Java開発者の優れた特性がここに表れているのではないでしょうか。しかし、開発者の本質として、25選に何が記載されるべきだったかという意見も数多くありました。 Javaが世界を変えたのは素晴らしいことですが、25選に米国以外の世界が十分には反映されていなかったことは反省材料です。たとえば、ブラジルの医療情報システムと税システムを管理するために書かれたJavaコード(2005年のDuke's Choice Award受賞)があります。また、世界人口の30 %を占める、低中所得の72か国で採用されているJavaベースの国家医療管理情報システムDHIS2(実は世界最大)もあります。このシステムの新しいCOVID-19監視パッケージは、モバイルでの使用に最適化されており、各政府が地域において行うパンデミック追跡に役立てられています。   思い出に感謝 思い出を楽しく振り返ったという多くのコメントをいただきました。たとえばPeter Van Wazer氏は、1990年代後半にIBMの20人からなるプログラマーのチームでThin Client Managerを書いていたころのことを思い出したそうです。同氏によれば、Thin Client Managerは「Javaで書かれた初めての大規模ビジネス・アプリ」だったということです。 Van Wazer氏は、AS/400ミニコンピュータが誕生した米国ミネソタ州ロチェスターにあるIBMの事業所でJavaを初めて学んだ人々の中の1人でした。各タスクがネットワーク・ステーションとの間で情報を送受信するために実装しなければならない一連のインタフェースを作成するため、Javaでシン・クライアントのタスク管理ツールをコーディングしていました。実行時に問題が起きてもアプリケーション全体が停止しないように、タスク管理ツールでエラーの処理を行いました。 Van Wazer氏は、「マネージャーはSun Microsystemsから、『このタスク管理ツールはJavaで書かれた初めてのフレームワークであり、Java言語で意図したとおりのものだ』と言われました」と振り返っていました。 Van Wazer氏は現在もロチェスターにいて、Mayo Clinicで契約Java開発者として働いています。   開発者ソフトウェアの決定版 前述のブラジルのソフトウェアは、Java ChampionでMicrosoftのJavaプロダクト・マネージャーであるBruno Borges氏が指摘してくださったものですが、それ以外に25の偉大なJavaアプリに欠けていたものは何でしょうか。 Java ChampionのVlad Mihalcea氏は、Hypersistence Optimizerを提案しました。このソフトウェアは、開発者がJava Persistence APIやHibernate(このツール自体も、複数の読者が25選に含めるべきだったと考えています)を使用し、Javaオブジェクトをリレーショナル・データベースにマッピングするという困難な作業を進める際に役立つものです。 別のTwitterコメントでは、Cassandra、Spring Framework、Apache Spark、Hazelcastオープンソース・インメモリ・データ・グリッド、Apache Kafkaが挙げられていました。同感ではありますが、25という数にはこだわりたかったのです。   Javaベースの言語であるLinotteは、フランス語で書かれたコードだけでプログラミングを教えるものです。この話題は、今回の記事に関するTwitterスレッドに登場しました。そこから思い当たったのが、Javaベースのビジュアル・プログラミング環境であるVRL-Studioです。ドイツのゲーテ大学フランクフルトのリサーチ・サイエンティストであり、Java ChampionのMichael Hoffer氏が開発しました。   頼れる金融ソフトウェア Twitterでは、thinkorswimデスクトップ・トレーディング・アプリは完全にJavaで書かれているという情報も寄せられました。TD Ameritradeがその事実を認めたのは筆者にとって幸運でした。さらに言えば、2019年にTD Ameritradeを買収したCharles Schwabもthinkorswimソフトウェアの採用を計画しています。 ‬ 読者のVictor Duran氏は、「スウェーデンの経済全体をキャッシュレス化した」として、SwishというJavaアプリを推薦しました。2020年5月にSwishで取り扱われた金額は、250億スウェーデン・クローナでした。米ドルに換算すると28億ドル余りになります。Swishを運営する会社の広報担当者によると、バックエンドの一部がJavaで書かれているということです。   ゲームと視覚化 もちろん、Javaゲームにもたくさんの選択肢がありますが、RunescapeとOld School Runescapeを含めていないという声もありました。この2つは、現在に至るまで数百万人が楽しんでいるJavaベースの人気アプリケーションです。 Java Magazineで多数の記事を執筆しているIan Darwin氏が、JavaベースのCrossFireは世界トップクラスのクロスワード・パズル作成プログラムであると記していました。このプログラムは、New York Times、Washington Post、Times of Londonやその他の出版社に向けてパズルを作成している多くのエキスパートが使用しています。New York Timesのパズルを作成しているWill Shortz氏のためのカスタム・エクスポート形式まで存在するそうです。「そしてもちろん、クロス・プラットフォームです」とDarwin氏は述べています。 Redditを利用しているある読者は、Jake2が含まれていなかったのが残念だと述べていました。Jake2は、すでにメンテナンスが終了しているQuake2ゲーム・エンジンを移植したものです。ただし、Jake3のWebページはLightweight Java Game Libraryにもリンクされています。このライブラリは、JavaバインディングによってVulkan、OpenCL、OpenAL、LibOVRなどにアクセスし、GPUや仮想現実に関連した機能をクロス・プラットフォームで実現するためのものであり、活発に開発が行われている模様です。 Art of Illusionは、高度なモデリングとレンダリングが可能な、無料のオープンソース・スタジオです。グラフィック言語を使い、サブディビジョン・サーフェスを使ったモデリング、スケルトン・アニメーション、テクスチャやマテリアルの作成などを行うことができます。 Project Looking Glassを挙げたコメントもありました。2006年以降は活動が途絶えていますが、Project Looking GlassはSunがスポンサーとなっていた3Dデスクトップ環境です。川原英哉氏が開発を始め、サンフランシスコで開催されたLinuxWorld Expo 2003でデモが行われました。Looking Glassは、その後に誕生したユーザー・インタフェースのさまざまな面に影響を与えました。 Project Looking Glassのリバーシブル・ウィンドウ   効率的な通信 コメントで寄せられたように、WordPressとTelegramのモバイル・アプリはどちらもJavaで書かれています。そして、Telegramは自己破壊型の暗号化チャット機能により、アクティブ・ユーザーが4億人を超える、世界屈指の人気アプリとなりました。 Jitsiは、JavaおよびWebRTCベースの一連のアプリケーションで、テレビ会議、録画、サイマルキャストを安全に実現します。現在、Jitsiは企業向け通信会社である8x8が所有していますが、強力な開発者コミュニティも存在し、オープンソース・プロジェクトを支えています。開発者が無料のミーティングのために使う以上の何かを行う場合、おそらく一番簡単な方法はアプリケーションにJavaScript APIを埋め込むことです。そうすれば、meet.jit.siにホストされた安全なテレビ会議ウィンドウを開くことができます。その裏で、meet.jit.siは偶然にもOracle Cloud Infrastructureで動作しています。   科学と AI のアプリケーション効率的な通信 最後のカテゴリでは、CERNの複数の研究者から、大型ハドロン衝突型加速器(LHC)のソフトウェアやその他のデータ分析ソフトウェアの一部がJavaで書かれているという声が寄せられました。このJavaで書かれているソフトウェアの中に、LHCのデータを取得して保存するLHC Logging Serviceがあります。こちらの2006年の論文からわかるように、LHC Logging Serviceでは長年にわたってJavaが使われています。 読者のMike Tung氏は、自ら起業した会社Diffbotについて教えてくれました。「私たちはAIを使って世界最大の自律型知識グラフを構築しており、現在では、エンティティが10億個、事実が1兆個をそれぞれ突破しています。私たちは、おそらく最大級のJavaインフラストラクチャを運用しており、主にJavaで書かれたAIを使っています。数千個のCPUコアを使い、Web全体をクロールして分析しています」とTung氏は説明しました。 Tung氏は、DiffbotのチームにPure Javaによる最速の行列乗算ライブラリの作者や、Javaベースの有名な知識グラフの作者がいることも付け加えました。 そこでひらめいたのが、Oracle Labsで新しく公開された、Tribuoです。この機械学習ライブラリには、分類、回帰、クラスタリングのツールに加え、Javaプログラムからscikit-learnやPyTorchを使えるインタフェースが含まれています。 もう1つあります。Aftab Ahmad氏は、「人間の脳のデータに関するものが何もなかったので、驚いています。1990年代は、Javaだけでなく、脳の時代でした」と記していました。   Ahmad氏は、最初の言語としてJavaを学びたい工学分野の学生向けに開講される大学院レベルのコースで教える準備をしており、実際の研究プロジェクトに活用できる優れたAPIを探しています。 2013年にDuke's Choice Awardを受賞した、Neuroph Javaニューラル・ネットワーク・フレームワークは候補の1つかもしれません。このフレームワークを使って人工頭脳を作る方法を説明したチュートリアルまであります。もっと優れた候補をご存じの方がいらっしゃいましたら、ぜひお知らせください。   さらに詳しく Java の偉大なアプリ 25選 Java 14、Java 15、Java 16、およびそれ以降でのプレビュー機能の役割 JDK 最優秀機能トーナメント Java の 25 年:テクノロジー、コミュニティ、ファミリー(英語) パーソナル・メトリック:開発者が生産性を高めるための5つのヒント(英語) 2020年のJava(英語) Javaプログラミングのキャリアを加速させる(英語) Javaの昔と今:企業をサポートし続けてきた25年(英語)   Alexa Morales オラクルの開発者コンテンツ担当ディレクター。Software Development誌の元編集長。15年超にわたり、テクノロジー・コンテンツ・ストラテジストおよびジャーナリストとして活動している。

※本記事は、Alexa Morales による "The code underpinning the Brazilian healthcare system—and other world-changing Java applications" を翻訳したものです。   本記事をPDFでダウンロード(英語) Java の偉大なアプリ 25選に対する読者の反応 August 28, 2020   Javaの偉大...

全国のOTNおすすめ技術者向けセミナー&イベント(2020年5月)

        5/12 【まずはここから】 はじめてのOracle Cloud Infrastructure オラクルでは最新アーキテクチャに基づいて設計された Oracle Cloud Infrastructure を日本国内では、東京・大阪の2拠点に、また世界各国で提供しています。 本セミナーでは、Oracle Cloud Infrastructure の特徴をすでに活用されているエンタープライズからスタートアップまでの様々なお客様の事例を交えてご紹介します。 5/12 OCHaCafe4 #2 Kubernetesのネットワーク OCHaCafe Season "おちゃかふぇ" Season 4の第2回目は、Kubernetesのネットワークに関する入門編をお届けしたいと思います。 - Dockerネットワークの基礎 - Kubernetesが求めるネットワーク要件とネットワーク・モデル - コンテナのネットワークインタフェース(CNI)と代表的なプラグイン - Pod間通信のセキュリティとネットワークポリシー 等々の基礎的な話題と、ネットワークポリシーのデモをご覧いただく予定です。 特にKubernetesについてこれから本格的に学ぼうとしている方々にお勧めしたいアジェンダとなっています。   5/13 【基本を知ろう】新サービスがついにリリース!データ統合分野の最新サービス情報御紹介  クラウド化が加速するIT環境において、データ統合・連携、ETL、CDCなどといったテクノロジーを適切に実装することが肝要となってきます。本ウェビナーでは、既にリリース済みのOCI Data Integrationなどのデータ統合領域でのサービスに加え、最新のサービス情報を共有させて頂きます。   5/14 一歩進んだ分析でビジネスにチカラを!! ~あなたもデータ アナリスト~  多種多様なデータが増え続けるなか、Excelを使用したデータ分析に限界を感じている方も多いのではないでしょうか? 本ハンズオン・ワークショップでは、データの可視化・分析クラウド サービスであるOracle Analytics Cloudを活用し、一歩進んだ分析を体験していただけます。動画をご覧になり、ご自身のペースでハンズオンを実施して頂き、随時、チャットによるご質問をお受けします。 5/21 MySQL Database Service HAが遂にリリース!検証結果祭り♪ お待たせしました!MySQL Database ServiceのHA (High Availability)機能がリリースされました。 今回のMySQL Technology Cafeでは、早くも3社のクラウドスペシャリストたちにお集まりいただき、MySQL Database Service HA機能の検証結果を共有いただきます! ブログには書かれていないあれこれ細かい話もどんどん質問してください。 テスト結果の報告だけではなく、パラメータやユーザー数などのテスト条件、テストを実施した背景のエピソードなども参考になると思います! 日本オラクルからも、MySQL Database Service HAの最新情報をお話しします。是非ご参加ください♪ 5/25 BlockchainGIG #9:今度こそわかる!Hyperledger Fabric(再)入門 今回は「そろそろHyperledger Fabricをわかってほしい」「今度こそHyperledger Fabricをわかりやすく説明したい」の願いを込めて、Hyperledger Fabric(再)入門をテーマにしてお届けします!   主要なエンタープライズブロックチェーン基盤に数えられ、その中でもユースケースでの実績数や開発者数がとりわけ多いHyperledger Fabric。一方で、「難しそう」「とっつきづらい」といったイメージをお持ちの方が多くいます。 なぜHyperledger Fabricは難しいと言われがちなのか?どうしたらできるだけ理解しやすく学べるのか?それらへの答えとしてここでは、コンポーネント、ネットワーク構造、トランザクション、そしてそれらを貫く設計思想について、特に複雑なところ、Hyperledger Fabric独特なところをピックアップし、重点的に解説していきます。 また、特別ゲストとして、Linux Foundation 日本担当バイスプレジデント 福安様より、グローバル含めた最近のHyperledger Fabricのトレンドや、日本におけるHyperledger Fabric動向をお話いただきます。 これを機会に、ぜひHyperledger Fabricを学んでください! 5/26 実例解説!侵入口から考える4つの脅威への最後の砦 企業のセキュリティ意識は高まっているにも関わらず、内部不正をはじめとする事件が多く発生しています。攻撃手法や手口・経路が異なり、必要な対策も違ってきます。現実的に起こりえる、具体的な攻撃シナリオをもとに、機密情報を格納したデータを中心とした防御の論点をご説明します。 5/26 新機能 Oracle APEX 21.1 紹介! Oracle APEXは1年に2回、アップデートがリリースされるよう計画されています。 そして、そろそろ、21.1のリリースが行われる予定です! 今回の勉強会では、Oracle APEX 21.1に含まれる新機能を紹介します。今回のアップデートも、多数の機能拡張が含まれています。 以下の新機能について紹介する予定です。 ・ネイティブ・マップ・コンポーネント ・Oracle JETコンポーネントの採用(日付ピッカー, カラー・ピッカー) ・データ・ローディング・ウィザードの拡張 ・ユニバーサル・テーマの機能拡張 ・ファセット検索と動的アクションの機能拡張 ・APEXから利用できるOracle Database MLE (MultiLingual Engine) ・FullCalendar v5の採用 ・マークダウンのネィティブ・サポート etc. 現時点では正式なリリース日は決まっていません。また、予定されている新機能も増減する可能性があります。そのため、リストした紹介する新機能は、あくまでも予定であり、リリースを確約するものではありません。 ※準備や時間の関係で、予定している項目の一部変更がある場合がございます。その際はご了承お願いいたします。   5/27 次世代クラウドデータウェアハウス 〜 誰もがデータ活用できるセルフサービス分析と機械学習 〜 データはあらゆる組織にとって重要なリソースであり、その価値を最大化するためには、組織やアプリケーションを越えて共有され、相乗効果が発揮される基盤が必要です。クラウドデータウェアハウスであるOracle Autonomous Data Warehouseは、サービス提供開始以来、数多くのお客様のデータ活用を支えてきました。 今回、Oracle Autonomous Data Warehouseはセルフサービス機能を大幅に拡張しました。これにより、アナリスト、シチズン・データサイエンティスト、業務部門ユーザーといったビジネス現場の利用者が、ITや他の専門家に助けを求めることなく、データの取り込み、洞察、機械学習を迅速に実行し生産性向上に役立てることが可能となっています。 セルフサービス機能のご紹介、デモンストレーションとともに、国内企業を含む活用事例をお話します。 5/27 マルチテナント・アーキテクチャ最新情報 マルチテナント・アーキテクチャは、Oracle Database 21c から中核となる技術です。 今回は、過去にお届けしてきたマルチテナントの概要説明を継承しつつ、Converged Database の基盤として期待される技術の最新情報も含めお届けします。   5/28 実践Kubernetesハンズオン ~OKEでKubernetesをバーチャル体験しよう~ Oracle Cloudは大規模なコンテナの管理、デプロイおよび運用に適したクラウドサービスを複数提供しています。 本ハンズオンセミナーではOracle Cloudが提供する下記コンテナ・サービスを実際に利用しKubernetes環境の構築からコンテナ・アプリケーションのデプロイ、CI/CDまでの一連の流れを体感いただけます。 5/31 機械学習入門:次世代AI「GAN」による仮想現実の生成 これまで同様、未経験および初学者の方を対象に、基礎的な内容で実施いたします。 今回は次世代AIの代名詞となっているテクノロジー「GAN(敵対的生成ネットワーク)」をテーマとして取り上げます。 研究が盛んなディープラーニング技術の中でも特に注目されているトレンドテクノロジーです。 既存データから、実在しない人物や、実在する著名人のデータを生成するなど、いわゆるフェイクニュースで何かと世間を騒がせている技術ですが、フェイスブック社のAI研究所所長であるAI先駆者、ヤン・ルカン氏が「機械学習において、この 10 年間でもっともおもしろいアイデア」と形容するGANに迫ります。 また、前回同様、「社会課題xテクノロジー」をコンセプトに活動するOracle DX推進室より横山氏を迎え、 デモを交え、実コードの概説をご紹介するセッションも予定しておりますので、 理論と実践を学べるmeet upとしてご期待ください。 AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。

        5/12 【まずはここから】 はじめてのOracle Cloud Infrastructure オラクルでは最新アーキテクチャに基づいて設計された Oracle Cloud Infrastructure を日本国内では、東京・大阪の2拠点に、また世界各国で提供しています。本セミナーでは、Oracle Cloud...

Database

Oracle Multimediaからsecure-file blobデータ型へのシンプルな移行

※本記事は、Bogapurapu laxmi krishna Raoによる"Simple migration from Oracle multimedia to secure-file blob data type"を翻訳したものです。   April 7, 2021   Oracle Database 19cのリリースではOracle Multimediaデータ型へのサポートが終了していますので、19c以降のリリースでこのデータ型を扱う方法についてガイダンスをお伝えしたいと思います。 オラクルでは、マルチメディア・コンテンツをSecureFiles LOBに格納し、APEX Multimedia Extension(AME)などサード・パーティ製品を使用することをお勧めしています。詳細は、『Desupport of Oracle Multimedia Component in Oracle 19c』(Doc ID 2555923.1)のドキュメントでシェアしています。 現在、Oracle Multimediaデータ型を使用していて、Oracle Database 19cへのアップグレードを計画されている場合は、既存のマルチメディア・コンテンツをSecureFiles LOBに移行する必要があります。 それでは、移行を行う方法をいくつか見ていきましょう。   解決策1:同じ表で列を更新する プロセスの概要: 既存の表のバックアップを取る BLOB データ型と securefile の新しい列を追加する ORDIMAGE 列のデータを BLOB列に更新する 表の古い列に unused と設定する 新しい列の名前をもとの列名に変更する 古い列名を削除する   この例で使用するオブジェクト: マルチメディア・データ型がある表 image_table/image_table_bkp マルチメディア・データ型が格納された列     IMAGE SECUREFiles LOBデータ型が格納された列 image_BLOB   表のバックアップ: 既存の表のバックアップをとる(任意)。バックアップを取得してデータを保護することを推奨 create table image_table_bkp as select * from image_table;   新しい列を追加することで、既存のマルチメディア列を新しい列に置き換える: SECUREFILE を保持するための IMAGE_BLOB 列を作成する SQL> ALTER TABLE image_table ADD (image_Blob BLOB) LOB(image_Blob) STORE AS SECUREFILE (tablespace multi_sf); Table altered. SQL> desc image_table Name                                      Null?    Type ----------------------------------------- -------- ---------------------------- ID                                                 NUMBER IMAGE                                              PUBLIC.ORDIMAGE IMAGE_BLOB                                         BLOB   新しい列を古い列データで更新する:  SQL> UPDATE IMAGE_TABLE I set I.IMAGE_BLOB=I.IMAGE.SOURCE.LOCALDATA; 2 rows updated. SQL> commit; Commit complete.     古い列に UNUSED とマークする:  SQL> alter table image_table SET UNUSED (IMAGE); Table altered. SQL> desc image_table Name                                      Null?    Type ----------------------------------------- -------- ---------------------------- ID                                                 NUMBER IMAGE_BLOB                                         BLOB     新しい列の名前を古い列名に変える:  SQL> alter table image_table rename column IMAGE_BLOB to image; Table altered. SQL> desc image_table Name                                      Null?    Type ----------------------------------------- -------- ---------------------------- ID                                                 NUMBER IMAGE                                              BLOB   古い列を削除する: SQL> alter table image_table DROP UNUSED COLUMNS CHECKPOINT 250; Table altered. SQL> desc image_table Name                                      Null?    Type ----------------------------------------- -------- ---------------------------- ID                                                 NUMBER IMAGE                                              BLOB     表のデータをチェックする: SQL>  exec check_space_secfile('IMAGE_TABLE','MULTI_SF'); Segment Blocks = 2072,  Bytes = 16973824 Used Blocks = 895,  Bytes = 7331840 Expired Blocks = 1102,  Bytes = 9027584 Unexpired Blocks = 0,  Bytes = 0 ============================================= PL/SQL procedure successfully completed.       解決策2:新しい表に移行する プロセスの概要: image_table に似た image_table_blob 表を作成する。ただし、列のデータ型は ORDIMAGE データ型ではなく BLOB とすること image_table のデータを image_table_blob に挿入する   この例で使用するオブジェクト: マルチメディア・データ型がある表 image_table_bkp マルチメディア・データ型が格納された列 IMAGE マルチメディア・データ型がある表 image_table_bLOB SECUREfiles LOB データ型が格納された列 image   元の表: SQL> select owner,table_name,column_name,DATA_TYPE from dba_tab_columns where table_name like 'IMAGE_TABLE_BKP'; OWNER                TABLE_NAME           COLUMN_NAME          DATA_TYPE -------------------- -------------------- -------------------- -------------------- MULTI_SF             IMAGE_TABLE_BKP      IMAGE                ORDIMAGE MULTI_SF             IMAGE_TABLE_BKP      ID                   NUMBER     バックアップ表を作成する:  create table image_table_blob (ID NUMBER,IMAGE BLOB) LOB(IMAGE) STORE AS SECUREFILE (tablespace multi_sf);   SQL> select owner,table_name,column_name,DATA_TYPE from dba_tab_columns where table_name like 'IMAGE_TABLE_BLOB';   OWNER                TABLE_NAME           COLUMN_NAME          DATA_TYPE -------------------- -------------------- -------------------- -------------------- MULTI_SF             IMAGE_TABLE_BLOB     ID                   NUMBER MULTI_SF             IMAGE_TABLE_BLOB     IMAGE                BLOB     新しいデータを表に挿入する:  insert into image_table_blob select I.ID,I.IMAGE.SOURCE.LOCALDATA  from image_table I;   新しい表のデータをクロスチェックする: SQL> exec check_space_secfile('IMAGE_TABLE_BLOB','MULTI_SF'); Segment Blocks = 2072,  Bytes = 16973824 Used Blocks = 895,  Bytes = 7331840 Expired Blocks = 1102,  Bytes = 9027584 Unexpired Blocks = 0,  Bytes = 0 ============================================= PL/SQL procedure successfully completed.     データを検証し、移行が成功したかどうかをチェックする 古い表を削除して、新しい表の名前を古い表の名前に変えてもいいですし、新しい表明をそのまま使っても構いません。 DROP TABLE image_table; RENAME IMAGE_TABLE_BLOB to image_table; 次回のブログでは、 APEX Multimedia Extention を使ってイメージのサイズ変更操作を実行する方法を取り上げます。   参考資料: https://support.oracle.com/knowledge/Oracle%20Database%20Products/2555923_1.html

※本記事は、Bogapurapu laxmi krishna Raoによる"Simple migration from Oracle multimedia to secure-file blob data type"を翻訳したものです。   April 7, 2021   Oracle Database...

Java

Java 15の内側:5つのカテゴリに分かれた14個のJEP

JDK 15に追加される、Hiddenクラス、Sealedクラス、テキスト・ブロック、レコード、EdDSAといった多くの優れた機能 ※本記事は、Alan Zeichick による "Inside Java 15: Fourteen JEPs in five buckets" を翻訳したものです。   August 28, 2020 本記事をPDFでダウンロード   Java 15には優れた機能が数多く搭載されています。2020年9月15日のリリースには、14個の重要なJDK拡張提案(JEP)が含まれています。本記事では、JEP自体に含まれる情報をもとに、新機能の概要を簡単にまとめたいと思います。 ここでは、14個のJEPを5つのカテゴリに分けています。詳しくは、それぞれのJEPのドキュメントをご覧ください。 楽しみな新機能:   JEP 339:エドワーズ曲線デジタル署名アルゴリズム(EdDSA)   JEP 371:Hiddenクラス 既存のJava SE標準への追加: JEP 378:テキスト・ブロック JEP 377:Zガベージ・コレクタ(ZGC) JEP 379:Shenandoah:一時停止時間短縮型ガベージ・コレクタ(正式版) レガシーJava SE機能の最新化: JEP 373:レガシーDatagramSocket APIの再実装 新しいものへの期待: JEP 360:Sealedクラス(プレビュー) JEP 375:instanceofのパターン・マッチング(2回目のプレビュー) JEP 384:レコード(2回目のプレビュー) JEP 383:外部メモリ・アクセスAPI(2回目のインキュベータ) 機能の削除と非推奨化: JEP 372;Nashorn JavaScriptエンジンの削除 JEP 374:バイアス・ロックの無効化と非推奨化 JEP 381:SolarisおよびSPARCポートの削除 JEP 385:RMIアクティブ化の削除に向けた非推奨化   楽しみな新機能 最初にお断りしておきますが、JEP 339で扱っているエドワーズ曲線デジタル署名アルゴリズム(EdDSA)は、暗号化に関して筆者が理解している範囲を少々上回っています。というより、完全に筆者の理解を超えています。しかし、このJEPは、既存のC言語実装であるECDSAよりも高パフォーマンスで、プラットフォームから独立したEdDSAの実装となるように設計されています。その重要な目的は、サイドチャネル攻撃を回避することです。 JDKのドキュメントには、以下のように書かれています。 EdDSAは最新の楕円曲線署名スキームで、JDKの既存の署名スキームに比べて有利な点がいくつかあります。このJEPの第1の目的は、RFC 8032で標準化されたとおりにこのスキームを実装することです。この新しい署名スキームは、ECDSAを置き換えるものではありません。 追加の実装の目的は以下のとおりです。 既存のECDSA実装(ネイティブCコードを使用したもの)よりもパフォーマンスが優れ、同じセキュリティ強度を備え、プラットフォームから独立したEdDSAの実装を開発します。たとえば、Curve25519を使用するEdDSA(セキュリティ・ビット数がおよそ126ビット)は、曲線secp256r1を使用するECDSA(セキュリティ・ビット数がおよそ128ビット)と同じくらいの処理時間となるはずです。 さらに、この実装ではシークレットによって分岐することはありません。これらの特性は、サイドチャネル攻撃を防ぐうえで大いに役立ちます。 これで皆さんは筆者以上の知識を得たはずです。近いうちに、EdDSAの解説記事がJava Magazineに掲載されることが期待できますね。 Hiddenクラス(JEP 371)とは、他のクラスのバイトコードから直接使用できないクラスです。Hiddenクラスを使うのは、実行時に動的にクラスを生成してリフレクション経由で間接的にそのクラスを使うフレームワークであると想定されています。動的に生成されたクラスは、限られた時間しか必要とされないかもしれません。したがって、そのようなクラスを、静的に生成されたクラスが生存している間ずっと保持し続けると、無駄なメモリ・フットプリントの増加につながる可能性があります。 また、動的に生成されたクラスは検出できません。名前によって独立して検出できると、問題になる可能性があります。動的に生成されたクラスは、静的に生成されたクラスの実装の詳細にすぎないという目的が損なわれるからです。 Hiddenクラスのリリースは、開発者が非標準APIである sun.misc.Unsafe::defineAnonymousClass を使わないようにするための土台となる作業です。オラクルは、将来的にこのクラスを非推奨にして削除することを予定しています。   既存の Java SE 標準への追加 JDK 13とJDK 14でのプレビューを経たテキスト・ブロック(JEP 378)は進化を続けます。テキスト・ブロックは複数行文字列リテラルであり、Project Amberによるものです。テキスト・ブロックにより、ほとんどのエスケープ・シーケンスを省略できるようになります。  テキスト・ブロックでは、予測可能な方法で文字列が自動的に書式設定されますが、それが十分でない場合は、開発者が書式設定の方法を制御できます。2回目となる今回のプレビューでは、改行と空白を制御するための新しいエスケープ・シーケンスが2つ導入されています。たとえば、\<行終端文字>エスケープ・シーケンスによって、改行文字の挿入が明示的に抑制されます。 String literal = "Lorem ipsum dolor sit amet, "+ "consectetur adipiscing elit, " + "sed do eiusmod tempor incididunt"; しかし今後は、\<行終端文字> を使うことにより、コードが読みやすくなります。 String literal = """ Lorem ipsum dolor sit amet, \ consectetur adipiscing elit, \ sed do eiusmod tempor incididunt\ """; \s エスケープ・シーケンスを使うと、文字列に続く空白が削除されないようにすることができます。そのため、次の例は、各行の長さが厳密に5文字である、3行のテキストを表します(3行目のgreenはすでに長さが5文字なので、 \s は不要です)。 String colors = """ red \s green blue\s\ """; Zガベージ・コレクタ(JEP 377)は、JDK 11で試験運用版機能として導入されました。今回のリリースで、試験運用版ではない正式版の機能となります。ZGCは、コンカレント、NUMA対応、低遅延のスケーラブルなガベージ・コレクタです。数テラバイトのヒープでも、ガベージ・コレクションによる一時停止が10ミリ秒未満になるように調整されています。オラクルのテストによれば、平均の一時停止時間は1ミリ秒未満、最大の一時停止時間は2ミリ秒未満です。図1に示すのは、Javaのパラレル・ガベージ・コレクタ、G1、ZGCの比較です。ZGCの一時停止時間は10倍に拡大しています。 図1: ガベージ・コレクタの一時停止時間の比較 ただし、多くのワークロードでは、G1(これが引き続きデフォルトです)の方がZGCよりもやや高速かもしれません。また、数百メガバイトしかない非常に小さなヒープでも、G1の方が高速かもしれません。そのため、どちらのガベージ・コレクタを使うべきかを確認するためには、自分で実際のワークロードを使ってテストする必要があります。 重要:ZGCは試験運用版ではなくなったため、使用時に-XX:+UnlockExperimentalVMOptionsを指定する必要はありません。 ZGCは、オラクルのOpenJDKビルドとOracle JDKに含まれます。ガベージ・コレクタのその他の選択肢として、Shenandoah(JEP 379)がいくつかのOpenJDKビルドで利用できます。   レガシー Java SE 機能の最新化 JEP 373では、レガシーDatagramSocket APIが再実装されています。これは主に、非常に古いコードの一部をリファクタリングすることが目的だと考えてください。このJEPによって、古くてメンテナンスしにくいjava.net.DatagramSocket APIとjava.net.MulticastSocket APIの実装が、よりシンプルでモダンな実装に置き換えられるからです。新しい実装は、メンテナンスやデバッグが容易なだけでなく、Project Loomの仮想スレッドでも動作するようになります。 非常に多くの既存コードがJDK 1.0で導入された古いAPIを使っているため、レガシー実装は削除されません。ちなみに、リファクタリングされたAPIがリグレッション・テストや一部の特殊ケースで問題を起こす場合は、JDK固有の新しいシステム・プロパティであるjdk.net.usePlainDatagramSocketImplを指定して、JDKでレガシー実装が使用されるように構成します。   新しいものへの期待 JDK 15では、Project Amberから生まれたSealedクラス(JEP 360)の第1回プレビューが行われます。SealedクラスおよびSealedインタフェース(「Sealed」は「封印された」という意味です)では、その拡張や実装が可能なクラスまたはインタフェースが制限されます。なぜこれが重要なのでしょうか。開発者は、特定のクラスやインタフェースを実装するコードを制御したいかもしれません。また、Sealedクラスによって、アクセス修飾子よりも宣言的にスーパークラスの使用を制限する方法も提供されます。次に例を示します。 package com.example.geometry; public sealed class Shape permits com.example.polar.Circle, com.example.quad.Rectangle, com.example.quad.simple.Square {...} Sealedクラスにする目的は、すべての許可済みサブクラスをクライアントのコードで把握できるようにすることです。特に、オリジナルのクラス定義が完全に内包的であることが求められており、かつ、許可されている場合にのみそのクラス(またはインタフェース)を拡張できるという仕組みを開発者が望まないといったユースケースもあるでしょう。 Sealedクラスには、いくつかの制約があります。 Sealed クラスとその許可済みサブクラスは、同じモジュールに配置しなければなりません。また、無名モジュールで宣言されている場合は、同じパッケージに配置しなければなりません すべての許可済みサブクラスは、Sealed クラスを直接拡張しなければなりません すべての許可済みサブクラスでは、スーパークラスから始まった Sealed クラス化をどのように継続するかを示す修飾子、つまり、final、sealed、non-sealed ( Sealed クラスでは、許可済みサブクラスでnon-sealedが宣言されるのを防ぐことはできません ) のいずれかを選ばなければなりません JDK 15では、instanceofのパターン・マッチング(JEP 375)の2回目のプレビューも行われます。こちらも、Project Amberで開発されたものです。Java 14で第1回プレビューが行われましたが、その段階と比較して何も変更されていません。 この機能の目的は、instanceof演算子でパターン・マッチングを行い、Javaを強化することです。パターン・マッチングにより、プログラムに含まれる一般的なロジック(条件に応じた、オブジェクトからのコンポーネント抽出)をより簡潔かつ安全に表現できます。手引きとして、Mala Gupta氏のすばらしい記事「Java 14におけるinstanceofのパターン・マッチング」を紹介します。 レコード(JEP 384)は人気の機能であり、Java 15で2回目のプレビューが行われます。レコードは、不変データの透過的なキャリアとして動作するクラスです。新しいJEPでは、コミュニティからのフィードバックに基づいて微調整が行われています。また、ローカルなクラスとインタフェースで、いくつかの追加形態が新たにサポートされています。レコードもProject Amberによるものです。 レコード・クラスはオブジェクト指向の構造で、シンプルな値集合を表します。そのため、レコード・クラスを使うと、プログラマーは拡張可能な動作ではなく、不変データのモデリングに集中できます。レコードでは、equalsメソッドやアクセッサ・メソッドなどのデータ駆動型メソッドが自動的に実装されます。また、名前的型付けや移行の互換性など、長年にわたるJavaの原則も維持されます。言い換えるなら、不変データを含むクラスがある場合、レコードを使えばコーディングしやすく、読みやすくなります。 最後に紹介する新機能は、2回目のインキュベータ・リリースとなる外部メモリ・アクセスAPI(JEP 383)です。このAPIにより、JavaプログラムがJavaヒープ外の外部メモリに安全かつ効率的にアクセスできます。この機能の目的は、java.nio.ByteBufferおよびsun.misc.Unsafeの置き換えを始めることです。この機能は、Java APIと非Java APIの連携を向上させるProject Panamaの一部です。 JEPドキュメントには、このイノベーションが求められる必要性が以下のようにうまくまとめられています。 外部メモリへのアクセスとなると、開発者はジレンマに直面します。ByteBuffer APIのように、安全であるものの制限が存在する(そしておそらく効率が悪い)方法を選ぶべきでしょうか。それとも、安全性の保証を捨て、サポートされていない危険なUnsafe APIを採用すべきでしょうか。 このJEPによって、安全でサポート対象となる効率的な、外部メモリ・アクセス用のAPIが導入されます。外部メモリ・アクセス問題に的を絞ったソリューションが提供されるので、開発者は既存APIが持つ制限と危険性から解放されます。また、新しいAPIは最初からJIT最適化を意識して設計されているので、開発者はパフォーマンスの向上という恩恵も受けます。   機能の削除と非推奨化 以下の内容が議論を呼ぶことはないはずです。 JEP 372は、Nashorn JavaScriptエンジンの削除に関する内容です。Nashorn JavaScriptエンジンとそのAPI、そしてjjsツールは、Java 11で非推奨となり、今回のリリースで削除されます。 バイアス・ロックの無効化と非推奨化(JEP 374)では、競合しないロックのオーバーヘッドを減らすため、HotSpot JVMで使われている古い最適化技術を取り除く作業が始まります。歴史的に見て、ロックにバイアスをかけるという方法では、通常のロック技術と比べてパフォーマンスが著しく向上していました。しかし、パフォーマンス上の優位性は、かつてほど際立つものではなくなっています。最新のプロセッサでは、アトミックな命令を実行するコストが低くなっているためです。 バイアス・ロックにより、複雑なコードが数多く導入されました。そしてその複雑さは、Javaチームが同期サブシステムで大幅な設計変更を行う際の障害になっています。バイアス・ロックをデフォルトでは無効化する一方で、再有効化の選択肢を開発者に残すことで、将来のリリースで完全に削除することが合理的かどうかを判断したいとJavaチームは考えています。 JEP 381(SolarisおよびSPARCポートの削除)では、Solarisオペレーティング・システムおよびSPARCアーキテクチャ限定のソース・コードがすべて削除されます。他に述べるべきことはありません。 JEP 385(RMIアクティブ化の削除に向けた非推奨化)は、Java 8以降でオプションとなっている、リモート・メソッド呼出しの古い部分をJavaから徐々に取り除くものです。 RMIアクティブ化の使用は少なく、その数は減少しています。Javaチームは、新しく書かれるアプリケーションでRMIアクティブ化を使っているケースを認識していません。さらに、RMIアクティブ化を使っている既存のアプリケーションがほとんどないという証拠もあります。オープンソースのさまざまなコードベースを検索したところ、このアクティブ化に関連するAPIに言及している箇所はほとんどないことがわかりました。この数年間、RMIアクティブ化に関して外部からバグ・レポートが寄せられたことはありません。 Javaプラットフォームの一部としてRMIアクティブ化を維持することにより、継続的なメンテナンス・コストが発生し、RMIも複雑になります。RMIアクティブ化は、アクティブ化以外のRMIに影響を与えずに削除できます。RMIアクティブ化が削除されても、開発者にとってのJavaの価値は低下しません。削除によって低下するのは、JDKの長期的なメンテナンス・コストです。そのため、この機能の削除に向けた作業を始めるべきタイミングが来ていると言えます。 まとめ Java 15では、JDKの6か月のリリース周期が継続されており、そこには一連の確かな新機能、機能変更、そしてプレビューおよびインキュベータが含まれています。Java 15では、ほとんどの開発者にとって優れた機能が数多く提供されます。この新リリースの感想をjavamag_us@oracle.comまで電子メールでお知らせいただくか、またはハッシュタグ#java15をつけてTwitterに投稿してください。 最後になりますが、8月中旬のJDK 15に関するWebキャスト(こちらから再生できます)のためにこのような大量の情報を集めてくれたことについて、オラクルのJava Platform Groupのプロジェクト管理担当シニア・ディレクターであるAurelio Garcia-Ribeyroに感謝をささげたいと思います。   さらに詳しく Java SE 15 のリファレンス実装(英語) JDK 15 リリース・ノート(英語)  Java 14、Java 15、Java 16、およびそれ以降でのプレビュー機能の役割 2020年の Java(英語) Oracle Java SE Support ロードマップ Java の次の 10年(英語) JDK 15 : Java 15 の新機能(英語) Sealed クラスによって Valhalla が一歩近くに(英語)   Alan Zeichick Java Magazine編集長。オラクルのContent Centralグループのエディター・アット・ラージ。メインフレームのソフトウェア開発者、テクノロジー・アナリストを経験した後、AI Expert、Network Magazine、Software Development Times、Eclipse Review、Software Test & Performanceの編集に携わった。Twitterのフォローは@zeichickから。

JDK 15に追加される、Hiddenクラス、Sealedクラス、テキスト・ブロック、レコード、EdDSAといった多くの優れた機能 ※本記事は、Alan Zeichick による "Inside Java 15: Fourteen JEPs in five buckets" を翻訳したものです。   August 28, 2020 本記事をPDFでダウンロード   Java 15には優れた機能が数多く搭載され...

Database

オラクルのコンバージド・データベースで開発を容易に進め、データを最適化するには

※本記事は、Tim Pirog による "How to Simplify Development and Optimize your Data with Oracle's Converged Database" を翻訳したものです。   March 16, 2021   データが支えるこのデジタル化社会において、企業が競争力を維持するためには、独自の資産を構築し、それを最大限に活用しなければなりません。そのため、データベース・パフォーマンスを管理し最適化しようとなると、開発者が矢面に立つことになります。開発者は今や、スタック全体の基盤テクノロジーを形作るだけでなく、幅広いハイブリッドな役割(DevOps、フルスタックなど)を担って、効率化やベスト・プラクティスを推し進める存在になっています。しかし、開発者がデータの生産性か開発者の生産性かの選択に迫られることも多いようです。 そのようなジレンマを解消してきたのがオラクルのコンバージド・データベースです。 開発者は、デジタル情勢が常に変化するということを誰よりもよく知っています。言語、フレームワーク、ツール、プロトコルなどがすぐに新しくなる環境では注力すべき目標が明確でないと、多くの要求や機会にすぐに圧倒されることでしょう。“Keep it Simple”(物事はシンプルに)というKISSの原則に沿いながら、開発者は最新の開発を懸命に追い求めますが、自社の競争力を維持するために必要な業務の効率性を犠牲にはできません。 単一目的データベースは、専用データベースとも呼ばれることも多く、ターゲットを絞った問題解決を目的として構築されています。しかし、業務の規模が拡大すると、複数の単一目的データベースを統合することが必要になります。  それに対抗するのがオラクルのコンバージド・データベースです。これは、複数の単一目的データベースを組み合わせるよりもシンプルで、簡単で、エラーが極めて発生しにくい機能一式を搭載したプラットフォームです。  この記事では、オラクルのお客様がシンプルとスマートを常に両立させるために役立つ、オラクルのコンバージド・データベースの主なメリットをいくつかご紹介します。開発をスマートに。複数のデータタイプを使用して開発する場合は、これが特に重要になります。 企業が成功に必要となる効率性と関連性を求める上で、複雑なプロセスとデータ統合の大規模なデジタル化は必須です。その結果、開発チームは難しい選択に迫られます。今の迅速なアプリケーション開発のために最適化するのか、後のより簡単なデータ取得を優先するのか、という選択です。つまり、開発者の生産性か、データの生産性か、という難しい二者択一です。 前者(開発者の生産性)の場合、開発チームは特定のプロジェクト向けに単一目的データベースを急いで用意することになるでしょう(特に、新規開発の場合はこの傾向が強くなります)。各データベースは最初のハードルを低くしており、多くの場合、当座の目的に合った便利なデータ・モデルと関連APIが搭載されています。各データベースにすぐに役立つ独自の運用特性が備わっているため、開発は迅速に進みます。しかし、プロジェクトの規模が大きくなり、追加のデータベースやクラウド・サービスが必要になると、複雑さが増してデータが断片化してしまいます。以前は魅力的だった各データベースの特性が規模の拡大によって機能しなくなり、得られた効率性はすぐに失われるのです。その結果、開発チームは、標準化(API、問合せ言語など)の不足、データの断片化、セキュリティ・リスク、管理面のギャップなど、長期的な困難に直面することになります。 単一目的データベースがもたらす統合の難しさを表したスナップショット   後者(データの生産性)の場合、特に再開発プロジェクトでは、開発者は企業の標準データベース上で構築する必要があるでしょう。それは通常、汎用的なリレーショナル・データベースか、リレーショナルなマルチモデル・データベースとなります。しかし、企業の標準データベースは決められたポリシーに従い、運用、セキュリティ、データ再利用(レポート、分析など)といった事柄を簡素化するものの、機能が限定されているためにイノベーションが遅れ、さらには妨げられる可能性もあります。これには、急速に変化する競争の激しい市場環境において、ビジネス全体が不利な状況に追い込まれるリスクがあります。 どちらのシナリオも理想からは程遠く、それぞれの向かう先には限界があります。ここで大きな疑問が生まれます。「第3のオプションはないのか?」  そのオプションこそが開発者の生産性とデータの生産性のバランスをとったクラス最高のソリューションであるオラクルのコンバージド・データベースです。 コンバージド・データベースは、簡単に言えば、マルチモデル、マルチテナント、マルチワークロードのデータベースです(以下の図を参照)。さらに具体的にオラクルのコンバージド・データベースについて説明すると、すべてのワークロード(OLAP、OLTP、IoTなど)に基づく最新の開発に必要となるデータ・モデルとアクセス手法を提供するものとなっています。さらにメリットとして、開発者や開発者チームが求めているツール、セキュリティ、統合、運用特性を提供します。すなわち、簡素化、合理化、そして大規模な環境での高いパフォーマンスを実現する強力でスマートな機能を備えています。 Oracle Database 19c は世界初のコンバージド・データベース ― 大規模環境の強力なソリューション向けに開発者の生産性とデータの生産性のバランスをとったデータベースである   開発者であれば、口で言うのは簡単だから実際にコードを見せてほしいと思うのではないでしょうか。それでは、2つの異なる環境について、いくつかのコード例と共に見ていきながら、オラクルのコンバージド・データベースによってデータ層がどのように開発向けに合理化されているかについて理解を深めましょう。 ここでは、民泊オンライン・アプリケーション内の3層の環境について2つの実装方法を確認することで、オラクルのコンバージド・データベースに本来備わる簡素性を明らかにします(例では、以下の図に示すとおり、“Host-a-bnb”という名のAirbnbに似たアプリケーションを利用します)。 このアプリケーションに必要となる固有のデータ・アセットは次のとおりです。 Key-Value ( ユーザーに後でパーソナライズされたクイック検索機能を提供するため、ユーザーの検索履歴を保存する ) グラフ( ソーシャルメディア機能を最適化して拡張するために、ユーザー同士の関係を保存する ) リレーショナル( ユーザーの予約と決済のトランザクションを保存する ) これらを基にした要件は以下のようにまとめられます(簡略化するために、キャッシュ・レイヤーは省略します)。 単一目的データベースのデプロイメントは、MongoDB(key-value)、Neo4j(グラフ)、Firebird(リレーショナル)の各データベースで構成される。 Oracle Databaseデプロイメントは、1つのプラガブル・データベースで構成され、そのデータベース内にkey-value、グラフ、リレーショナルの各データに対応した表を保有する。 Host-a-bnbの3層環境の概略図:複数の単一目的データベースで実行する場合とオラクルのコンバージド・データベースで実行する場合   この構成内で、"Host-a-bnb" Webアプリケーションを、それぞれ対応するデータ層に接続するという、比較的面倒ではない部分に絞り込んでコーディングしてみましょう。 単一目的データベースとオラクルのコンバージド・データベースのデータ層接続に関する比較   直前の図からわかるように、単一目的データベースへの接続には余分な処理が必要になります。もちろんこの例は単純なものですが、それでも今後複雑になることが予感されます。以下のコードは、それぞれの単一目的データベースに対して単純な問合せを実行しています。これを見ると、もうすでに厄介な状態になり始めています。 単一目的データベースのデータ層内を問い合わせる簡易サンプル   これに対して、オラクルのコンバージド・データベースの統合アプローチでは、期待どおりの方法でこのデータを問い合わせることができます。1つの問合せでデータが結合され、期待した結果が返されます。面倒な作業も、データベース間やデータタイプ間の変換も必要ありません。 オラクルのコンバージド・データベースのデータ層内を問い合わせる簡易サンプル   単一目的データベースとオラクルのコンバージド・データベースのデータ層内への問い合わせ (QL) に関する比較 ここで明らかとなった最も注目すべき点は、単一目的データベースはデータの問合せにおいて開発チームに負担をかけるということです。それぞれのデータベースに固有の問合せ言語(QL、上の図を参照)が必要となります。それに加えて、問合せが実行されると、データはバラバラな状態で、多くの場合は順次的に取得されます。これは開発の立場から言えばまったく効率的ではありません。各開発者が1つのアプリケーションについて、3つの異なるデータセットを最小化する方法を考案する必要があるからです。さらに、開発者は期待した結果を生成するために、手動でデータ統合を実行する必要があります。 上記の問合せのコード例を見ればわかるように、オラクルのコンバージド・データベースは、1つのQLでデータベース全体にわたってすべての問合せを実行することで、この問題を根本から解決します。そのため、開発者ではなくデータベースに力仕事を任せることができます。 単一目的データベースとオラクルのコンバージド・データベースのデータ層内へのデータ問合せに関する対象比較 上の表に示すとおり、単一目的データベース製品では断片的な状況は避けられず、統合を実装するために多大な労力を費やすことになります。その解決策として、オラクルのコンバージド・データベースは、必要な統合を最小限に抑えるだけでなく、プラットフォームのアーキテクチャ全体に最先端の機能を融合させた包括的製品を提供することで、単一目的データベースが持つリスクを回避しています。たとえば、同じ表内に異なるデータタイプを格納でき、それらを結合することも可能です。つまり、別のデータタイプを追加するためだけに別の製品を用意する必要がなくなります。新しい列や表を作成するだけで済み、簡単です。 単一目的データベースとオラクルのコンバージド・データベースのデータ層内への分析問合せに関する対照比較 業務上のデータ利用はデータ管理の1つの側面に過ぎません。データベース間でのデータ移動が必要になることの多いETL(抽出、変換、ロード)操作について言えば(たとえば、分析やレポートのためにOLTPからOLAPに移動する)、オラクルのコンバージド・データベースを使用すれば、開発者はリソースを消費するETL操作をする必要がありません。このような直感的でシンプル、かつ高品質の統合パターンが、オラクルのコンバージド・データベース全体に広がっています。 オラクルのコンバージド・データベースは長年のイノベーションの上に築かれており、効率性とパフォーマンスを最大限に発揮するように作られたツール一式も同時に提供されています。オラクルは世界第2位の規模のソフトウェア企業として、CI/CDプロセス全体で開発者を支援する幅広い製品を提供しています。これには以下が含まれます。 1. Helidon.io マイクロサービス開発を簡素化できる軽量のマイクロサービス Java フレームワーク。特に、どこでも実行できる点が優れています。 2. Coherence データを確実に保存するグリッドベースのアプリケーションを構築するための、スケーラブルでフォルト・トレラントな、クラウド対応型の分散プラットフォーム。 3. Docker/cri-o と Kubernetes オラクルのサポートを得て、コンテナ化とオーケストレーションを目的として、モノリシックなアプリケーションと並行してクラウドネイティブのアプリケーションを構築してデプロイすることが可能。 4. WLS Kubernetes Operator 最も容易な Oracle WebLogic 開発、デプロイ、管理を推進。 WLS Kubernetes Operator によって、既存のスキルセットを拡大しながら、容易に最新化できます。 5. Verrazzano 従来型のアプリケーションやクラウドネイティブのアプリケーションをマルチクラウドおよびハイブリッドのシナリオでデプロイするためのエンタープライズ・コンテナ・プラットフォーム。 6. Terraform と Oracle Cloud Infrastructure (OCI) 安全かつ予測可能な形での Oracle Cloud Infrastructure の作成、変更、改善が可能。 開発者の役割は常に拡大しています。また、かつてない勢いで、ITプロセスがコードドリブンになっています。オラクルもそのことを十分に理解しています。だからこそ、CI/CDプロセス全体を通じて開発者に配慮した環境を提供しているのです。どのようなタスクでも、開発者がどこにいても、オラクルのツールセットは、シンプルとスマートを常に両立させるためにお役に立ちます。開発をスマートに。盤石の信頼性を備えるシンプルでセキュアなテクノロジーを皆さまに。 複雑化が進み、効率化がますます要求される現代においては、それが確かな力になります。 オラクルの最新テクノロジーの詳細については、Oracle LiveLabsをご覧の上で、Oracle Cloud Free Tier上で無償でお試しください。オラクルは皆さまの側でいつでも支援いたします。

※本記事は、Tim Pirog による "How to Simplify Development and Optimize your Data with Oracle's Converged Database" を翻訳したものです。   March 16, 2021   データが支えるこのデジタル化社会において、企業が競争力を維持するためには、独自の資産を構築し、それを最大限に活用しなければなりません。そ...

Linux

CentOSに代わる安定したRHEL互換OSをお求めでしょうか?Oracle Linuxを検討すべき3つの理由

※本記事は、Honglin Su による "Need a stable, RHEL-compatible alternative to CentOS? Three reasons to /consider Oracle Linux" を翻訳したものです。   December 12, 2020   本ブログをお読みいただいているということは、おそらくCentOSユーザーであり、将来的に代替OSを検討しなければならない位置にいらっしゃるのではないでしょうか。 Oracle Linuxへの切り替えは簡単です。代替を検討すべき理由をいくつかご紹介いたします。   14年間以上ダウンロード、使用、配布を無償で提供 2006年のOracle Linuxリリース4のデビュー以来完全無償でご利用いただいており、ダウンロードも簡単です。メジャーリリースやアップデートリリースに関しても、14年以上もの間無償で提供しております。エラッタリリースに関しては、2012年より無償でご利用いただいています。無償のソースコード、バイナリ、アップデート、エラッタは、どれも自由に配布可能な上、本番環境でも無償でご利用いただけます。これにはOracleとの間で書類への署名が発生することもなければ、商標や著作権の削除も必要もありません。 Oracle Linuxでは、Red Hat Enterprise Linux(RHEL)のメジャーバージョンである。4、5、6、7や、最近だと8に相当するリリースをそれぞれご用意しています。Oracle LinuxのリリースはRed Hatに一貫して追従しており、エラッタは通常24時間以内にリリースされ、アップデートリリースは5営業日内にご利用いただけます。また、メジャーバージョンは、3か月以内にリリースされます。 このような要素が相まって、Oracle Linuxは開発、テスト、本番での使用において理想的な選択肢と言えるでしょう。すべてのシステムを最新かつセキュアに保ちながら、該当する場合は個別のシステムそれぞれに最適なサポート範囲を決定します。   互換性を確保してさらに強化 Oracle Linuxは、アプリケーションバイナリにおいてRed Hat Enterprise Linux(RHEL)との100%の互換性を誇っています。2006年から、Oracle Linuxを実行している企業様が互換性に関するバグを報告したことはありません。 Oracleでは、Oracle Linux向けのUnbreakable Enterprise Kernel(UEK)と、Red Hat Compatible Kernel(RHCK)の2種類のカーネルオプションをご用意しております。これらはどちらもOracleによりサポートされています。UEKは、プロセススケジューラ、メモリ管理、ファイルシステム、ネットワークスタックにおけるパフォーマンスとスケーラビリティを大幅に向上します。 Oracleでは、実行しているカーネルのソースは、分離が簡単であるべきだと考えています。元のメインラインChangeLogとGitHubのChangeLogと共に、UEKソースを公開するのはこのためです。 UEKやRHCKでの実行を問わず、Oracle LinuxはRHELとの互換性を完全に保ちます。 あらゆるワークロードに向けた設計 Oracle Linuxは、あらゆるワークロードに加え、x86やArmなどの幅広いシステムに対応できるよう設計されています。Oracle Linuxは、お好きなOracle製ハードウェアやサードパーティ製ハードウェア、Oracleクラウドインフラストラクチャや別のパブリッククラウドで、Oracle製アプリケーションとサードパーティ製アプリケーションの両方を最適にサポートできるよう設計されています。 アクセスも簡単です。是非お試しください。 CentOSからOracle Linuxへの移行に関するよくあるご質問 Download the script to switch from CentOS to Oracle Linux インストール用メディアやアップデートはOracle Linux yumサーバーで無償で利用可能 GitHubのUEKソースコード 詳細は、oracle.com/linuxにアクセスしてください。

※本記事は、Honglin Su による "Need a stable, RHEL-compatible alternative to CentOS? Three reasons to /consider Oracle Linux" を翻訳したものです。   December 12, 2020   本ブログをお読みいただいているということは、おそらくCentOSユーザーであり、将来的に代替OSを検討しなけ...

Oracle Technology Network

全国のOTNおすすめ技術者向けセミナー、イベント(2021年3月)

セミナー&イベント Oracle Database Technology Night - Automatic Workload Repository(AWR)の分析 4月13日(火)開催 Oracle Databaseの分析に使用するAWRですが、まだ使いこなせていない方も多いと思います。 今回のTech NightではAWRの分析方法をサンプルAWRレポートを使用して解析していきます。 まだ使用したことのない方のために、最初にAWRの概要として簡単な分析方法やバージョンの違いなどについても説明します。     これからの時代のために、ブロックチェーンから生まれた技術で、データベースの耐改ざん性をさらに高めよう ~コンバージド・データベースの世界~ 4月8日(木)開催 内部不正対応、監査対応、法律規制対応、品質検査対応、脱炭素対応、企業内には様々な目的のために、「耐改ざん性が必要」なデータが存在します。もちろん、すでに様々なセキュリティ対応を実施されていらっしゃる方が多いと思いますが、それでもなお、改ざんされるリスクにさらされたデータが存在しているのが実情ではないでしょうか。 そのような高まる耐改ざん性へのニーズにシンプルに対応するため、オラクルはブロックチェーンの思想からうまれた技術を、Blockchain Tableとしてデータベースに実装し、様々なデータの耐改ざん性と証跡性を、シンプルかつ容易に実現可能としています。本セミナーではそのBlockchain Tableについて、その生まれた背景をユースケースやデモをまじえてご紹介します。   4月限定!試験直前対策セミナー 4月実施分 再受験無料キャンペーンで受験を予定している方必見。4月限定で ORACLE MASTER Bronze DBA、Silver DBA、Silver SQLとオラクル認定 Java 資格 Silver の試験直前対策セミナー(無償)を実施します。事前に配布されるサンプル問題にチャレンジしてからセミナーにご参加ください。   ※再受験無料キャンペーンは 5月31日 まで Oracle Cloud ウェビナーシリーズ Oracle Cloud ウェビナーシリーズでは、Oracle Cloud の概要や最新情報、またクラウドを活用してビジネス課題・システム課題を解決したお客様事例をご紹介いたします。進化し続ける オラクルの IaaS/PaaS や Oracle Database をはじめとする、さまざまな製品についての最新情報や技術情報などお役に立つ情報を業務部門からIT部門のエンジニアの方々までの幅広い皆様へ向けてお届けします。さまざまなテーマや理解度レベルのコンテンツを取り揃えていますので、ぜひご活用ください。 4月7日(水) 【まずはここから】はじめての Oracel Cloud Infrastructure 4月8日(木) ブロックチェーンから生まれた技術で、データの耐改ざん性を高めるメリットとは? ~コンバージド・データベースの世界~ 4月14日(水) とっておきの方法! Oracle Database の自動アップグレードのお勧め手法 省力・最新化 概要編 4月15日(木) コンバージド DB の価値を最大化するマイクロサービスの特長と実践 4月21日(水) 本当にできるの?ミッションクリティカルシステムのクラウド移行 第1回:検討すべき10のポイント 4月22日(木) もはや標準語!知っておきたい JSON での開発と運用最新テクニック ~コンバージド・データベースの世界~ 4月28日(水) 宇宙目線で地方創生DX ~佐賀県における Space SAGA プロジェクト~   Systems Webinarsオンデマンド・見逃し配信開始 製品ごとのプレイリストをご用意しました。 開催日時の制約を受けることなく、自由な時間に何度でも自由に視聴いただけます。製品概要をご説明する「基礎編」、技術解説をおこなう「技術編」など多数コンテンツをご用意しています。各々、小一時間(45~60分)で学べるコンテンツとして是非ご活用ください。 Oracle Exadata Cloud@Customer ウェビナー Oracle Exadata ウェビナー Oracle Private Cloud Appliance ウェビナー ( 製品概要編 / セキュリティ対策編 ) AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。  

セミナー&イベント Oracle Database Technology Night - Automatic Workload Repository(AWR)の分析 4月13日(火)開催 Oracle Databaseの分析に使用するAWRですが、まだ使いこなせていない方も多いと思います。 今回のTech NightではAWRの分析方法をサンプルAWRレポートを使用して解析していきます。まだ使用したことのな...

CLOUD INFRASTRUCTURE SECURITY

Velero を使用した OKE 環境のバックアップ

※本記事は、Seshadri Dehalisan による "Backing up your OKE environment with Velero" を翻訳したものです。   今やKubernetesは、コンテナ化したアプリケーションをデプロイメントする企業にとって、究極のソリューションになりました。オラクルでは、そのKubernetes向けにOracle Container Engine for Kubernetes(OKE)を提供しています。これは、Oracle Cloud Infrastructure(OCI)にてコンテナ化したワークロードをデプロイするための、フルマネージドで、スケーラブルで、可用性の高いサービスになります。このOKEにより、コンテナ化したワークロードを大規模かつスムーズに実行できるようになりますが、その一方で、ディザスタ・リカバリやバックアップ要件といった重要なワークロードを実行するための基本的なビジネス要件は、顧客ニーズと密接な関係があります。 ディザスタ・リカバリおよびバックアップソリューションを堅牢なものにするには、クラスタのメタデータ定義のバックアップと、Kubernetesクラスタの永続データのバックアップ機能の提供が必要です。現在市場で入手できるテクノロジーはさまざまありますが、このガイドでは、オープンソース・ツールのVeleroを使用して、OKEクラスタのバックアップにソリューションを提供することを目指します。また、このVeleroを利用したソリューションを拡大させれば、ディザスタ・リカバリや、コンテナ化したKubernetesクラスタを他のプロバイダからOCIへ移行することも可能になります。さらにこちらのブログにて説明されているとおり、バックアップおよびディザスタ・リカバリのユースケースに、OKEと合わせてKastenを使用することも可能です。 Veleroのデプロイメント・プロセス Veleroでは、Resticを使用して永続ボリュームをバックアップします。Resticは軽量なクラウド・ネイティブのバックアップ・プログラムで、バックアップの分野で幅広く採用されています。Veleroでは、デプロイメント、Restic DaemonSets、およびカスタム・リソース定義を含め、Kubernetesオブジェクトを作成することで、バックアップおよびリストアを可能にします。 インストール条件 OKEのクラスタおよびkubectlへのアクセスをローカルにインストールします。 kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME 10.0.0.4 Ready node 2d21h v1.18.10 10.0.0.4 Oracle Linux Server 7.9 5.4.17-2036.100.6.1.el7uek.x86_64 docker://19.3.11 10.0.0.5 Ready node 2d21h v1.18.10 10.0.0.5 Oracle Linux Server 7.9 5.4.17-2036.100.6.1.el7uek.x86_64 docker://19.3.11 クライアント環境(Linux、Mac、Windows)によって、インストール・ステップは異なります。またOCI Cloud ShellからVeleroを先にインストールしておくことも可能です。 Macでは、以下のコマンドにてVeleroをインストールできます。 brew install velero Veleroをローカルにインストールできたら、以下のように、Veleroにて適切なKubernetesリソースを作成できます。  velero install \ --provider aws \ --bucket velero \ --prefix oke \ --use-restic \ --secret-file /Users/xxxx/velero/velero/credentials-velero \ --backup-location-config s3Url=https://tenancyname.compat.objectstorage.region.oraclecloud.com,region=region,s3ForcePathStyle="true" \ --plugins velero/velero-plugin-for-aws:v1.1.0 \ --use-volume-snapshots=false OCI Object StorageはS3互換であるため、上のコード・ブロックでもわかるとおり、プロバイダおよびオブジェクト・ストレージでは、バックアップ目的で、AWS S3リファレンスを使用します。この特徴により、EKSワークロードのOCIへのシームレスな移行も可能になります。またuse-resticパラメータにより、resticを使用して永続ボリュームをバックアップすることが可能になります。インストールが完了すると、Veleroにて少数のKubernetesリソースが作成されますが、これらはデフォルトでvelero名前空間に作成されます。 secret-fileは、VeleroにてOCI Object Storageのバケットへのバックアップに使用される資格証明を参照します。Managing User Credentialsに記載されているとおり、これらの資格情報は顧客秘密キーとして作成する必要があります。またオブジェクト・ストレージにバックアップするユーザー・プロファイルは、バックアップが記述されるバケットを管理できるものである必要があります。 資格証明ファイルの例は以下のとおりです。 aws_access_key_id=40xxxxxxxxxxxxxxxxxxxxxxxxxxxxxa32f8494a aws_secret_access_key=YyuSZxxxxxxxxxxxxxxxxxxxxxxxxxxxxxRDYzNnv0c= kubectl get pods --namespace velero -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES restic-2lp99 1/1 Running 2 23h 10.234.0.26 10.0.0.5 restic-6jz9k 1/1 Running 0 23h 10.234.0.137 10.0.0.4 velero-84f5449954-46hnk 1/1 Running 0 23h 10.234.0.136 10.0.0.4         Veleroを使用できるようになったので、次は永続ボリューム要求を使用する簡単なNginxデプロイメントを作成しましょう。       ストレージ・クラスを作成します(クラスタ・リソース)。     永続ボリュームを作成します(クラスタ・リソース)。     ポッドおよび永続ボリューム要求(PVC)が入る名前空間を作成します。     PVCを作成します(名前空間はスコープ済み)。     ポッドを作成します。 ストレージ・クラスを作成する ストレージ・クラスを作成するには、以下のコマンドを実行します。 kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: oci-fss1 provisioner: oracle.com/oci-fss parameters: # Insert mount target from the FSS here mntTargetId: ocid1.mounttarget.oc1.us_ashburn_1.aaaaaa4np2sra5lqmjxw2llqojxwiotboaww25lxxxxxxxxxxxxxiljr ストレージ・クラスでは、OCI File Storageサービスのマウント・ターゲットがKubernetesに提供されています。File Storageサービスの詳細については、こちらの資料を参照してください。 必要な名前空間を作成する 必要な名前空間を作成するには、以下のコマンドを実行します。 kubectl create namespace testing   永続ストレージを作成する 永続ストレージを作成するには、以下のコマンドを実行します。 apiVersion: v1 kind: PersistentVolume metadata: name: oke-fsspv1 spec: storageClassName: oci-fss1 capacity: storage: 100Gi accessModes: - ReadWriteMany mountOptions: - nosuid nfs: # Replace this with the IP of your FSS file system in OCI server: 10.0.0.3 # Replace this with the Path of your FSS file system in OCI path: /testpv readOnly: false 永続ボリューム要求を作成する 永続ボリューム要求を作成するには、以下のコマンドを実行します。 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: oke-fsspvc1 spec: storageClassName: oci-fss1 accessModes: - ReadWriteMany resources: requests: storage: 100Gi volumeName: oke-fsspv1 ポッドがPVCを使用していること、およびFile Storageがポッドにて利用可能で、またマウントされていることを確認しましょう。   kubectl exec -it oke-fsspod3 -n testing -- bash root@oke-fsspod3:/# mount |egrep -i nfs 10.0.0.3:/testpv on /usr/share/nginx/html type nfs root@oke-fsspod3:/# cd /usr/share/nginx/html root@oke-fsspod3:/usr/share/nginx/html# ls -lrt *.dmp|wc -l 65 root@oke-fsspod3:/usr/share/nginx/html# ls -lrt *.dmp|head -5 -rw-r--r--. 1 root root 75853 Feb 8 04:37 randomfile1.dmp -rw-r--r--. 1 root root 77341 Feb 8 04:37 randomfile2.dmp -rw-r--r--. 1 root root 76599 Feb 8 04:37 randomfile3.dmp -rw-r--r--. 1 root root 75066 Feb 8 04:38 randomfile4.dmp -rw-r--r--. 1 root root 75008 Feb 8 04:38 randomfile5.dmp   ボリュームのアノテーション Veleroでは、ポッドにはボリューム名がアノテーションとして付与されることが前提になります。ボリューム名は以下のコマンドにて追加できます。 kubectl -n testing annotate pod/oke-fsspod3 backup.velero.io/backup-volumes=oke-fsspv1   バックアップ・プロセス クラスタをバックアップする OKEクラスタをバックアップするには、以下のコマンドを発行します。 ./velero backup create backup-full-cluster-demo --default-volumes-to-restic=true Backup request "backup-full-cluster-demo" submitted successfully. Run `velero backup describe backup-full-cluster-demo` or `velero backup logs backup-full-cluster-demo` for more details. ./velero backup describe backup-full-cluster-demo Name: backup-full-cluster-demo Namespace: velero Labels: velero.io/storage-location=default Annotations: velero.io/source-cluster-k8s-gitversion=v1.18.10 velero.io/source-cluster-k8s-major-version=1 現行のリリースでは、Veleroは、永続ボリュームの動的プロビジョニングと合わせてリストアしようとします。したがって、静的に作成された永続ボリュームは別にバックアップする必要があります。このタスクを完了するには、以下のコマンドを使用します。 ./velero backup create backup-pv-only-demo --default-volumes-to-restic=true --include-resources pve Backup request "backup-pv-only-demo" submitted successfully. Run `velero backup describe backup-pv-only-demo` or `velero backup logs backup-pv-only-demo` for more details. % ./velero backup describe backup-pv-only-demo --details Name: backup-pv-only-demo Namespace: velero Labels: velero.io/storage-location=default Annotations: velero.io/source-cluster-k8s-gitversion=v1.18.10 velero.io/source-cluster-k8s-major-version=1 velero.io/source-cluster-k8s-minor-version=18 Phase: Completed リストア リストアのケースを作成するため、PVC、ポッド、名前空間、クラスタ・スコープの永続ボリュームを削除してみましょう。これらが削除される要因としては、偶発的な損失、オペレーターのミス、ディザスタ・リカバリなどがあります。データがリストアされていることを確認するため、ポッドにて作成されたランダム・ファイルも削除しましょう。 kubectl exec -it oke-fsspod3 -n testing -- bash root@oke-fsspod3:/# cd /usr/share/nginx/html root@oke-fsspod3:/usr/share/nginx/html# root@oke-fsspod3:/usr/share/nginx/html# ls -lrt *.dmp |wc -l 65 root@oke-fsspod3:/usr/share/nginx/html# rm *.dmp root@oke-fsspod3:/usr/share/nginx/html# ls -lrt *.dmp |wc -l ls: cannot access ’*.dmp’: No such file or directory 0 ポッドと関連するリソースを削除するには、以下のコマンドを実行します。 ~ % kubectl delete pod oke-fsspod3 -n testing pod "oke-fsspod3" deleted ~ % kubectl delete pvc oke-fsspvc1 -n testing persistentvolumeclaim "oke-fsspvc1" deleted ~ % kubectl delete namespace testing namespace "testing" deleted ~ % kubectl delete pv oke-fsspv1 persistentvolume "oke-fsspv1" deleted velero % kubectl get pv No resources found ポッドと永続ボリュームが削除されたので、次はリストアです。既存のバックアップを確認し、以下のとおり適切なリストア・コマンドを発行しましょう。 % velero % ./velero backup get NAME STATUS ERRORS WARNINGS CREATED EXPIRES STORAGE LOCATION SELECTOR backup-full-cluster-demo Completed 0 0 2021-02-07 21:46:23 -0600 CST 28d default backup-full-cluster-demo-1 Completed 0 0 2021-02-07 21:52:25 -0600 CST 28d default backup-full-cluster-demo-2 Completed 0 0 2021-02-07 22:44:09 -0600 CST 29d default backup-pv-demo-1 Completed 0 0 2021-02-07 21:55:08 -0600 CST 29d default backup-pv-demo-2 Completed 0 0 2021-02-07 22:46:23 -0600 CST 29d default 永続ボリュームをリストアする 永続ボリュームのリストアには、以下のコマンドを使用します。   velero % ./velero restore create --from-backup backup-pv-demo-2 Restore request "backup-pv-demo-2-20210208215719" submitted successfully. Run `velero restore describe backup-pv-demo-2-20210208215719` or `velero restore logs backup-pv-demo-2-20210208215719` for more details. velero % ./velero restore describe backup-pv-demo-2-20210208215719 Name: backup-pv-demo-2-20210208215719 Namespace: velero Labels: Annotations: Phase: Completed Started: 2021-02-08 21:57:22 -0600 CST Completed: 2021-02-08 21:57:23 -0600 CST Backup: backup-pv-demo-2 Namespaces: Included: all namespaces found in the backup Excluded: Resources: Included: * Excluded: nodes, events, events.events.k8s.io, backups.velero.io, restores.velero.io, resticrepositories.velero.io Cluster-scoped: auto Namespace mappings: Label selector: Restore PVs: auto % kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE oke-fsspv1 100Gi RWX Retain Available testing/oke-fsspvc1 oci-fss1 31s 続いてクラスタをリストアします。リストアする必要のあるポッドは1つのみですが、この例では、クラスタのリストア機能をわかりやすく示すため、フル・クラスタ・リストアを発行します。   % ./velero restore create --from-backup backup-full-cluster-demo-2 Restore request "backup-full-cluster-demo-2-20210208220018" submitted successfully. Run `velero restore describe backup-full-cluster-demo-2-20210208220018` or `velero restore logs backup-full-cluster-demo-2-20210208220018` for more details. % ./velero restore describe backup-full-cluster-demo-2-20210208220018 Name: backup-full-cluster-demo-2-20210208220018 Namespace: velero Labels: Annotations: Phase: Completed Started: 2021-02-08 22:00:20 -0600 CST Completed: 2021-02-08 22:01:04 -0600 CST % kubectl get pods --namespace testing -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES oke-fsspod3 1/1 Running 0 12m 10.234.0.30 10.0.0.5 % kubectl get pods --namespace testing -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES oke-fsspod3 1/1 Running 0 12m 10.234.0.30 10.0.0.5 % kubectl exec -it oke-fsspod3 -n testing -- bash root@oke-fsspod3:/# cd /usr/share/nginx/html root@oke-fsspod3:/usr/share/nginx/html# ls -lrt *.dmp |wc -l 65 前のブロックにて確認したとおり、ポッドおよび関連する永続ボリュームがリストアされています。このプロセスが機能することは、k8s v1.18.10およびv1.17.13にてテスト済みです。詳細はこちらの資料を参照してください。 このプロセスを他のユースケースへ拡大する このブログは、永続ボリュームを使用したOKEの基本的なバックアップおよびリカバリにフォーカスしています。Veleroでは、他の大半のバックアップ・ツールと同様、定期的に自動バックアップが実行されるよう設定することもできます。また以下のようなユースケースにもVeleroを使用できます。 オブジェクト・ストレージを別のリージョンに変更し、OKEクラスタに対するリージョンごとのディザスタ・リカバリを可能にする。(このプロセスでは、組織のデータ・レジデンシー要件に従う必要があります) OCIのパフォーマンスとセキュリティ・レベルの高さ、および価格的メリットを享受できるよう、他のクラウド・プロバイダからOKEへKubernetesのデプロイメントを移行する。 オンプレミスのKubernetesから移行する。 Oracle Cloud Container Engine for Kubernetesが持つ堅牢性とスケーラビリティに、Veleroのディザスタ・リカバリ機能を組み合わせることで、より本番環境に即したKubernetesプラットフォームの使用が可能になります。

※本記事は、Seshadri Dehalisan による "Backing up your OKE environment with Velero" を翻訳したものです。   今やKubernetesは、コンテナ化したアプリケーションをデプロイメントする企業にとって、究極のソリューションになりました。オラクルでは、そのKubernetes向けにOracle Container Engine...

CLOUD INFRASTRUCTURE SECURITY

OCI DNSでGoDaddyドメインを管理するには

※本記事は、Cody Brinkman による "How to manage your GoDaddy domain with OCI DNS" を翻訳したものです。   February 19, 2021   GoDaddyでドメインを登録したけれども、DNSゾーンの管理にはOracle Cloud Infrastructure(OCI)を使用したいというユーザーの方を最近よく見かけます。なぜOCI DNSを使用するのでしょうか。解説していきましょう。 この記事は、OCIを使用してDNSゾーンの管理を始めたいけれども、実際に「Change」ボタンをクリックするのが怖いという方に安心していただくために執筆しています。この話をするとほぼ必ず、「待って、本当にその方法でアプリケーションにアクセスできなくなることはない?絶対に?」と言われますし、そう思われるのも当然です。よくわかります。DNS管理は怖いのです。DNS管理でエラーが発生すると、場合によってはアプリケーションがアクセス不可になります。多くの企業にとって、この停止時間は大きな財務的損失につながります。 ここではGoDaddyを例として使用しますが、以下の手順はほとんどのレジストラに当てはまります。このプロセスは、正しく実行すれば停止時間ゼロですぐに完了し、痛みを伴うこともありません。 前提条件 •    既存のドメイン名。オラクルはレジストラではないため、ベンダーからドメインを購入していただく必要があります。 •    OCIテナンシーおよび適切な権限を持つユーザー。DNS用のポリシーの例についてはこちらのドキュメントをご覧ください。 •    公開Webサイト。このサイトのIPアドレスが、ホスト名の解決先になります。私はAlways Freeのコンピュート・インスタンスをデプロイして、そこにシンプルなNginx Webサーバーをインストールしました。 OCI DNSでゾーンを管理する理由 •    DNSの応答性:DNS問合せでの業界屈指の応答時間によって、アプリケーションのパフォーマンスを最適化できます。DNSレコードは1分以内に世界中に伝播されます。 •    サポート対象のレコード:オラクルは27の一般的なDNSリソース・レコード・タイプをサポートしています(A、AAAA、CNAME、DNSKEY、MX、NS、SOA、TXTなど)。 •    セカンダリDNS:重要なアプリケーションの可用性を確保するために、プライマリDNSを冗長的に補完するセカンダリDNSをセットアップできます。 •    DDoSからの保護:OCI DNSは、SYNフラッド攻撃、UDPフラッド攻撃、ICMPフラッド攻撃、NTP増幅攻撃など、一般的なレイヤー3およびレイヤー4の攻撃から保護します。分散型サービス妨害攻撃(DDoS)からの保護はすべてのOCIアカウントに適用されるため、構成作業は不要です。悪意のあるアクティビティの監視機能も追加費用なしで24時間提供されています。 •    グローバル・エニーキャスト・ネットワーク:エニーキャスト・ネットワーク内では、リクエストを行っているユーザーにもっとも近いポイント・オブ・プレゼンスがDNSリクエストに応答するため、可能な限り最速のDNSパフォーマンスが実現されます。 DNSとは ドメイン・ネーム・システム(DNS)はインターネットの電話帳です。WebブラウザはIPアドレスによって相互に通信します。IPアドレスは32ビット(IPv4)または128ビット(IPv6)の数値であり、IPv4のIPアドレスは192.168.17.43のようになります。人はoracle.comなどのドメイン名を使用してオンラインで情報にアクセスします。Webブラウザに何か入力するたびに、入力した内容が、機械が読み取れるIPアドレスに変換されます。その変換を実行するのがDNS解決であり、ホスト名が、コンピュータが読み取れるIPアドレスに変換されるのです。 DNS解決の仕組み ドメイン名の解決は複数のフェーズに分かれます。1回のステップで終わるプロセスとなる場合もあれば、複数のサーバーとの通信が発生する場合もあります。以下の図では、ブラウザにホスト名を入力したときの処理の流れを説明しています。 最初に、ブラウザとオペレーティング・システムが自らのキャッシュをチェックし、oracle.comがないか探します。IPアドレスが見つからない場合、このリクエストが次のレベルに送信されます。 次のレベルはリゾルバ・サーバー。これは利用中のインターネット・サービス・プロバイダ(ISP)です。このリゾルバが自らのレコードをチェックし、oracle.comのIPがないか探します。必要な情報がこのサーバーのキャッシュ内に存在する場合は、リゾルバがこのホスト名のIPアドレスを送り返します。サーバーのキャッシュ内に存在しない場合は、このリクエストが次のレベルに送信されます。 これらのISP DNSリゾルバは、他のDNSサーバーに対して正しいIPアドレスを問い合わせるように構成されています。DNS階層内の1つ目にあるトップレベル・サーバーはルート・サーバーです。13セットのルート・サーバーが世界中に戦略的に配置されています。これら13セットを12の事業者によって運用することで、正確性、可用性、セキュリティを確保しています。ルート・サーバーはドメイン名をIPアドレスにマッピングするわけではなく、トップレベル・ドメイン(TLD)サーバーに関する情報を保持しています。TLDは、.com、.org、.govなど、ドメイン名の末尾のセクションです。この記事の例では、ルート・サーバーはリゾルバに対して、.comドメイン用のTLDへの経路を案内します。 次に、リゾルバはそのTLDサーバーに、oracle.comのIPアドレスを問い合わせます。しかし、このTLDサーバーもそのIPアドレスについて知りません。これらのサーバーでは、セカンドレベル・ドメインのデータが管理されています。そのため、TLDはリゾルバに対して次の最終のレベルとなる、権威DNSサーバーへの経路を案内します。 DNSルックアップ・リクエストの最終地点まで来ました。権威DNSサーバーはoracle.comおよび既存のサブドメインのIPアドレスを知っています。 目的を達成しました。リゾルバはoracle.comのIPアドレスを送り返し、Webブラウザがユーザーをサイトに案内します。ホスト名のレコードはローカル・マシンにキャッシュされ、これ以降のリクエストでは最初のステップで取得できるようになります。 手順が多いように思えますが、このプロセス全体にかかる時間は数ミリ秒です。人の目では、DNS階層全体を辿ったリクエストとローカル・キャッシュ内でのリクエストの差に気づくことはできません。 TTL とはなにか、それがなぜ重要か 私の経験上、Time to Live(TTL)はもっとも理解されていないDNSの要素です。TTLはDNSレコードの有効期限であり、DNSサーバーまたはリゾルバに対して問合せをキャッシュする期間を指定します。この期間を過ぎると、新しい問合せのリクエストが実行されます。TTLが長いほど、サーバーがその情報をキャッシュに保持する期間が長くなり、TTLが短いほど、サーバーがその情報をキャッシュに保持する期間が短くなります。 oracle.comの例を見てみましょう。TTLが3,600秒のレコードを作成して、oracle.comのIPアドレスを1.2.3.4と指定した場合、サーバーはそのレコードに関する情報を1時間保持します。このレコードの解決先を1.2.3.55に変更した場合でも、同じリゾルバを使用するすべての人に、このレコードの最初の情報(1.2.3.4)が送信されることになります。TTLが期限切れになるまで、このサーバーに問合せが送信されないためです。1時間が経った後、下流のサーバーが更新され、oracle.comは新しい値(1.2.3.55)に解決されるようになります。 変更の頻度が高いレコードについては、TTLを可能な限り短い期間にするのがベストプラクティスです。そうすることで、変更が発生した場合に、エンドユーザーに最新の情報が送信されます。今はDNSの管理情報を変更しようとしていますので、移行の前にレコードのTTLの値を小さくすることをお勧めします。そうすれば、DNS階層内のサーバーのキャッシュが新しいDNS情報に更新されるタイミングがより早くなります。移行の完了後は、TTLを通常のレベルまで増やしてかまいません。 DNS解決とTTLの簡単な解説は以上です。何か新しい学びはあったでしょうか。それでは、この記事をクリックしていただいた、その本題に入りましょう。 OCI DNS でGoDaddyドメインを管理するための設定手順 1. GoDaddy ドメインに移動します。   2. 「Manage DNS」を選択します。 3. Advanced Features で、「Export Zone File」 をクリックします。 4. OCI テナンシーにログインし、「Networking」→「DNS Management」に移動します。 5.  パブリック・ゾーンを作成します。OCI DNS では他のタイプのゾーンも作成できますが、今回は他のタイプのゾーンは使用しません。 6. 「Import」をクリックし、ダウンロードしたテキスト・ファイルを選択します。私の場合、インポートするには ' parked ' レコードを削除する必要がありました。このレコードがすべての GoDaddy ドメインに存在するかは不明です。 7. これで、GoDaddy にあったすべてのレコードを使用したゾーンが作成されました。これらのレコードを簡単に確認して、すべての情報が正しくインポートされたことを確認しましょう。これらのネーム・サーバーの情報は、次のステップで使用します。 8. ドメインについては、これまで一切変更を行っていません。OCIで作成したゾーンは、現時点ではGoDaddy内の登録済みドメインに接続されていません。先ほど説明しましたが、権威ネーム・サーバーがドメインのIPアドレスを管理し、それをユーザーのブラウザに送信します。そのため、GoDaddyに対してGoDaddyのデフォルトの権威ネーム・サーバーの使用を停止し、代わりにOCIのネーム・サーバーを使用するように指定する必要があります。このプロセスによって、ゾーン管理がGoDaddyからOCIに切り替わります。では、GoDaddyに戻って、ドメインで使用されるネーム・サーバーを変更しましょう。 9.  「Enter my own nameservers (advanced)」 をクリックして、OCIゾーンのネーム・サーバーを入力します。 10. これで、ドメインが OCI によって管理されるようになりました。新しいレコードを作成してテストしてみましょう。 11. この変更を公開します。バックエンドでネーム・サーバーが更新されるため、新しいレコードが Web上に公開されるまでに少し時間がかかります。その間も、既存のゾーンはすべて通常通り機能します。 次のステップ この記事では、サイトを停止することなく、ゾーン管理をGoDaddyからOCIに切り替える方法について確認しました。最初に、ゾーン情報をコピーするために、GoDaddyからエクスポートしてOCI DNSにインポートしました。実際の管理は、GoDaddyに対して、ドメインに元々設定されているデフォルトのネーム・サーバーではなくOCIのネーム・サーバーを使い始めるようにGoDaddyに指定した時点で切り替えられました。この例ではGoDaddyを使用しましたが、他のドメイン・レジストラにも同じ手順が適用されます。 これらの手順は簡単なように見えますが、まずはテスト・ゾーンを移行することをお勧めします。この手順によって、今実行していることや、本番用のゾーンを移行する段階でのサイトの動作について正確に把握できるからです。 このプロセスをテストするには、Oracle Cloud Infrastructureの30日間の無償トライアルを活用するのが良いでしょう。このトライアルには、300米ドル分のクレジットとAlways Freeサービスが含まれています。  

※本記事は、Cody Brinkman による "How to manage your GoDaddy domain with OCI DNS" を翻訳したものです。   February 19, 2021   GoDaddyでドメインを登録したけれども、DNSゾーンの管理にはOracle...

全国のOTNおすすめ技術者向けセミナー、イベント(2021年2月)

セミナー&イベント Oracle Database Technology Night - Automatic Workload Repository(AWR)の分析 2月25日(木)開催 Oracle Databaseの分析に使用するAWRですが、まだ使いこなせていない方も多いと思います。 今回のTech NightではAWRの分析方法をサンプルAWRレポートを使用して解析していきます。 まだ使用したことのない方のために、最初にAWRの概要として簡単な分析方法やバージョンの違いなどについても説明します。   MySQLを活用した、エンタープライズグレードのSaaS提供基盤選びのポイント 2月25日(木)開催 SaaS基盤の選択ポイントは「シェア」ではなく、「機能」「コスト」「セキュリティ」「安定性・継続性」などのバランスです。 今回のウェビナーでは、SaaSを提供する企業が選択すべき「エンタープライズグレードのSaaS基盤」についての基本的な要件や、設計・構築時の注意点を解説します。OCIと他のSaaS提供基盤との比較、SaaSビジネスの成長と収益の安定化に向けた施策についてもご紹介します。 新サービス提供基盤を比較・検討中の方、現在利用中のSaaS基盤のコスト削減を目指したい方、SaaS基盤の課題解決のためのヒントを得たい方は必見のセミナーです。   【より深く知ろう】ユースケースから学ぶ、マルチクラウドの本当のところ 2月25日(木)開催 いよいよマルチクラウド時代がクラウド化への一つの解として本格的に検討が進んできました。 今回は、そんなマルチクラウドの実際のケースをいくつか取り上げて、特にOracle Cloud Infrastructure を他のクラウドサービスと組み合わせた場合のメリットや構成上の注意点などについて、エンジニアの視点からお伝えします。   本音トーク!Oracle ACE×しばちょう先生、Oracle Database 21c & Oracle Cloudどこまで使える? 3月4日(木)開催 2021年1月14日、Oracle Database 21c が Oracle Cloud 上で提供開始されました。世界で最も強力なコンバージド・データベース・エンジンの提供というオラクルの戦略を継続するリリースで、Blockchain Table や インデータベース機械学習(ML)用AutoMLなど、様々な機能が提供されています。 本セッションでは、株式会社野村総合研究所でOracle ACEの大塚 紳一郎氏と、米国本社開発部門のGlobal X-Teamで活躍中の柴田 長氏(しばちょう先生)に登壇頂き、データベース技術者の観点でコンバージドの良さや、Oracle Cloud 上で提供されることの良さを中心に、対談形式でお話し頂きます。   ゲーム業界におけるMySQL Vol.2 〜スクウェア・エニックスの検証結果に学ぶ、MySQL HeatWaveを用いたクエリ爆速術〜 3月4日(木)開催 ゲーム業界のリーディングカンパニーであるスクウェア・エニックスは、オラクルが提供するMySQL Database Serviceの分析エンジンHeatWaveをリリースに先駆けて検証されました。 今回のウェビナーでは、スクウェア・エニックスで実際にHeatWaveの検証を担当された神津様に検証結果をご紹介いただきます。HeatWaveによるSQL文の大幅な処理性能向上の結果とともに、実際の運用に向けた課題と解決策についても解説いただく予定です。   Oracle University 無償オンラインセミナー登録受付中 3月実施分 Oracle University の無償オンラインセミナーに参加しませんか。ORACLE MASTER Silver SQL 傾向と対策セミナーや Java モジュールシステム入門セミナー等人気のトピックを解説します。   Oracle Cloud ウェビナーシリーズ 進化し続ける オラクルの IaaS/PaaS や Oracle Database をはじめとする、さまざまな製品についての最新情報や活用事例および技術情報を業務部門からIT部門のエンジニアの方々までの幅広い皆様へ向けてウェビナーを通じてお届けします。さまざまなテーマや理解度レベルのコンテンツを取り揃えていますので、ぜひご活用ください。 2月26日(金) 実践Kubernetesハンズオン ~OKEでKubernetesをバーチャル体験しよう~ 3月3日(水) 【まずはここから】はじめてのOracle Cloud Infrastructure 3月18日(木) 【ことはじめ】はじめてのコンテナ活用をクラウドで   Systems Webinarsオンデマンド・見逃し配信開始 製品ごとのプレイリストをご用意しました。 開催日時の制約を受けることなく、自由な時間に何度でも自由に視聴いただけます。製品概要をご説明する「基礎編」、技術解説をおこなう「技術編」など多数コンテンツをご用意しています。各々、小一時間(45~60分)で学べるコンテンツとして是非ご活用ください。 Oracle Exadata Cloud@Customer ウェビナー Oracle Exadata ウェビナー Oracle Private Cloud Appliance ウェビナー ( 製品概要編 / セキュリティ対策編 ) AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。  

セミナー&イベント Oracle Database Technology Night - Automatic Workload Repository(AWR)の分析 2月25日(木)開催 Oracle Databaseの分析に使用するAWRですが、まだ使いこなせていない方も多いと思います。 今回のTech NightではAWRの分析方法をサンプルAWRレポートを使用して解析していきます。まだ使用したことのな...

Database

Multilingual Engine:Oracle DatabaseでJavaScriptを実行する

photo by Max Langelott on Unsplash ※本記事は、Alina Yurenkoによる"Multilingual Engine: Executing JavaScript in Oracle Database"を翻訳したものです。 Alina Yurenko、Alexander Ulrich、Lucas Braun、Hugo Guiroux、Stefan Dobre  この記事は同時掲載記事です。オリジナル記事はこちらをご覧ください      Oracle Database 21c以降、GraalVMを利用するJavaScriptを実行できるようになりました。本ブログ記事では、この新機能の内部について少しご紹介します。また、今後のブログ記事で、Oracle APEXでのサーバー側言語としてのJavaScriptについても取り上げる予定です。 Oracle DatabaseでJavaScriptを実行する Multilingual Engine:Oracle Database内にGraalVMを埋め込む 今後の展望 オラクルは先ごろ、世界最先端のデータベースの次世代版となるOracle Database 21cをリリースしました。このリリースには、データベース内で直接JavaScriptコードを実行できる機能が追加されています。Oracle Databaseでは長い間、PL/SQL、JavaおよびCでのサーバー側プログラミングがサポートされてきました。サーバー側ビジネス・ロジックは、多くのエンタープライズ・アプリケーションで重要な役割を果たします。1つは、データをロジックに移動するのではなく、ロジックがデータのある場所で実行されるということです。これによって、ネットワーク上の不要なデータ送信がなくなり、特にテラバイト以上のデータがある場合など、データを多用するオペレーションのパフォーマンスを大幅に向上することができます。次に、たとえばデータベース内のビジネス・ルールを保管および実行することにより、すべてのアプリケーションだけでなく、データにアクセスするユーザーのルール順守が保証され、セキュリティとコンプライアンスの要件の実装を大幅に簡略化できます。最後に、通常のSQL文に加えて、よく使用する機能を中心的な場所に保管して、シンプルなユーザー定義の機能として実行できるため、すべてのアプリケーションでコードをレプリケートする必要がなくなります。この機能は、ロジックが複雑な場合や、頻繁に変わる傾向がある場合に特に実用的です。 21cでは、サポートされる言語セットが拡張され、現在最も普及し人気の高いプログラミング言語の1つであるJavaScriptが含まれるようになりました。開発者は、この人気の高い言語を使用してデータベースのプログラミングを行い、JavaScriptで利用可能なツールおよびライブラリの豊富なエコシステムを利用できるようになります。PL/SQLと同様、サーバー・サイドJavaScriptの実行もデータベースと緊密に統合されており、データとSQLの実行に近い、データベース・セッションのプロセス内でコードは実行されます。この緊密な統合により、一方のJavaScriptと、もう一方のSQLおよびPL/SQLとの間の効率的なデータ交換が可能になります。 21cリリースでは、Oracle Database内のJavaScriptのサポートは、ローコード・アプリケーション・フレームワークであるOracle Application Express(APEX)に焦点を当てています。Oracle Database 21cとAPEX 20.2以降、開発者はサーバー側ロジックをJavaScriptでAPEXアプリケーション(動的なアクションやプロセスなど)に実装でき、PL/SQLだけに制限されなくなります。これは、APEX開発者にとって、APEXアプリケーションで生産性を向上し、クールな新機能を実現できる可能性を秘めた、期待すべき新機能だと考えます。APEXでのJavaScriptの新機能の詳細については、今後のブログ記事で詳しく紹介する予定です。 APEX以外にも、Oracle Database内でのサーバーサイドJavaScriptの実行は、汎用PL/SQL APIを介して利用できます。本ブログ記事では、このAPIと、21cで利用できる機能をいくつか、手短に紹介します。さらに、内部を少し見ていくほか、サーバー・サイドJavaScript実行がどのように実装されるかについての詳細も説明します。 Oracle Database内でのJavaScript実行を支えているコンポーネントは、Multilingual Engine(略称:MLE)と呼ばれています。この名前から、MLEは単なるJavaScriptエンジンではないということがお分かりいただけるかもしれません。内部を見てみると、MLEの主な要素はGraalVMであると言えます。 複数のプログラミング言語を高いパフォーマンスで実行できるポリグロット・ランタイムです。MLEは、GraalVMを基盤として、他のさまざまなシナリオにとっても興味深いと考えられるGraalVM独自の埋込み機能を使用して、構築されました。まずは、ユーザー向けの機能を見てみましょう。   Oracle DatabaseでJavaScriptを使用する Oracle Databaseは、コード・スニペットにおけるエスケープの問題を回避するために、PL/SQLの引用構文を提供しています。 DECLARE ctx DBMS_MLE.context_handle_t := DBMS_MLE.create_context(); BEGIN DBMS_MLE.eval(ctx, 'JAVASCRIPT', q'~console.log("Hello, World!");~'); DBMS_MLE.drop_context(ctx); END; / DBMS_MLEパッケージでは、たとえばJavaScriptとPL/SQLで値を交換するための追加のプロシージャを提供しています。 デフォルトで、JavaScriptの関数console.log()によって、PL/SQLパッケージDBMS_OUTPUTのバッファに書込みが行われます。このスニペットがSQL Developer Web、SQLclまたはSQL*Plusなどのクライアントを介して実行される場合、console.log()によって生成される出力が、それらのツールのコンソール上に表示されます。クライアントによっては、クライアントでDBMS_OUTPUTの取得を有効化しなければならない場合があります。たとえば、SQL*Plusでは、DBMS_OUTPUTによって生成される出力の表示は、SET SERVEROUTPUT設定によって制御されます。DBMS_OUTPUTパッケージを使用して、出力を手動で取得することもできます。 DBMS_MLEはセッション内の1つのコンテキストに限定されません。DBMS_MLE.create_context()を使用して、セッション内で複数のコンテキストを作成できます。それぞれのコンテキストは完全に独立したJavaScriptランタイムです。開発者がコンテキストをきめ細かく制御できるのは、強力な機能です。同じセッション内の個別のコンテキストにおいてさまざまなアプリケーションの独立性を実現し、アプリケーション間の干渉を防ぎます。ただし、そのコンテキストは、まったく制約がないわけではないことに注意してください。それぞれのコンテキストは、現在のデータベース・セッション内のメモリを消費します。データベース・リソースへの影響を最小限に抑えるため、アプリケーションで厳密に求められているよりも多くのコンテキストを同時に作成すべきではありません。また、参照されなくなったら、直ちにコンテキストを削除すべきです。これは、SQLカーソルなどのリソースと変わりません。 APEXアプリケーションなどのサーバー側プログラミングにとって本当に価値あるものとなるには、JavaScriptコードがデータベースとやり取りできるようにする必要があります。MLEは、SQLおよびPL/SQL文の実行をサポートするmle-js-oracledb JavaScriptモジュールを提供しています。次の例では、このモジュールを使用して、現在の時刻を返す簡単なSQL問合せを実行します。 DECLARE ctx DBMS_MLE.context_handle_t := DBMS_MLE.create_context(); user_code clob := q'~ const oracledb = require("mle-js-oracledb"); const sql = "SELECT SYSTIMESTAMP as ts FROM dual" // execute query const result = oracledb.defaultConnection().execute(sql); console.log(JSON.stringify(result.rows)); ~'; BEGIN DBMS_MLE.eval(ctx, 'JAVASCRIPT', user_code); DBMS_MLE.drop_context(ctx); END; / mle-js-oracledb APIは、通常のクライアント側の、node.js向けOracle Databaseドライバにほぼ従っています。mle-js-oracledbを使用すれば、アプリケーションで任意のSQL文(問合せ、DML文、無名PL/SQLブロックなど)を実行し、値をプレースホルダにバインドし、問合せ結果をフェッチすることができます。node-oracledb APIを使用する既存のJavaScriptコードは通常、ほとんど手間をかけずにmle-js-oracledbに適合できます。node-oracledbと比べると、実際にここでデータベースと接続する必要はありません。MLEでJavaScriptコードから実行されるすべてのSQL文は、現在のデータベース・セッション内で実行されます。oracledb.defaultConnectionメソッドによって、現在のセッションを表す接続オブジェクトが返されます。APEXで実行されるサーバー・サイドJavaScriptコードは、便利なことに、apex.connプロパティを介して接続オブジェクトにアクセスすることができます。 問合せ結果をフェッチし、プレースホルダをSQL文にバインドする際に、mle-js-oracledbは一方のPL/SQL型と他方のJavaScript型の間で変換を行います。node-oracledbと同様に、デフォルトで、PL/SQL型はもっとも近いJavaScript型にそれぞれマッピングされます。上記の例では、結果列tsにPL/SQL型TIMESTAMP WITH TIME ZONEがあり、JavaScriptの日付値としてフェッチされます。  ただし、PL/SQL型とネイティブのJavaScript型間の変換は常に適切とは限りません。データタイプの変換によって、精度が失われる場合があります。次の例では、数値を返すSQL問合せを実行します。exp(4)の結果を小数点以下3桁で切り捨て、結果が54.598になると想定します。 DECLARE ctx DBMS_MLE.context_handle_t := DBMS_MLE.create_context(); user_code clob := q'~ const oracledb = require("mle-js-oracledb"); // SQL NUMBER結果:54.598 const sql = "SELECT trunc(exp(4), 3) AS n FROM dual"; const result = oracledb.defaultConnection().execute(sql); // 浮動小数点のJavaScriptの数値をフェッチ // 出力:54.598000000000006 console.log(result.rows[0][0].toString()); ~'; BEGIN DBMS_MLE.eval(ctx, 'JAVASCRIPT', user_code); DBMS_MLE.drop_context(ctx); END; / デフォルトで、Oracleの数値はJavaScriptの数値としてフェッチされます。より精度の高いOracleの数値形式から精度の低いJavaScriptの数値形式への変換は、予期しない結果につながることがあります。この例では、返されるJavaScriptの54.598000000000006という数値は、浮動小数点の変換により、SQL問合せの実際の結果とはわずかに異なります。この影響は、この特定の例ではそれほど重要には思えないかもしれませんが、たとえば、通貨の値を扱おうとすると、その影響は非常に大きくなります。通貨の値では、数値の精度はきわめて重要です。 この状況を改善するため、mle-js-oracledb APIでは、開発者が特定のアプリケーションのシナリオにおいて適切なデータ表現を選べるようにしています。先の2つの例のように、SQLの値は、ネイティブのJavaScript型としてフェッチすることができ、既存のJavaScriptコードと都合よく統合します。そのため、代替方法として、MLEでは、一部のPL/SQL型のためのJavaScript APIを提供しています。これにより、ネイティブのJavaScript型への変換の必要がなくなり、精度が失われることもありません。次の例では、先ほどと同じSQL問合せを実行し、結果のNUMBER列をフェッチします。ただし、今回はmle-js-oracledbに対してOracleNumberオブジェクトとして列をフェッチするように指示します。 DECLARE ctx DBMS_MLE.context_handle_t := DBMS_MLE.create_context(); user_code clob := q'~ const oracledb = require("mle-js-oracledb"); const sql = "SELECT trunc(exp(4), 3) AS n FROM dual"; // NUMBER列をOracleNumberとしてフェッチ const options = { fetchInfo: { N: { type: oracledb.ORACLE_NUMBER } } }; const result = oracledb.defaultConnection().execute(sql, [], options); // 10進数のOracleNumber値を表示 // 出力:54.598 console.log(result.rows[0][0].toString()); ~' BEGIN DBMS_MLE.eval(ctx, 'JAVASCRIPT', user_code); DBMS_MLE.drop_context(ctx); END; / OracleNumberオブジェクトは、元のSQLの値と完全に同じ10進数値を表します。さらに、OracleNumberオブジェクトは、NUMBER型に関して、対応するPL/SQL演算と同じセマンティクを用いる、小数点以下桁数の精度を計算する方法を提供します。   Multilingual Engine:Oracle Database内にGraalVMを埋め込む ここまでで、Oracle Database 21cではどのようにJavaScriptを実行し、APEXアプリケーションがサーバー・サイドJavaScriptロジックからメリットを得られるかを見てきました。しかし、内部的にこれはどのような仕組みになっているのでしょうか。本記事の導入部分では、Multilingual Engine(MLE)の中核をなしているのが、Oracle Database内のGraalVMの埋込みであると言及しました。GraalVMはJavaやその他の言語向けのすばらしいスタンドアロン・ランタイムであるだけでなく、ネイティブ・アプリケーションにも埋め込むことが可能です。MLEは、GraalVMで、JavaScriptの実行などのプログラミング言語の機能によって既存のアプリケーションがいかに強化されるかを示す興味深い例となっています。この記事の残りの部分では、MLEのアーキテクチャの側面の一部に焦点を当て、この埋込みを可能にするGraalVMの機能について説明します。 以下の図は、MLEのアーキテクチャを簡略化したものです。このいくつかのボックスと矢印が何を表しているのかを見ていきましょう。   MLEのアーキテクチャ   まずは最下層から見ていきます。Oracle Database(Cで記述されたネイティブ・アプリケーション)はどのようにして実際にGraalVMを呼び出し、JavaScriptコードを実行しているのでしょうか。いずれにしても、GraalVMはJavaに実装されています。答えは、GraalVMのネイティブ・イメージです。このネイティブ・イメージは、Javaアプリケーションを、JVMなしで実行できるスタンドアロンの実行可能ファイルにコンパイルできます。この機能は特に、メモリ要件が低く、ほぼ直ちに起動するJavaマイクロサービスに使用されています。しかし、スタンドアロンの実行可能ファイルとともに、ネイティブ・イメージは、既存のアプリケーション内にロードできる共有ライブラリも生成することができます。これが、Multilingual Engineを実現する中核テクノロジーです。ネイティブ・イメージはMLEランタイムと、JavaScriptランタイム(すべてJavaで実装)などの必要とされるすべてのGraalVMコンポーネントを、オンデマンドでデータベース・プロセスにロードされる共有ライブラリにコンパイルします。 Multilingual Engineのロード後、データベース・プロセスによって、MLEのネイティブ・イメージの機能が呼び出され、コンテキストが管理され、実際にJavaScriptコードが実行されます。同様に、MLEによって、JavaScriptコードへのSQL実行といったサービスを提供するために、ネイティブのデータベース機能が呼び出されます。こうした呼び出しパスの一部を、上の図に描写しています。赤の部分(ネイティブのデータベース・コード)から青の部分(MLEネイティブ・イメージ)へ、またその逆へかかるすべての矢印です。このような呼出しが頻繁に発生するため、これはパフォーマンスにとって非常に重要となります。幸いにも、GraalVMでは、ネイティブ・イメージ内のCコードと関数間の呼出しを非常に効率的に実装し、C関数間の通常の呼出しではオーバーヘッドはほとんどありません。 ここまでのところでは、Oracle DatabaseでのGraalVMの埋込みは、イメージの構築時と似ているように見えます。MLEネイティブ・イメージがロードされる際の起動の手間を最小化します。さらに、イメージの構築時に初期化されており、実行時に読込み専用のアクセスとなっているネイティブ・イメージのヒープ領域は、データベース・プロセス間で透過的に共有されます。これにより、MLEでJavaScriptコードを実行する複数の同時データベース・セッションのシナリオにおいて、MLEのメモリの影響が低減します。 Node.jsなどの埋込みのシナリオでは、JavaScriptランタイムのメモリは、オペレーティング・システムから直接割り当てられます。Oracle Databaseへの統合については、MLEはそれよりも深く到達しなければなりません。Oracle Databaseには、特にデータベースのワークロードについてリソース使用率を最適化するための、CPUやメモリなどのOSリソースを高度に管理できる機能も含まれます。さらに、Oracle Databaseは個々のテナントが、設定されているリソースの上限を超えることのないマルチテナント・システムです。JavaScript実行が同じルールに従うように、MLEはOracle Database Resource Managerと統合されています。MLEでは、JavaScriptランタイムに必要なすべてのメモリを、オペレーティング・システムから直接ではなく、データベース・サービスを介して割り当てます。このような割当はすべてリソース・マネージャのポリシーの対象となります。同様に、リソース・マネージャによって設定されているCPU上限はJavaScriptコードに対して有効であり、データベースによって要求された場合は、確実にJavaScriptコードをキャンセルすることができます。このため、MLEコンポーネントはデータベース・インスタンスでメモリを無駄に使用しません。 それでは、より上層部に目を向けて、MLEコンテキストの管理について説明しましょう。MLEコンテキストはDBMS_MLE APIを使用して作成することができます。前述のように、MLEでは開発者がデータベース・セッションでのコンテキスト管理を制御できるため、柔軟なプログラミング・モデルが提供されます。ただし、これはMLEの特別な機能ではありません。アプリケーションおよびランタイムの状態をカプセル化し、論理的に独立している複数のコンテキストを使用できる機能は、GraalVMの奥深くに組み込まれています。組込み開発者向けのGraalVM Polyglot APIでは、複数の独立したコンテキストの作成がサポートされます。MLEのコンテキスト管理用APIは、Polyglot API上に直接実装されます。 MLEでは同じデータベース・セッションで複数のコンテキストを作成できることを思い出しながら、MLEランタイムで管理されるポリグロット・コンテキストへの直接マッピングをコンテキストがどのように処理するかを表した以下の図を見ていきましょう。 MLEコンテキスト管理  コンテキストによってアプリケーションの状態がカプセル化される一方で、GraalVMでは、ポリグロット・エンジン内の他のプログラム・リソースが管理されます。データベース・セッションを通じて、MLEは複数のコンテキストの下で同じポリグロット・エンジンを使用します。同じJavaScriptアプリケーション・コードが、エンジンを共有する同じセッションの複数のコンテキストで評価される場合、基盤となるエンジンは、それらのコンテキスト全体でJavaScriptコードの実行可能ファイルの表現を再利用できます。これにより、複数のコンテキストが使用される場合に、メモリ・フットプリントを低減できるほか、JavaScriptコードを解析するための時間を短縮できます。 GraalVMの強力な機能の1つが、言語の相互運用性です。GraalVMのTruffle言語実装フレームワーク上に実装されるJavaScriptやその他のプログラミング言語は、オーバーヘッドがほとんどない状態でメソッドを呼び出し、互いのデータにアクセスすることで、相互にやり取りすることができます。MLEは、GraalVMの言語の相互運用性を使用して、制御された安全な方法でJavaScriptコードがデータベース機能にアクセスできるようにしています。前のセクションで、mle-js-oracledbモジュールによって提供される、SQL実行向けのJavaScript APIについて触れました。このJavaScript APIは、実のところ、MLE SQL Driverというコンポーネントの上に実装されています。このコンポーネントでは、安全なAPIを使用して、現在のデータベース・セッション内でのSQL文実行がサポートされます。mle-js-oracledbモジュールのJavaScript実装によって、Truffleの言語相互運用性プロトコルを介してMLE SQLドライバが呼び出され、SQL文が実行され、問合せ結果がフェッチされます。Truffleの多言語呼出しの効率的な実装のおかげで、このやり取りでは、2つのJavaScript関数の呼出しに比べて、実質的にオーバーヘッドがありません。 以上で、Multilingual Engineの一部の内部機能に関する紹介は終わりです。要約すると、Oracle Database Multilingual Engineによって、開発者は、幅広く使用されている最新のプログラミング言語であるJavaScriptで、サーバー側ロジックを記述できます。MLEは、GraalVMの独自の機能によって、Oracle Databaseなどの高度に複雑なシステムへの埋込みがいかに可能になるかを示す一例となっています。   今後の展望 Oracle Database 21cでJavaScriptがサポートされるようになったことで、GraalVMをOracle Databaseに近づける最初の一歩を踏み出せました。私たちは、今後のリリースでMultilingual Engineを改善していく予定であり、最新の言語による開発者のサーバー側プログラミングの体験をさらに向上でき、Oracle Database上に優れたアプリケーションを実現できる素晴らしい機能がたくさんあると考えています。そのような機能の1つとして考えられるのが、JavaScriptモジュールを最優先としてサポートすることと、NPMパッケージ向けの統合とツールを提供することです。また、さらに多くの言語をサポートすることで、MLEを真の多言語にすることも目指します。 MLEの使用についてフィードバックを共有したい場合や、今後のリリースに向けて機能をリクエストしたい場合は、こちらまでお送りください。  GraalVMの詳細や始め方については、oracle.com/graalvmをご覧ください。  

photo by Max Langelott on Unsplash ※本記事は、Alina Yurenkoによる"Multilingual Engine: Executing JavaScript in Oracle Database"を翻訳したものです。 Alina Yurenko、Alexander Ulrich、Lucas Braun、Hugo Guiroux、Stefan Dobre この記事...

Java

コマンドライン・ユーティリティを書く楽しさ(パート1):重複ファイルの検索

※本記事は、Andrew Binstockによる"The joy of writing command-line utilities, Part 1: Finding duplicate files"を翻訳したものです。 実用的なユーティリティを含む小さなプロジェクトをステップ・バイ・ステップで作成する 著者:Andrew Binstock 2020年8月14日 自分用のコマンドライン・ユーティリティを書くというのは、Javaの知識を深めるとともに、実際に完成させるサイド・プロジェクトを作る方法としてふさわしく、実り多いものです。 たとえば、重複ファイルについて考えてみます。多くの人と同様に、筆者もラップトップに大量の音楽や写真を保存しています。時には、こういった内容を含むフォルダの管理が面倒なこともあります。ある写真や曲が重複しているかもしれないと思うものの、実際にどうなっているかがよくわからない場合は、特にそうです。この問題に対処する場合、1つまたは複数のディレクトリをスキャンして重複ファイルを見つけるユーティリティを実行できれば便利です。 本記事では、そのようなユーティリティを作成するプロジェクトについて説明します。これは、Java言語を使いこなすことができ、さらにスキルを伸ばしたいと思っている中級プログラマーにとって格好のプロジェクトです。あまり使われていないJava APIを紹介しつつ、設計やテストで発生する、予期しない問題のいくつかにも触れたいと思います。 また、本記事の全体を通して、無害と思われる小さな決断がもたらす影響に特に注目し、その代案について考えてみます。コーディングしながらこのような小さな決断を記録するという手法は、正当に評価されているとは言えません。この点については、以前の記事で説明しました。読み進めればおわかりになるでしょうが、小さな決断による影響について検討することで、ソフトウェアをさらに便利なものにするための新たな方法を見つけやすくなります。 小さなユーティリティを作るメリットの1つは、問題の領域に焦点が絞り込まれることです。しかし、設計の問題が紛れ込むことがそれによって防止されるわけではありません。まずは、この問題から取り上げることにします。   設計 ここでの目的は、簡単なコマンドライン・ユーティリティを作ることです。このユーティリティでは、1つまたは複数のディレクトリを指定することにより、そこに含まれるすべての重複ファイルをグループ化したリストを生成させることができます。 現時点でのコマンドラインは、次のようになります。 java –jar jar-name directory1 [directory2...directoryn] とても便利な追加機能として、サブディレクトリを検索するかどうかをプログラムに伝える機能が考えられます。ここでは、サブディレクトリをスキャンすることをデフォルトの動作とします。そうすれば、トップレベルの音楽ディレクトリを指定することで、その下位にあるすべての曲を処理させることができます。つまり、コマンドライン・スイッチはこの機能を有効にするのではなく、無効にするオプションということになります。ここでは、–nosubdirsを使うことにします。 最後のオプションは、–hまたは–helpです。時間がたって使い方を忘れてしまったときに、説明を表示する機能です。このような小さな仕様でも、いくつかの暗黙的な選択を行いました。たとえば、ヘルプ引数にハイフン2つを使うGNUスタイルのコマンド・オプション(--help)は使用しませんでした(これはささいなことのように思えるかもしれませんが、広く使ってもらうためにこのツールを作るとしたら、ハイフン2つの形式もおそらく含めるでしょう)。 次に、出力について考えます。情報を単純に表示するだけにしたいため、データはstdoutに書き出すことにしました。この出力は、キャプチャしてテキスト・ファイルに格納する必要があります。この選択でも、コマンドライン・オプションがもう1つ省かれています。それは出力ファイルを指定する機能で、高度なバージョンには搭載されているでしょう。この機能は、–oコマンドライン・オプションで表現するのが一般的です。 現時点で、重複ファイルが見つかった場合のサンプル出力は、次のようになります。 These files are the same:   D:\GoogleDrive\misc\NotesOnReed.txt   D:\GoogleDrive\misc\NotesOnReed2.txt These files are the same:   D:\GoogleDrive\misc\Chad-smiling.jpg   D:\GoogleDrive\misc\Chad-1988-atCamp.jpg 重複ファイルがない場合、プログラムで何も出力しないのではなく、そのことを示す1行を出力する必要があります。 ここまでは、ユーザーからの視点による基本設計のあらましを述べました。このような単純な決断により、コーディング時に機能を決める必要をなくすことができます。そういった決断はまた、YAGNI(「You ain't gonna need it」を縮めたもので、今後必要になるかもしれないがすぐには必要ない機能を作ってはいけないという意味)をどの程度認めるかを判断する際にも役立ちます。 実装を始める前に、目指すべき堅牢さのレベルを決めておく必要があります。筆者にとって、堅牢さの基本的な基準は2つあります。1つはキャッチされない例外をスローしないことです。もう1つは、何も出力されないケースがあってはならないことです。これらの決断は最低限の内容に見えますが、それによって以下に示す一連の要件が加わります。 存在しないディレクトリを指定した場合、エラー・メッセージを生成する必要がある 空のディレクトリを指定した場合、警告メッセージを生成する必要がある(ただし、空のサブディレクトリについては警告しない) すべてのI/O例外をキャッチする必要がある(ユーザーへの報告も行う必要がある) かなり単純に思えますが、それは一部の厄介な問題に対処しないようにしているからです。たとえば、ユーティリティの実行中にファイルが追加または削除されたディレクトリは、どのように扱うべきでしょうか。つまり、重複ファイルを重複リストに含める前に、そのファイルがまだ存在していることを確認する必要があるでしょうか。隠しファイルや、他のファイルへのシンボリック・リンクはどう扱えばよいでしょうか。ひとまずは、変更のないディレクトリのみを扱い、シンボリック・リンクはたどらないことにします。 前もってこのような決断をしておくことで、コードを書くときに余計なことに惑わされることがなくなります。   実装 ここで提示する設計には、主要なアクションが5つあります。コマンドライン処理、指定したディレクトリのファイル一覧の作成、ファイルのフィンガープリントの取得、重複チェックのためのフィンガープリント比較、そして出力の生成です。それぞれについて考えてみます。 ここで示すコードは、すべてJava 8をベースとしています。この選択に大きな意図があるわけではなく、それより後のJavaリリースに含まれる機能はこのプロジェクトに必要ないというだけです。コードは、FileDedupeプロジェクトのリリース1.0としてGitHubで公開しています。 コマンドライン処理:先ほど説明したコマンドラインはとてもシンプルであるため、コマンドライン・オプションを扱う無数のライブラリのいずれかを選んで使う必要はありません。ここでは、渡された引数を1つずつ調べることにします。引数は、ハイフンで始まるオプションが見つかるか、リストの最後に到達するまでは、すべてディレクトリとして扱います。このコードに、特筆すべきことはありません。 しかし、このアプローチから、ファイルとディレクトリに関連する多くのコマンドライン処理で対処されていない問題が浮かび上がります。たとえば、.と..は有効なディレクトリとして扱われるようにすべきでしょうか。そうすれば、フルパスを使わずにディレクトリを指定できることになります。これは、名前がハイフンで始まるディレクトリがなければ、うまく動作します。ハイフンで始まっていれば、ディレクトリではなく、オプションとして扱うでしょう。筆者の場合、そのようなディレクトリはないと認識しているため(少なくとも、「ない」と思っています)、この問題は無視することにします。ただし、その場合、ハイフンで始まるディレクトリはフルパスを使用して指定する必要があることをヘルプ情報に記しておくべきでしょうか。エッジ・ケースはソフトウェア開発を急ぐ開発者にとって強敵です。課題が概念的にはシンプルに見えるからです。エッジ・ケースに対処すれば、ユーティリティの信頼性が向上します。 ディレクトリのファイル一覧:ディレクトリ構造をたどり、そこに含まれる(場合によっては、そのサブディレクトリに含まれる)ファイルの一覧を取得するというのは、とてもありふれた操作です。そのため、この操作を行うJavaコードはほぼ決まっています。java.nio.fileパッケージのFiles APIには、ディレクトリをクロールするwalk()メソッドがあります。このメソッドでは、Pathエントリのストリームを返します。次のコードでは、このAPIを使って、ファイル名のストリームをArrayListに変換しています。 Files.walk( dir, skipSubDirs? 1 : Integer.MAX_VALUE )         .filter( p -> p.toFile().isFile() )         .peek( System.out::println )         .collect( Collectors.toCollection( ArrayList::new )); Files.walkにはいくつかの亜種があります。ここで使っているのは、検索するサブディレクトリの深さを整数で指定できるものです。.filterは、ファイルのみが返されるようにするために使っています。このコードでは、ファイル名の出力も行っています。このユーティリティの高度なバージョンでは、最後の機能はコマンドラインの–verboseオプションで制御することになるでしょう。 この処理で発生した例外はすべて捕捉します。例外が発生した場合は、nullではなく空のArrayListを返します。nullポインタ例外を避けるために、nullではなく空のコレクションを返すのは良いことです。しかし、これには言外の意味があります。具体的に言えば、大きなディレクトリを処理している途中で例外が発生した場合、ディレクトリにファイルがなかったとプログラムが勘違いしてしまうのです。この点については、さまざまな選択肢が考えられますが、ユーザーにエラーを通知することにしたいと思います。どのトップレベル・ディレクトリでエラーが起きたかを明示し、重複リストは返さないものとします。I/Oに問題があるディレクトリでは、後でファイルを削除したくはありません。 この選択には大きな制限が伴います。先ほどの要件に基づけば、ディレクトリにファイルがない場合はユーザーに通知します。しかし、前述のエラーによって空のArrayListが返されるため、そのメッセージが表示されてしまいます。つまり、ユーザーにはまずエラーが起きてディレクトリがスキップされることが表示され、次にディレクトリにファイルがなかったと表示されます。後ほどこれを改善したいと思った場合の適切な解決策は、Optionalを返すことです。そうすることで、呼び出す側のメソッドがエラーと有効な空のリストを区別できます。 以上でファイル一覧を取得したため、次は重複を検知する方法について考えてみます。   ファイルの内容のフィンガープリントを取得する ここでの目的は、それぞれのファイルの内容を一意に識別する方法を見つけ出すことです。暗号ハッシュはファイルに対して一意な値を生成しますが、機密要件を満たすために大量の計算が必要になるため、今回のニーズの解決策としては重すぎます。たとえば、暗号ハッシュでは、1バイトや2バイトしか違わない2つのファイルからまったく異なるハッシュが生成される必要があります。今回は、ファイルが一意であれば、似ているかどうかは関係ありません。そのため、いかなる暗号計算でも、このユーティリティにメリットをもたらすことはありません。 ここで必要なものは、どんなファイルの内容からも一意な値が生成されると保証されているチェックサム生成ツールだけです。つまり、2つのファイルから同じ値が生成されれば、確実に同じ内容であることがわかればよいのです。そのようなチェックサムで特に有名なのがCRC-32です。ありがたいことに、標準Javaライブラリのjava.util.zipパッケージにはCRC-32チェックサム・ルーチンが含まれています。チェックサム生成ツールでは、このチェックサム・ルーチンを次のコードで使用しています。 CheckedInputStream check = new CheckedInputStream( file, new CRC32() ); BufferedInputStream in = new BufferedInputStream( check ); try {     while( in.read() != -1 ) {         // ファイルをすべて読み取る     }     in.close(); } catch( IOException e ) {     System.err.println( "Error reading file: " + filename );     throw( new IOException( e.toString() )); } return( check.getChecksum().getValue() ); このAPIのコードは通常とは異なります。まず、このコードの1行目で行っているように、それぞれのファイルについてCheckedInputStreamを作成し、それをJavaライブラリのCRC-32ルーチンに向ける必要があります。次に、空のループでファイルの内容をすべて読み取り、最後にCheckedInputStreamに戻ってチェックサムの値を取得しています。 Javaで提供されている選択肢は、CRC-32だけではありません。Adler-32アルゴリズムを使うこともできます。こちらの方が高速ですが、小さなファイルのチェックサムを計算する場合の信頼性は低下します。このJava APIでは他のチェックサム・アルゴリズムを追加することもできますが、このプロジェクトの場合はCRC-32でまったく問題ありません。 ここまでで、すべてのファイルのチェックサムを取得しました。他に必要なのは、そこから重複したファイルを見つけ出す方法だけです。   重複ファイルの検索 重複チェックにはいくつかの方法があります。1つの方法は、大きなArrayListを作成し、ファイル名とそれに対応するチェックサムからなるエントリを格納することでしょう。次に、チェックサムでソートし、ソート済みのリストをたどって2回以上出現するチェックサムを探し、それを出力します。ソートされているため、重複があれば連続して出現することになります。 ここでは、ソート処理を省いた、別のアプローチを選択しました。具体的には、チェックサム(long型)をキーとし、それに対応するファイル名のArrayListを値とするHashMapを作成します。HashMapへのエントリの追加は、チェックサムが表内にすでに存在するかどうかをチェックしながら行います。チェックサムが存在する場合は、そのチェックサムに対応するArrayListにファイル名を追加します。チェックサムが存在しない場合は、そのチェックサムと、現時点ではそれに対応するファイル名1つだけが含まれるArrayListを使って、新しいエントリを表内に作成します。次に示すように、コードは簡単です。 ArrayList tableEntry = dupesTable.get( checksum ); if( tableEntry == null ) {    // 表内でエントリがまだ見つからない場合     ArrayList<String> entry = new ArrayList<>();     entry.add( filename );     dupesTable.put( checksum, entry ); } else {     tableEntry.add( filename ); // チェックサムが表内にすでに存在する場合 } 最初の行では、チェックサムを使ってHashMap(dupesTableという名前です)のエントリをチェックしています。nullが返された場合、そのチェックサムに対応するエントリがないことを意味します。その場合は、新しいArrayListを作成し、そこにファイル名を1つ追加してから、チェックサムとともにHashMapに追加しています。結果がnullでない場合、そのチェックサムは表内にすでに存在し、ArrayListに1つまたは複数のファイル名が格納されています。最初のget()関数で返されるのがそのArrayListであり、今度はそこに新しいファイル名を追加しています。 すべてのファイル・エントリをロードした後は、HashMapを順番にたどります。対応するファイル名のArrayListに2つ以上のエントリがあるチェックサムを探し、そのファイル名をすべてstdoutに出力します。HashMap全体をたどり終われば、それで終了です。 この設計と、大きな配列のエントリをソートするという先ほどの設計には大きなメリットがあり、それが役立つことがあるかもしれません。具体的に言えば、今後のバージョンで、重複のないファイルのみを出力するコマンドライン・オプションを追加することもできます。この機能は、バックアップ・ディレクトリがあり、オリジナルとバックアップの同期がとれているかどうかがわからない場合に便利です。その場合、重複していないファイルがあれば、そのファイルのバックアップが適切に行われなかったことを示しています。もちろん、この機能を実現する場合は、対応するArrayListに含まれるファイル名がただ1つであるチェックサムのみを出力するでしょう。 設計段階から現在まで、この機能について特に考えてはきませんでした。しかし、この機能も実現可能な実装になっているとわかるのはありがたいと言えるでしょう。   テスト ここまでで説明したFileDedupeユーティリティは、それなりの規模で大変良好に動作します。60万個を超えるファイルが存在するディスク全体を対象にしても、問題なく実行されました。このような大きな規模で実行する場合、問題となるのは、不具合によって見落とされている重複があるかどうかを確実に知ることができない点です。この点から、テストに関する問題が浮かび上がってきます。 現在の多くの開発者と同じように、筆者もコーディングをしながら単体テストを記述しています。しかし、単体テストでは、ここで知る必要があることがすべてわかるわけではありません。ユーザー受入テスト(UAT)も実行する必要があります。このテストで多数のファイルを含む大きなディレクトリを作り、既知の重複ファイルを混ぜておきます。その後、すべての重複ファイルがリストに出力されることを確認する必要があります。 これを適切に行うためには、さらにコードを書く必要があります。この作業は、本プロジェクトの記事のパート2(近日公開予定)で取り上げます。筆者がその作業を終えるころ、皆さんはFileDedupeに対して十分なテストを確実に実行できると安心しているでしょう。   まとめ FileDedupeのパフォーマンスは十分で、筆者のニーズにおいてはまったく問題ありません。頻繁な実行を想定したユーティリティではないため、この点はさほど心配していません。しかし、不安がないというわけではありません。FileDedupeは、友人や同僚と共有できるようにしたいと考えています。中には、かなり大量の写真や曲のコレクションを抱えている人もいるかもしれません。その場合、このユーティリティが想像よりも遅ければ、多少の感謝はしてもらえるかもしれませんが、大喜びとはいかないのではないかと心配しています。 筆者は自分が書いたソフトウェアに多少の自負を持っているため、できる限り高速に実行させたいと考えています。今は、問題なく動作することがわかり、先ほど触れたテストをすべて終えたところであるため、このソフトウェアの最適化を行うことができます。 処理に時間がかかるのは、すべてのファイルにおいてすべてのバイトを読み取ってCRC-32チェックサムを計算する部分です。もっと高速なアルゴリズムを使うこともできますが、次回記事では、興味深い最適化について説明したいと思います。通常、この方法を使えば、チェックサムを計算するのは、ファイルの一部だけで済むようになります。FileDedupeの処理速度は、CPUの速度というよりはI/Oの速度によって決まるため、この点は重要です(ただし、古いマシンやVMでは、CPUによって決まる可能性もあります)。そのため、I/Oを減らすためにできることを行えば、すべてパフォーマンスの向上に直結します。皆さんは、すべてのファイルのチェックサムを計算することなく、内容が重複しないファイルを特定する方法を思いつくことができるでしょうか。次の記事が出るまでの間、じっくりと考えてみてください。 このユーティリティのコードに外部依存性はなく、多数のコメントを含むいくつかのファイルで構成されています。コードはGitHubで公開しており、簡単にビルドできるようにMavenファイルも含めています。本記事のように、小さなプロジェクトに特化し、実用的で実践を重視した記事が好きだという方は、ぜひお知らせください。   Andrew Binstock Andrew Binstock(@platypusguy):Java Magazineの前編集長。Dr.Dobb's Journalの元編集長。オープンソースのiText PDFライブラリを扱う会社(2015年に他社によって買収)の共同創業者。16刷を経て、現在もロング・テールとして扱われている、Cでのアルゴリズム実装についての書籍を執筆。以前はUNIX Reviewで編集長を務め、その前にはC Gazetteを創刊し編集長を務めた。妻とともにシリコン・バレーに住んでおり、コーディングや編集をしていないときは、ピアノを学んでいる。

※本記事は、Andrew Binstockによる"The joy of writing command-line utilities, Part 1: Finding duplicate files"を翻訳したものです。 実用的なユーティリティを含む小さなプロジェクトをステップ・バイ・ステップで作成する 著者:Andrew Binstock 2020年8月14日 自分用のコマンドライン・ユーティリティを書く...

Java

JavaのOptionalクラス:nullポインタ例外を防ぐためのさらに11のレシピ

※本記事は、Mohamed Tamanによる"The Java Optional class: 11 more recipes for preventing null pointer exceptions"を翻訳したものです。 アプリケーション開発を効率化しつつ、Optionalクラスのアンチパターンと「設計の臭い」を回避する方法 著者:Mohamed Taman 2020年7月20日 皆さんはnullポインタ例外にうんざりしていませんか。前号のJava Magazineの記事「Optionalクラスを意図されたとおりに使うための12のレシピ」では、Optionalクラスについて説明しました。Optionalクラスは、存在しないかもしれない値を格納するコンテナ型です。前回の記事では、Optionalを使うべきときと、Optionalを使うべきではないときについて確認しました。さらに、いくつかの特殊ケースや誘惑は、わなに相当する場合があることも紹介しました。このわなに引っかかった場合、コードの質が低下することや、さらには予期しない動作の原因となることがあります。12のレシピは、次の3つの分類に対応していました。 Optionalを使っているのにnullになるのはなぜですか 値がない場合、何を返せば(または、何を設定すれば)よいですか どうすればOptional値を効率的に使用できますか 本記事では、次の2つの分類に対応する11のレシピについてさらに説明します。 どうすればOptionalのアンチパターンを回避できますか Optionalは好きですが、どうすればもっとプロらしくできますか 前回の記事は、Optionalクラスの表面をなぞっただけにすぎません。すなわち、前回説明したのは、null問題を効率よく防ぎ、このクラスのメソッドを最大限に活用して、Optionalを使った場合に起こり得る、いくつかのパフォーマンス面の問題を防ぐベスト・プラクティスです。しかし、一部の開発者にとって、Optionalを正しく使うのは、見た目ほど単純ではありません。 他のプログラミング言語の機能と同じく、Optionalにも正しい使い方と誤った使い方があります。そこで、開発者や筆者自身のコードで気づいたOptionalのいくつかのアンチパターンと「設計の臭い」について、詳しく掘り下げたいと思います。その後、Stream APIや変換などを扱うさらに高度なレシピを紹介し、Optionalクラスに含まれる他のメソッドすべての使い方について解説したいと思います。[編集注:「設計の臭い」という用語は、「基本的な設計原則に違反することが示され、設計の質に悪影響を与える設計上の構造」を指します(Girish Suryanarayana氏、Ganesh Samarthyam氏、Tushar Sharma氏著、『Refactoring for Software Design Smells:Managing Technical Debt』より)。] まずは、この学習の目的を理解してください。Optionalは、Javaでのnullポインタ例外の数を減らそうとするものです。つまり、値の欠落を示すnull参照を使わずに、「結果がない」ことを示す、メソッドの戻りタイプが可能な、より柔軟なAPIを作成できるようにしています。 筆者の意見では、OptionalクラスがJavaの初期のころから存在したのであれば、ほとんどのライブラリやアプリケーションにおいて、戻り値の欠落はおそらく効果的に処理されているでしょう。それによってnullポインタ例外の可能性が低下し、全体的なバグの件数も減っているでしょう。残念ながら、Javaのはじまりはそうではありませんでした。幸いにも、今のJavaにはOptionalがあります。 ここで本題に入ります。   どうすればOptionalのアンチパターンを回避できますか 大ざっぱに言えば、この質問はOptionalクラスの機能の主な目的を正しく理解していないという分類になります。このような不理解は多くのアンチパターンにつながります。それによってコードが冗長になり、本来の効率が失われてしまいます。また、場合によっては、質の悪いAPI設計が公開されることや、メモリの問題を招くこともあります。 レシピ13:Optionalをフィールド型として使わない。筆者がレビューしたコードの45 %で、Optionalがフィールド型として使われていました。次に例を示します。 public class Account { enum TYPE {SAVINGS, CREDIT, DEBIT;} private String name; private Optional<TYPE> type = Optional.of(TYPE.SAVINGS); private OptionalDouble balance = OptionalDouble.empty(); public Account(String name, OptionalDouble balance) { this.name = Objects.requireNonNull(name, () -> "Account Name cannot be null."); this.balance = balance; } public Account(String name, OptionalDouble balance, Optional<TYPE> type) { this(name, balance); this.type = type; } public void setType(Optional<TYPE> type) { this.type = type; } public Optional<TYPE> getType() { return type; } ... } まず、この種のコードのようにしてnull参照を避けるのは、アンチパターンです。さらに、これによってAccountクラスが使いづらくなっています。Accountオブジェクトのメソッド、コンストラクタ、setter引数を満足するため、コール元にOptionalを使うことを求めているからです。このようなことをした場合、コードが無駄に複雑で冗長なものになるでしょう。 Account acc = new Account("Euro Account", OptionalDouble.of(1453.70), Optional.of(Account.TYPE.CREDIT)); acc.getBalance().ifPresent(System.out::println); acc.getType().ifPresent(System.out::println); Optionalを使うたびに、メモリ使用量のフットプリントが増加します。Optionalによって、元の値をラップする、別のオブジェクトの層ができるからです。Optionalクラスはもともと、データタイプとしてではなく、主にメソッドの戻りタイプとして使うことが想定されていました。そのため、できる限りシンプルな状態を保ってください。 public class Account { [access_modifier] [static] [final] String name; [access_modifier] [static] [final] Type type = TYPE.SAVINGS; [access_modifier] [static] [final] double balance = 0.0; ... } APIメモ:OptionalクラスではSerializableインタフェースを実装していません。そのため、JavaBeanプロパティで使うことはまったく意図されていません。 レシピ14:コンストラクタ引数でOptionalを使わない。Optionalはフィールド型として使うように設計されたものではないため、以下のルールが成立します。 ルール:Optionalは、フィールド型や、コンストラクタ、メソッド、setterの引数として使わない。 Optionalの使用に関する別のアンチパターンは、すべてのコンストラクタ引数に、元の値をラップしたOptionalを受け取ることを強制する傾向が開発者にあることです。開発者は、一部の値が存在しない可能性があり、Optionalであることを示すために、このようなことをします。たとえば、次の例のbalanceがそれに当たります。 public class Account { ... private String name; private OptionalDouble balance = OptionalDouble.empty(); public Account(String name, OptionalDouble balance) { this.name = Objects.requireNonNull(name, () -> "Account Name cannot be null."); this.balance = balance; } public OptionalDouble getBalance() { return balance; } ... } ここでの問題は何でしょうか。コードが定型挿入文のように見えることに加え、次のようにしてAccountオブジェクトを作成しようとした場合、失敗します。 Account acc = new Account("Euro Account", null); acc.getBalance().ifPresent(System.out::println); acc.getType().ifPresent(System.out::println); System.out.println(acc.getName()); 開発者がOptionalを使って防ごうとしたNullPointerExceptionに注目してください。これを、balanceのgetterメソッドで修正しましょう(コンストラクタが変わっている点を確認してください)。 private Double balance = 0.0; public Account(String name, Double balance) { this.name = Objects.requireNonNull(name, () -> "Account Name cannot be null."); this.balance = balance; } public Optional<Double> getBalance() { return Optional.ofNullable(balance); } APIメモ:すべてのgetterメソッドがOptionalを返すべきだという主張を経験則として受け取らないでください。これでは、Optionalクラスが過剰使用になります。このテクニックは、必要な場合にのみ使用してください。 レシピ15:メソッドの引数でOptionalを使わない。メソッドの引数としてOptionalを使うことに関しては、大きな議論があります。筆者は、メソッドの引数としてOptionalを使うべきだという意見には納得しておらず、これは「設計の臭い」の発生源になる一般的なアンチパターンだと考えています。ところで、コード・インスペクタの中には、SonarQubeなどのように、パラメータにOptionalを使うことを正式に「設計の臭い」と見なしているものもあります。 レシピ14のルールには挙げましたが、メソッドの引数としてOptionalを使うことが適切かどうかを確認してみます。 ある開発者が、名前をマッチングして従業員のリストを検索するメソッドを書いたとします。このメソッドは、部署でフィルタリングすることもできます。開発者はIDEを開き、深く考えず、次のように打ち込み始めます。 public class Employee { int id; String name; String department; ... } public class EmployeeService { public List<Employee> searchEmployee( List<Employee> employees, String name, Optional<String> department){ // employeesとnameのnullチェック return employees.stream() .filter(employee -> employee.getName().matches(name)) .filter(employee -> employee.getDepartment().matches(department.orElse(".*"))) .collect(toList()); } } このコードは、部署の存在を扱うエレガントなソリューションとして有望に見えます。そのため、開発者はこのコードをコード・リポジトリにリリースします。 その後、別の開発者がこのコードをプルして使い始め、「部署にかかわらず、すべての従業員を検索する必要がある」と考えます。 service.searchEmployee(employees, ".*", null) .stream() .forEach(System.out::println); すると、コードからNullPointerExceptionがスローされます。これは明らかに、最初のメソッド設計で想定されている、部署パラメータの扱いではありません。さらに、この例外によって、このメソッドを正しく使うことが難しくなります。メソッドがnullチェックを怠っているため、次のようにメソッドのコール元がOptionalに依存せざるを得なくなるからです。 EmployeeService service = new EmployeeService(); var employees = List.of( new Employee(1, "Mohamed Taman", "Business Development"), new Employee(1, "Malik Taman", "HR")); service.searchEmployee(employees, "Mo.*", Optional.empty()) .stream() .forEach(System.out::println); service.searchEmployee(employees, ".*", Optional.<String>of("HR.*")) .stream() .forEach(System.out::println); この設計上の問題を修正してわかりやすくするために、オーバーロードしたメソッドを2つ追加します。この2つのメソッドは同じ実装を共有していますが、異なる2つのことを行います。 private List<Employee> searchEmployee0( List<Employee> employees, String name, String department) { Objects.requireNonNull(employees, "Employees can't be null."); Objects.requireNonNull(name, "Name can't be null."); final var departmentFilter = Objects.requireNonNullElse(department, ".*"); return employees.stream() .filter(employee -> employee.getName().matches(name)) .filter(employee -> employee.getDepartment().matches(departmentFilter)) .collect(toList()); } public List<Employee> searchEmployee( List<Employee> employees, String name) { return searchEmployee0(employees, name, ".*"); } public List<Employee> searchEmployee( List<Employee> employees, String name, String department) { return searchEmployee0(employees, name, department); } これで安全にメソッドを使えるようになり、実行時に驚くようなことは起こらなくなります。 service.searchEmployee(employees, "Mo.*") .stream() .forEach(System.out::println); service.searchEmployee(employees, ".*", "HR.*") .stream() .forEach(System.out::println); service.searchEmployee(employees, ".*", null) .stream() .forEach(System.out::println); 参考:このトピックに関して、Grzegorz Ziemoński氏がDZoneで「Optionalメソッドのパラメータ」(英語)というすばらしい記事を書いています。 レシピ16:Optionalはsetterの引数として使わない。コードによっては、Java Persistence API(JPA)エンティティのプロパティが存在しないことを示すためにOptionalが使われることがあります。筆者はこの設計を好みません。setterメソッドに、元の値をラップするOptionalを引数として受け取ることを強制するからです。このアプローチでは、コードが複雑になり、setterメソッドのコール元に余計な依存性が追加されるからです。 @Entity public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id int id; ... @Column(name = "employee_address") private Optional<String> address; // 省略可能なフィールドであるため、nullになる可能性がある public void setAddress(Optional<String> address) { this.address = address; } public Optional<String> getAddress() { return address; } ... } そのため、これはアンチパターンであり、「設計の臭い」であると筆者は考えます。Optionalはシリアライズできないことも覚えておいてください。この問題を回避するためには、次のようにします。 @Column(name = "employee_address") private String address;  // 省略可能なフィールドであるため、nullになる可能性がある public Employee() { } public void setAddress(String address) { this.address = address; } public Optional<String> getAddress() { return Optional.ofNullable(address); } レシピ17:Optionalを使って配列やコレクションが空であることをアサートしない。確かに先ほど、Optionalは主にメソッドの戻りタイプとして使うべきだと言いました。しかし、すべての戻り値をOptionalインスタンスでラップし、空やnull値を返すことを避けようとしているコードを見たことがあります。そのような実装は一般的なアンチパターンであり、「設計の臭い」と見なされます。次の例について考えてみます。 public class EmployeeService { public Optional<List<Employee>> getEmployeeByDepartment(String department) { // nullまたは空のリストを返す可能性がある List<Employee> employees = searchEmployee(".*", department); // そのため、Optionalでラップする return Optional.ofNullable(employees); } } このコードによって、余分な層が1つ増えることになります。この場合は次のようにします。CollectionsクラスのemptySet()、emptyList()、emptyMap()の各メソッドを使い、単純に空のコレクションや配列を返します。 public List<Employee> getEmployeeByDepartment(String department) { var employees = searchEmployee(".*", department); return employees != null? employees : Collections.emptyList(); } APIメモ:CollectionsクラスのemptySet()、emptyList()、emptyMap()の各メソッドは、Java 1.5以降に存在します。   Optionalは好きですが、どうすればもっとプロらしくできますか ここまでで紹介した一連のレシピは、Optionalを使った場合に起こり得るさまざまなアンチパターンや「設計の臭い」の問題の多くに関するものでした。次は、Stream APIでOptionalを使うさまざまな方法や、Optionalのその他の重要な側面について確認します。 レシピ18:OptionalのofNullable()とof()の機能を混同しない。ofNullable()メソッドとof()メソッドは、いずれもOptionalを作成する静的メソッドですが、さまざまな方法で値をラップしたOptionalインスタンスを作成します。もちろん、値がないことを示す空のOptionalインスタンスは、empty()メソッドで作成します。しかし、中にはofNullable()とof()を混同している開発者もおり、それが問題につながることもあります。 null以外の値でOptionalを作成する場合は、of()メソッドを使います。このメソッドにnullを渡した場合、即座にnullポインタ例外がスローされるからです。その場合、何も作成されません。 Employee emp = new Employee(1, "Mohamed Taman", "Business Development"); Optional<Employee> employee = Optional.of(emp); nullであるかもしれない値でOptionalを作成する場合は、ofNullable()メソッドを使います。ここで値がnullの場合は、空のOptionalが返されます。それ以外の場合は、渡された元の値をラップしたOptionalが返されます。 Employee emp = service.getEmployee(int id);  // empはnullである可能性がある Optional<Employee> employee = Optional.ofNullable(emp); レシピ19:数値には、汎用のOptionalを使うよりも数値のOptionalを使う方がよい。Optionalにプリミティブ値をボクシングして格納しなければならないこともあります。たとえば、次のような場合です。 Optional<Integer> price = Optional.of(156); Optional<Long> peopleCount = Optional.of(156_978_934_24L); Optional<Double> finalPrice = Optional.of(230.17d); オートボクシングは、パフォーマンスに影響する場合もあることに注意してください。つまり、int、long、doubleなどのプリミティブ値のラッパーであり、 getAsInt()、getAsLong()、getAsDouble()で必要に応じてアンラップできる数値版のOptionalを使う方がよいということです。 OptionalInt price = OptionalInt.of(156); OptionalLong peopleCount = OptionalLong.of(156_978_934_24L); OptionalDouble finalPrice = OptionalDouble.of(230.17d); APIメモ:OptionalInt、OptionalLong、OptionalDoubleの各クラスは、Java 8以降に存在します。 レシピ20:ラップした値をfilter()メソッドでフィルタリングする。Optionalクラスには、Stream APIの同名のメソッドと同じようなfilter()メソッドがあります。超過引き出しが可能なために負の値もとることができる、銀行の引き落とし口座について考えてみます。 // accountIdについて、nullチェックと空チェックを行い、口座番号体系を確認する Optional<Account> accountFiltered = getAccount(accountId); if (accountFiltered.isPresent()) { Account account = accountFiltered.get(); if (!account.isOverdraftAllowed()) { throw new IllegalStateException("Overdraft is not allowed for this account."); } account.addToBalance(value); updateAccount(account); } ラップされている値が見つかった場合に、事前定義されたルールでチェックを行うために、filter()メソッドを使います。値が存在し、条件に一致した場合、filter()はその値を表すOptionalを返します。そうでない場合は、空のOptionalを返します。これを使って、先ほどの実装をよりエレガントでわかりやすくなるように書き換えます。 getAccount(accountId) .filter(account -> account.isOverdraftAllowed()) .ifPresentOrElse(account -> { account.addToBalance(amount); updateAccount(account); }, () -> { throw new IllegalStateException("Overdraft is not allowed for this account."); }); APIメモ:filter()メソッドは、Java 8以降に存在します。 レシピ21:値の抽出や変換を行うmap()とflatMap()を使うべきタイミングを知る。アプリケーションでは、ラップされているOptional値を変換しなければならない場合もあります。その場合、ラップされている値に対して処理を行い、新しい結果を返す何らかの変換マッピング関数を適用します。これを行うために使うのが、map()メソッドとflatMap()メソッドです。この2つのメソッドは、いずれも変換を行うものですが、その仕組みは異なります。 String getAddress()メソッドがあるEmployeeクラスについて考えてみます。このアプリケーションでは、IDを使って従業員を検索します。従業員が存在する場合は、その従業員の住所を取得します(住所がnullでない場合)。住所が空でない場合は、トリミングして大文字に変換し、その結果を返します。コードは次のようになります。 var finalAddress = "No address found."; Optional<Employee> optEmployee = employeeRepository.getEmployeeBy("1284"); if (optEmployee.isPresent()) { Employee employee = optEmployee.get(); if (employee.getAddress() != null && !employee.getAddress().isBlank()) { finalAddress = employee.getAddress().trim().toUpperCase(); } } System.out.println(finalAddress); または、getEmployeeBy(String id)メソッドによる検索で返されたOptional<Employee>とmap()関数を併せて使うことにより、もう少し読みやすくわかりやすいコードで同じ機能が実現します。 1 var finalAddress = employeeRepository.getEmployeeBy(1284) 2 .map(Employee::getAddress) 3 .filter(Predicate.not(String::isBlank)) 4 .map(String::trim) 5 .map(String::toUpperCase) 6 .orElse("No Address found."); map関数(2行目)では、2つのことを行っています。従業員IDが存在して住所がnullでない場合にのみ、ラップされたString値がOptional<String>に変換されます。それ以外の場合は、6行目のコードが適用され、No Address foundが結果として返されます。 ただし、従業員と住所が存在する場合は、filterメソッド(3行目)を使って、住所が空でないかどうかがチェックされます。その後、map関数(4行目)で住所がトリミングされ、それをOptionalでラップしたものが結果となります。そして最後のmap関数が適用されて、住所がOptionalでラップした大文字に変換されます。大変きちょうめんな処理です。 次に、Employeeクラスについて、getAddress()メソッドはあるものの、住所をStringとして返すのではなく、Optional<String>を返す場合について再度考えてみます。この場合、最初からnullを避けようとすることが必要です。そして、先ほどと同じ要件を適用してみます。同じコードを使った場合、問題が起きます。2行目のmap関数がネストされたOptional<Optional<String>>を返すからです。これはとても臭います。この問題は、2行目のmap()をflatMap()にするだけで修正されます。そしてすべては魔法のようにうまくいくはずです。 1 var finalAddress = employeeRepository.getEmployeeBy(1284) 2 .flatMap(Employee::getAddress) 3 ... 一般的な経験則:マッピング関数がOptionalを返す場合、map()メソッドではなくflatMap()メソッドを使います。これにより、Optionalからフラットな結果を取得します。 APIメモ:Optionalクラスのmap()メソッドとflatMap()メソッドは、Java 8以降に存在します。Predicate.not()メソッドとString.isBlank()メソッドは、Java 11以降に存在します。 レシピ22:コードでOptionalとStream APIをチェーンできるかアプリケーションで次のようなことをしなければならない場合があります。 public Optional<Employee> getEmployeeBy(String id) { return Optional.ofNullable(...); } public List<Employee> getEmployeesBy(List<String> ids) { return ids.stream() .map(this::getEmployeeBy) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); } 与えられた従業員IDのリストに対応する従業員のリストを取得し、在籍している従業員のみを返しています。続いてmap()を使い、Stream<Employee>を生成して、最終的なリストにまとめています。この処理では、Stream APIを使って多くのやり取りが行われています。Optional.stream()を使ってOptional APIをStream APIに接続することで、このやり取りが効率化されます。これにより、filter()とmap()をflatMap()メソッドに置き換えることができます。 public List<Employee> getEmployeesBy1(List<String> ids) { return ids.stream() .map(this::getEmployeeBy) .flatMap(Optional::stream) .collect(Collectors.toList()); } APIメモ:Optional.stream()メソッドは、Java 9以降に存在します。 レシピ23:Optionalクラスについての結論。次のように、==を使って2つのOptionalエンティティの等価性をテストしたいことや、複数のOptionalオブジェクトを同期させたいことや、識別ハッシュ・コードのチェックを実行したいことがあるかもしれません。 Optional<String> first = Optional.of("Java is turning 25"); Optional<String> second = Optional.of("Java is turning 25"); System.out.println(first == second); // falseが返される // または synchronized(first){ ... } これを行ってはいけません。同一性の影響を受ける何らかの操作をOptionalに対して実行することは避ける必要があります。その理由は、Optionalが値ベースのクラスであるからです。同一性の影響を受けるOptionalインスタンスを実行することは、予測できない結果を引き起こす可能性があるため、避けるべきです。 また、等価性をチェックするために、次のようにしてOptional値をアンラップする必要はありません。 if (first.get().equals(second.get())) … ; // trueを返す 次のようにします。 if (first.equals(second)); /// trueを返す このコードがうまく動作するのは、Optional.equals()において、Optionalオブジェクトではなく、ラップされた値が比較されるからです。その理由は、Optionalが値ベースのクラスだからです。   まとめ Optionalを正しく使うのは、見た目ほど単純ではありません。 本記事では、Optionalクラスの特に一般的なアンチパターンと「設計の臭い」のいくつかを詳しく取り上げ、それらを避ける方法について説明しました。このような問題を避けるために役立ついくつかの例を、代替ソリューションとともに提示しました。さらに、Optional.of()とOptional.ofNullable()を混同してしまうというありがちな間違いも取り上げました。このようなエラーは、問題につながることがあるからです。 最後に、高度なレシピとして、Stream APIとOptional APIのチェーン、Optionalでラップされた値の変換、map()とflatMap()を使うべき条件も説明しました。そして、値ベースのOptionalクラスに対し、同一性の影響を受けるすべての操作を避けることに関する結論も共有しました。 詳しく学びたい方は、Java SE 15のOptionalに関するドキュメントや、Raoul-Gabriel Urma氏、Mario Fusco氏、Alan Mycroft氏による書籍『Java 8 in Action』(Manning、2014年)、プレゼンテーション「あなたはOptionalクラスを適切に使っているか」(英語)をご覧ください。   Mohamed Taman Mohamed Taman(@_tamanm):SiriusXI InnovationsのCEOであり、Effortel Telecommunicationsの最高ソリューション・アーキテクト。セルビアのベオグラードを拠点とするJava Champion、Oracle Groundbreaker、JCPメンバーであり、Jakarta EEのAdopt-a-SpecプログラムとOpenJDKのAdopt-a-JSRのメンバーを務める。

※本記事は、Mohamed Tamanによる"The Java Optional class: 11 more recipes for preventing null pointer exceptions"を翻訳したものです。 アプリケーション開発を効率化しつつ、Optionalクラスのアンチパターンと「設計の臭い」を回避する方法 著者:Mohamed Taman 2020年7月20日 皆さんはnullポ...

Java

Raspberry PiでJavaFXを使ってみる

※本記事は、Frank Delporteによる"Getting started with JavaFX on Raspberry Pi"を翻訳したものです。 Javaも動作する安価な単一ボード・コンピュータにより、従来のソフトウェア開発並みにハードウェア開発が簡単に 著者:Frank Delporte 2020年7月6日  [編集注:Raspberry Pi単一ボード・コンピュータに興味がある方は、Java Magazineの寄稿者であるAlexa Weber Moralesが数か月前に執筆した記事「OracleのエンジニアがRaspberry Piプロジェクトを気に入っている理由」(英語)もご覧ください。] 電子部品を使った実験を行いたいなら、理想的な開始点になるのがRaspberry Pi単一ボード・コンピュータです。そして、この安価なハードウェアとJava開発者が毎日使っているソフトウェア・ツールとを組み合わせることができれば、新たな世界が開かれることになります。初めてJavaで制御してLEDを点滅させたとき、筆者はアハ体験を味わいました。 本記事では、Gerrit Grunwald氏のTilesFXライブラリを使って、JavaFXのダッシュボード型アプリケーションを構築する方法を説明します。図1にユーザー・インタフェースを示します。 図1:JavaFXアプリケーションのユーザー・インタフェース Raspberry Pi 3B+ボードでこのアプリケーションが動作している動画を見ることもできます。この動画は、タッチスクリーン・インタフェースのデモにもなっています。 本記事で使用するコードと技術は、ARM v7またはARM v8プロセッサを搭載したRaspberry Piコンピュータでのみ有効です。Wikipediaに掲載されている、Raspberry Piの仕様表で、このタイプのプロセッサが搭載されているボードの概要がわかります。 Model A+、バージョン3 Model B、バージョン2、3、4 Compute Module、バージョン3 このプロジェクトで使っているその他の電子部品は、ほとんどがArduino/Piスターター・キットに含まれています。しかし、キットにない部品を使ってみたい場合でも、まずこのプロジェクトで使った部品で始めてから、ニーズに合わせて適応させることができます。今回使った部品は以下のとおりです。 Raspberry Pi 3 Model B+ Raspbian OSをインストールした32 GB(以上)のSDカード ディスプレイ、マウス、キーボード LEDと抵抗(通常は330Ωで可) 任意の押しボタンスイッチ HC-SR04距離センサー ブレッドボードと導線   Raspberry Piボードの準備 新しいRaspberry Piボードを使ってゼロから始める場合は、オペレーティング・システムをインストールしたSDカードを準備します。このプロジェクトでは、フル・バージョンのRaspbian OSを使います。イメージ・ツールをダウンロードしてください。今回使ったイメージャは、2020年3月にリリースされたバージョン1.2です(図2および図3参照)。Raspbian Fullを必ず選択してください。   図2:イメージャ・ツールのダウンロード・サイト   図3:OSでRaspbian Fullオプションを選択 SDカードの準備ができたら、Raspberry Piに挿入してオペレーティング・システムを起動し、手順に従って設定し、Wi-Fiネットワークに接続します。   JDKとJavaFXのインストール Raspbianのリリース・ノートには、今回使用するバージョン2019-06-20にOpenJDK Java 11が含まれていることが示されています。 2019-06-20: * Based on Debian Buster * Oracle Java 7 and 8 replaced with OpenJDK 11 Javaのバージョンは、次のようにして確認できます。 $ java -version openjdk version "11.0.3" 2019-04-16 OpenJDK Runtime Environment (build 11.0.3+7-post-Raspbian-5) OpenJDK Server VM (build 11.0.3+7-post-Raspbian-5, mixed mode) つまり、このボードではJava 11ベースの任意のプログラムを実行できるようになっています。しかし、Java 11以降、JavaFXはすでにJDKの一部ではなくなっているため、Raspberry PiですぐにJavaFXプログラムを実行することはできません。 ありがたいことに、BellSoftがLiberica JDKを提供しています。Raspberry Pi向けのバージョンにはJavaFXが含まれているため、単純な開始コマンドjava -jar yourapp.jarを使って、パッケージ化したJavaFXアプリケーションを実行することができます。代替JDKをインストールするためには、次のようにしてBellSoftのダウンロード・リンクからダウンロードします。 $ cd /home/pi $ wget https://download.bell-sw.com/java/13/bellsoft-jdk13-linux-arm32-vfp-hflt.deb $ sudo apt-get install ./bellsoft-jdk13-linux-arm32-vfp-hflt.deb $ sudo update-alternatives --config javac $ sudo update-alternatives --config java これが終わったら、バージョンを再度確認します。次のように表示されるはずです。 $ java --version openjdk version "13-BellSoft" 2019-09-17 OpenJDK Runtime Environment (build 13-BellSoft+33) OpenJDK Server VM (build 13-BellSoft+33, mixed mode) 筆者のテスト用Piボードでは、別のバージョンのLiberica JDKも保持しています。バージョンの切り替えはとても簡単で、update-alternativesコマンドを使います(図4参照)。 図4:Liberica JDKのバージョンの切り替え GitHubのソース・コードのChapter_04_Java/scriptsフォルダには、複数のバージョンのLiberica JDKに対応したインストール・スクリプトがあります。各バージョンの正しいダウンロード・リンクも含まれています。図5をご覧ください。 図5:Liberica JDKの各バージョン用のスクリプト    Raspberry Piのさまざまな番号体系 ボードのGPIO(汎用入出力)コネクタに部品を接続する前に、ピンを特定する3つの番号体系について見ていきます。GPIOコネクタを扱う際に、紛らわしい場合があります。ここで簡単に説明しますが、詳しい情報についてはGPIOピンアウト総合ガイド(英語)をご覧ください。 ヘッダーのピン番号:これはヘッダーの論理番号です。片方の列には偶数番号のピンが、もう片方の列には奇数番号のピンが並んでいます。図6をご覧ください。 図6:ヘッダーのピン番号  BCM番号:これはBroadcomチャネル番号と呼ばれ、Raspberry Piで使われているチップ内部の番号を指します。 WiringPi番号:WiringPiは、Pi4J(このJavaプロジェクトでライブラリとして使っています)がGPIOを制御するために使用するベース・フレームワークです。異なる番号体系が使われていることには、歴史的な理由があります。もっとも初期のRaspberry Piボードの開発が続いているときは、8つのピンしか想定されていませんでした。しかし、設計が進化してピンが追加されたとき、追加されたピンを指定できるようにWiringPiの番号が拡張されました。 Java開発者が各種ヘッダー・タイプ、ピン、機能の違いを理解しやすくするために、小さなライブラリを作成しました。このライブラリは、be.webtechie.pi-headers Mavenリポジトリで公開しています。番号を見つけてボード上の該当するピンに対応付けることが簡単になるように、このライブラリと小さなJavaFXアプリケーションを使って図7に示す概略イメージを作成しました。詳しくは、「Java MavenライブラリとしてのRaspberry Piの歴史、バージョン、ピンとヘッダー」(英語)をご覧ください。 図7:ボード上のピンに対応する番号    ハードウェアの接続 Piボードをフル活用に近づけるために、ハードウェアを追加してみます。ここでは、LED、押しボタンスイッチ、距離センサーを接続します。表1、図8、図9をご覧ください。 表1:ピンと適切なデバイスとの対応付け 図8:実体配線図 図9:回路図  図10は筆者のセットアップです。正しいピンを見つけやすくなっているRasPiOブレッドボード・ブリッジを使っています。ブリッジのコネクタにはBCM番号が論理順に並んでいますが、もう少しスペースを確保するために、別のブレッドボードも使っています。Portsplusも、同様の便利なボードを提供しています。  図10:RasPiOブレッドボード・ブリッジを使ったセットアップの写真    LEDが正しい極性の向きで接続されているかどうかをテストするため、LEDとGPIOピンとの間のケーブル(図10のオレンジ色のケーブル)を抜き、3.3Vピン(またはブレッドボードの+の列)に直接接続します。LEDが点灯しない場合、向きを変える必要があります。   ターミナルからLEDとボタンをテストする 接続をテストするためには、ターミナルからgpioコマンドを実行します。 重要な注:Raspberry Pi 4ボードを使っている場合は、必ずバージョン2.52のgpioユーティリティを使ってください。Pi 4ボードのプロセッサ内部の配線は以前のボードとは異なるため、必要に応じてユーティリティのアップデートが提供されています。バージョンの確認は、ターミナルからgpio -vコマンドで行います。必要に応じて、次のコマンドで新しいバージョンをインストールしてください。 $ gpio -v gpio version: 2.50 $ cd /tmp $ wget https://project-downloads.drogon.net/wiringpi-latest.deb $ sudo dpkg -i wiringpi-latest.deb $ gpio -v gpio version: 2.52 次のようにして、LEDをオン(1)、オフ(0)することができます。 $ gpio mode 29 out $ gpio write 29 1 $ gpio write 29 0 WiringPiピン27のボタンの状態(1=押されている、0=押されていない)を読み取るためには、次のようにします。 $ gpio mode 27 in $ gpio read 27 1 JavaでLEDをオン/オフする 本当におもしろくなるのはここからです。次のコードでは、500ミリ秒間隔でオン/オフを10回切り替えるようにWiringPiピン29を設定しています。コマンドは、先ほどターミナルで使ったものと同じものを使っています。以下の内容を含む、HelloGpio.javaという名前のファイルを作成します。 public class HelloGpio { public static void main (String[] args) { System.out.println("Hello Gpio"); try { Runtime.getRuntime().exec("gpio mode 29 out"); var loopCounter = 0; var on = true; while (loopCounter < 10) { System.out.println("Changing LED to " + (on ? "on" : "off")); Runtime.getRuntime().exec("gpio write 29 " + (on ? "1" : "0")); on = !on; Thread.sleep(500); loopCounter++; } } catch (Exception ex) { System.err.println("Exception from Runtime: " + ex.getMessage()); } } }   このコードではJava 11(以降)を使っているため、Javaファイルはコンパイルせずに実行できます。 $ java HelloGpio.java Hello Gpio Changing LED to on Changing LED to off Changing LED to on … 距離センサーの導入 本記事のサンプル・アプリケーションでは、ArduinoやPiの多くのスターター・キットに含まれる、一般的な超音波距離センサーを使います。このセンサーはHC-SR04というモジュールで、詳しい情報やサンプルはオンラインで入手できます。このモジュールには、入力と出力の両方のGPIO接続が必要です。このセンサーは、コウモリが闇の中を壁にぶつからずに飛ぶのと同じ仕組みで動作します。つまり、超音波の反射を使い、音波を跳ね返す物体との距離を計算します。 サンプル・アプリケーションとこのモジュールを使って距離の測定を行うためには、以下の手順を実行する必要があります。 モジュールに5ボルトの電源を供給する必要があります。 アプリケーションで、少なくとも10マイクロ秒の間、トリガー・ピンをhighにセットする必要があります。 モジュールでは、40 kHzのシグナルを複数回(通常は8回)送信し、シグナルがいつ戻ってくるかを検出します。 超音波がセンサーに戻ってくるために必要な時間と同じ時間だけ、エコー・ピンがhighにセットされます。 アプリケーションで、エコー・ピンがhigh状態になっている時間を計測することにより、音速に基づいて距離を計算できます。   完全なアプリケーション 1つのJavaファイルを実行するのは、セットアップのテストにはよいですが、これは最初の一歩にすぎません。このサンプル・アプリケーションでは、JavaとRaspberry Piボード上のGPIOポートとの間の接続にPi4Jライブラリを使っています。Pi4Jは、Raspberry Piにインストールする必要があります。Mavenの依存性を使ってPi4JをJavaアプリケーションに組み込むこともできます。このサンプル・アプリケーションの完全なソース・コードは、GitHubに掲載しています。 以下のMaven依存性を、POMファイルで指定しています。 JavaFXの拡張javafx-web依存性(以下を含む)  JavaFXアプリケーションのベースとなるjavafx-controls TilesFXが必要とするWebコンポーネント ロギング ダッシュボード・タイル用のTilesFX GPIOポートを使用するためのPi4J <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-web</artifactId> <version>11.0.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>eu.hansolo</groupId> <artifactId>tilesfx</artifactId> <version>11.13</version> </dependency> <dependency> <groupId>com.pi4j</groupId> <artifactId>pi4j-core</artifactId> <version>1.2</version> </dependency>   以下に、ハードウェアを操作するためのクラスを示します。 GpioHelperクラス:GPIOポートに関連するすべての機能をこのクラスにまとめています。最初に、ハードウェア・コンポーネントを接続するピンの定義と、Pi4JのGpioControllerの初期化を行っています。拡張機能は別のクラスで扱います。具体的には、ButtonChangeEventListenerとDistanceSensorMeasurementですが、詳しくは後ほど説明します。その他のメソッドとgetterは、後ほどUIで使います。 public class GpioHelper { private static final Logger logger = LogManager.getLo