【PL/SQL】パッケージ仕様と本体の分離設計|依存関係・再コンパイル・デプロイ順序まで

【PL/SQL】パッケージ仕様と本体の分離設計による保守性向上 PL/SQL

PL/SQLパッケージは、公開インターフェースを定義する仕様(Specification)と、実装を書く本体(Body)に分かれます。この分離をうまく使うと、呼び出し側に見せるAPIを安定させたまま、内部実装だけを安全に変更できます。

この記事では、単に「SpecとBodyがある」という説明ではなく、実務で問題になりやすい依存関係、再コンパイル、デプロイ順序、権限、private実装、パッケージ状態まで含めて整理します。より広いパッケージ設計は パッケージ設計でコード管理と再利用性を極める、プロシージャ・ファンクション単体の設計は プロシージャ・ファンクション完全ガイド も参考になります。

この記事で扱うこと

  • パッケージ仕様と本体の役割
  • 公開APIとprivate実装の分け方
  • 仕様変更と本体変更の依存関係・INVALID影響
  • ACCESSIBLE BYAUTHIDGRANT EXECUTE の設計
  • package state、再コンパイル、ORA-04068 の注意点
  • デプロイ順序、レビュー観点、確認SQL
スポンサーリンク

パッケージ仕様と本体の役割

パッケージ仕様は、外部から見える公開APIです。呼び出し側が知るべきプロシージャ、ファンクション、型、定数、例外を宣言します。一方、パッケージ本体は、仕様で宣言したサブプログラムの実装と、外部へ公開しないprivateな処理を持ちます。

Specification公開契約。呼び出し側が依存するシグネチャや型を定義します。
Body実装詳細。private関数、内部定数、初期化処理などを置きます。
依存関係呼び出し側は基本的に仕様へ依存し、本体の実装詳細へ依存しません。
保守性仕様を安定させるほど、本体の改善を安全に進めやすくなります。

最小構成の例

まずは、公開する関数を仕様に置き、実装を本体に置く基本形です。コードブロックは仕様と本体で分けておくと、デプロイやレビューでも見やすくなります。

math-util-spec.sql
CREATE OR REPLACE PACKAGE math_util AS
  FUNCTION add(
    p_a IN NUMBER,
    p_b IN NUMBER
  ) RETURN NUMBER;

  FUNCTION subtract(
    p_a IN NUMBER,
    p_b IN NUMBER
  ) RETURN NUMBER;
END math_util;
/
math-util-body.sql
CREATE OR REPLACE PACKAGE BODY math_util AS
  FUNCTION add(
    p_a IN NUMBER,
    p_b IN NUMBER
  ) RETURN NUMBER
  IS
  BEGIN
    RETURN p_a + p_b;
  END add;

  FUNCTION subtract(
    p_a IN NUMBER,
    p_b IN NUMBER
  ) RETURN NUMBER
  IS
  BEGIN
    RETURN p_a - p_b;
  END subtract;
END math_util;
/

仕様を見れば、外部利用者は関数名、引数、戻り値を理解できます。本体側の実装が単純加算なのか、ログを取るのか、別テーブルを参照するのかは、呼び出し側から隠せます。

公開APIとprivate実装を分ける

仕様には、外部から呼ばせたいものだけを置きます。内部検証、変換、共通チェック、SQL組み立てのような補助処理は本体のprivate関数に閉じ込めます。これにより、内部処理の名前や引数を自由に変えられます。

loader-api-spec.sql
CREATE OR REPLACE PACKAGE customer_loader AS
  PROCEDURE load_customers(
    p_batch_id IN NUMBER
  );
END customer_loader;
/
loader-api-body.sql
CREATE OR REPLACE PACKAGE BODY customer_loader AS
  FUNCTION is_valid_email(
    p_email IN VARCHAR2
  ) RETURN BOOLEAN
  IS
  BEGIN
    RETURN p_email IS NOT NULL
       AND REGEXP_LIKE(p_email, '^[^@]+@[^@]+\.[^@]+$');
  END is_valid_email;

  PROCEDURE load_customers(
    p_batch_id IN NUMBER
  ) IS
  BEGIN
    FOR r IN (
      SELECT customer_id, customer_name, email
      FROM customer_stg
      WHERE batch_id = p_batch_id
    ) LOOP
      IF is_valid_email(r.email) THEN
        INSERT INTO customers(customer_id, customer_name, email)
        VALUES (r.customer_id, r.customer_name, r.email);
      ELSE
        INSERT INTO customer_load_error(batch_id, customer_id, message)
        VALUES (p_batch_id, r.customer_id, 'invalid email');
      END IF;
    END LOOP;
  END load_customers;
END customer_loader;
/

この例では、外部に公開するのは load_customers だけです。is_valid_email は本体内のprivate関数なので、あとから正規表現を変えたり別名にしたりしても、呼び出し側へ影響しません。ロギング設計は ロギングとトレーサビリティを両立する監査設計 と組み合わせると運用しやすくなります。

安全な変更と破壊的変更

パッケージ分離の価値は、変更影響を分類できることです。本体だけの変更は比較的安全ですが、仕様変更は呼び出し側のコンパイルや実行に影響します。

安全寄り本体内のSQL改善、private関数の修正、ログ追加、内部アルゴリズム変更。
注意例外の投げ方、トランザクション制御、戻り値の意味変更。
破壊的公開プロシージャの削除、引数名・型・順序変更、公開型の変更。
互換追加デフォルト付き引数や新しい公開サブプログラム追加。ただし呼び出し側の名前指定に注意。

仕様は「契約」なので、変更するときはバージョニングや移行期間を考えます。本体変更で済むなら、仕様を変えずに済ませる設計を優先します。

依存関係を確認する

仕様に依存しているオブジェクトは USER_DEPENDENCIES で確認できます。リリース前に、どのビュー、パッケージ、プロシージャが対象パッケージを参照しているか把握しておきます。

check-dependencies.sql
SELECT name,
       type,
       referenced_owner,
       referenced_name,
       referenced_type
FROM user_dependencies
WHERE referenced_name = 'CUSTOMER_LOADER'
ORDER BY type, name;

SELECT owner,
       name,
       type,
       referenced_owner,
       referenced_name,
       referenced_type
FROM all_dependencies
WHERE referenced_name = 'CUSTOMER_LOADER'
ORDER BY owner, type, name;

仕様変更が入る場合は、依存先の再コンパイルやテスト範囲が広がります。本体変更だけなら呼び出し側の依存影響は小さくなりますが、実行時の動作変更はもちろんテスト対象です。

INVALIDとコンパイルエラーを確認する

デプロイ後は、パッケージ本体と依存オブジェクトがVALIDか確認します。コンパイルエラーは USER_ERRORS、オブジェクト状態は USER_OBJECTS で見ます。

check-invalid-errors.sql
SELECT object_name, object_type, status
FROM user_objects
WHERE object_name IN ('CUSTOMER_LOADER')
   OR status = 'INVALID'
ORDER BY object_type, object_name;

SELECT name, type, line, position, text
FROM user_errors
WHERE name = 'CUSTOMER_LOADER'
ORDER BY sequence;

コンパイルエラーの読み方や SHOW ERRORSUSER_ERRORS の使い方は PL/SQLコンパイル時エラーと警告の対処 でも詳しく扱っています。

デプロイ順序

パッケージのリリースでは、仕様、本体、依存オブジェクトの順序を意識します。仕様が変わる場合と本体だけ変わる場合で、必要な作業は変わります。

deploy-order.sql
-- 仕様変更あり
@customer_loader.pks
@customer_loader.pkb
@dependent_package.pkb

-- 本体だけ変更
@customer_loader.pkb

-- 最後に状態確認
SELECT object_name, object_type, status
FROM user_objects
WHERE status = 'INVALID';

仕様変更ありのリリースは、依存オブジェクトの再コンパイルとテストをセットで考えます。本体だけのリリースは軽くできますが、package stateを持つ場合は実行中セッションへの影響に注意します。

package stateとORA-04068

パッケージ本体にグローバル変数や初期化処理があると、セッションごとにpackage stateを持つことがあります。実行中セッションがある状態で本体を再コンパイルすると、次回参照時に既存状態が破棄され、ORA-04068 が発生することがあります。

stateful-package.sql
CREATE OR REPLACE PACKAGE session_cache AS
  PROCEDURE set_value(p_value IN VARCHAR2);
  FUNCTION get_value RETURN VARCHAR2;
END session_cache;
/

CREATE OR REPLACE PACKAGE BODY session_cache AS
  g_value VARCHAR2(100);

  PROCEDURE set_value(p_value IN VARCHAR2) IS
  BEGIN
    g_value := p_value;
  END;

  FUNCTION get_value RETURN VARCHAR2 IS
  BEGIN
    RETURN g_value;
  END;
END session_cache;
/

状態を持つパッケージは、再デプロイ時のセッション影響を考慮します。状態を持たせない設計にする、接続プールを切り替える、メンテナンス時間にリリースするなどの対策が必要です。ORA-04068 の詳細は ORA-04068完全ガイド を確認してください。

AUTHIDと権限設計

パッケージ仕様には AUTHID を指定できます。デフォルトはDefiner Rightsで、所有者権限で実行されます。呼び出し者権限で動かしたい共通部品では AUTHID CURRENT_USER を検討します。

authid-package.sql
CREATE OR REPLACE PACKAGE order_api
AUTHID DEFINER
AS
  PROCEDURE create_order(
    p_customer_id IN NUMBER,
    p_amount      IN NUMBER
  );
END order_api;
/

GRANT EXECUTE ON order_api TO app_user;

GRANT EXECUTE は「仕様レベル」ではなく、パッケージオブジェクトに対して付与します。呼び出し側は仕様で公開されたサブプログラムだけを呼び出せます。AUTHIDと権限管理は AUTHIDと権限管理の設計、権限分離の実装は 権限分離とセキュリティパッケージ が参考になります。

ACCESSIBLE BYで内部パッケージを絞る

内部用パッケージを外部から呼ばせたくない場合は、利用可能なPL/SQLユニットを制限する ACCESSIBLE BY を検討できます。APIパッケージは公開し、helperパッケージはAPIからだけ呼ばせる構成にできます。

accessible-by.sql
CREATE OR REPLACE PACKAGE order_internal
AUTHID DEFINER
ACCESSIBLE BY (order_api)
AS
  PROCEDURE validate_order(
    p_customer_id IN NUMBER,
    p_amount      IN NUMBER
  );
END order_internal;
/

CREATE OR REPLACE PACKAGE order_api
AUTHID DEFINER
AS
  PROCEDURE create_order(
    p_customer_id IN NUMBER,
    p_amount      IN NUMBER
  );
END order_api;
/

ACCESSIBLE BY は環境バージョンや利用方針を確認して使います。使えない環境でも、公開APIパッケージと内部helperパッケージを分けるだけで、レビューしやすい構成になります。

ファイル分割と命名規則

Git管理やCI/CDでは、仕様と本体を別ファイルに分けると差分が読みやすくなります。一般的には .pks を仕様、.pkb を本体として扱います。

file-layout.txt
database/
  packages/
    customer_loader.pks
    customer_loader.pkb
    order_api.pks
    order_api.pkb
  deploy/
    001_create_specs.sql
    002_create_bodies.sql
    003_grants.sql
    004_check_invalid_objects.sql

レビューでは、仕様ファイルの差分を特に重く見ます。仕様に変更がある場合は、呼び出し側への影響、互換性、移行期間、依存オブジェクトの再コンパイルを確認します。

運用観測性を入れる

大きなパッケージでは、どの処理が動いているかを運用から追えるようにします。DBMS_APPLICATION_INFO でモジュール名やアクション名を設定すると、V$SESSION やAWRで処理状況を追いやすくなります。

application-info.sql
CREATE OR REPLACE PACKAGE BODY customer_loader AS
  PROCEDURE load_customers(p_batch_id IN NUMBER) IS
  BEGIN
    DBMS_APPLICATION_INFO.SET_MODULE(
      module_name => 'CUSTOMER_LOADER',
      action_name => 'LOAD_CUSTOMERS'
    );

    -- load logic
    NULL;

    DBMS_APPLICATION_INFO.SET_MODULE(NULL, NULL);
  EXCEPTION
    WHEN OTHERS THEN
      DBMS_APPLICATION_INFO.SET_MODULE(NULL, NULL);
      RAISE;
  END load_customers;
END customer_loader;
/

運用観測性は DBMS_APPLICATION_INFO完全ガイドDBMS_APPLICATION_INFOで運用観測性を底上げ と組み合わせると実務的です。

レビュー観点

仕様の安定性公開APIの名前、引数、戻り値、型は長期運用に耐えるか。
private化内部処理を仕様に出しすぎていないか。
互換性既存呼び出しを壊す変更がないか。
権限AUTHIDGRANT EXECUTE の方針が明確か。
状態package stateを持つ必要があるか。再コンパイル時の影響は許容できるか。
依存USER_DEPENDENCIES で影響範囲を確認したか。
デプロイ仕様、本体、GRANT、INVALID確認の順序が決まっているか。
ログ運用で処理名・実行ID・失敗原因を追えるか。

まとめ

PL/SQLパッケージの仕様と本体を分離する目的は、公開APIを安定させ、実装を安全に変更できるようにすることです。仕様には外部へ見せる契約だけを置き、本体にはprivate実装、内部SQL、ログ、補助関数を閉じ込めます。

実務では、仕様変更と本体変更の影響差、依存関係、INVALID確認、デプロイ順序、package state、ORA-04068AUTHIDACCESSIBLE BY まで含めて設計します。Specを小さく安定させ、Bodyを改善し続けられる構成にすることが、長期運用で効いてきます。