Skip to content

データモデル設計 - 国際貨物輸送管理システム

概要

本ドキュメントは、国際貨物輸送管理システムの永続化層データモデルを定義する。 ドメインモデル分析で識別した 6 つの境界付けられたコンテキスト(Booking / Routing / Tracking / Handling / Billing / Shared Domain)に対応する 16 テーブルを設計する。 shipper(荷主)テーブルと、Spring Security 用の users / user_roles テーブルを含む。

設計方針

  • DB: PostgreSQL 16.x(本番)、H2(テスト)
  • ORM: MyBatis(XML マッパー)
  • マイグレーション: Flyway(V1__init.sql 形式)
  • ID 戦略: サロゲートキー(BIGSERIAL)+ 業務キー(VARCHAR)の併用
  • 命名規則: スネークケース(PostgreSQL 慣習)
  • 監査カラム: 全テーブルに created_at / updated_at を付与

概念データモデル

全コンテキストのエンティティとその主要リレーションシップを俯瞰する。

uml diagram


論理データモデル

Shared Domain

共有ドメインとして全コンテキストが参照する場所マスタ。UN/LOCODE(国連貿易港コード)を業務キーとする。

uml diagram


Booking Context

貨物の予約・旅程情報を管理する。cargo が集約ルートで、leg が旅程の各区間を表す。荷主情報は shipper テーブルに正規化し、FK 参照とする。

uml diagram


Routing Context

航海スケジュールと運送区間を管理する。voyage が集約ルートで、carrier_movement が個々の移動区間を表す。

uml diagram


Tracking Context

貨物追跡の状態・イベント・例外を管理する。tracking_activity が集約ルート。

uml diagram


Handling Context

荷役作業の実績と税関申告を管理する。handling_activity が集約ルート。

uml diagram


Billing Context

精算書・明細・支払記録を管理する。参考実装には存在しない新規コンテキスト。

uml diagram


Security Context

Spring Security の UserDetailsService が利用するユーザー認証・認可テーブル。

uml diagram


テーブル定義

location(場所マスタ)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
unlocode VARCHAR(5) UK, NOT NULL UN/LOCODE(業務キー。例: JPTYO
name VARCHAR(100) NOT NULL 場所名称(例: Tokyo
country_code VARCHAR(2) ISO 3166-1 alpha-2 国コード
time_zone VARCHAR(50) タイムゾーン(例: Asia/Tokyo
created_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード更新日時

shipper(荷主)

注記: 旧設計で cargo テーブルに存在した shipper_nameshipper_email カラムは本テーブルへの正規化に伴い削除した。

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
shipper_code VARCHAR(20) UK, NOT NULL 荷主コード(業務キー。SHP-XXXXXX 形式)
shipper_type VARCHAR(20) NOT NULL 荷主種別(INDIVIDUAL / CORPORATE
name VARCHAR(200) NOT NULL 荷主名称
email VARCHAR(200) NOT NULL メールアドレス
phone VARCHAR(50) 電話番号
contract_number VARCHAR(50) 契約番号(法人のみ。NULLable)
discount_rate NUMERIC(5,4) DEFAULT 0.0000 割引率(0.0000〜0.1500、最大 15%)
created_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード更新日時

DDL

CREATE TABLE shipper (
    id              BIGSERIAL PRIMARY KEY,
    shipper_code    VARCHAR(20)  NOT NULL UNIQUE,  -- SHP-XXXXXX 形式
    shipper_type    VARCHAR(20)  NOT NULL,          -- INDIVIDUAL / CORPORATE
    name            VARCHAR(200) NOT NULL,
    email           VARCHAR(200) NOT NULL,
    phone           VARCHAR(50),
    contract_number VARCHAR(50),                   -- 法人のみ(NULLable)
    discount_rate   NUMERIC(5,4) DEFAULT 0.0000,   -- 0.0000〜0.1500 (最大 15%)
    created_at      TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

cargo(貨物)

注記: shipper_nameshipper_email カラムは削除し、shipper_id(FK → shipper.id)による参照に変更した。

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
booking_id VARCHAR(20) UK, NOT NULL 予約 ID(業務キー)
shipper_id BIGINT FK → shipper.id, NOT NULL 荷主 ID
booking_status VARCHAR(30) NOT NULL 予約状態(BookingStatus 列挙値)
transport_status VARCHAR(30) NOT NULL 輸送状態(TransportStatus 列挙値)
routing_status VARCHAR(30) NOT NULL 経路決定状態(例: ROUTED, MISROUTED, NOT_ROUTED
cargo_type VARCHAR(20) NOT NULL, DEFAULT 'GENERAL' 貨物種別(GENERAL / PERISHABLE / HAZARDOUS / REFRIGERATED
weight_kg NUMERIC(10,3) NOT NULL 重量(kg)
declared_value NUMERIC(15,2) 申告価格(保険料算出用)
spec_origin_unlocode VARCHAR(5) FK → location.unlocode 出荷元(RouteSpecification)
spec_destination_unlocode VARCHAR(5) FK → location.unlocode 仕向地(RouteSpecification)
spec_arrival_deadline DATE 到着期限(RouteSpecification)
origin_unlocode VARCHAR(5) FK → location.unlocode 実際の出発地
booking_amount_value INTEGER NOT NULL 予約金額(最小通貨単位、例: 円)
booking_amount_currency VARCHAR(3) NOT NULL 通貨コード(ISO 4217、例: JPY
consignee_name VARCHAR(200) 荷受人名
consignee_email VARCHAR(200) 荷受人メールアドレス
tracking_number VARCHAR(20) 追跡番号(発行後に設定)
next_expected_location_unlocode VARCHAR(5) 次の予定場所(UN/LOCODE)
next_expected_handling_event_type VARCHAR(30) 次の予定荷役タイプ
next_expected_voyage_number VARCHAR(20) 次の予定航海番号
last_known_location_unlocode VARCHAR(5) 最終確認場所(UN/LOCODE)
current_voyage_number VARCHAR(20) 現在の航海番号
last_handling_event_type VARCHAR(30) 最後の荷役タイプ
last_handling_event_location VARCHAR(5) 最後の荷役場所(UN/LOCODE)
last_handling_event_voyage VARCHAR(20) 最後の荷役航海番号
created_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード更新日時

leg(輸送区間)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
cargo_id BIGINT FK → cargo.id, NOT NULL 親貨物 ID
voyage_number VARCHAR(20) FK → voyage.voyage_number, NOT NULL 航海番号
load_location_unlocode VARCHAR(5) FK → location.unlocode, NOT NULL 積込場所(UN/LOCODE)
unload_location_unlocode VARCHAR(5) FK → location.unlocode, NOT NULL 荷降場所(UN/LOCODE)
load_time TIMESTAMP 積込予定日時
unload_time TIMESTAMP 荷降予定日時
seq_number INTEGER NOT NULL 区間順序(1 始まり)
created_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード更新日時

voyage(航海)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
voyage_number VARCHAR(20) UK, NOT NULL 航海番号(業務キー)
created_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード更新日時

carrier_movement(運送区間)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
voyage_id BIGINT FK → voyage.id, NOT NULL 親航海 ID
departure_location_unlocode VARCHAR(5) FK → location.unlocode, NOT NULL 出発地(UN/LOCODE)
arrival_location_unlocode VARCHAR(5) FK → location.unlocode, NOT NULL 到着地(UN/LOCODE)
departure_date TIMESTAMP NOT NULL 出発日時
arrival_date TIMESTAMP NOT NULL 到着日時
seq_number INTEGER NOT NULL 区間順序(1 始まり)
created_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード更新日時

tracking_activity(追跡レコード)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
tracking_number VARCHAR(20) UK, NOT NULL 追跡番号(業務キー)
booking_id VARCHAR(20) NOT NULL 予約 ID(参照整合性は書き込み側で保証)
transport_status VARCHAR(30) NOT NULL 輸送状態(TransportStatus 列挙値)
created_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード更新日時

tracking_handling_event(追跡イベント)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
tracking_id BIGINT FK → tracking_activity.id, NOT NULL 親追跡レコード ID
event_type VARCHAR(30) NOT NULL 荷役タイプ(HandlingType 列挙値)
event_time TIMESTAMP NOT NULL イベント発生日時
location_unlocode VARCHAR(5) FK → location.unlocode イベント発生場所(UN/LOCODE)
voyage_number VARCHAR(20) 関連する航海番号
created_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード更新日時

tracking_exception_event(追跡例外イベント)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
tracking_id BIGINT FK → tracking_activity.id, NOT NULL 親追跡レコード ID
exception_type VARCHAR(50) NOT NULL 例外種別(例: CUSTOMS_HOLD, DAMAGE, DELAY
occurred_at TIMESTAMP WITH TIME ZONE NOT NULL 例外発生日時
escalation_flag BOOLEAN NOT NULL, DEFAULT FALSE エスカレーション判定フラグ(US15 紛失時)
description VARCHAR(500) 例外内容の詳細
resolved_at TIMESTAMP WITH TIME ZONE 解決日時(NULL = 未解決)
resolution_notes TEXT 対応内容メモ
created_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード更新日時

handling_activity(荷役作業記録)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
booking_id VARCHAR(20) NOT NULL 予約 ID(参照整合性は書き込み側で保証)
event_type VARCHAR(30) NOT NULL 荷役タイプ(RECEIVE / LOAD / UNLOAD / CUSTOMS / CLAIM)
event_completion_time TIMESTAMP NOT NULL 荷役完了日時
location_unlocode VARCHAR(5) FK → location.unlocode, NOT NULL 作業場所(UN/LOCODE)
voyage_number VARCHAR(20) 関連する航海番号(LOAD / UNLOAD 時に設定)
operator_name VARCHAR(200) 作業員名
created_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード更新日時

customs_declaration(税関申告)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
handling_activity_id BIGINT FK → handling_activity.id, NOT NULL 関連荷役作業 ID
declaration_number VARCHAR(50) UK, NOT NULL 申告番号(業務キー)
declared_at TIMESTAMP NOT NULL 申告日時
status VARCHAR(30) NOT NULL 申告状態(例: PENDING, CLEARED, HELD
cleared_at TIMESTAMP 通関完了日時(NULL = 未完了)
remarks VARCHAR(500) 備考・メモ
created_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード更新日時

invoice(精算書)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
invoice_number VARCHAR(30) UK, NOT NULL 精算書番号(業務キー)
booking_id VARCHAR(20) UK, NOT NULL 予約 ID(UNIQUE 制約で二重請求を防止)
total_amount_value INTEGER NOT NULL 合計金額(最小通貨単位)
total_amount_currency VARCHAR(3) NOT NULL 通貨コード(ISO 4217)
tax_rate NUMERIC(5,4) NOT NULL, DEFAULT 0.1000 消費税率(デフォルト 10%)
tax_amount NUMERIC(15,2) NOT NULL, DEFAULT 0 消費税額
payment_status VARCHAR(30) NOT NULL 支払状態(PENDING / CONFIRMED / OVERDUE / REFUNDED
issued_at TIMESTAMP WITH TIME ZONE 発行日時
due_date DATE 支払期日
discount_amount_value INTEGER 割引金額(最小通貨単位)
discount_amount_currency VARCHAR(3) 割引通貨コード
created_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード更新日時

invoice_line_item(精算明細)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
invoice_id BIGINT FK → invoice.id, NOT NULL 親精算書 ID
description VARCHAR(200) NOT NULL 明細項目説明
amount_value INTEGER NOT NULL 明細金額(最小通貨単位)
amount_currency VARCHAR(3) NOT NULL 通貨コード(ISO 4217)
seq_number INTEGER NOT NULL 明細順序(1 始まり)
created_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP NOT NULL, DEFAULT NOW() レコード更新日時

payment(支払記録)

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
invoice_id BIGINT FK → invoice.id, NOT NULL 親精算書 ID
paid_amount_value INTEGER NOT NULL 支払金額(最小通貨単位)
paid_amount_currency VARCHAR(3) NOT NULL 通貨コード(ISO 4217)
paid_at TIMESTAMP NOT NULL 支払日時
payment_method VARCHAR(30) NOT NULL 支払方法(例: BANK_TRANSFER, CREDIT_CARD
transaction_reference VARCHAR(100) 取引参照番号(外部決済システムの ID)
created_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード作成日時
updated_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード更新日時

users(ユーザー)

Spring Security の UserDetailsService が参照するユーザー認証テーブル。

カラム名 データ型 制約 説明
id BIGINT PK, NOT NULL サロゲートキー(BIGSERIAL)
username VARCHAR(50) UK, NOT NULL ログイン名
email VARCHAR(200) UK, NOT NULL メールアドレス
password VARCHAR(255) NOT NULL パスワード(BCrypt ハッシュ)
enabled BOOLEAN NOT NULL, DEFAULT TRUE アカウント有効フラグ
created_at TIMESTAMP WITH TIME ZONE NOT NULL, DEFAULT NOW() レコード作成日時

DDL

CREATE TABLE users (
    id           BIGSERIAL PRIMARY KEY,
    username     VARCHAR(50)  NOT NULL UNIQUE,
    email        VARCHAR(200) NOT NULL UNIQUE,
    password     VARCHAR(255) NOT NULL,  -- BCrypt ハッシュ
    enabled      BOOLEAN NOT NULL DEFAULT TRUE,
    created_at   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

user_roles(ユーザーロール)

カラム名 データ型 制約 説明
user_id BIGINT PK, FK → users.id, NOT NULL 親ユーザー ID
role VARCHAR(50) PK, NOT NULL ロール名(ROLE_ADMIN / ROLE_OPERATOR / ROLE_SHIPPER 等)

DDL

CREATE TABLE user_roles (
    user_id  BIGINT      NOT NULL REFERENCES users(id),
    role     VARCHAR(50) NOT NULL,  -- ROLE_ADMIN / ROLE_OPERATOR / ROLE_SHIPPER 等
    PRIMARY KEY (user_id, role)
);

設計上の判断

1. サロゲートキーと業務キーの併用

判断: 全テーブルに BIGSERIAL のサロゲートキー(id)を設け、業務上の識別子(booking_idvoyage_numberunlocode 等)には UNIQUE 制約を付与する。

根拠: 外部キー参照を BIGINT に統一することでインデックス効率が向上する。業務キーはドメインモデルの一部であり、別途管理することで業務ルールの変更に対応しやすい。


2. location テーブルへの参照方式

判断: 参考実装では VARCHAR で場所 ID を文字列管理していたが、本設計では location.unlocode を外部キーとして参照する。

根拠: UN/LOCODE は国際標準の 5 文字コードであり、それ自体が意味を持つ自然キーである。文字列参照でも JOIN 効率は許容範囲内であり、可読性が高まる。


3. 金額の表現(INTEGER + VARCHAR(3)

判断: 金額を INTEGER(最小通貨単位)と VARCHAR(3)(ISO 4217 通貨コード)の 2 カラムで表現する。NUMERIC / DECIMAL は使用しない。

根拠: 浮動小数点演算による精度誤差を排除するため、円・セントなど最小通貨単位で整数管理する。複数通貨対応のため通貨コードを常に付随させる。これはドメインモデルの MoneyAmount 値オブジェクトに対応する。


4. 列挙値のカラム型(VARCHAR(30)

判断: BookingStatusTransportStatusHandlingType 等の列挙型カラムは VARCHAR(30) で表現し、PostgreSQL の ENUM 型は使用しない。

根拠: PostgreSQL ENUM 型は値の追加・変更にスキーマ ALTER が必要でマイグレーション時のリスクが高い。VARCHAR ならば Flyway マイグレーションで CHECK 制約を追加・変更するだけで済み、テスト(H2)との互換性も維持できる。


5. コンテキスト間の参照整合性

判断: 異なるコンテキスト間(例: handling_activity.booking_idcargo.booking_id)には DB 外部キー制約を設けない。コンテキスト内の参照(例: leg.cargo_idcargo.id)には外部キー制約を設ける。

根拠: DDD の境界付けられたコンテキスト間はイベント連携を前提とする疎結合設計であり、DB 外部キーによる強結合は将来のサービス分割を妨げる。整合性はアプリケーション層で保証する。


6. Billing Context の新規設計

判断: 参考実装(Jakarta EE)には Billing Context が存在しなかったが、本設計では invoiceinvoice_line_itempayment の 3 テーブルを新規追加する。

根拠: ドメインモデル分析で識別した SETTLED(BookingStatus)と Invoice エンティティを実現するために必要。経理担当者のユースケース(精算書生成・支払確認)を支える永続化構造として設計した。


7. 監査カラムの全テーブル付与

判断: created_atupdated_at を全テーブルに NOT NULL DEFAULT NOW() で付与する。updated_at の更新は MyBatis マッパー側で CURRENT_TIMESTAMP をセットする。

根拠: 国際貨物輸送は規制上の監査要件が高く、全レコードの作成・更新タイムスタンプが必要。PostgreSQL のトリガーで自動更新する方法もあるが、H2 との互換性を優先してアプリケーション側で制御する。


Flyway マイグレーション方針

ファイル命名規則

src/main/resources/db/migration/
  V1__init.sql           # 初期スキーマ全テーブル作成
  V2__seed_locations.sql # 初期 UN/LOCODE マスタデータ
  V3__add_xxx.sql        # 機能追加に伴うスキーマ変更

マイグレーションルール

  • バージョン番号は連番とし、番号の欠番を作らない
  • 既存マイグレーションファイルの編集は禁止(Flyway チェックサム検証)
  • ロールバックは U プレフィックスのファイル(Undo マイグレーション)で対応する
  • 本番とテスト(H2)で同一マイグレーションスクリプトを使用するため、PostgreSQL 固有の構文(BIGSERIAL など)は H2 互換形式で記述する

V1__init.sql の構成イメージ

-- Shared Domain
CREATE TABLE location ( ... );

-- Security Context
CREATE TABLE users ( ... );
CREATE TABLE user_roles ( ... );

-- Booking Context
CREATE TABLE shipper ( ... );
CREATE TABLE cargo ( ... );   -- shipper_id FK あり
CREATE TABLE leg ( ... );

-- Routing Context
CREATE TABLE voyage ( ... );
CREATE TABLE carrier_movement ( ... );

-- Tracking Context
CREATE TABLE tracking_activity ( ... );
CREATE TABLE tracking_handling_event ( ... );
CREATE TABLE tracking_exception_event ( ... );  -- escalation_flag / resolution_notes あり

-- Handling Context
CREATE TABLE handling_activity ( ... );
CREATE TABLE customs_declaration ( ... );

-- Billing Context
CREATE TABLE invoice ( ... );  -- tax_rate / tax_amount / booking_id UNIQUE あり
CREATE TABLE invoice_line_item ( ... );
CREATE TABLE payment ( ... );