Slide 1

Slide 1 text

異なる設計思想のフレームワークを 経験して得た学び 天久秀樹 / PyCon JP 2026 1

Slide 2

Slide 2 text

⾃⼰紹介 ⽒名:あめちゃん 所属:株式会社iimon SNS:@amedama3(X) 備考:今回が⼈⽣で初登壇です! 2

Slide 3

Slide 3 text

この発表のスタンス 優劣の話はしません “Laravel”も”Django”も、成熟した良いフレームワークです。 今⽇話すのは「どちらが上か」ではなく、「なぜそう設計されているのか」 という思想の違いです。 3

Slide 4

Slide 4 text

本セッションの想定対象者 想定対象者 ● 他のフレームワーク開発からDjangoを⽤いた開発をした(する)⽅ ● フレームワークわからないけど、どんな感じで使うか知りたい⽅ 4

Slide 5

Slide 5 text

本⽇の発表内容 1. なぜDjangoは「app」で切るのか - ディレクトリ構成 / マイグレーションの扱い 2. Djangoのモデルは管理画⾯まで作れる? - 空⽂字とNULL / 管理画⾯ 3. まとめ 5

Slide 6

Slide 6 text

前提となる2つの開発環境 Laravel Django ⾔語:PHP ⾔語:Python 担当領域:業務系Webアプリの機能開発 担当領域:同規模の業務系Webアプリ 開発期間:数年、複数プロダクト 開発期間:現⾏プロダクトで進⾏中 6

Slide 7

Slide 7 text

なぜDjangoは「app」で切るのか 最初の⼾惑い 「どこにファイルを置けばいいのか分からない」 7

Slide 8

Slide 8 text

なぜDjangoは「app」で切るのか Laravel のディレクトリ構成 app/ ├ Http/Controllers/ │ ├ UserController.php │ └ PostController.php ├ Models/ │ ├ User.php │ └ Post.php └ Services/ routes/ └ web.php database/ └ migrations/ Laravel 役割(レイヤ)ごとに 置き場所が決まっている Controller は Controllers/ へ、 Model は Models/ へ格納。 コンポーネントの関⼼事が明確に分離されま す。 「何を作るか」が決まれば、ファイルを作成す べき場所に迷うことがありません。 8

Slide 9

Slide 9 text

なぜDjangoは「app」で切るのか Django のディレクトリ構成 - 並べて⾒ると Laravel Django app/ ├ Http/Controllers/ │ ├ UserController.php │ └ PostController.php ├ Models/ │ ├ User.php │ └ Post.php database/migrations/ users/ ├ models.py ├ views.py ├ admin.py └ migrations/ posts/ ├ models.py └ views.py 役割(レイヤ)で切り、その中に機能が並ぶ 機能(app)で切り、その中に役割が並ぶ Controllersディレクトリ内にUserもPostもいる usersディレクトリ内にmodelsもviewsもいる 同じようにモデルやビューを持つフレームワークでも、ディレクトリのまとめ⽅が真逆 9

Slide 10

Slide 10 text

次の⼾惑い 「マイグレーションが、appごとに散らばっている…?」 10

Slide 11

Slide 11 text

なぜDjangoは「app」で切るのか Laravelのマイグレーションファイル管理 単⼀のディレクトリ管理 database/migrations/ ├ 2026_01_10_000000_create_users_table.php ├ 2026_02_03_000000_create_posts_table.php ├ 2026_03_15_000000_add_status_to_posts.php └ 2026_04_02_000000_add_index_to_users.php プロジェクト全体で1つの場所にマイグレーションを 集約 ⼀本の⼀元的な時系列 ファイル名に付与された⽇時プレフィックス (YYYY_MM_DD_…)により、すべてのテーブル変更履 歴が⾃動的に並びます。 ● ● 全変更の実⾏順序が直感的に理解しやすい ロールバックの位置が分かりやすい 11

Slide 12

Slide 12 text

なぜDjangoは「app」で切るのか Djangoのマイグレーションファイル管理 appごとに独⽴した連番 users/migrations/ ├ 0001_initial.py └ 0002_alter_user_email.py posts/migrations/ ├ 0001_initial.py ├ 0002_add_status.py └ 0003_add_index.py マイグレーション履歴がapp単位に完全に分離して管 理されます。 全体を貫く時系列は存在しない Laravelのような「プロジェクト全体で1本の履歴」 は、どこにもない。 12

Slide 13

Slide 13 text

なぜDjangoは「app」で切るのか appをまたぐ順序はdependenciesが繋ぐ 時系列ではなく、依存関係で並ぶ # posts/migrations/0001_initial.py 他のappのモデルにFKを張ると、makemigrationsが 「postsはusersの後」という関係を⾃動で記録する。 class Migration(migrations.Migration): dependencies = [ ("users", "0001_initial"), ] operations = [ ... ] 実⾏順はDjangoが⾃動決定 dependencies に書かれた依存関係をもとに、Django が実⾏順を⾃動で解決する。 ● ● ファイル名の⽇時に縛られない柔軟な管理 複数アプリにまたがる複雑な依存も安全に解決 13

Slide 14

Slide 14 text

なぜDjangoは「app」で切るのか なぜ? ― appは「単体で配布される」前提だから appは、このプロジェクト専⽤の部品ではなく 他のプロジェクトにそのまま持っていける「再利⽤の単位」 django.contrib.auth djangorestframework Django本体に同梱される認証機能も、ただの 「app」として提供されている。 pip installしてINSTALLED_APPSに⾜すだけで動 く。 サードパーティも同じ仕組みに乗る。 14

Slide 15

Slide 15 text

なぜDjangoは「app」で切るのか 実際、公式のappの定義にもこう書かれている 引⽤ (英⽂) 和訳 "The term application describes a Python package that provides some set of features. Applications may be reused in various projects." 「アプリケーションとは、何らかの機能⼀式を提供す るPythonパッケージのことである。アプリケーショ ンは様々なプロジェクトで再利⽤されうる」 出典 Django公式ドキュメント (Applications) docs.djangoproject.com/en/stable/ref/applications/ 15

Slide 16

Slide 16 text

なぜDjangoは「app」で切るのか だからマイグレーションもappに同梱される 配布される app models.py views.py migrations/ ← スキーマも⼀緒に 別のプロジェクト pip install migrate を実⾏すれば 特別な追加設定なしで、必要なデータベーステーブル まで⾃動で⼀気に揃います。 ⚠ テーブル定義が本体の migrations に混ざっていたら、app を pip install しても動かせない ※ Laravelもパッケージから loadMigrationsFrom() で配布は可能。違いは「パッケージ開発者向けの opt-in」か「標準」か 16

Slide 17

Slide 17 text

なぜDjangoは「app」で切るのか Laravel出⾝者が躓いた4つのポイント 躓き 簡単な説明 ① アプリの粒度 どこまでを1アプリにすればいいのか判断がつかない。 ② INSTALLED_APPS登録忘れ 作っただけでは動かない。Laravelなら作れば⾃動で認識される。 ③ appをまたぐ循環インポート users ⇄ posts で詰まる。 ④ apps.py / AppConfig 何のために存在するのか最初は意味不明。 それぞれどう向き合ったのか、次のスライドで⾒ていきます 17

Slide 18

Slide 18 text

なぜDjangoは「app」で切るのか ⼀番苦労したのは「粒度」の判断 慣れれば解決した ②④ 登録忘れは⼿順が⾝につけば起きない。 AppConfigは「appの設定を書く場所」と役割を知れば⼾惑わない。 循環インポートは「機能で切った代償」 ③ ① レイヤ切りのLaravelでは起きない問題。 解法は⽂字列参照 FK("users.User") と settings.AUTH_USER_MODEL。 粒度だけは、慣れでは解決しなかった 何個作っても「この切り⽅でいいのか」に確信が持てない 「単体で配布できるか?」が粒度の判断基準 18

Slide 19

Slide 19 text

なぜDjangoは「app」で切るのか ここまでのまとめ 1 Django の app は「機能」ではなく「再利⽤‧配布の単位」 2 マイグレーションのapp同梱も、その前提からの必然 3 粒度に迷ったら「これを単体で配布できるか?」と問う 19

Slide 20

Slide 20 text

Djangoのモデルは管理画⾯まで作れる? きっかけ blank=Trueだけ書かれたコードが、レビューで気になった 20

Slide 21

Slide 21 text

なぜDjangoのモデルは管理画⾯まで作れるのか 「値がない」はNULLで表すのが常識だと思っていた ● SQLの世界では、「値がない」は NULL という専⽤の状態で表す ● 空⽂字 "" は「⻑さ0の⽂字列」という⽴派な値であり、「値がない」とは別物 ● 多くのDB設計では、NULLと空⽂字を区別して扱う ● Laravelもこの流儀に乗っていて、フォームの空⽂字を⾃動でNULLに変換する仕組み (ConvertEmptyStringsToNull)が標準で⼊っている Djangoを扱ってみて、この常識が通⽤しなかった 21

Slide 22

Slide 22 text

なぜDjangoのモデルは管理画⾯まで作れるのか 実際、DB(MySQL)の公式もこう⾔っている 引⽤(和訳) 「0や空⽂字は実際の値であるのに対し、 NULLは『値を持たない』ことを意味する」 ― MySQL 8.4 Reference Manual (Working with NULL Values) 参考ソース(公式ドキュメント) https://dev.mysql.com/doc/refman/8.4/en/working-with-null.html https://dev.mysql.com/doc/refman/8.4/en/problems-with-null.html 22

Slide 23

Slide 23 text

なぜDjangoのモデルは管理画⾯まで作れるのか Laravelのnullable() は「DBの話」だけをしている DB層:スキーマの定義 // migration 宣⾔しているのは「DBカラムがNULLを許可する」と いうデータベース仕様のみです。 Schema::table("users", function (Blueprint $table) { $table->string("nickname")->nullable(); }); 検証層:アプリケーションの仕様 ⼊⼒検証(バリデーション)は FormRequest など、 別の場所で別途記述します。 マイグレーションとバリデーションは独⽴した世界で す。 23

Slide 24

Slide 24 text

なぜDjangoのモデルは管理画⾯まで作れるのか Djangoでは「null=」と「blank=」が分かれている 2つの異なる宣⾔が同居 class User(models.Model): nickname = models.CharField( 同じフィールド定義の中に、DB⽤の設定(null)とアプ リケーション検証⽤の設定(blank)が同居していま す。 max_length=50, null=False, # DB: NULLを許可しない blank=True, # 検証: 空入力を許可 ) 素朴な疑問:「なぜ冗⻑なのか?」 「なぜ同じような役割のものが2つもあるのか?」「1つ にまとめられないのか?」という疑問が⽣まれます。 24

Slide 25

Slide 25 text

なぜDjangoのモデルは管理画⾯まで作れるのか 1つの定義が2つのレイヤを語る nickname = CharField(null=, blank=) ↓ ↓ null= → DB層 blank= → フォーム / 検証層 カラムが NULL を格納できるか ⼊⼒フォームで空を許すか (データベース‧スキーマの仕様) (アプリケーション‧バリデーションの仕様) 1つのフィールド定義が、DBとフォームの2つのレイヤを同時に定義している 25

Slide 26

Slide 26 text

なぜDjangoのモデルは管理画⾯まで作れるのか 「値がない」が2種類あると、経路で挙動がズレる フォーム経由 API‧シェル経由 未⼊⼒は空⽂字 "" として保存される 未設定は NULL として保存されうる Djangoのフォームは空⼊⼒を "" に正規化する null=True なら None がそのままDBに⼊る 同じ「未⼊⼒」なのに、DBには "" と NULL の2通りの値が混在する → 検索‧⽐較‧ユニーク制約が壊れやすくなる 26

Slide 27

Slide 27 text

なぜDjangoのモデルは管理画⾯まで作れるのか だから公式ドキュメントはこう⾔っている “Avoid using null on string-based fields such as CharField and TextField.” ーーCharFieldやTextFieldのような⽂字列フィールドでは、nullの使⽤を避けよ ― Django公式ドキュメント(Model field reference / null) ー https://docs.djangoproject.com/en/stable/ref/models/fields/ ⽂字列フィールドに null=True を使わない 「値がない」の表現を空⽂字 "" の1つに寄せる、という Djangoの規約です。 【有名な例外】 unique=True の任意項⽬ この場合は null=True が正解です。NULLは⼀意性制約に 数えられませんが、空⽂字 "" は2件⽬で⼀意性違反にな ります。 27

Slide 28

Slide 28 text

なぜDjangoのモデルは管理画⾯まで作れるのか 「値がない」をどこに寄せるか Laravel Django ● 「値がない」は NULL に寄せる ● ● フォームの空⽂字は ConvertEmptyStringsToNull ミドル ウェアが⾃動でNULLに変換 ⽂字列の「値がない」は空⽂字 "" に寄 せる ● フォームの未⼊⼒は "" として保存し、 ⽂字列に null=True は付けない DBの流儀(値なし=NULL)に合わせた ● Webの流儀(HTTPにNULLは存在しな い、空欄は空⽂字)に合わせた ● 28

Slide 29

Slide 29 text

管理画⾯ モデル定義だけで、どこまでできるのか 29

Slide 30

Slide 30 text

なぜDjangoのモデルは管理画⾯まで作れるのか Laravelの場合管理画⾯は「作るもの」 ● ルート・コントローラ・ビューを自前で実装。一覧、検索、作成、編集、削除…モ デルごとにCRUD一式を書く。 ● Filamentといった管理画面用のパッケージを用いることもできる。 ● 「モデル定義から自動で生える」わけではなく、Resource定義などを別途書く。 ● 工数はモデル数に比例して積み上がる。「管理画面だけで数週間」は珍しくな い。 30

Slide 31

Slide 31 text

なぜDjangoのモデルは管理画⾯まで作れるのか admin.site.register で管理画⾯が完成する # posts/admin.py この数⾏を記述するだけで、データ管理に必要な 基本機能がすべて⼿に⼊る from django.contrib import admin from .models import Post  データの⼀覧表⽰ admin.site.register(Post)  新規データ作成‧詳細編集フォーム  レコードの個別‧⼀括削除  モデル定義に基づく⾃動バリデーション Laravelで数週間かけていたものが、この5⾏だった 31

Slide 32

Slide 32 text

なぜDjangoのモデルは管理画⾯まで作れるのか なぜ? ― モデル定義がUIの材料を全部持っている フィールド型 CharField / DateField …  ⼊⼒ウィジェットが決まる 検証ルール blank= / max_length …  フォームバリデーションが決まる 表⽰名 verbose_name  ラベル‧画⾯表記が決まる モデルに仕様が揃っているから、UIは「導出」できる 32

Slide 33

Slide 33 text

なぜDjangoのモデルは管理画⾯まで作れるのか なぜ管理画⾯まで作れるのか Djangoのモデルは “DBスキーマ定義” ではなく “ドメイン定義” DBスキーマ定義 ドメイン定義 単にデータベースのテーブル構造を定義するもの。 システム的なデータの格納ルールに限定されます。 型‧検証‧表⽰名まで含めた「仕様の置き場所」。 開発や運⽤に必要なビジネスルールを表現します。 だから、null= と blank= が同居し、管理画⾯まで導出できる データベース制約(null)と管理画⾯の必須バリデーション(blank)を、⽭盾なく⼀つのモデルに集約可能です。 33

Slide 34

Slide 34 text

なぜDjangoのモデルは管理画⾯まで作れるのか 「値がない」が2つあるコード 課題:表現が2つある状態 解決:Djangoの規約 # 「値がない」の表現が2つ(Noneと"")あると… 「⽂字列の空は空⽂字に寄せる」 if value is None or value == "": # 未入力として扱う # この冗長な分岐が、あらゆる境界に増殖する 表現を「空⽂字("")」の1つに統⼀することで、シス テム全体に散らばる複雑な条件分岐そのものを綺麗に消 し去ることができます。 制約(規約)は、コードを縛るためでなく、分岐を消すためにある 34

Slide 35

Slide 35 text

なぜDjangoのモデルは管理画⾯まで作れるのか 管理画⾯を「作らなくてよくなった」 Laravel ● ● ● 管理画⾯はBladeテンプレートで1画⾯ ずつ⼿作り ⼀覧‧検索‧編集フォーム…モデルごと にビューを書き、項⽬が増えるたびに画 ⾯も直す 「管理画⾯の実装」がスケジュールの中 で1つの塊として存在していた Django ● ● ● admin.py に数⾏書くだけで、⼀覧‧編 集‧削除の画⾯が揃う モデルにフィールドを⾜せば、管理画⾯ にも⾃動で反映される 「管理画⾯の実装」という⼯程そのもの が、ほぼ消えた 35

Slide 36

Slide 36 text

まとめ 36

Slide 37

Slide 37 text

まとめ 振り返り appの思想 モデルの思想 Djangoは app を 再利⽤‧配布の単位に置いた Djangoはモデル定義を 上位レイヤまで貫通させた だから機能で切り、マイグレーションも同梱され、粒 度の基準は「配布できるか」になる だから null= と blank= が同居し、フォームも管理画⾯ もモデルから導出される 37

Slide 38

Slide 38 text

まとめ 制約に逆らうと、痛い。 制約は、思想の現れ。 フレームワークの規約に違和感を覚えたら、それは思想を学ぶ⼊⼝。 「なぜこうなっているのか」を⼀段掘ると、規約は味⽅になる。 38

Slide 39

Slide 39 text

まとめ 2つ⽬のフレームワークを学ぶと、 1つ⽬の「何が⾃由だったのか」が 初めて⾔語化できる Laravelの「レイヤで切る⾃由」も、Djangoに出会って初めて⾒えた 39

Slide 40

Slide 40 text

まとめ 最初に読んでおきたいDjangoドキュメント 1 Applications 「appとは何か」「INSTALLED_APPSやAppConfigは何のためにあるか」が公式の⾔葉で書かれている。 docs.djangoproject.com/en/stable/ref/applications/ 2 Migrations 「マイグレーションはappに同梱して配布する設計」という内容の原⽂。dependenciesが⾃動⽣成される仕組みも書かれている。 docs.djangoproject.com/en/stable/topics/migrations/ 3 Model field reference ― null / blank 「⽂字列にnullを避ける」の理由と、unique制約の例外が書かれている。 docs.djangoproject.com/en/stable/ref/models/fields/#null 4 The Django admin site register以外のlist_display や search_fields など、実務で使う管理画⾯カスタマイズの⼀覧などが書かれている。 docs.djangoproject.com/en/stable/ref/contrib/admin/ 40

Slide 41

Slide 41 text

会社紹介 速いもんシリーズ エンジニア積極採⽤中です!!! テックブログも⽇々更新中! 41

Slide 42

Slide 42 text

ご清聴ありがとうございました 42