Spring Boot + Database RiderでDB権限問題の解決方法
こんにちは。Nstockエンジニアのryan5500です。
今回は、DBユーザーの権限を絞っているときのDatabase Riderの使い方について書きます。
背景
現在、私達はSpring Bootを利用してWebサービスを開発しています。このサービスの統合テストのためにテストごとにDBに任意のテストデータが入った状態にしたいと考え、Database Riderというライブラリを利用しています。
Database Riderとは
Database Riderは、YAMLやCSV形式など、様々な種類のデータ形式でテストデータを読み込み、テスト実行前にDBに流し込んでくれるライブラリです。テストメソッドに@DataSet
アノテーションを設定する形で利用します。
CSV形式のテストデータの場合
私達は、CSV形式のテストデータで利用しています。CSV形式で利用する場合は、2種類のファイルが必要です。
- テーブル名と対応するファイル名の、レコード内容を示したCSVファイル群
- e.g.
user.csv
,product.csv
など
- e.g.
- テストデータのテーブルへの追加順を決める
table-ordering.txt
- こちらは必須ではありません
table-ordering.txt
は、以下のようにテーブルの名前だけが順に書かれており、この順にレコード追加が実施されます。テーブルに外部キー制約を設定していて、特定の順序でデータを流し込みたい際などに便利です。
user
product
product_metadata
これらを一つのフォルダにまとめて用意する形になります。フォルダ構成としては以下のようになります。
└── testdata-1
├── user.csv
├── product.csv
├── product_metadata.csv
└── table-ordering.txt
これらのファイルを1フォルダにまとめ、以下のようにテストコードで@DataSet
アノテーションを用いて指定することで、テスト実行前にDBにテストデータをセットしてくれます。
// 以下はpresentation層を統合テストする場合のイメージです。
@SpringBootTest
@DBRider // Database Riderを使うためのアノテーション
public DummyApiTest {
...
@Test
@DataSet(value = "testdata-1/user.csv") // src/test/resources/datasets以下のパスを指定
// まるでuserのデータしか指定していないようだが、
// 末尾の拡張子を見てcsvファイル形式でテストデータを流し込みたいという意図を理解し、
// 同じ階層のフォルダ内にあるtable-ordering.txtを見て、
// テーブル名に対応したcsvファイル群の内容を流し込んでくれる。
void test_ユーザー一覧が表示できること() {
}
...
}
課題:Spring Bootアプリケーションで利用するDBユーザーの権限を制限していると、Database Riderのテストデータ流し込みに失敗する
Database Riderはデフォルトの挙動ではSpring Bootアプリケーションが利用するdataSource
を利用してテストデータを流し込みます。その際に既存のデータを削除するためにDELETE文, その後テストデータを流し込むためにINSERT文のクエリを発行します。
原因:テストデータ流し込み前の、データ削除時に権限エラーとなる
私達は、DBユーザーの権限ポリシーとして必要最低限の権限を持つようにしています。そのポリシーに従って、Spring Bootアプリケーションが利用するDBユーザーの権限を、一部テーブルに対してDELETE文を発行できないように絞っていました。
そのため、Database Riderのテストデータ流し込み時に例外が出て、テストが正しく動かない状態となりました。
解決策:専用のDatabase Rider dataSourceの設定と利用
この対策として、Database Riderでテストデータをテスト用のDBに流し込む際には、より権限の強いDBユーザーをDatabase Riderが利用できる必要がありました。
そのため、Database Riderのみが利用する、より強い権限を持つDBユーザーを利用してDBアクセスを行う、dataSource
を用意しました。
dataSourceを用意するコード例
具体的には、以下のようにして、Database Rider用のdataSource
を設定しました。以後、サンプルコードのパッケージ名はcom.example とします。
// Database Rider用のDataSourceのBean定義
@Component
@TestConfiguration // テスト時にのみ読み込まれるコンポーネントであることを指定
public class DBRiderDataSourceConfig {
@Bean(name = "dbRiderDataSource")
@ConfigurationProperties("spring.db-rider-datasource")
// application.propertiesにある特定のnamespaceの値から設定値を取得する。
//
// このdataSourceはテスト時にしか初期化されないため、
// 利用するDB Roleのクレデンシャルは、
// テスト時にのみ利用される設定ファイルに設定する。
public DataSource dataSource() {
return DataSourceBuilder
.create()
.type(TenantAwareHikariDataSource.class)
.build();
}
}
Database Rider用のdataSourceが利用するDB Roleのクレデンシャルは、テスト時のみに利用される設定ファイルである、test/resources/application.properties
に設定しました。
// 通常のdataSourceだとspring.datasourceだが、違うものだとわかるよう、
// spring.db-rider-datasourceネームスペースに定義
spring.db-rider-datasource.driver-class-name=org.postgresql.Driver
spring.db-rider-datasource.url=jdbc:postgresql://localhost:5432/app
spring.db-rider-datasource.username=postgres # テーブルの内容を削除する強い権限を持つDB Roleを指定する。
spring.db-rider-datasource.password=complicated_password_replace_me
// 統合テストコードの中で、上記のdataSourceを指定する様子
@SpringBootTest
@DBRider(name = "dbRiderDataSource")
// DBRiderアノテーションのnameで、bean名を指定することで、
// Database Riderが利用するdataSourceを指定できる
public DummyApiTest {
...
}
実装詳細: @Primaryアノテーションを用いたdataSourceの優先設定
また、Database Rider用のdataSource
を追加した際に、統合テスト実行時に、Spring Bootアプリケーションが参照するdataSource
が見つからない、というエラーが出ました。
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name '(クラス名)' defined in file [/workspace/target/classes/com/example/usecase/(クラス名).class]: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'com.example.domain.SomeRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
これは、DI時に、同じ型のデータが存在するので、どちらをDIすべきかわからないために起こっていました。
そこで、通常のdataSource
設定の側に、@Primary
というアノテーションを追加し、DI時の優先度を設定する形にしました。
@Component
@Configuration
public class DataSourceAdminConfig {
@Bean
@Primary // これを追加
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder
.create()
.type(TenantAwareHikariDataSource.class)
.build();
}
}
注意点: 強い権限のDBユーザーについてはテスト時にのみ利用する等管理が必要
今回は、Spring Bootアプリケーションで利用するDBユーザーよりも権限の強いDBユーザーを用意しました。
このDBユーザーは、権限が強いので、テスト時にのみ利用するように管理する必要があります。
まとめ
今回は、DBユーザーの権限を絞っているときのDatabase Riderの使い方について書きました。
Nstockではエンジニアを募集しています!
👨👩👧👦カジュアル面談から気軽にお話しましょう🤞
Discussion