|
Springは間違いなく最も人気のあるJavaフレームワークの一つですが、同時に、使いこなすには熟練を要する強力なツールでもあります。基本的な概念は比較的簡単に理解できますが、熟練したSpring開発者になるには、かなりの時間と労力が必要です。
この記事では、Spring、特にWebアプリケーションとSpring Bootにおけるよくある間違いをいくつか紹介します。Spring Bootのウェブサイトに記載されているように、Spring Bootは本番環境対応アプリケーションの構築方法について、かなり頑固な考え方を維持しています。この記事では、この考え方に倣い、標準的なSpring Boot Webアプリケーション開発にうまく統合できるいくつかのテクニックの概要を説明します。 Spring Boot にまだ馴染みがないけれど、下記のトピックを試してみたいという方のために、この記事の GitHub リポジトリも作成しました。読み進めている途中で不明な点があれば、コードをクローンしてローカルコンピュータで使用してみることをお勧めします。 1. よくある間違い1: 下層に重点を置きすぎる ソフトウェア開発において「自分の作品ではない」という症候群が蔓延しているため、私たちはこのよくある間違いに対処しています。症状としては、共通のコードを頻繁に書き換えてしまうことなどが挙げられ、多くの開発者がこれを経験しています。 特定のライブラリの内部構造と実装を理解することは、概ね有益かつ必要であり(そして素晴らしい学習プロセスとなり得ます)、常に同じ低レベルの実装の詳細を扱うことは、ソフトウェアエンジニアのキャリアにとって有害です。Springのような抽象フレームワークが存在するのには理由があります。それらは、反復的な手作業から解放し、ドメインオブジェクトやビジネスロジックといった高レベルの詳細に集中することを可能にするからです。 したがって、抽象化を積極的に活用しましょう。次に特定の問題に直面したときは、まずその問題を解決するライブラリがSpringに既に統合されているかどうかをざっと検索してみてください。そうすれば、適切な既製のソリューションが見つかるかもしれません。例えば、この記事の他の例でも使用する非常に便利なライブラリがProject Lombokです。Lombokは定型的なコードジェネレーターとして使用されるので、面倒な開発者でも簡単にライブラリに慣れることができます。例えば、Lombokを使った「標準Java Bean」はどのようなものか見てみましょう。
予想どおり、上記のコードは次のようにコンパイルされます。
ただし、IDE で Lombok を使用する予定の場合は、IntelliJ IDEA バージョンのプラグイン (こちらを参照) をインストールする必要がある可能性があることに注意してください。 2. よくある間違いその2:内部構造の「漏れ」 内部構造を公開することは決して良い考えではありません。サービス設計の柔軟性を損ない、不適切なコーディング慣行を助長するからです。内部構造の「漏洩」は、特定のAPIエンドポイントからデータベース構造にアクセス可能になることで顕在化します。例えば、次のPOJO("Plain Old Java Object")クラスは、データベース内のテーブルを表しています。
TopTalentEntity データにアクセスする必要があるエンドポイントがあるとします。TopTalentEntity インスタンスを返すのが魅力的かもしれませんが、より柔軟な解決策は、API エンドポイント上で TopTalentEntity データを表す新しいクラスを作成することです。
このようにすれば、データベースバックエンドの変更によってサービスレイヤーに追加の変更を加える必要がありません。TopTalentEntityに「password」フィールドを追加し、データベースから取得したユーザーパスワードのハッシュ値を保存することを検討してください。TopTalentDataのようなコネクタがない場合、サービスフロントエンドの変更を忘れると、不要な機密情報が誤って公開される可能性があります。 3. よくある間違いその3:関心の分離の欠如 プログラムの規模が大きくなるにつれて、コードの構成はますます重要な問題になります。皮肉なことに、優れたソフトウェアエンジニアリングの原則の多くは、規模が大きくなるにつれて崩れ始めます。特に、プログラムアーキテクチャの設計が十分に考慮されていない場合は顕著です。開発者が犯しがちなミスの一つは、コードに関する懸念事項を混同することです。そして、これは非常に簡単に起こり得ます。 通常、関心の分離を破るには、既存のクラスに新しい機能を「注ぎ込む」だけで済みます。これは短期的には良い解決策ですが(初心者にとっては入力が少なくて済むため)、テスト中、メンテナンス中、あるいはその間のどの段階でも、将来的には必ず問題が生じます。データベースからTopTalentDataを返す以下のコントローラーを考えてみましょう。
一見すると、このコードは問題ないように見えます。TopTalentEntityインスタンスから取得したTopTalentDataのリストを提供しているだけです。しかし、よく見ると、TopTalentControllerが実際に何らかの処理を行っていることがわかります。つまり、リクエストを特定のエンドポイントにマッピングし、データベースからデータを取得し、TopTalentRepositoryから受信したエンティティを別の形式に変換しているのです。より「クリーンな」解決策としては、これらの処理をそれぞれ独自のクラスに分離することが挙げられます。例えば、次のようになります。
この階層構造のもう一つの利点は、クラス名を調べることで機能がどこにあるのかを特定できることです。さらに、テスト中に必要に応じて、任意のクラスをモック実装に簡単に置き換えることができます。 4. よくある間違いその4: 例外処理の欠如または不適切な例外処理 一貫性というテーマはSpring(またはJava)に限ったものではありませんが、Springプロジェクトに取り組む際には依然として考慮すべき重要な側面です。コーディングスタイルについては議論の余地がありますが(多くの場合、チーム内または会社全体で合意形成が行われます)、共通の標準を持つことで、最終的には生産性が大幅に向上します。これは特に複数人で構成されたチームに当てはまります。一貫性があれば、手渡しによる引き継ぎに多大なリソースを費やしたり、異なる責任分担について長々と説明したりすることなく、円滑なコミュニケーションが可能になります。 様々な設定ファイル、サービス、コントローラーを含むSpringプロジェクトを考えてみましょう。命名において意味的な一貫性を保つことで、検索しやすい構造が生まれ、新しい開発者でも独自の方法でコードを管理できるようになります。例えば、設定クラスに.Configサフィックスを追加し、サービスレイヤーを.Serviceで、コントローラーを.Controllerで終わらせるといった具合です。 一貫性というトピックと密接に関連しますが、サーバーサイドのエラー処理は特に重視する価値があります。適切に記述されていないAPIの例外レスポンスに対処しなければならなかった経験があれば、その理由はおそらくお分かりでしょう。例外を適切に解析するのは骨の折れる作業であり、それらの例外の根本原因を特定するのはさらに骨の折れる作業だからです。 API開発者として理想的には、ユーザー向けのすべてのエンドポイントをカバーし、それらを共通のエラー形式に変換する必要があります。これは通常、次のようなトラブルシューティングを回避するのではなく、汎用的なエラーコードと説明を提供することを意味します。a)「500 内部サーバーエラー」メッセージを返す。b) 例外のスタックトレースをユーザーに直接返す。(実際には、これらの方法は絶対に避けるべきです。クライアントが問題に対処するのが困難になるだけでなく、内部情報が公開されてしまうからです。) たとえば、一般的なエラー応答の形式は次のようになります。
同様の問題は、多くの一般的なAPIで頻繁に発生しますが、簡単かつ体系的にログに記録できるため、結果は非常に良好であることが多いです。例外をこの形式に変換するには、メソッドに `@ExceptionHandler` アノテーションを付与します(アノテーションの例は第6章にあります)。 5. よくある間違いその5: 不適切なマルチスレッド処理 デスクトップアプリケーションであれWebアプリケーションであれ、Springであれそうであれ、マルチスレッドは解析が非常に難しいことで知られています。並列実行によって引き起こされる問題は恐ろしく、捉えどころがなく、デバッグが非常に困難になることも少なくありません。実際、問題の性質上、並列実行の問題に遭遇した途端、デバッガーを完全に放棄し、エラーの根本原因が見つかるまでコードを「手動で」検査しなければならない場合もあります。残念ながら、こうした問題には万能の解決策はありません。個々のケースごとに状況を評価した上で、最善と思われるアプローチで問題に対処してください。 もちろん、理想的にはマルチスレッドエラーを完全に回避したいはずです。繰り返しますが、万能の解決策はありませんが、マルチスレッドエラーのデバッグと防止に関する実用的な考慮事項がいくつかあります。 5.1. グローバル状態を避ける まず、「グローバル状態」の問題を念頭に置いてください。マルチスレッドアプリケーションを作成する場合は、グローバルに変更される可能性のあるものを注意深く監視し、可能であれば削除する必要があります。グローバル変数を変更可能なままにしておく必要がある場合は、同期を慎重に使用し、プログラムのパフォーマンスを追跡して、新たに発生する待機時間によってシステムパフォーマンスが低下しないようにする必要があります。 5.2. 変動を避ける これは関数型プログラミングから直接派生したもので、OOPにも適用され、クラスと状態の変更を避けるべきであると宣言しています。つまり、すべてのモデルクラスでsetterメソッドを廃止し、private finalフィールドを持つことを意味します。これらのフィールドの値は構築時にのみ変更されます。こうすることで、競合状態が発生せず、オブジェクトのプロパティにアクセスする際に常に正しい値が返されることが保証されます。 5.3. キーデータを記録する アプリケーションが例外に遭遇する可能性のある箇所を評価し、重要なデータはすべて事前にログに記録してください。エラーが発生した場合、どのリクエストが受信されたかに関する情報が得られ、アプリケーションが失敗した原因をより深く理解できるようになります。ただし、ログ記録によって追加のファイルI/Oが発生し、アプリケーションのパフォーマンスに重大な影響を与える可能性があることに改めて留意してください。そのため、ログ記録を過度に使用しないでください。 5.4. 既存の実装の再利用 独自のスレッドを作成する必要がある場合(例えば、異なるサービスに非同期リクエストを送信する場合など)、独自のソリューションを作成するのではなく、既存のセキュリティ実装を再利用してください。これは主に、ExecutorServicesとJava 8の簡潔な関数型[CompletableFutures](http://www.nurkiewicz.com/2013/05/java-8-definition-guide-to-.html)を使用してスレッドを作成することを意味します。Springでは、[DeferredResult](http://docs.spring.io/springing-framework/docs/4.0.x/springing-frame-reference/html/mvc.html # mvc.ann-async)クラスを通じて非同期リクエスト処理も可能です。 6. よくある間違いその6: アノテーションベースの検証を使用していない 以前のTopTalentサービスで、新しいTopTalentを追加するためのエンドポイントが必要だったとします。さらに、何らかの理由で、新しい名詞はすべて10文字にする必要があるとします。これを実現する一つの方法は、次のようになります。
しかし、上記のアプローチは(構造の不備を除けば)真に「クリーン」な解決策とは言えません。複数の型(つまり、TopTalentData が空であってはならない、TopTalentData.name が空であってはならない、TopTalentData.name が10文字以上でなければならない)の妥当性をチェックし、データが無効な場合は例外をスローしています。 Hibernate Validator (http://hibernate.org/validator/) をSpringに統合することで、データ検証をよりクリーンに実行できます。まずは、addTopTalentメソッドをリファクタリングして検証をサポートしましょう。
さらに、TopTalentData クラスで検証するプロパティを指定する必要があります。
これで、Spring はメソッドが呼び出される前にリクエストをインターセプトしてパラメータを検証するようになり、追加の手動テストが不要になります。 同じ機能を実現する別の方法は、独自のアノテーションを作成することです。通常、カスタムアノテーションはHibernateの組み込み制約セットを超える必要がある場合にのみ使用しますが(この例では `@Length` は存在しないものとします)、文字列の長さを検証するための2つの追加クラスを作成できます。1つは検証用、もう1つはプロパティへのアノテーション付与用です。
このような場合、関心の分離のベストプラクティスとして、プロパティがnullの場合(isValidメソッドでs == null)、プロパティを有効としてマークする必要があることに注意してください。プロパティにこれが追加の要件である場合は、@NotNullアノテーションを使用してください。
7. よくある間違い7: (依然として) XMLベースの構成を使用する 以前のバージョンの Spring では XML が必要でしたが、現在ではほとんどの構成は Java コードまたはアノテーションを介して実行できます。XML 構成は、単に追加の不要な定型コードです。 この記事(および付属のGitHubリポジトリ)では、Springの設定にアノテーションを使用しています。Springは、スキャン対象となる最上位パッケージディレクトリが`@SpringBootApplication`複合アノテーションで宣言されているため、どのBeanを結合するかを認識しています(以下を参照)。
複合アノテーション(詳細は[Springのドキュメント](http://docs.spring.io/autorepo/docs/springing-boot/current/reference/html/using-boot-using-springbootapplication-annot.html)を参照してください)は、SpringにBeanをスキャンするパッケージを指示するだけです。今回の場合、これはトップレベルパッケージ(co.kukurin)が取得に使用されることを意味します。
@Configuration アノテーションが付けられた追加のクラスがある場合は、Java ベースの構成もチェックされます。 8. よくある間違いその8:プロフィールを無視する サーバーサイド開発においてよくある課題は、異なる構成タイプ(通常は本番環境用と開発環境用)を区別することです。テスト環境からデプロイメント環境に切り替えるたびに、様々な構成項目を手動で変更する代わりに、プロファイルを使用する方が効率的です。 次のようなシナリオを考えてみましょう。ローカル開発ではインメモリデータベースを使用し、本番環境ではMySQLデータベースを使用しています。つまり、両方に異なるURLと(できれば)異なる認証情報を使用してアクセスする必要があるということです。では、この2つの異なる設定ファイルをどのように実現するかを見てみましょう。 8.1. APPLICATION.YAML ファイル
8.2. APPLICATION-DEV.YAML ファイル
コード変更中に本番環境のデータベースに誤って操作を実行してしまうのを避けたい場合は、デフォルトプロファイルを `dev` に設定することをお勧めします。その後、サーバー側で JVM に `-Dspring.profiles.active=prod` パラメータを指定して手動でプロファイルを上書きできます。あるいは、オペレーティングシステムの環境変数を希望のデフォルトプロファイルに設定することもできます。 9. よくある間違いその9: 依存性注入を受け入れられない Spring の依存性注入を適切に使用すると、必要な構成クラスをすべてスキャンしてすべてのオブジェクトを接続できるようになります。これは分離に非常に役立ち、クラス間の密結合を介してテストを行うよりもテストがはるかに簡単になります。
Spring に接続を処理させます。
Misko Hevery氏のGoogle Talkでは、依存性注入の「理由」について詳しく説明されているので、実際にどのように使用されるかを見てみましょう。「関心の分離(よくある間違い #3)」のセクションでは、サービスとコントローラクラスを作成しました。TopTalentServiceが正しく動作すると仮定してコントローラをテストするとします。これは、実際のサービス実装の代わりにモックオブジェクトを挿入する別の設定クラスを提供することで実現できます。
次に、SampleUnitTestConfig を構成クラスとして使用するように Spring に指示することで、モック オブジェクトを挿入できます。
その後、コンテキスト構成を使用して、Bean をユニット テストに挿入できます。 10. よくある間違いその10:テスト不足、または不適切なテスト ユニットテストの概念は古くから存在していますが、多くの開発者は(特に「必須」でない場合は)それを「忘れる」か、後から追加してしまう傾向があります。これは明らかに望ましくない状況です。なぜなら、テストはコードの正確性を検証するだけでなく、プログラムが様々なシナリオでどのように動作するかを示すドキュメントとしても機能するからです。 Webサービスのテストでは、「純粋な」単体テストのみを実行することは稀です。HTTP通信では通常、SpringのDispatcherServletを呼び出し、実際のHttpServletRequestを受信した際に何が起こるかを観察する必要があるためです(「統合」テストとして、検証やシリアル化の処理など)。MockMVCをベースに構築された、RESTサービスのテストを簡素化するJava DSLであるREST Assuredは、非常に洗練されたソリューションを提供することが実証されています。依存性注入を含む次のコードスニペットを検討してください。
SampleUnitTestConfig クラスは TopTalentService のモック実装を TopTalentController に接続し、他のすべてのクラスはアプリケーションクラスのパッケージのサブパッケージディレクトリをスキャンして標準構成を推測します。RestAssuredMockMvc は、軽量な環境をセットアップし、/toptal/get エンドポイントに GET リクエストを送信するために使用されます。 11. 春のマスターになろう Springは習得しやすい強力なフレームワークですが、完全に使いこなすにはある程度の投資と時間が必要です。長期的には、時間をかけてフレームワークに慣れることで生産性が確実に向上し、最終的にはよりクリーンなコードを書くことができ、より優れた開発者になれるでしょう。 |