PlantUMLでER図を書くときのコツ

# 概要

PlantUMLでER図を書くことがあったのですが、
その時線がごちゃごちゃになって苦しんだのでTipsを残します。

- PlantUMLを使ったことがないけどER図を書く必要がある
- エンティティの箱がどんどん右側に増えていって困る
- リレーションの線の長さを変えたい

このような悩みを持つ初心者の方向けの内容です。

# 事前準備

## 今回使うテーブルについて

- 今回使うテーブルは下記のような感じです。  
(RailsアプリケーションのテーブルのER図を書いていたので、  
テーブルの命名などはActiveRecordのやり方に沿っています。)
- エンティティの中身については触れません。

```plantuml
@startuml tables
entity companies as "companies\n会社" {
  + id [PK]
  --
  name [会社名]
  created_at [作成日]
  updated_at [更新日]
}

entity positions as "positions\n役職マスタ" {
  + id [PK]
  --
  company_id
  name [役職名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_positions as "user_positions\nユーザー役職マスタ関連" {
  + id [PK]
  --
  user_id
  position_id
  created_at [作成日]
  updated_at [更新日]
}

entity users as "users\nユーザー" {
  + id [PK]
  --
  company_id
  login_id [ログインID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

entity user_profiles as "user_profiles\nユーザー詳細" {
  + id [PK]
  --
  user_id
  birthday[生年月日]
  name [ユーザー名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_subordinate_users as "user_subordinate_users\nユーザー部下ユーザー関連" {
  + id [PK]
  --
  user_id
  subordinate_user_id [部下ユーザーID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

@enduml
```


<details><summary>エンティティをまとめたPlantUML</summary><div>

```tables.pu
@startuml tables
entity companies as "companies\n会社" {
  + id [PK]
  --
  name [会社名]
  created_at [作成日]
  updated_at [更新日]
}

entity positions as "positions\n役職マスタ" {
  + id [PK]
  --
  company_id
  name [役職名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_positions as "user_positions\nユーザー役職マスタ関連" {
  + id [PK]
  --
  user_id
  position_id
  created_at [作成日]
  updated_at [更新日]
}

entity users as "users\nユーザー" {
  + id [PK]
  --
  company_id
  login_id [ログインID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

entity user_profiles as "user_profiles\nユーザー詳細" {
  + id [PK]
  --
  user_id
  birthday[生年月日]
  name [ユーザー名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_subordinate_users as "user_subordinate_users\nユーザー部下ユーザー関連" {
  + id [PK]
  --
  user_id
  subordinate_user_id [部下ユーザーID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

@enduml
```

</div></details>

## 初期状態のER図

特に何も工夫していない状態では下記のような感じです。

```erd.pu
@startuml erd
' tables.pu はこのファイルと同じディレクトリに存在している想定です。
!include tables.pu

companies ||-o{ users
companies ||-o{ positions
users ||-o| user_profiles
users ||-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions
@enduml
```

ここから出力される図はこんな感じになります。

```plantuml
@startuml tables
entity companies as "companies\n会社" {
  + id [PK]
  --
  name [会社名]
  created_at [作成日]
  updated_at [更新日]
}

entity positions as "positions\n役職マスタ" {
  + id [PK]
  --
  company_id
  name [役職名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_positions as "user_positions\nユーザー役職マスタ関連" {
  + id [PK]
  --
  user_id
  position_id
  created_at [作成日]
  updated_at [更新日]
}

entity users as "users\nユーザー" {
  + id [PK]
  --
  company_id
  login_id [ログインID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

entity user_profiles as "user_profiles\nユーザー詳細" {
  + id [PK]
  --
  user_id
  birthday[生年月日]
  name [ユーザー名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_subordinate_users as "user_subordinate_users\nユーザー部下ユーザー関連" {
  + id [PK]
  --
  user_id
  subordinate_user_id [部下ユーザーID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

companies ||-o{ users
companies ||-o{ positions
users ||-o| user_profiles
users ||-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions

@enduml
```

うーん、読めないですよね。いっそ存在しない方がましなER図・・・。
この図をいくつかの修正でまあまあ見やすい図にしていきたいと思います。

# ER図を書くコツ一覧

## エンティティを表示する方向を決める

エンティティを表示する方向を、
上下左右で指定したいと思います。

```diff:erd.pu
 @startuml erd
 !include tables.pu
 
-companies ||-o{ users
+companies ||-d-o{ users
 companies ||-o{ positions
-users ||-o| user_profiles
-users ||-o{ user_subordinate_users
+users ||-l-o| user_profiles
+users ||-d-o{ user_subordinate_users
 users ||-o{ user_positions
 positions ||-o{ user_positions
 @enduml
```

指定するとこんな感じになります。

```plantuml
@startuml tables
entity companies as "companies\n会社" {
  + id [PK]
  --
  name [会社名]
  created_at [作成日]
  updated_at [更新日]
}

entity positions as "positions\n役職マスタ" {
  + id [PK]
  --
  company_id
  name [役職名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_positions as "user_positions\nユーザー役職マスタ関連" {
  + id [PK]
  --
  user_id
  position_id
  created_at [作成日]
  updated_at [更新日]
}

entity users as "users\nユーザー" {
  + id [PK]
  --
  company_id
  login_id [ログインID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

entity user_profiles as "user_profiles\nユーザー詳細" {
  + id [PK]
  --
  user_id
  birthday[生年月日]
  name [ユーザー名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_subordinate_users as "user_subordinate_users\nユーザー部下ユーザー関連" {
  + id [PK]
  --
  user_id
  subordinate_user_id [部下ユーザーID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

companies ||-d-o{ users
companies ||-o{ positions
users ||-l-o| user_profiles
users ||-d-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions

@enduml
```

かなり見やすさが改善されました。

### 指定の仕方

`companies ||-d-o{ users` の意味は、
「`companies`の下に向かって(**d**own)`users` を表示する」ということです。

`-r-` や `-u-` のように指定することで、
上下左右のどこにエンティティを表示するかを変更できます。

- 指定できる方向
  - l: left
  - r: right
  - u: up
  - d: down

使わなくてもいい感じに表示される時はわざわざ指定する必要はないですが、
今回のER図のような場合には有用です。

- 参考
  - [ユースケース図の構文と機能 - PlantUML](https://plantuml.com/ja/use-case-diagram)  
「矢印の方向を変えるには」を参照してください。

## リレーションの線の長さを変える

リレーションの線の長さを変えることができます。

```diff:erd.pu
 @startuml erd
 !include tables.pu

-companies ||-d-o{ users
+companies ||-d--o{ users
 companies ||-o{ positions
 users ||-l-o| user_profiles
 users ||-d-o{ user_subordinate_users
 users ||-o{ user_positions
 positions ||-o{ user_positions
 @enduml
```

変更点は `-` を1個追加しただけです。
ER図は下記のようになります。

```plantuml
@startuml tables
entity companies as "companies\n会社" {
  + id [PK]
  --
  name [会社名]
  created_at [作成日]
  updated_at [更新日]
}

entity positions as "positions\n役職マスタ" {
  + id [PK]
  --
  company_id
  name [役職名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_positions as "user_positions\nユーザー役職マスタ関連" {
  + id [PK]
  --
  user_id
  position_id
  created_at [作成日]
  updated_at [更新日]
}

entity users as "users\nユーザー" {
  + id [PK]
  --
  company_id
  login_id [ログインID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

entity user_profiles as "user_profiles\nユーザー詳細" {
  + id [PK]
  --
  user_id
  birthday[生年月日]
  name [ユーザー名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_subordinate_users as "user_subordinate_users\nユーザー部下ユーザー関連" {
  + id [PK]
  --
  user_id
  subordinate_user_id [部下ユーザーID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

companies ||-d--o{ users
companies ||-o{ positions
users ||-l-o| user_profiles
users ||-d-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions

@enduml
```

少しの変更で、かなりリレーションの流れが分かりやすくなりました。

### どういう時に便利？

上記の図だとそこまでありがたみが感じられないのですが、
下記のように、線が交差してしまった時などに力を発揮します。

#### 線が交差してしまう

下記の図は、`erd.pu`のエンティティの表示方向などを
少しアレンジした図です。
この図では、リレーションの線が交差してしまっています。

```another_erd.pu
@startuml erd
!include tables.pu

companies ||-r-o{ users
companies ||-u-o{ positions
users ||-u-o| user_profiles
users ||-d-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions
@enduml
```

```plantuml
@startuml tables
entity companies as "companies\n会社" {
  + id [PK]
  --
  name [会社名]
  created_at [作成日]
  updated_at [更新日]
}

entity positions as "positions\n役職マスタ" {
  + id [PK]
  --
  company_id
  name [役職名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_positions as "user_positions\nユーザー役職マスタ関連" {
  + id [PK]
  --
  user_id
  position_id
  created_at [作成日]
  updated_at [更新日]
}

entity users as "users\nユーザー" {
  + id [PK]
  --
  company_id
  login_id [ログインID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

entity user_profiles as "user_profiles\nユーザー詳細" {
  + id [PK]
  --
  user_id
  birthday[生年月日]
  name [ユーザー名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_subordinate_users as "user_subordinate_users\nユーザー部下ユーザー関連" {
  + id [PK]
  --
  user_id
  subordinate_user_id [部下ユーザーID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

companies ||-r-o{ users
companies ||-u-o{ positions
users ||-u-o| user_profiles
users ||-d-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions

@enduml
```

#### 線を伸ばすことで交差を解消

下記のように修正することで、線の交差を解消することができます。

```diff:another_erd.pu
 @startuml erd
 !include tables.pu

 companies ||-r-o{ users
-companies ||-u-o{ positions
+companies ||-u--o{ positions
 users ||-u-o| user_profiles
 users ||-d-o{ user_subordinate_users
 users ||-o{ user_positions
 positions ||-o{ user_positions
@enduml
```

```plantuml
@startuml tables
entity companies as "companies\n会社" {
  + id [PK]
  --
  name [会社名]
  created_at [作成日]
  updated_at [更新日]
}

entity positions as "positions\n役職マスタ" {
  + id [PK]
  --
  company_id
  name [役職名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_positions as "user_positions\nユーザー役職マスタ関連" {
  + id [PK]
  --
  user_id
  position_id
  created_at [作成日]
  updated_at [更新日]
}

entity users as "users\nユーザー" {
  + id [PK]
  --
  company_id
  login_id [ログインID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

entity user_profiles as "user_profiles\nユーザー詳細" {
  + id [PK]
  --
  user_id
  birthday[生年月日]
  name [ユーザー名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_subordinate_users as "user_subordinate_users\nユーザー部下ユーザー関連" {
  + id [PK]
  --
  user_id
  subordinate_user_id [部下ユーザーID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

companies ||-r-o{ users
companies ||-u--o{ positions
users ||-u-o| user_profiles
users ||-d-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions

@enduml
```
# 出来上がったER図

出来上がった図はこんな感じです。
そんなに複雑なことはしていない割りに、結構スッキリしました。


```plantuml
@startuml tables
entity companies as "companies\n会社" {
  + id [PK]
  --
  name [会社名]
  created_at [作成日]
  updated_at [更新日]
}

entity positions as "positions\n役職マスタ" {
  + id [PK]
  --
  company_id
  name [役職名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_positions as "user_positions\nユーザー役職マスタ関連" {
  + id [PK]
  --
  user_id
  position_id
  created_at [作成日]
  updated_at [更新日]
}

entity users as "users\nユーザー" {
  + id [PK]
  --
  company_id
  login_id [ログインID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

entity user_profiles as "user_profiles\nユーザー詳細" {
  + id [PK]
  --
  user_id
  birthday[生年月日]
  name [ユーザー名]
  created_at [作成日]
  updated_at [更新日]
}

entity user_subordinate_users as "user_subordinate_users\nユーザー部下ユーザー関連" {
  + id [PK]
  --
  user_id
  subordinate_user_id [部下ユーザーID]
  password [パスワード]
  created_at [作成日]
  updated_at [更新日]
}

companies ||-d--o{ users
companies ||-o{ positions
users ||-l-o| user_profiles
users ||-d-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions

@enduml
```

```erd.pu
@startuml tables
!include tables.pu

companies ||-d--o{ users
companies ||-o{ positions
users ||-l-o| user_profiles
users ||-d-o{ user_subordinate_users
users ||-o{ user_positions
positions ||-o{ user_positions
@enduml
```

# おわりに

最初のうちは「もう Excel とか Draw.io で描けばええやん・・・？」と思っていたのですが、
コツさえ掴めばなんとなく見やすく描けるなと思いました。
他にもTipsを見つけたら追記していきます。

# 参考サイト・記事

- [ER図の文法と機能 - PlantUML](https://plantuml.com/ja/ie-diagram)
- [PlantUML で ER 図（ERD）を描く（似非ではないです） - Qiita](https://qiita.com/Tachy_Pochy/items/752ef6e3d38e970378f0)
- [打倒！PlantUMLのなにこれレイアウト. How difficult it is to adjust the… | by Takuo | VELTRA Engineering | Medium](https://medium.com/veltra-engineering/how-difficult-it-is-to-adjust-the-layout-using-plantuml-997884410db5)

# (おまけ)Mermaidで書いてみた

Mermaidで同じような記事を書こうと思ったのですが、
Mermaidはそもそも方向の指定の概念がなかったので、記事はボツになりました。
Mermaidで作成したER図をここに載せておきます・・・。

- 参考記事: [Entity Relationship Diagrams | Mermaid](https://mermaid.js.org/syntax/entityRelationshipDiagram.html)

```mermaid
erDiagram
    companies ||--o{ users : has
    companies ||--o{ positions : has
    users ||--o| user_profiles : has
    users ||--o{ user_subordinate_users : has
    users ||--o{ user_positions : has
    positions ||--o{ user_positions : has
    companies {
      int id PK
      string name "会社名"
      datetime created_at "作成日"
      datetime updated_at "更新日"
    }

    positions {
      int id PK
      int company_id FK
      string name "役職名"
      datetime created_at "作成日"
      datetime updated_at "更新日"
    }

    user_positions {
      int id PK
      int user_id FK
      int position_id FK
      datetime created_at "作成日"
      datetime updated_at "更新日"
    }

    users {
      int id PK
      int company_id FK
      string login_id "ログインID"
      string password "パスワード"
      datetime created_at "作成日"
      datetime updated_at "更新日"
    }

    user_profiles {
      int id PK
      int user_id FK
      date birthday "生年月日"
      string name "ユーザー名"
      datetime created_at "作成日"
      datetime updated_at "更新日"
    }

    user_subordinate_users {
      int id PK
      int user_id FK
      int subordinate_user_id FK "部下ユーザーID"
      string password "パスワード"
      datetime created_at "作成日"
      datetime updated_at "更新日"
    }
```

<details>

<summary>上記MermaidのER図のソースコード</summary>
    
    ```mermaid
    erDiagram
        companies ||--o{ users : has
        companies ||--o{ positions : has
        users ||--o| user_profiles : has
        users ||--o{ user_subordinate_users : has
        users ||--o{ user_positions : has
        positions ||--o{ user_positions : has
        companies {
          int id PK
          string name "会社名"
          datetime created_at "作成日"
          datetime updated_at "更新日"
        }
    
        positions {
          int id PK
          int company_id FK
          string name "役職名"
          datetime created_at "作成日"
          datetime updated_at "更新日"
        }
    
        user_positions {
          int id PK
          int user_id FK
          int position_id FK
          datetime created_at "作成日"
          datetime updated_at "更新日"
        }
    
        users {
          int id PK
          int company_id FK
          string login_id "ログインID"
          string password "パスワード"
          datetime created_at "作成日"
          datetime updated_at "更新日"
        }
    
        user_profiles {
          int id PK
          int user_id FK
          date birthday "生年月日"
          string name "ユーザー名"
          datetime created_at "作成日"
          datetime updated_at "更新日"
        }
    
        user_subordinate_users {
          int id PK
          int user_id FK
          int subordinate_user_id FK "部下ユーザーID"
          string password "パスワード"
          datetime created_at "作成日"
          datetime updated_at "更新日"
        }
    ```


</details>