Java - TECH PLAY - TECH PLAY

TECH PLAY

Java

イベント

該当するコンテンツが見つかりませんでした

マガジン

技術ブログ

2026 年 8 月 31 日週、 Claude Fable 5.1 が AWS で利用可能になりました 。Anthropic によると、Claude Fable 5.1 は、コーディング、科学研究、エンタープライズワークフローにわたる高度なタスクに、最新鋭のインテリジェンスを提供します。Claude Fable 5.1 は、作業が何時間にもわたって続き、多数のアプリケーションを横断的に使用する、長時間かつ重要度の高い作業向けに構築されています。ソフトウェアプロジェクトのより多くの部分を単独で担当でき、長時間のセッションを通じて、コードベース全体にわたる機能、コードレビュー、パフォーマンス関連の作業を扱うことができます。 Anthropic は、Fable 5.1 を 対象モデル に指定しています。対象モデルとは、提供先を問わず、追加のデータ保持、安全性レビュー、アクセスポリシーが適用される Claude モデルのカテゴリです。Claude Fable 5.1 は、新しい aws_review データ保持モード により、最大 30 日間のデータ保持と、Amazon 担当者が行う人的レビューの対象となります。このモードでは、AWS はお客様のプロンプトと出力内容を AWS の境界内で人的な安全性レビュー用に保持します。 provider_data_share モードはレガシーモードであり、Amazon Bedrock はモデルプロバイダーとデータを共有しません。加えて、AWS と Anthropic のパートナーシップにより構築された エンタープライズフロンティアセーフガード (EFS) により、対象となるお客様は、自分の管理するクラウド環境にデータを保持したまま、対象モデルを使用できます。 Claude Fable 5.1 にアクセスするには、Amazon Bedrock と Claude Platform on AWS の 2 つの方法があります。詳細については、 Amazon Bedrock の Claude Fable 5.1 モデルカード と「 Claude Platform on AWS 」を参照してください。  8 月 31 日週のリリース 私が注目したリリースをいくつかご紹介します。 Amazon Linux 2027 (AL2027) がパブリックプレビュー中 : AL2027 は Amazon Linux オペレーティングシステムの次期バージョンです。カーネル 7.1 以降で動作し、パフォーマンス、スケール、セキュリティを念頭に置いて AWS 上のクラウドネイティブワークロード専用に構築されています。AL2023 のベースラインに基づいて構築された AL2027 は、ウェブアプリケーション、データベース、コンテナ化されたマイクロサービス、AI/ML ワークロード、および大規模インフラストラクチャを実行する、安全かつ安定した AWS ネイティブなオペレーティングシステムを必要とするお客様向けに設計されています。 Amazon EC2 R9g および R9gd のメモリ最適化インスタンス : これらのインスタンスは AWS Graviton5 プロセッサを使用しており、Amazon EC2 で実行されるメモリを大量に消費するワークロードに最適な料金パフォーマンスを提供します。R9g および R9gd インスタンスは、AWS Graviton4 ベースの R8g および R8gd インスタンスと比較して最大 25% 優れたコンピューティングパフォーマンスを提供します。データベースでは最大 30%、ウェブアプリケーションでは最大 35%、機械学習では最大 35% 高速になります。詳細については、 Daniel のブログ記事 をお読みください。 コンテナイメージ関数用 AWS Lambda SnapStart : Lambda SnapStart はオプトイン機能です。リソースをプロビジョニングしたり、複雑なパフォーマンス最適化を実装したりしなくても、応答性が高くスケーラブルなアプリケーションを簡単に構築できます。 これまで、SnapStart はマネージドランタイム (Python、.NET、Java) でのみサポートされていました。今後は、SnapStart をコンテナイメージに使用して、ML 推論やインタラクティブ API などの遅延の影響を受けやすいワークロードの起動時間を数秒から 1 秒未満に短縮できます。 AWS Agent Registry の一般提供開始 : AWS Agent Registry は、組織内のエージェント、ツール、スキル、MCP サーバー、およびカスタムリソース用に、管理されたプライベートなカタログおよび検出レイヤーを提供します。プレビューで導入された機能 (手動および URL ベースのレコード作成、承認ワークフロー、セマンティック検索、キーワード検索、AWS CloudTrail 監査証跡) に加えて、今回 Registry には新しいエンタープライズ向けの機能が追加されました。詳細については、 AI ブログの記事 をご覧ください。 Amazon Redshift が Apache Iceberg v3 テーブルのサポートを開始 : Amazon Redshift のデータレイクにある Apache Iceberg v3 テーブルからの読み取りと書き込みが可能になります。今回のリリースにより、Amazon Redshift にはデフォルトの列値、行系統、および削除ベクトルのサポートが導入されます。Amazon Redshift の Graviton ベースのプロビジョニングクラスターとサーバーレスクラスターは、新しい v3 フォーマットをサポートしています。詳細については、「 Redshift の Apache Iceberg v3 の機能 」をご覧ください。 AWS のお知らせに関する詳しいリストについては、「 AWS の最新情報 」ページをご覧ください。 AWS のその他のニュース 興味深いと思われるその他のプロジェクトやニュース項目をいくつかご紹介いたします。 2026 年のガートナーマジッククアドラントの戦略的クラウドプラットフォームサービス部門で AWS がリーダーに選出 : ガートナー社は 2026 年のマジッククアドラントの戦略的クラウドプラットフォームサービス部門で AWS を 16 年連続でリーダーに認定し、実行能力の軸で再び AWS を最高ランクに位置付けました。この評価は、インフラストラクチャと AI からセキュリティ、運用に至るまで、最も広範で奥深いクラウド機能セットを提供するという当社の取り組みが反映されたものであると考えており、お客様には安心して構築、革新、スケールに取り組んでいただけます。 AWS Certified AI Business Strategist : この新しい認定は、組織内の AI イニシアチブを評価し、支持し、拡大する専門家を対象としています。これには、チーム全体で導入を推進する基幹業務リーダー、顧客に AI の価値をわかりやすく説明する営業担当者、実験から本稼働までのクライアント戦略を導くコンサルタント、AI への投資をビジネスの成果へと結びつけるプログラムマネージャーなどが挙げられます。ベータ試験の登録は 2026 年 9 月 1 日に開始され、試験の配信は 9 月 29 日から開始されます。 エージェントセキュリティ: マシンスピードでの検知と対応 : 私たちは、セキュリティは AI の導入後ではなく、その前に進化すべきだと考えています。この信念のもと、私たちのチームは SANS Institute と協力して、2026 年発行の Cloud Security Exchange eBook に新たな章を設けました。本章では、エンタープライズ規模でエージェントワークロードを保護するための実践的なフレームワークについて説明しています。また、評価、試験運用、大規模運用を問わず、エージェンティック AI 導入のあらゆる成熟段階にあるセキュリティチームに対して、具体的なアーキテクチャパターン、実装ガイダンス、フレームワークを提供し、エージェントワークロードの保護についてもさらに深く掘り下げています。 AWS のブログ記事一覧については、 AWS ブログ ページをご確認ください。 AWS の詳細について学び、今後予定されている AWS 主催の対面イベントやバーチャルイベント 、 スタートアップイベント 、 開発者向けイベント ( AWS re:Invent 、 AWS Summit 、 AWS Community Day など) を閲覧して、ご参加ください。 AWS Builder Center に参加して、ビルダーとつながり、ソリューションを共有し、開発をサポートするコンテンツにアクセスしましょう。 9 月 7 日週のニュースは以上です。9 月 14 日週に再びアクセスして、新たな1週間のまとめをぜひお読みください! — Channy 原文は こちら です。
ファイルサーバー等の検索システム構築において、事前に必要なストレージ容量を試算する際の手順を解説します。 今回は、FSCrawler (*1) を使って検索用ストレージサイズの概算見積りを行ってみたいと思います。 (*1) Elastic の正式な製品ではありませんが、ファイルを検索できるよう Elasticsearch に登録してくれるオープンソースです。 目次 1. 検証の概要 2. 環境 3. FSCrawler の初期化と設定 4. 検索用ファイルの配置 5. FSCrawlerの実行 6. 登録確認 7. インデックスサイズの確認 8. セマンティック検索:なしで再計測 9. セマンティック検索:「あり」と「なし」のストレージサイズの比較 10. 概算見積りの計算ポイントと影響要因 11. まとめ 12. 参考URL 1. 検証の概要 本記事では、サンプルファイルを FSCrawler で Elasticsearch に取り込み、作成されたインデックスの物理ストレージサイズを取得することで、 ストレージ容量の見積り倍率(元のファイルサイズに対するインデックスサイズの割合)を算出する手順を解説します。 2. 環境 Windows 11 Elasticsearch 9.5.2 (Self-Managed, Trial License) Elastic Cloud (セマンティック検索用) FSCrawler 3.0 Java 25.0.4.1 3. FSCrawler の初期化と設定 下記を参考にして FSCrawler をインストールします。 https://fscrawler.readthedocs.io/en/fscrawler-3.0/installation.html FSCrawler の初期設定を行います。 参考URL:  https://fscrawler.readthedocs.io/en/fscrawler-3.0/user/getting_started.html set JAVA_HOME=jdk-25.0.4.1のインストールフォルダ set FS_JAVA_OPTS=-Xmx2g -Xms2g bin\fscrawler.bat --setup C:\Users\username\.fscrawler\fscrawler\_settings.yaml ファイルが生成されるので、これを編集します。 (下記はあくまでもサンプルです。適宜修正してください。) name: "test" fs: url: "C:/tmp/es" update_rate: "15m" includes: - "**/*.pdf" - "**/*.docx" - "**/*.xlsx" - "**/*.pptx" excludes: - "**/*.bak" hash_algorithm: "SHA-256" raw_metadata: true ocr: enabled: false elasticsearch: urls: - "https://127.0.0.1:9200" index: "test_docs" index_folder: "test_folder" api_key: "YOUR_API_KEY" ca_certificate: "ca.crtファイルのパス" #pipeline: "my_pipeline" semantic_search: "true" 検索対象のフォルダを C:\tmp\es としておきます。 今回は、*.pdf, *.docx, *.xlsx, *.pptx ファイルを検索対象とします。 セマンティック検索:あり としておきます。 API_KEYは、Kibana 上で発行しておきます。 ※セマンティック検索を行う場合、EIS の設定を行っておくなど、Elasticsearch 側での事前準備が必要です。 4. 検索用ファイルの配置 C:\tmp\es フォルダ配下に検索対象となるテスト用ファイル(例: 合計 1 GB の PDF や Office 文書)を配置します。 ここでは、 東京都防災ホームページ  からダウンロードした 「東京都くらし防災」(全ページ) (13.5MBのpdf) を C:\tmp\es\pdf\kb2023-tokyo-all.pdf として配置します。 5. FSCrawlerの実行 FSCrawlerを実行してインデックス化を行います。 set JAVA_HOME=jdk-25.0.4.1のインストールフォルダ set FS_JAVA_OPTS=-Xmx2g -Xms2g bin\fscrawler.bat FSCrawlerの実行に成功すると、C:\Users\username\.fscrawler\test\_checkpoint.json ファイルが作成されます。 また、Elasticsearch の test_docs インデックスにドキュメントが登録されます。 6. 登録確認 Kibana の DevTool から下記のクエリを実行してみます(キーワード検索)。 GET /test_docs/_search { "query": { "match": { "content": "寝室での注意事項" } } } ドキュメントが返却されます。 続いて、下記のリクエストも実行してみます(セマンティック検索)。 GET /test_docs/_search { "query": { "match": { "content_semantic": "寝室での注意事項" } }, "highlight": { "fields": { "content_semantic": { "number_of_fragments": 2, "order": "score" } } } } こちらもドキュメントが返却されます。 7. インデックスサイズの確認 Kibana の Stack Management / Index Management の画面から test_docs インデックスのページを表示します。 プライマリインデックスのサイズが 568.65 KB, レプリカと合わせると 1.11 MB であることがわかります。 8. セマンティック検索:なしで再計測 content フィールドに対するセマンティック検索用データ(ベクトルデータ)を生成しないようにして再計測してみます。 また、content フィールドの analyzer をデフォルトの standard ではなく、日本語用の analyzer を使うよう設定しておきます。 ※ Elasticsearchに事前に analysis-icu, analysis-kuromoji のインストールが必要です。 登録先のインデックスを test2_docs とします。 下記のリクエストを Dev Tool から発行して test2_doc インデックスを作成しておきます。 PUT /test2_docs/ { "settings": { "index": { "number_of_replicas": 1 }, "analysis": { "char_filter": { "windows_separator": { "type": "mapping", "mappings": [ """\\ => /""" ] }, "ja_normalizer": { "type": "icu_normalizer", "name": "nfkc_cf", "mode": "compose" } }, "tokenizer": { "fscrawler_path": { "type": "path_hierarchy" }, "ja_kuromoji_tokenizer": { "mode": "search", "type": "kuromoji_tokenizer", "discard_compound_token": true, "user_dictionary_rules": [ ] } }, "filter": { "ja_search_synonym": { "type": "synonym_graph", "lenient": false, "updateable": false, "expand": true, "synonyms": [ ] } }, "analyzer": { "fscrawler_path": { "char_filter": [ "windows_separator" ], "tokenizer": "fscrawler_path" }, "ja_kuromoji_index_analyzer": { "type": "custom", "char_filter": [ "ja_normalizer", "kuromoji_iteration_mark" ], "tokenizer": "ja_kuromoji_tokenizer", "filter": [ "kuromoji_baseform", "kuromoji_part_of_speech", "cjk_width", "ja_stop", "kuromoji_number", "kuromoji_stemmer" ] }, "ja_kuromoji_search_analyzer": { "type": "custom", "char_filter": [ "ja_normalizer", "kuromoji_iteration_mark" ], "tokenizer": "ja_kuromoji_tokenizer", "filter": [ "kuromoji_baseform", "kuromoji_part_of_speech", "cjk_width", "ja_stop", "kuromoji_number", "kuromoji_stemmer", "ja_search_synonym" ] } } } }, "mappings": { "dynamic_templates": [ { "raw_as_text": { "path_match": "meta.raw.*", "mapping": { "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } }, "type": "text" } } } ], "properties": { "attachment": { "type": "binary" }, "attributes": { "properties": { "acl": { "properties": { "flags": { "type": "keyword" }, "permissions": { "type": "keyword" }, "principal": { "type": "keyword" }, "type": { "type": "keyword" } } }, "group": { "type": "keyword" }, "owner": { "type": "keyword" } } }, "content": { "type": "text", "analyzer": "ja_kuromoji_index_analyzer", "search_analyzer": "ja_kuromoji_search_analyzer" }, "file": { "properties": { "checksum": { "type": "keyword" }, "content_type": { "type": "keyword" }, "created": { "type": "date", "format": "date_optional_time" }, "extension": { "type": "keyword" }, "filename": { "type": "keyword", "store": true }, "filesize": { "type": "long" }, "indexed_chars": { "type": "long" }, "indexing_date": { "type": "date", "format": "date_optional_time" }, "last_accessed": { "type": "date", "format": "date_optional_time" }, "last_modified": { "type": "date", "format": "date_optional_time" }, "url": { "type": "keyword", "index": false } } }, "meta": { "properties": { "altitude": { "type": "text" }, "author": { "type": "text" }, "comments": { "type": "text" }, "contributor": { "type": "text" }, "coverage": { "type": "text" }, "created": { "type": "date", "format": "date_optional_time" }, "creator_tool": { "type": "keyword" }, "date": { "type": "date", "format": "date_optional_time" }, "description": { "type": "text" }, "format": { "type": "text" }, "identifier": { "type": "text" }, "keywords": { "type": "text" }, "language": { "type": "keyword" }, "latitude": { "type": "text" }, "longitude": { "type": "text" }, "metadata_date": { "type": "date", "format": "date_optional_time" }, "modifier": { "type": "text" }, "print_date": { "type": "date", "format": "date_optional_time" }, "publisher": { "type": "text" }, "rating": { "type": "byte" }, "relation": { "type": "text" }, "rights": { "type": "text" }, "source": { "type": "text" }, "title": { "type": "text" }, "type": { "type": "text" } } }, "path": { "properties": { "real": { "type": "keyword", "fields": { "fulltext": { "type": "text" }, "tree": { "type": "text", "analyzer": "fscrawler_path", "fielddata": true } } }, "root": { "type": "keyword" }, "virtual": { "type": "keyword", "fields": { "fulltext": { "type": "text" }, "tree": { "type": "text", "analyzer": "fscrawler_path", "fielddata": true } } } } } } } } 先ほど実行していた fscrawler.bat を Ctrl+C で停止させます。 C:\Users\username\.fscrawler\fscrawler\_settings.yaml ファイルを修正します。 name : test -> test2 index : test_docs -> test2_docs index_folder : test_folder -> test2_folder semantic_search : true -> false name: "test2" fs: url: "C:/tmp/es" update_rate: "15m" includes: - "**/*.pdf" - "**/*.docx" - "**/*.xlsx" - "**/*.pptx" excludes: - "**/*.bak" hash_algorithm: "SHA-256" raw_metadata: true ocr: enabled: false elasticsearch: urls: - "https://127.0.0.1:9200" index: "test2_docs" index_folder: "test2_folder" api_key: "YOUR_API_KEY" ca_certificate: "ca.crtファイルのパス" #pipeline: "my_pipeline" semantic_search: "false" fscrawler.bat を再実行します。 set JAVA_HOME=jdk-25.0.4.1のインストールフォルダ set FS_JAVA_OPTS=-Xmx2g -Xms2g bin\fscrawler.bat test2_docs インデックスへドキュメントが登録されます。 Kibana の Stack Management / Index Management の画面から test2_docs インデックスのページを表示します。 プライマリインデックスのサイズが 223.02 KB, レプリカと合わせると 446.04 KB となっています。 9. セマンティック検索:「あり」と「なし」のストレージサイズの比較 条件 元ファイルのサイズ プライマリインデックスのサイズ インデックスサイズ/元ファイルサイズの比率 セマンティック検索あり 13870.63 KB 568.65 KB 4.1% セマンティック検索なし 13870.63 KB 223.02 KB 1.6% セマンティック検索用のベクトルデータを生成しない分、ストレージサイズが減少したことがわかります。 ※この結果は、入力ファイルにより変動するので、実際に使用するファイルで試すことをお勧めします。 10. 概算見積りの計算ポイントと影響要因 実際の容量見積もりを行う際は、以下の要素を考慮して計算式を組み立てます。 テキスト抽出比率 : PDF や Office ファイル内のテキストデータ割合に依存します。スキャン画像主体の PDF ではインデックスサイズが小さくなり、テキスト主体の文書では大きくなります。 レプリカ数の乗算 : 本番環境で冗長化のためにレプリカ数を  1  に設定する場合、ストレージ要件は  プライマリサイズ × 2  になります。 要件に近い形でサンプルデータのインデックス登録を行い、ストレージサイズを取得します。 サンプルデータで算出した倍率を全体のファイルサーバー容量に掛け合わせることで、Elasticsearch 用ストレージの概算見積もりが可能となります。 想定ストレージサイズ = 対象ファイルの総容量 x 検証で得られたインデックス比率 x (1 + レプリカ数) ※注意 FSCrawler は、基本的に 1 ファイル = 1 ドキュメントとしてインデックスに登録します。 11. まとめ 今回は FSCrawler を活用し、サンプルファイルを用いて Elasticsearch の検索用ストレージサイズを概算見積もりする手順をご紹介しました。 今回の検証における主なポイントは以下の通りです。 セマンティック検索(ベクトルデータ)の影響 : セマンティック検索を有効にすると、テキストデータに加えてベクトルデータが保持されるため、ストレージサイズが大きくなります(今回の検証では元ファイル比で 1.6% から 4.1% に増加)。 実ファイルを用いた事前検証の重要性 : PDF や Office 文書内のテキスト抽出比率によってサイズが変動するため、本番環境に近いサンプルファイルで試算することが精度向上の鍵となります。 冗長化構成(レプリカ数)の考慮 : プライマリサイズだけでなく、本番環境のレプリカ設定(例: レプリカ 1 の場合はプライマリ × 2)を忘れずに計算式へ組み込む必要があります。 検索システムの新規構築や移行におけるクラスタ設計・キャパシティプランニングの際、ぜひ本記事の手順を参考に試算してみてください。 12. 参考URL https://qiita.com/daixque/items/83a04da18c51ba29324e https://fscrawler.readthedocs.io/en/fscrawler-3.0/ The post FSCrawler を使って検索用ストレージサイズの概算見積りを行う first appeared on Elastic Portal .
はじめに こんにちは。商品基盤部の藤本です。 私たちのチームでは、生成AIコーディングエージェント(以下、AIエージェント。主にClaude Code)を使って開発に取り組んでいます。以前からAIエージェントに一貫した実装をしてもらうため、ルールを書いて指示する運用を続けてきました。しかし、ルール同士の矛盾やレビューだけでは遵守を保証できないといった課題がありました。本記事ではこの課題と、ArchUnitによる機械的な検証へ落とし込むまでの取り組みを紹介します。 目次 はじめに 目次 背景・課題 ルールの読者はAIであるという前提の転換 自然文のルールからArchUnitの実行可能な検証への変換 ルール文書・テスト・実コードの三者整合性を担保する方法 アドホックな知識からルールへの体系的な移行 複数リポジトリへのルール展開の検討と見送り 検証の必須ゲート化 まとめ 背景・課題 AIエージェントに一貫した実装をしてもらうには、コーディング規約やアーキテクチャの制約をルールとして書き、AIエージェントに読ませる必要があります。私たちのチームでも命名規約やレイヤー間の依存関係に関する制約を、Markdownのルール文書として整備してきました。 ただ、ルールの数が増えるにつれて2つの問題が現れました。 1つ目は、ルール同士の矛盾や重複です。新しいルールを追加するとき、既存のルールと似た内容が別の場所にも書かれていたり、条件が食い違っていたりする場面がありました。ルールの数が少ないうちは目視で気づけますが、数が増えるほど見落としが発生しやすくなります。 2つ目は、ルールの遵守を保証する手段が、レビューに依存してしまう点です。AIエージェントの生成したコードのルール準拠を、毎回目視で確認する運用ではレビュアーの負担は増え続けます。レビューで見逃した違反は、そのまま実装に残ってしまいます。 さらに、ルールの書き方自体にも見直すべき点がありました。人間向けの文書と同じ作法をAIエージェントにそのまま適用しても、意図した通りに機能しない場面がありました。 ルールの読者はAIであるという前提の転換 私たちは当初、人間向けのドキュメントを書くときと同じ作法でルール同士を相互参照させたり、背景の説明を書き添えたりしていました。人間がドキュメントを読むときは関連する複数のルールを渡り歩きながら理解を組み立てるため、相互参照がその手がかりです。 しかし、AIエージェントがルールを読む場面は、実装やレビューのたびに発生します。そのたびに複数のファイルを渡り歩いて文脈を組み立てる必要があると、参照が一段増えるごとに読み落としや誤読の起点が増えてしまいます。 そこで私たちは、「ルールの読者はAIである」という前提に立ち返りました。人間向けの相互参照は、複数の文書をまとめて理解する読み手を助けるための工夫です。AIエージェントに同じ役割を期待する必要はなく、それぞれのルールが単体で完結し、判断に必要な情報がその中でそろうように書き直すことにしました。単体で完結する形にしたことで同じ説明が複数のルール文書に重複することを懸念していました。実際に運用してみると重複は目立って増えておらず、人間のレビュアーがルール文書を読む頻度や体験にも変化はありません。 この転換は書き方を変えるだけでは不十分で、ルールが本当に守られているかを機械的に検証できる形に変換する必要がある、という次の課題につながりました。 自然文のルールからArchUnitの実行可能な検証への変換 前提を転換しただけでは、ルールが実際に守られているかどうかまでは保証できません。次に取り組んだのは、自然文で書いていたルールをArchUnitで実行可能な検証に変換することです。 ArchUnitは、Javaのアーキテクチャ制約をテストコードとして記述し、ビルド時に検証できるライブラリです。パッケージの依存関係やクラスの命名規則、メソッドの呼び出し制約などをDSLとして表現できます。 たとえば、「ドメイン層のコードではリフレクションを使用しない」という規約は、これまでルール文書に自然文で書き、実装時にレビュアーが目視で確認していました。この規約をArchUnitで検証しようとすると、まず思いつくのは、パッケージ単位で禁止する書き方です。 なお、本記事のコード例は、実際にはSpockで実装しているルールをJUnit 5形式に書き直したものです。 import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @AnalyzeClasses (packages = "com.example.domain" ) class ArchitectureRuleTest { @ArchTest static final ArchRule domain_should_not_use_reflection = noClasses() .should() .dependOnClassesThat() .resideInAPackage( "java.lang.reflect.." ) .because( "リフレクションはドメイン層の意図を分かりにくくするため禁止する" ); } しかし、この書き方には問題がありました。 dependOnClassesThat().resideInAPackage(...) は、対象パッケージのクラスへの依存を広く検出します。メソッド呼び出しだけでなくフィールドや引数の型宣言も依存に含まれるため、リフレクションを直接呼び出していないコードまで違反として検出してしまいます。 私たちのプロジェクトでは、O/Rマッパーが生成するコードの一部が java.lang.reflect.Method 型のフィールドを持っていました。生成コードが呼んでいるのは、内部でリフレクションを使うライブラリのヘルパーメソッドです。生成コード自身がリフレクションAPIを直接呼び出しているわけではありません。しかし、パッケージ指定による型参照チェックでは、この生成コードも違反として検出されてしまいます。 そこで、検出対象を「型参照」ではなく「メソッド呼び出し」に絞った ArchCondition として実装しました。 import java.util.Set; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.codeUnits; @AnalyzeClasses (packages = "com.example.domain" ) class ReflectionRuleTest { private static final Set<String> CLASS_LOOKUP_METHODS = Set.of( "forName" , "newInstance" , "getMethod" , "getDeclaredMethod" , "getMethods" , "getDeclaredMethods" , "getField" , "getDeclaredField" , "getFields" , "getDeclaredFields" , "getConstructor" , "getDeclaredConstructor" , "getConstructors" , "getDeclaredConstructors" ); @ArchTest static final ArchRule domain_should_not_call_reflection = codeUnits().should( new ArchCondition<JavaCodeUnit>( "not call reflection / dynamic-dispatch APIs" ) { @Override public void check(JavaCodeUnit codeUnit, ConditionEvents events) { codeUnit.getMethodCallsFromSelf().forEach(call -> { var target = call.getTarget(); var ownerName = target.getOwner().getFullName(); var ownerPackage = target.getOwner().getPackageName(); var name = target.getName(); boolean violation = (ownerName.equals( "java.lang.Class" ) && CLASS_LOOKUP_METHODS.contains(name)) || ownerPackage.equals( "java.lang.reflect" ) || ownerPackage.equals( "java.lang.invoke" ); if (violation) { events.add(SimpleConditionEvent.violated( codeUnit, codeUnit.getFullName() + " calls " + ownerName + "#" + name)); } }); } }); } このテストでは、コードが実際に呼び出したメソッドの呼び出し先(オーナーの型とメソッド名)を1件ずつ確認します。 java.lang.reflect パッケージや java.lang.invoke パッケージへの呼び出しは検出対象です。加えて、 java.lang.Class が持つ forName や getDeclaredMethod のような動的なメソッド探索・生成系のメソッド呼び出しも検出対象にしています。 java.lang.Class 自体は java.lang パッケージに属しており、 java.lang.reflect パッケージの外にあります。そのためパッケージ名だけでは判定できず、対象にしたいメソッド名を CLASS_LOOKUP_METHODS として自分たちで列挙しています。これはArchUnitが提供するAPIではなく、プロジェクト側で定義した定数です。型を参照しているだけのコードは対象にならないため、生成コードを誤検出することもありません。 検出範囲もメソッド本体だけでなく、コンストラクタやフィールドの初期化子まで含めた全コードユニット( codeUnits() )にしています。これによって、レビュアーが目視で確認していた範囲を、CIで機械的に検証できるようになりました。 命名規則やレイヤー間の依存関係も同様に、目視確認からArchUnitでの検証へ順次置き換えていきました。自然文のルールをすべて機械的に検証できるわけではありません。ただし、パッケージ構造やクラス間の関係など、構造的に表現できる制約は、この方法でカバーできます。 実際に機械的な検証へ変換したルールには、次のようなものがあります。 日時を扱うクラスの now() メソッドを、 Clock 引数を指定せずに直接呼び出すことを禁止するルール(テストで時刻を固定できるようにするための制約) 特定のインタフェースを実装したrecordのcompact constructorに Objects.requireNonNull 呼び出しを必須にするルール(nullチェックの実装漏れを防ぐ制約) UseCase層のクラスが持つ特定のメソッドに、 @Transactional アノテーションを必須にするルール(トランザクション境界の付け忘れを防ぐための制約) これらはいずれも、以前は自然文のルール文書とレビューでの目視確認に頼っていたものです。 ルール文書・テスト・実コードの三者整合性を担保する方法 ルールをArchUnitのテストに変換しても、それだけでは安心できません。ルール文書とテストコード、そして実コードの3つは、別々のファイルに書かれているため、時間が経つとずれていく可能性があります。 実際に私たちのルール文書にある例を紹介します。あるUseCaseクラスの設計規約では、公開メソッドに @Transactional を必須にし、 isolation 属性はデフォルトのまま変更しないことを原則としています。ただし、バッチ処理で定期的に更新されるデータを参照する1つのUseCaseだけ、例外として REPEATABLE_READ を指定してよいことになっています。 // OK: 分離レベルを上げる場合は理由をコメントで明示する(唯一の例外) @Transactional (isolation = Isolation.REPEATABLE_READ) // バッチで定期的に更新されるデータを参照するため、整合性を保つ public ResultDTO process(SomeCommand command) { /* ... */ } この例外は、ルール文書に書くだけでは終わりません。ArchUnitのテストコード側にも、この特定のクラスを検査対象から除外する条件を書く必要があります。 import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; @AnalyzeClasses (packages = "com.example.usecase" ) class UseCaseTransactionalRuleTest { @ArchTest static final ArchRule isolation_should_be_default = methods() .that().areDeclaredInClassesThat().resideInAPackage( "com.example.usecase.." ) .and().areDeclaredInClassesThat().areAnnotatedWith(Service. class ) .and().areDeclaredInClassesThat().doNotHaveFullyQualifiedName(SampleUseCase. class .getName()) .and().areAnnotatedWith(Transactional. class ) .should( new ArchCondition<JavaMethod>( "have the default isolation level" ) { @Override public void check(JavaMethod method, ConditionEvents events) { var isolation = method.getAnnotationOfType(Transactional. class ).isolation(); if (isolation != Isolation.DEFAULT) { events.add(SimpleConditionEvent.violated( method, method.getFullName() + " has isolation " + isolation)); } } }); } ルール文書の例外規定と、テストコードの doNotHaveFullyQualifiedName(...) は、別々のファイルに書かれた対応関係です。この対応が崩れると、新しく追加した例外が検査対象のままになって意図せずテストが失敗したり、逆に例外ではないクラスが誤って除外されたままになったりします。私たちのルール文書には、「新たに例外が必要になった場合は、テストコードの除外条件も合わせて更新する」という注意書きを直接添えています。これによって、ルール文書とテストコードの対応関係を明示しています。ただし、この注意書き自体も、人が読んで実行することに変わりはありません。例外の宣言をコード側に持たせ、テストがそれを直接参照する形にすれば、注意書きなしでも対応関係を保てます。この点は今後改善する余地として残っています。 もう1つの工夫は、ルール文書自体に「検査の保証範囲」を明記することです。たとえば、先ほどのUseCaseの設計規約には、次のような記載があります。 機械的チェックの現状: process への @Transactional 必須とisolation制約は、ArchUnitのテストで既に機械的に検査されている。一方、メソッドの公開範囲や引数の形に関する規約はまだ自動検査されておらず、レビューでの目視確認に依存する。 この記載があることで、AIエージェントも人間のレビュアーもルール文書を読むだけで、どこまでが保証されていて、どこからが目視確認頼みかを判断できます。 こうした工夫に加えて、ルールを新しく追加したときには、AIエージェントに三者を突き合わせて確認してもらうこともあります。対象は、ルール文書とArchUnitのテストコード、実コードです。矛盾点や、ルール文書の説明と実装の食い違い、目視確認に頼っている部分の取りこぼしがないかを検証してもらう狙いです。 アドホックな知識からルールへの体系的な移行 ルールをArchUnitで検証する仕組みや、三者の整合性を保つ運用を整えても、その対象になるルール自体がどこにあるかが分かりにくいという問題が残っていました。 私たちのプロジェクトには、設計判断の理由や実装パターンをまとめた知識ベースがありました。この知識ベースには、「なぜこの設計を選んだか」という背景説明と、「必ず守るべき制約」が同じ文書に混在していました。背景説明は読み手の理解を助けるものであり、検証の対象にはなりません。一方、制約は本来、検証の対象になり得るものです。両者が同じ文書に混ざっていると、どの記述がArchUnitで検証すべき対象なのかが分かりません。 そこで、知識ベースと制約を別々のディレクトリに分けました。背景説明や実装パターンは .claude/knowledge/ に置き、必ず守るべき制約は .claude/rules/ へ配置しました。なお、 .claude/rules/ は現在Claude Codeが標準で読み込むディレクトリですが、 .claude/knowledge/ はこのプロジェクト独自の配置です。標準的な文書配置がまだ定まっていなかった時期に、リポジトリの直下には置きたくないという理由で .claude/ 配下に作ったものです。 たとえば、アーキテクチャに関する文書は、同じ architecture.md という名前で両方のディレクトリに存在します。 .claude/knowledge/architecture.md には、オニオンアーキテクチャを採用した理由や各層の実装パターンといった、背景の説明が書かれています。 .claude/rules/architecture.md には、Controller・UseCaseをファットにしないための具体的な閾値や、NG・OKのコード例が書かれています。これらは、コード生成時に従うべき強制基準だけに絞られています。後者の冒頭には、次のような記載があります。 各層の責務概要・実装パターン・選択理由は .claude/knowledge/architecture.md に記載してあり、本ファイルはその知識を前提とした上で「コード生成時に従う強制基準」を定義する。 この参照は、ルールを単体で完結させるという前提の転換と矛盾しているように見えます。しかし、コード生成時に守るべき強制基準は .claude/rules/architecture.md 側で完結しています。 .claude/knowledge/architecture.md を読まなくても遵守の判断はできます。knowledge側は、なぜその基準になったかという任意の背景情報であり、参照しなくても強制基準の適用に支障はありません。 .claude/knowledge/ は .claude/rules/ と異なり無条件では読み込まれず、CLAUDE.mdの案内に沿って必要な場面ごとに参照先が示される配置です。そのため、AIエージェントが両者を区別せずまとめて読み込んでしまう事態は今のところ起きていません。 この整理によって、「検証すべき制約の一覧」がルール文書側にまとまりました。新しいルールを追加するときも、書く場所を選ぶ時点で性質を判断するようになりました。単なる背景知識なのか、AIエージェントに守らせたいルールなのか、それとも機械的に検証できる制約なのか、という観点です。 複数リポジトリへのルール展開の検討と見送り 1つのリポジトリでルールとArchUnitによる検証の仕組みが定着したところで、次に考えたのは、他のリポジトリでも同じ仕組みを使えるようにすることでした。命名規約やアーキテクチャの制約には、プロジェクトが違っても通用する部分があります。1つのリポジトリで整備したルールを他のプロジェクトでもそのまま使えれば、同じルールをゼロから作り直す手間を省けます。 そこで、ルールをマーケットプレイス形式で管理し複数のリポジトリへ配布する仕組みと、配布したルールを管理するツールを用意する計画を立てました。ここでの「マーケットプレイス形式」は、既存のプラグイン配布基盤を指すものではなく、複数のリポジトリへルールを公開・取得できるようにする、自前の配布基盤のことを指しています。 しかし、計画を進める中で見えてきたのはツールの設計上の課題ではなく、運用面の課題でした。ルールを共有する仕組み自体は用意できます。ただし、それぞれのリポジトリを担当するメンバーが、他のリポジトリで整備されたルールを積極的に取り込むかどうかは別の問題です。実際には、他のリポジトリのルールを取り込む動きはほとんど生まれませんでした。 取り込みが進まなかった背景には、大きく2つの理由があると考えています。1つは、リポジトリをまたいで本当に共有できるルールが想定していたほど多くなかったことです。プロジェクト固有の事情に依存するルールが大半で、汎用的に使い回せる部分は一部に留まりました。もう1つは、ルールを取り込むこと自体が、前述したルール文書・テスト・実コードの整合性を確認する運用という新たな運用コストを増やしてしまうことです。既存のルールをそのまま使うのではなく、自分たちのリポジトリの実装に合わせて調整し、整合性を保ち続ける必要があり、その手間が取り込みのハードルになっていました。なお、この2つの理由は、実際の取り込み状況から私たちが振り返って推測したものです。 この結果を受けてマーケットプレイスと管理ツールの計画は取りやめました。仕組みを作ることが目的化してしまうと、実際には使われない仕組みを維持するコストだけが残ってしまいます。需要が確認できていない段階で大掛かりな仕組みを作るより、まずは個別のリポジトリでルールと機械的な検証の仕組みを定着させることを優先する判断です。 この判断を見直す条件があるとすれば、共有できるルールの数がたまたま増えることではなく、共通化に必要な材料がそろうことだと考えています。ルールを先に一般化してから展開するトップダウンの手順は、複数チームでの実例が積み重なっていない段階では成立しにくいというのが、今回の見送りから得た実感です。各チームがルールの背景を記録しつつ、記録した内容をチーム横断で比較する仕組みと運用が必要です。ルール文書・テスト・実コードの三者突き合わせと同様に、この比較もAIエージェントに任せられる見通しが立った時点で、改めて展開を検討したいと考えています。 検証の必須ゲート化 ArchUnitのテストを書いても、実行される保証がなければ意味がありません。テストコードとして存在していても、実行タイミングが曖昧だと気づかないうちに検証が素通りしてしまうことがあります。 私たちのプロジェクトには、SpockベースのArchUnitルールとは別の検証ルールもあります。たとえば、参照型を返すメソッドに @Nonnull ・ @Nullable のいずれかを必須にするルールなどがあります。このルールは check タスクに組み込まれていましたが、 test タスクには組み込まれていませんでした。これは意図的な設計ではありませんでした。Gradleの標準的なタスク依存構造( check が test に依存する一方、逆方向の依存はない)上、静的な検証系のタスクが慣習的に check 側へ接続されることによるものでした。この違いに気づかないまま ./gradlew test だけをローカルで実行してプッシュしたところ、 check 側のルール違反がローカルでは検出されず、CIで初めて検出される事態が起きました。テストが通ったことを確認しても、 check の検証は素通りしていたということです。 そこで、プッシュ前に実行すべきコマンドを ./gradlew check に統一しました。 check タスクはSpotlessによるフォーマットチェック・全テスト・ルールの検証をまとめて実行するため、 test だけを実行して安心してしまう事態を防げます。この方針はルール文書側にも明文化しています。 あわせて、CI側でもプルリクエストごとに検証しています。ただし、CIでは check タスクをそのまま呼ぶのではなく、構成要素を複数のジョブに分けて並列実行しています。DBを使うテストと使わないテストを別ジョブに分けてDBコンテナの要否を切り分け、フォーマットは「チェックして落とす」のではなく「自動修正してコミットする」別ジョブにしているためです。ルールを検証するジョブは、GitHubのブランチ保護ルールで必須ステータスチェックに指定されています。ルールの検証に失敗すると、そのプルリクエストはマージできません。ローカルでの実行規律だけでなく、CIでも強制することで、実行を忘れたまま気づかずマージしてしまう事態を防いでいます。 新しいルールを追加するときは、検証が実際に機能しているかどうかも確認します。たとえば、ドメイン層のパッケージ配置に関するルールを追加したときは、意図的にルールに違反する配置のダミークラスを用意し、検証が失敗(FAILED)することを確認しています。ルールを追加した時点で既存のコードに違反がないかどうかも確認し、違反がゼロであることを確かめてからマージしています。 こうして、ArchUnitなどを用いた検証は、書いただけのテストコードではなく、プッシュ前に必ず通過するゲートとして機能するようになりました。 まとめ 本記事では、AIエージェント向けにコーディングルールを書き、ArchUnitで機械的に検証する取り組みを紹介しました。 AIエージェントは、指示すればすぐに多くのコードを書いてくれます。しかし、その分だけルールに違反したコードが生まれる機会も増えます。レビューだけでこれを検出しようとすると、コードが増えるほどレビュアーの負担も増えていき、いずれ追いつかなくなります。ArchUnitでルールを実行可能な形にしておけば、検証の速度をコードが生成される速度に合わせられます。 この検証の仕組みを維持する作業にも同じ考え方が当てはまります。ルール文書とテストコード、実コードの整合性を確認する作業を人手だけで追いかけようとすると、ルールが増えるほど負担が増えていきます。そこで、この確認作業もAIエージェントに依頼しています。ルールを守る対象であるAIエージェントに、ルールを整備する側も手伝ってもらう、という体制です。 ルールの読者はAIであるという前提に立ち返ったことは、書き方を変えるだけの話ではありませんでした。ルールを「読んで理解してもらうもの」から「実行して確認できるもの」に位置づけを変える、という判断につながりました。AIエージェント向けにコーディングルールを整備することを検討している方がいれば、ぜひ参考にしてみてください。今後は、まだレビューでの目視確認に依存している規約も対象に含め、ArchUnitに限らずさまざまな手段で機械的に検証できる範囲を広げていきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com

動画

書籍