バッチロケールエラーの改善事例

バッチロケールエラーの改善事例

1. はじめに

プロジェクトを進めていると、要件に対応する中で別の問題を発見することがよくあります。今回は、マイページに部署ごとの役割情報を追加する顧客要件に対応する過程で発見した、部署名の多言語データの保存エラーと、その改善経験についてまとめます。

2. 問題を発見した背景

この問題は、マイページに部署ごとの役割情報を表示する機能を追加している途中で発見しました。顧客要件は、ユーザーが所属する部署と、その部署で持っている役割をマイページで確認できるようにすることでした。

フロントエンドでは、バックエンドから返される部署名と役割情報を画面に表示すればよい作業でした。しかし開発中、一部の部署名が空の値として渡される現象を確認しました。最初は、フロントエンドのマッピングの問題やレスポンスデータの処理に問題があるのではないかと疑いました。ところがAPIレスポンスを確認すると、フロントエンドで値が欠落したのではなく、バックエンドから返される部署名自体が空になっていました。

その後、バックエンドの参照ロジックを確認しました。参照ロジックは、現在の画面言語に合わせて、韓国語であるkoを基準に部署名を取得していました。この動作自体は正常でした。問題はDBに保存された多言語データにありました。DBを直接確認したところ、韓国語の部署名がkoではなく、enの言語コードで保存されていました。

つまり、画面は韓国語の部署名をkoから探していましたが、実際の韓国語の値はenに入っていたため、空の値として表示されていたのです。

3. 原因分析

問題を確認する際、まずバックエンドがどの言語コードを基準に部署名を検索しているのかを確認しました。検索側は韓国語であるkoを基準に値を取得していたため、APIレスポンスが空の値として返される原因は、検索条件よりも保存されているデータ側にあると判断しました。

その後、部署の作成経路をたどって確認したところ、病院連携バッチで新規部署をStageとして登録する際に、共通登録メソッドを使用していました。このメソッドは基本的に、SpringのLocaleContextHolderから現在のロケールを取得し、言語コードとして使用します。

public Tenant registerTenant(TenantCdo tenantCdo) { 
    return registerTenant( 
        tenantCdo, 
        LocaleContextHolder.getLocale().getLanguage() 
   ); 
} 

Webリクエストでは、リクエストコンテキストを通じてロケールを決定できます。しかし、バッチはユーザーがブラウザから直接呼び出すフローではありません。そのため、バッチ実行時点では明確なユーザーリクエストのロケールが存在せず、LocaleContextHolderはJVMのデフォルトロケールを使用することになりました。その環境ではデフォルトロケールがenに設定されており、その結果、病院アダプターから返された韓国語の部署名がenキーに保存されました。

4. 新規部署登録経路の改善

コードの修正は、病院連携バッチで新規部署をStageとして登録する経路に重点を置きました。問題となったデータは、バッチが新しい部署を作成する際に生成された翻訳データだったため、部署全体の同期ロジックを大きく変更するのではなく、新規Stage登録時に言語コードを明示的に渡す方向で整理しました。

既存のregisterTenant(TenantCdo)メソッドは、Webリクエストで引き続き使用できるように維持しました。その代わり、リクエストロケールが存在しない、または信頼しにくいバッチや同期の経路では、言語コードを直接受け取れるようにオーバーロードを追加しました。

public Tenant registerTenant(TenantCdo tenantCdo) { 
    return registerTenant( 
        tenantCdo, 
        LocaleContextHolder.getLocale().getLanguage() 
   ); 
} 
 
public Tenant registerTenant( 
    TenantCdo tenantCdo, 
    String languageCode 
) { 
    Tenant tenant = Tenant.fromCdo( 
        tenantCdo, 
        languageCode 
   ); 
 
    return tenant; 
} 

実際の新規Stage生成経路では、病院アダプターから返される部署名が韓国語であることを基準に、koを直接渡しました。

return (Stage) tenantLogic.registerTenant(stageCdo, "ko"); 

これにより、既存のWebリクエスト経路には影響を与えず、バッチで新たに生成される部署データを意図した言語コードで保存できるようになります。既存のStage名の変更やその他の修正フローまで全て変更したのではなく、問題が発生した新規登録箇所に必要な処理を追加したのです。

5. データ補正SQL

コード修正はその後新たに生成される部署データへの対応であるため、すでに誤って保存されている既存データは別途補正する必要がありました。

DBを確認した結果、この問題は開発環境だけに限定されたものではありませんでした。開発、ステージング、運用の全環境で同様に確認され、元のテーブルだけでなく、参照性能のために別途構成された参照用データにも同じ値が反映されていました。

データ補正SQLは、運用データへの影響を最小限に抑える方針で作成しました。単純にlanguage_code = 'en'のデータを全てkoに変更する方法は危険だと判断し、次の条件も併せて適用しました。

  • 部署データに該当する項目のみを修正します。
  • 有効なデータのみを修正します。
  • 言語コードがenとして保存されているデータのみを修正します。
  • 登録者がシステムアカウントであるデータのみを修正します。
  • すでに同じ部署にkoの翻訳が存在する場合は除外します。

実際の補正クエリは、次のような形式で作成しました。

update cm_tenant_translation tt 
set language_code = 'ko', 
    modified_by = 'system-fix', 
    modified_on = now() 
from cm_tenant t 
where t.id = tt.tenant_id 
  and t.tenant_type = 'STAGE' 
  and tt.valid_yn = true 
  and tt.language_code = 'en' 
  and tt.registered_by = 'system' 
  and not exists ( 
    select 1 
    from cm_tenant_translation ko 
    where ko.tenant_id = tt.tenant_id 
      and ko.language_code = 'ko' 
      and ko.valid_yn = true 
  ); 

この条件により、実際の英語データやユーザーが直接登録したデータが誤って変更されるリスクを減らしました。実際のFlywayスクリプトでは、元のテーブルであるcm_tenant_translationだけでなく、参照用に使用されるqm_tenant_viewのnamei18n、qm_membership_viewのtenant_namei18nも併せて補正しました。

update qm_tenant_view v 
set namei18n = (v.namei18n - 'en') 
    || jsonb_build_object('ko', v.namei18n ->> 'en'), 
    modified_on = now() 
where v.tenant_type = 'STAGE' 
  and v.namei18n ? 'en' 
  and not (v.namei18n ? 'ko'); 
 
update qm_membership_view m 
set tenant_namei18n = (m.tenant_namei18n - 'en') 
    || jsonb_build_object('ko', m.tenant_namei18n ->> 'en'), 
    modified_on = now() 
where m.tenant_type = 'STAGE' 
  and m.tenant_namei18n ? 'en' 
  and not (m.tenant_namei18n ? 'ko'); 

6. 適用と検証

開発環境では、まずクエリでデータを修正した後、マイページ画面で部署ごとの役割情報が正常に表示されるかを確認しました。このとき、DBの値だけを確認するのではなく、実際の画面で部署名が正しく表示されるかを確認しました。ユーザーが体験する問題は画面上で発生していたため、最終検証も画面を基準に行うのが適切だと判断しました。

ステージング環境と運用環境では、クエリを直接手動で実行するのではなく、Flywayマイグレーションで反映しました。運用データに影響を与える作業であるため、同じスクリプトをバージョン管理し、デプロイ履歴に残す方法のほうが安全だと考えました。

ステージング環境でまずFlywayの適用結果を確認した後、運用環境に反映しました。適用後は、クエリで対象データが正常にkoへ変更されたかを確認し、画面でも部署名が正常に表示されるかを確認しました。

7. 学んだこととまとめ

今回の作業を通じて、バッチ処理は一般的なユーザーリクエストとは異なる基準で設計する必要があることを改めて確認しました。Webリクエストでは自然に存在するロケール、ユーザー、ヘッダー情報が、バッチでは存在しなかったり、環境のデフォルト値に置き換えられたりする可能性があります。バッチで生成するデータの言語が明確であれば、リクエストコンテキストに依存するよりも、コードで明示するほうが安全です。

また、データ補正作業では、修正範囲を絞り込む条件が重要でした。registered_by、tenant_type、valid_yn、既存のkoデータの有無を併せて確認したことで、システムバッチが作成した対象データのみを補正できました。

今回の事例は、マイページ画面で一部の部署名が空の値として返されるという小さな現象から始まりました。しかし、画面、APIレスポンス、DBの保存構造、バッチの実行環境を併せて確認することで実際の原因を突き止め、新規データの生成経路と既存データの両方を整理することができました。

Lynn

Site footer